187 lines
3.5 KiB
Go
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
|
|
}
|