recipe-card/recipe/recipe.go
2017-07-08 01:17:01 -04:00

187 lines
3.5 KiB
Go

package recipe
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/tblyler/goatomic"
"github.com/tblyler/recipe-card/doc"
)
// ValidCategoriesOrder defines keys of info in order of importance
var ValidCategoriesOrder = []string{
"serves",
"oven temperature",
"ingredients",
"preparation",
"tips",
}
// validCategories defines keys of Info
var validCategories = map[string]bool{
"oven temperature": true,
"serves": true,
"ingredients": true,
"preparation": true,
"tips": true,
}
// Recipe stores information regarding a specific recipe
type Recipe struct {
Title string `json:"title"`
Info map[string][]string `json:"info"`
// FIXME support non-docx
DocxPath string `json:"docx_path"`
ScanPaths []string `json:"scan_paths"`
Image []byte
}
// Summary outputs a nice summary of Info
func (r *Recipe) Summary() (output string) {
for _, category := range ValidCategoriesOrder {
if info, exists := r.Info[category]; exists {
if output != "" {
// add an extra newline between categories
output += "\n"
}
output += category + "\n" + strings.Join(info, "\n")
}
}
return
}
// ParseFiles for the recipe
func (r *Recipe) ParseFiles() error {
dir := filepath.Dir(r.DocxPath)
// get a list of recipe scans
infos, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
for _, info := range infos {
if info.IsDir() {
continue
}
name := strings.ToLower(info.Name())
// FIXME support non-jpeg
if !strings.HasSuffix(name, ".jpeg") && !strings.HasSuffix(name, ".jpg") {
continue
}
r.ScanPaths = append(r.ScanPaths, filepath.Join(dir, info.Name()))
}
sort.Strings(r.ScanPaths)
file, err := os.Open(r.DocxPath)
if err != nil {
return err
}
stat, err := file.Stat()
if err != nil {
return err
}
docx, err := doc.NewDocx(file, stat.Size())
if err != nil {
return err
}
r.Image = docx.Image
lines, err := docx.Text()
if err != nil {
return err
}
r.Info = make(map[string][]string)
titleIsNext := false
currentGroup := ""
for _, line := range lines {
if r.Title == "" {
if titleIsNext {
r.Title = line
continue
}
if strings.Contains(strings.ToLower(line), "recipe") {
titleIsNext = true
}
continue
}
lowerLine := strings.ToLower(strings.Replace(line, ":", "", -1))
if _, exists := validCategories[lowerLine]; exists {
currentGroup = lowerLine
continue
}
// make sure a current group is set
if currentGroup == "" {
continue
}
r.Info[currentGroup] = append(r.Info[currentGroup], line)
}
return nil
}
// RecipesFromPath generates Recipe instances from a path
func RecipesFromPath(dirPath string) (recipes []*Recipe, err error) {
// get the absolute path of the directory and clean it
dirPath, err = filepath.Abs(dirPath)
if err != nil {
return
}
stat, err := os.Stat(dirPath)
if err != nil {
return
}
if !stat.IsDir() {
return nil, fmt.Errorf("Not a directory %s", dirPath)
}
err = filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
// skip directories and non-docx files
// FIXME support non-docx
if info.IsDir() || !strings.HasSuffix(strings.ToLower(path), ".docx") {
return nil
}
recipes = append(recipes, &Recipe{
DocxPath: path,
})
return nil
})
wg := goatomic.WorkerGroup{}
for _, recipe := range recipes {
wg.Add(1)
go func(recipe *Recipe) {
recipe.ParseFiles()
wg.Done()
}(recipe)
}
wg.Wait()
return
}