diff --git a/.gitignore b/.gitignore index 2fc5558..bf5d733 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ **/testdata/*.md +.cookdocs +.cookdocs.yml +.cookdocs.yaml + /site /cook-docs /cmd/cook-docs/cook-docs diff --git a/.goreleaser.yml b/.goreleaser.yml index 413e47d..ec6d67a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -41,6 +41,9 @@ signs: - artifacts: checksum args: ["-u", "{{ .Env.GPG_FINGERPRINT }}", "--output", "${signature}", "--detach-sign", "${artifact}"] +release: + draft: true + brews: - tap: owner: nicholaswilde diff --git a/cmd/cook-docs/command_line.go b/cmd/cook-docs/command_line.go index d815dce..cf9a818 100644 --- a/cmd/cook-docs/command_line.go +++ b/cmd/cook-docs/command_line.go @@ -8,6 +8,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/nicholaswilde/cook-docs/pkg/types" ) var version string @@ -22,8 +23,8 @@ func possibleLogLevels() []string { return levels } -func initializeCli() { - logLevelName := viper.GetString("log-level") +func initializeCli(config *types.Config) { + logLevelName := config.LogLevel logLevel, err := log.ParseLevel(logLevelName) if err != nil { log.Errorf("Failed to parse provided log level %s: %s", logLevelName, err) @@ -48,7 +49,8 @@ func newCookDocsCommand(run func(cmd *cobra.Command, args []string)) (*cobra.Com command.PersistentFlags().StringP("recipe-search-root", "c", ".", "directory to search recursively within for recipes") command.PersistentFlags().StringP("log-level", "l", "info", logLevelUsage) command.PersistentFlags().StringSliceP("template-files", "t", []string{"recipe.md.gotmpl"}, "gotemplate file paths relative to each recipe directory from which documentation will be generated") - + command.PersistentFlags().IntP("word-wrap", "w", 120, "word wrap line length for recipe steps section") + viper.SetConfigName(".cookdocs") viper.SetConfigType("yaml") viper.AddConfigPath("/etc/cook-docs/") @@ -69,6 +71,12 @@ func newCookDocsCommand(run func(cmd *cobra.Command, args []string)) (*cobra.Com viper.AutomaticEnv() viper.SetEnvPrefix("COOK_DOCS") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) + viper.RegisterAlias("wordWrap", "word-wrap") + viper.RegisterAlias("dryRun", "dry-run") + viper.RegisterAlias("ignoreFile", "ignore-file") + viper.RegisterAlias("recipeSearchRoot", "recipe-search-root") + viper.RegisterAlias("logLevel", "log-level") + viper.RegisterAlias("templateFiles", "template-files") err = viper.BindPFlags(command.PersistentFlags()) return command, err } diff --git a/cmd/cook-docs/main.go b/cmd/cook-docs/main.go index ba0da06..29c6a78 100644 --- a/cmd/cook-docs/main.go +++ b/cmd/cook-docs/main.go @@ -1,87 +1,89 @@ -package main - -import ( - "os" - "path" - "strings" - "sync" - - "github.com/aquilax/cooklang-go" - "github.com/nicholaswilde/cook-docs/pkg/cook" - "github.com/nicholaswilde/cook-docs/pkg/document" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -func retrieveInfoAndPrintDocumentation(recipeSearchRoot string, recipePath string, templateFiles []string, waitGroup *sync.WaitGroup, dryRun bool) { - defer waitGroup.Done() - - recipeInfo := cook.ParseRecipeInformation(recipePath) - - recipeData, err := cooklang.ParseFile(recipeInfo.RecipePath) - - if err != nil { - log.Warnf("Error parsing file for recipe %s, skipping: %s", recipeInfo.RecipePath, err) - return - } - - recipeData = cook.MergeRecipeData(recipeInfo, recipeData) - - document.PrintDocumentation(recipeSearchRoot, recipeData, recipeInfo, templateFiles, dryRun) -} - -func cookDocs(_ *cobra.Command, _ []string) { - initializeCli() - - recipeSearchRoot := viper.GetString("recipe-search-root") - - var fullRecipeSearchRoot string - if path.IsAbs(recipeSearchRoot) { - fullRecipeSearchRoot = recipeSearchRoot - } else { - cwd, err := os.Getwd() - if err != nil { - log.Warnf("Error getting working directory: %s", err) - return - } - fullRecipeSearchRoot = path.Join(cwd, recipeSearchRoot) - } - - recipePaths, err := cook.FindRecipePaths(fullRecipeSearchRoot) - if err != nil { - log.Errorf("Error finding recipe paths: %s", err) - os.Exit(1) - } - log.Infof("Found recipes [%s]", strings.Join(recipePaths, ", ")) - - templateFiles := viper.GetStringSlice("template-files") - log.Debugf("Rendering from optional template files [%s]", strings.Join(templateFiles, ", ")) - - dryRun := viper.GetBool("dry-run") - waitGroup := sync.WaitGroup{} - - for _, r := range recipePaths { - waitGroup.Add(1) - - // On dry runs all output goes to stdout, and so as to not jumble things, generate serially - if dryRun { - retrieveInfoAndPrintDocumentation(fullRecipeSearchRoot, r, templateFiles, &waitGroup, dryRun) - } else { - go retrieveInfoAndPrintDocumentation(fullRecipeSearchRoot, r, templateFiles, &waitGroup, dryRun) - } - } - waitGroup.Wait() -} - -func main() { - command, err := newCookDocsCommand(cookDocs) - if err != nil { - log.Errorf("Failed to create the CLI commander: %s", err) - os.Exit(1) - } - if err := command.Execute(); err != nil { - log.Errorf("Failed to start the CLI: %s", err) - os.Exit(1) - } -} +package main + +import ( + "os" + "path" + "strings" + "sync" + + "github.com/nicholaswilde/cook-docs/pkg/cook" + "github.com/nicholaswilde/cook-docs/pkg/document" + "github.com/nicholaswilde/cook-docs/pkg/types" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func retrieveInfoAndPrintDocumentation(recipePath string, waitGroup *sync.WaitGroup, config *types.Config) { + defer waitGroup.Done() + + recipe, err := cook.ParseFile(recipePath, config) + if err != nil { + log.Warnf("Error parsing file for recipe %s, skipping: %s", recipePath, err) + return + } + + document.PrintDocumentation(recipe) +} + +func GetFullSearchRoot(searchRoot string) (string, error) { + var fullSearchRoot string + if path.IsAbs(searchRoot) { + fullSearchRoot = searchRoot + } else { + cwd, err := os.Getwd() + if err != nil { + return "", err + } + fullSearchRoot = path.Join(cwd, searchRoot) + } + return fullSearchRoot, nil +} + +func cookDocs(_ *cobra.Command, _ []string) { + var config types.Config + viper.Unmarshal(&config) + + initializeCli(&config) + + fullSearchRoot, err := GetFullSearchRoot(config.RecipeSearchRoot) + if err != nil { + log.Warnf("Error getting working directory: %s", err) + return + } + + recipePaths, err := cook.FindRecipeFilePaths(fullSearchRoot) + if err != nil { + log.Errorf("Error finding recipe paths: %s", err) + os.Exit(1) + } + log.Infof("Found recipes [%s]", strings.Join(recipePaths, ", ")) + + log.Debugf("Rendering from optional template files [%s]", strings.Join(config.TemplateFiles, ", ")) + + waitGroup := sync.WaitGroup{} + + for _, r := range recipePaths { + waitGroup.Add(1) + + // On dry runs all output goes to stdout, and so as to not jumble things, generate serially + if config.DryRun { + retrieveInfoAndPrintDocumentation(r, &waitGroup, &config) + } else { + go retrieveInfoAndPrintDocumentation(r, &waitGroup, &config) + } + } + waitGroup.Wait() +} + +func main() { + command, err := newCookDocsCommand(cookDocs) + if err != nil { + log.Errorf("Failed to create the CLI commander: %s", err) + os.Exit(1) + } + if err := command.Execute(); err != nil { + log.Errorf("Failed to start the CLI: %s", err) + os.Exit(1) + } +} diff --git a/docs/configuration.md b/docs/configuration.md index bacb586..2b054c5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,22 +4,23 @@ Here is a list of the current supported parameters: -| Command Line | Environmental Variable | Config File | Default | Description | -|---------------------------------|------------------------------|--------------------|--------------------|----------------------------------------------------------------------------------------------------| -| -d, --dry-run | COOK_DOCS_DRY_RUN | dry-run | false | don't actually render any markdown files just print to stdout passed | -| -h, --help | N/A | N/A | N/A | help for cook-docs | -| -i, --ignore-file string | COOK_DOCS_IGNORE_FILE | ignore-file | .cookdocsignore | filename to use as an ignore file to exclude recipe directories | -| -j, --jsonify | COOK_DOCS_JSONIFY | jsonify | false | parse the recipe and display it in json format | -| -l, --log-level string | COOK_DOCS_LOG_LEVEL | log-level | info | level of logs that should printed, one of (panic, fatal, error, warning, info, debug, trace) | -| -c, --recipe-search-root string | COOK_DOCS_RECIPE_SEARCH_ROOT | recipe-search-root | . | directory to search recursively within for recipes. | -| -t, --template-files strings | COOK_DOCS_TEMPLATE_FILES | template-files | [recipe.md.gotmpl] | gotemplate file paths relative to each recipe directory from which documentation will be generated | -| -v, --version | N/A | N/A | N/A | diplay the version of cook-docs | +| Command Line | Environmental Variable | Config File | Default | Description | +|---------------------------------|-------------------------------|-------------------|---------------------|-----------------------------------------------------------------------------------------------------| +| -d, --dry-run | COOK_DOCS_DRY_RUN | dryRun | false | don't actually render any markdown files just print to stdout passed | +| -h, --help | N/A | N/A | N/A | help for cook-docs | +| -i, --ignore-file string | COOK_DOCS_IGNORE_FILE | ignoreFile | .cookdocsignore | filename to use as an ignore file to exclude recipe directories | +| -j, --jsonify | COOK_DOCS_JSONIFY | jsonify | false | parse the recipe and display it in json format | +| -l, --log-level string | COOK_DOCS_LOG_LEVEL | logLevel | info | level of logs that should printed, one of (panic, fatal, error, warning, info, debug, trace) | +| -c, --recipe-search-root string | COOK_DOCS_RECIPE_SEARCH_ROOT | recipeSearchRoot | . | directory to search recursively within for recipes. | +| -t, --template-files strings | COOK_DOCS_TEMPLATE_FILES | templateFiles | [recipe.md.gotmpl] | gotemplate file paths relative to each recipe directory from which documentation will be generated | +| -w, --word-wrap int | COOK_DOCS_WORD_WRAP | wordWrap | 120 | word wrap line length for recipe steps section | +| -v, --version | N/A | N/A | N/A | diplay the version of cook-docs | ## Config Files Configuration files may be used to set the default app settings. -The config file name is `.cookdocs.yaml` or `.cookdocs.yml` and can be located in +The config file name is `.cookdocs`, `.cookdocs.yaml` or `.cookdocs.yml` and can be located in any of the following locations: - `/etc/cook-docs/` @@ -28,14 +29,18 @@ any of the following locations: ```yaml title=".cookdocs.yaml" --- -dry-run: false -ignore-file: .cookdocsignore +dryRun: false +ignoreFile: .cookdocsignore jsonify: false -log-level: info -template-files: +logLevel: info +templateFiles: - recipe.md.gotmpl +wordWrap: 120 ``` +!!! note + The variables in the config file can be both in the `Command Line` or `Config File` format. E.g. `dry-run` and `dryRun`. + ## Environmental Variables Environmental variables are also supported. They start with the prefix `COOK_DOCS_` and use diff --git a/docs/templates.md b/docs/templates.md index ea1ec09..27ba4e8 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -21,7 +21,7 @@ this: {{ template "cook.cookwareSection" . }} -{{ template "cook.stepsSection" . }} +{{ template "cook.stepsSection" . -}} {{ template "cook.sourceSection" . }} ``` @@ -49,26 +49,29 @@ can be used in the templates you supply. ### Components -| Name | Description | -|------------------------------------------|---------------------------------------------------------------------------| -| `cook.ingredientsHeader` | The ingredients header | -| `cook.ingredients` | An unordered list of the ingredients | -| `cook.cookwareHeader` | The cookware header | -| `cook.cookware` | An unordered list of cookware | -| `cook.stepsHeader` | The steps header | -| `cook.steps` | A list of steps. Each step has its own sub heading labeled as `Step #` | -| `cook.stepsWithQuotedCommentsHeader` | The steps with block quotes header | -| `cook.stepsWithQuotedComments` | A list of steps with block quoted comments in between | -| `cook.stepsWithAdmonishedCommentsHeader` | The steps with admonitions headder | -| `cook.stepsWithAdmonishedComments` | A list of steps with comments as admonitions in between | -| `cook.sourceHeader` | Source header | -| `cook.source` | The `source` as a single unordered list item | -| `cook.metadataHeader` | The `Metadata` header | -| `cook.metadata` | An unordered list of the `Metadata`. `ImageName` and `title` are included | -| `cook.commentsHeader` | The comments header | -| `cook.comments` | An unordered list of the comments | -| `.Metadata.title` | The title of the recipe taken fromt the recipe file name. | -| `.Metadata.ImageName` | The new image name if an image file is found | +| Name | Description | +|------------------------------------------|------------------------------------------------------------------------------| +| `cook.ingredientsHeader` | The ingredients header | +| `cook.ingredients` | An unordered list of the ingredients | +| `cook.cookwareHeader` | The cookware header | +| `cook.cookware` | An unordered list of cookware | +| `cook.stepsHeader` | The steps header | +| `cook.steps` | A list of steps. Each step has its own sub heading labeled as `Step #` | +| `cook.stepsWithQuotedCommentsHeader` | The steps with block quotes header | +| `cook.stepsWithQuotedComments` | A list of steps with block quoted comments in between | +| `cook.stepsWithAdmonishedCommentsHeader` | The steps with admonitions headder | +| `cook.stepsWithAdmonishedComments` | A list of steps with comments as admonitions in between | +| `cook.sourceHeader` | Source header | +| `cook.source` | The `source` as a single unordered list item | +| `cook.metadataHeader` | The `Metadata` header | +| `cook.metadata` | An unordered list of the `Metadata`. | +| `cook.commentsHeader` | The comments header | +| `cook.comments` | An unordered list of the comments | +| `.Info.RecipeName` | The name of the recipe taken fromt the recipe file name | +| `.Info.ImageFileName` | The image name if an image file is found | +| `.Info.ImageFilePath` | The image path if an image file is found | +| `.Info.NewRecipeFilePath` | The new recipe file name after removal of spaces and converting to lowercase | +| `.Info.RecipeFilePath` | The file path of the recipe file | See [template.go][6] for how each key is defined. @@ -77,19 +80,17 @@ See [template.go][6] for how each key is defined. ## Metadata -`cook-docs` uses the `Metadata.title` and `Metadata.ImageName` keys for the -recipe title, taken from the `*.cook` filename and name of the formatted image -name. If the parsed recipe uses these keys, they will be overwritten by -`cook-docs`. +Any [metadata][10] from the recipe `*.cook` file will be written to the `cook.metadataSection`. -```title="Overwritten Recipe Metadata" ->> title: My recipe title ->> ImageName: My image name +``` +>> source: https://www.gimmesomeoven.com/baked-potato/ +>> time required: 1.5 hours +>> course: dinner ... ``` The names of the markdown and image files are made lowercase and the spaces are replaced -by dashes. E.g. `My Recipe Name.cook -> my-recipe-name.md` +by dashes. E.g. `My Recipe Name.cook -> my-recipe-name.md` and `My Recipe Name.png -> my-recipe-name.png`. ## Custom Sections @@ -116,12 +117,15 @@ Then use it later in the template. ## Cooklang Parser `cook-docs` uses [aquilax's][1] [cooklang-go][2] parser to parse `cooklang` -recipes. The data output may then be directly used inside of the `cook-docs` -template files. +recipes. The recipes are then merged with custom cook-docs data, such as +`Info` and `Config`. The data output may then be directly used inside of +the `cook-docs` template files. + +See [`parser.go`][3] for the basic structure latyout. -See [`parser.go`][3] for the structure latyout. +The `jsonify` option may also be used to output -```json title="Example parsed output" +```json title="Example parsed `cook-docs` output" Output: { "Steps": [ @@ -320,7 +324,26 @@ See [`parser.go`][3] for the structure latyout. } ], "Metadata": { - "servings": "6" + "servings": "6", + "source": "https://www.somewebsite.com/pizza-balls" + }, + "Config": { + "DryRun": false, + "Jsonify": true, + "IgnoreFile": ".cookdocsignore", + "RecipeSearchRoot": ".", + "LogLevel": "info", + "TemplateFiles": [ + "recipe.md.gotmpl" + ], + "WordWrap": 120 + }, + "Info": { + "ImageFilePath": "/home/nicholas/git/nicholaswilde/cook-docs/cmd/cook-docs/testdata/My Test Recipe.png", + "ImageFileName": "My Test Recipe.png", + "RecipeName": "My Test Recipe", + "RecipeFilePath": "/home/nicholas/git/nicholaswilde/cook-docs/cmd/cook-docs/testdata/My Test Recipe.cook", + "NewRecipeFilePath": "/home/nicholas/git/nicholaswilde/cook-docs/cmd/cook-docs/testdata/my-test-recipe.md" } } } @@ -335,6 +358,11 @@ delimiters. See [Text and spaces][4]. {{- define "custom.section" . -}} {{- end -}} ``` + +!!! note + To remove the double EOF new lines when `.Metadata.source` is missing from the recipe file but present in the template file, double new lines is added to the beginning of `cook.sourceSection` and white space is removed from the end + of `cook.stepsSection`. + [1]: https://github.com/aquilax [2]: https://github.com/aquilax/cooklang-go [3]: https://github.com/aquilax/cooklang-go/blob/490a595d639b679a4f2053a309647882db37e569/parser.go @@ -344,3 +372,4 @@ delimiters. See [Text and spaces][4]. [7]: https://github.com/nicholaswilde/cook-docs/issues/3 [8]: https://github.github.com/gfm/#block-quotes [9]: https://squidfunk.github.io/mkdocs-material/reference/admonitions/ +[10]: https://cooklang.org/docs/spec/#metadata diff --git a/pkg/cook/recipe_finder.go b/pkg/cook/recipe_finder.go index 1d9854a..45459e5 100644 --- a/pkg/cook/recipe_finder.go +++ b/pkg/cook/recipe_finder.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/viper" ) -func FindRecipePaths(recipeSearchRoot string) ([]string, error) { +func FindRecipeFilePaths(recipeSearchRoot string) ([]string, error) { ignoreFilename := viper.GetString("ignore-file") ignoreContext := util.NewIgnoreContext(ignoreFilename) recipePaths := make([]string, 0) diff --git a/pkg/cook/recipe_finder_test.go b/pkg/cook/recipe_finder_test.go index d322e90..1c3f7f8 100644 --- a/pkg/cook/recipe_finder_test.go +++ b/pkg/cook/recipe_finder_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestFindRecipePaths(t *testing.T) { - recipeDirs, _ := FindRecipePaths(".") +func TestFindRecipeFilePaths(t *testing.T) { + recipeDirs, _ := FindRecipeFilePaths(".") assert.Equal(t, "testdata/Recipe.cook", recipeDirs[0]) } diff --git a/pkg/cook/recipe_info.go b/pkg/cook/recipe_info.go index b05ce04..b90ee20 100644 --- a/pkg/cook/recipe_info.go +++ b/pkg/cook/recipe_info.go @@ -1,75 +1,79 @@ -package cook - -import ( - "errors" - "os" - "path/filepath" - "strings" - - "github.com/aquilax/cooklang-go" - log "github.com/sirupsen/logrus" -) - -type RecipeDocumentationInfo struct { - ImagePath string - RecipePath string - RecipeName string - NewFileName string -} - -func GetNewFileName(recipeDocInfo RecipeDocumentationInfo) string { - path := filepath.Dir(recipeDocInfo.RecipePath) - fileName := strings.Replace(recipeDocInfo.RecipeName, " ", "-", -1) - fileName = strings.ToLower(fileName) + ".md" - return filepath.Join(path, fileName) -} - -func GetRecipeName(recipePath string) string { - fileName := filepath.Base(recipePath) - return strings.TrimSuffix(fileName, filepath.Ext(fileName)) -} - -func GetImagePath(recipeDocInfo RecipeDocumentationInfo) (string, error) { - var err error - path := filepath.Dir(recipeDocInfo.RecipePath) - imagePath := filepath.Join(path, recipeDocInfo.RecipeName) + ".jpg" - _, err = os.Stat(imagePath) - if errors.Is(err, os.ErrNotExist) { - imagePath = filepath.Join(path, recipeDocInfo.RecipeName) + ".png" - } - _, err = os.Stat(imagePath) - if errors.Is(err, os.ErrNotExist) { - log.Warnf("Image file %s missing.", imagePath) - return "", err - } - return imagePath, nil -} - -func ParseRecipeInformation(recipePath string) RecipeDocumentationInfo { - var recipeDocInfo RecipeDocumentationInfo - - recipeDocInfo.RecipePath = recipePath - - recipeDocInfo.RecipeName = GetRecipeName(recipePath) - - recipeDocInfo.NewFileName = GetNewFileName(recipeDocInfo) - - imagePath, err := GetImagePath(recipeDocInfo) - - if err == nil { - recipeDocInfo.ImagePath = imagePath - } - - return recipeDocInfo -} - -func MergeRecipeData(recipeInfo RecipeDocumentationInfo, recipeData *cooklang.Recipe) *cooklang.Recipe { - - recipeData.Metadata["title"] = recipeInfo.RecipeName - - if len(recipeInfo.ImagePath) > 0 { - recipeData.Metadata["ImageName"] = filepath.Base(recipeInfo.ImagePath) - } - - return recipeData -} +package cook + +import ( + "errors" + "os" + "path/filepath" + "strings" + + "github.com/nicholaswilde/cook-docs/pkg/types" + "github.com/aquilax/cooklang-go" + log "github.com/sirupsen/logrus" +) + +type RecipeDocumentationInfo struct { + ImagePath string + RecipeFilePath string + RecipeName string + NewRecipeFilePath string +} + +func GetNewRecipeFilePath(info types.Info) string { + path := filepath.Dir(info.RecipeFilePath) + fileName := strings.Replace(info.RecipeName, " ", "-", -1) + fileName = strings.ToLower(fileName) + ".md" + return filepath.Join(path, fileName) +} + +func GetRecipeName(recipePath string) string { + fileName := filepath.Base(recipePath) + return strings.TrimSuffix(fileName, filepath.Ext(fileName)) +} + +func GetImagePath(info types.Info) (string, error) { + var err error + path := filepath.Dir(info.RecipeFilePath) + imagePath := filepath.Join(path, info.RecipeName) + ".jpg" + _, err = os.Stat(imagePath) + if errors.Is(err, os.ErrNotExist) { + imagePath = filepath.Join(path, info.RecipeName) + ".png" + } + _, err = os.Stat(imagePath) + if errors.Is(err, os.ErrNotExist) { + log.Warnf("Image file %s missing.", imagePath) + return "", err + } + return imagePath, nil +} + +func ParseFile(recipePath string, config *types.Config) (*types.Recipe, error) { + var info types.Info + var recipe types.Recipe + + info.RecipeFilePath = recipePath + + info.RecipeName = GetRecipeName(recipePath) + + info.NewRecipeFilePath = GetNewRecipeFilePath(info) + + imagePath, err := GetImagePath(info) + + if err == nil { + info.ImageFilePath = imagePath + info.ImageFileName = filepath.Base(imagePath) + } + + recipeData, err := cooklang.ParseFile(recipePath) + + if err != nil { + log.Warnf("Error parsing file for recipe %s, skipping: %s", recipePath, err) + return nil, err + } + + recipe.Metadata = recipeData.Metadata + recipe.Steps = recipeData.Steps + recipe.Info = info + recipe.Config = *config + + return &recipe, nil +} diff --git a/pkg/cook/recipe_info_test.go b/pkg/cook/recipe_info_test.go index 343a042..1bd3d20 100644 --- a/pkg/cook/recipe_info_test.go +++ b/pkg/cook/recipe_info_test.go @@ -3,16 +3,16 @@ package cook import ( "testing" - "github.com/aquilax/cooklang-go" + "github.com/nicholaswilde/cook-docs/pkg/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestGetNewFileName(t *testing.T) { - var recipeInfo RecipeDocumentationInfo - recipeInfo.RecipePath = "testdata/Recipe.cook" +func TestGetNewRecipeFilePath(t *testing.T) { + var recipeInfo types.Info + recipeInfo.RecipeFilePath = "testdata/Recipe.cook" recipeInfo.RecipeName = "Recipe" - fileName := GetNewFileName(recipeInfo) + fileName := GetNewRecipeFilePath(recipeInfo) assert.Equal(t, "testdata/recipe.md", fileName) } @@ -22,34 +22,21 @@ func TestGetRecipeName(t *testing.T) { } func TestGetImagePath(t *testing.T) { - var recipeInfo RecipeDocumentationInfo - recipeInfo.RecipePath = "testdata/Recipe.cook" + var recipeInfo types.Info + recipeInfo.RecipeFilePath = "testdata/Recipe.cook" recipeInfo.RecipeName = "Recipe" imagePath, err := GetImagePath(recipeInfo) require.NoError(t, err) assert.Equal(t, "testdata/Recipe.png", imagePath) - recipeInfo.RecipePath = "testdata/Recipe2.cook" + recipeInfo.RecipeFilePath = "testdata/Recipe2.cook" recipeInfo.RecipeName = "Recipe2" imagePath, err = GetImagePath(recipeInfo) require.NoError(t, err) assert.Equal(t, "testdata/Recipe2.jpg", imagePath) - recipeInfo.RecipePath = "testdata/Recipe3.cook" + recipeInfo.RecipeFilePath = "testdata/Recipe3.cook" recipeInfo.RecipeName = "Recipe3" _, err = GetImagePath(recipeInfo) require.Error(t, err) } - -func TestMergeRecipeData(t *testing.T) { - var recipeInfo RecipeDocumentationInfo - var recipeData = cooklang.Recipe{ - Steps: make([]cooklang.Step, 0), - Metadata: make(map[string]string), - } - recipeInfo.RecipeName = "Recipe" - recipeInfo.ImagePath = "testdata/recipe.jpg" - recipeData2 := MergeRecipeData(recipeInfo, &recipeData) - assert.Equal(t, recipeData2.Metadata["title"], "Recipe") - assert.Equal(t, recipeData2.Metadata["ImageName"], "recipe.jpg") -} diff --git a/pkg/document/generate.go b/pkg/document/generate.go index 542d512..871db4e 100644 --- a/pkg/document/generate.go +++ b/pkg/document/generate.go @@ -1,78 +1,74 @@ -package document - -import ( - "bytes" - "encoding/json" - "os" - "regexp" - - "github.com/aquilax/cooklang-go" - "github.com/nicholaswilde/cook-docs/pkg/cook" - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" -) - -func getOutputFile(recipeInfo cook.RecipeDocumentationInfo, dryRun bool) (*os.File, error) { - if dryRun { - return os.Stdout, nil - } - log.Debug(recipeInfo.NewFileName) - return os.Create(recipeInfo.NewFileName) -} - -func applyMarkDownFormat(output bytes.Buffer) bytes.Buffer { - outputString := output.String() - re := regexp.MustCompile(` \n`) - outputString = re.ReplaceAllString(outputString, "\n") - - re = regexp.MustCompile(`\n{3,}`) - outputString = re.ReplaceAllString(outputString, "\n\n") - - output.Reset() - output.WriteString(outputString) - return output -} - -func PrintDocumentation(recipeSearchRoot string, recipeData *cooklang.Recipe, recipeInfo cook.RecipeDocumentationInfo, templateFiles []string, dryRun bool) { - jsonify := viper.GetBool("jsonify") - - if jsonify { - log.Infof("Printing json output for recipe %s", recipeInfo.NewFileName) - j, err := json.MarshalIndent(recipeData, "", " ") - if err != nil { - log.Fatal(err) - } - log.Info(string(j)) - return - } - - log.Infof("Generating markdown file for recipe %s", recipeInfo.NewFileName) - - t, err := newRecipeDocumentationTemplate(recipeSearchRoot, recipeInfo, templateFiles) - if err != nil { - log.Warnf("Error getting template data %s: %s", recipeInfo.RecipePath, err) - return - } - - outputFile, err := getOutputFile(recipeInfo, dryRun) - if err != nil { - log.Warnf("Could not open recipe markdown file %s, skipping recipe: %s", recipeInfo.NewFileName, err) - return - } - - if !dryRun { - defer outputFile.Close() - } - - var output bytes.Buffer - err = t.Execute(&output, recipeData) - if err != nil { - log.Warnf("Error executing template %s: %s", recipeInfo.RecipePath, err) - } - - output = applyMarkDownFormat(output) - _, err = output.WriteTo(outputFile) - if err != nil { - log.Warnf("Error generating documentation file for recipe %s: %s", recipeInfo.NewFileName, err) - } -} +package document + +import ( + "bytes" + "encoding/json" + "os" + "regexp" + + "github.com/nicholaswilde/cook-docs/pkg/types" + log "github.com/sirupsen/logrus" +) + +func getOutputFile(newFileName string, dryRun bool) (*os.File, error) { + if dryRun { + return os.Stdout, nil + } + return os.Create(newFileName) +} + +func applyMarkDownFormat(output bytes.Buffer) bytes.Buffer { + outputString := output.String() + re := regexp.MustCompile(` \n`) + outputString = re.ReplaceAllString(outputString, "\n") + + re = regexp.MustCompile(`\n{3,}`) + outputString = re.ReplaceAllString(outputString, "\n\n") + + output.Reset() + output.WriteString(outputString) + return output +} + +func PrintDocumentation(recipe *types.Recipe) { + + if recipe.Config.Jsonify { + log.Infof("Printing json output for recipe %s", recipe.Info.NewRecipeFilePath) + j, err := json.MarshalIndent(recipe, "", " ") + if err != nil { + log.Fatal(err) + } + log.Info(string(j)) + return + } + + log.Infof("Generating markdown file for recipe %s", recipe.Info.NewRecipeFilePath) + + t, err := newRecipeDocumentationTemplate(recipe) + if err != nil { + log.Warnf("Error getting template data %s: %s", recipe.Info.RecipeFilePath, err) + return + } + + outputFile, err := getOutputFile(recipe.Info.NewRecipeFilePath, recipe.Config.DryRun) + if err != nil { + log.Warnf("Could not open recipe markdown file %s, skipping recipe: %s", recipe.Info.NewRecipeFilePath, err) + return + } + + if !recipe.Config.DryRun { + defer outputFile.Close() + } + + var output bytes.Buffer + err = t.Execute(&output, recipe) + if err != nil { + log.Warnf("Error executing template %s: %s", recipe.Info.RecipeFilePath, err) + } + + output = applyMarkDownFormat(output) + _, err = output.WriteTo(outputFile) + if err != nil { + log.Warnf("Error generating documentation file for recipe %s: %s", recipe.Info.NewRecipeFilePath, err) + } +} diff --git a/pkg/document/template.go b/pkg/document/template.go index a304943..7c62bbe 100644 --- a/pkg/document/template.go +++ b/pkg/document/template.go @@ -12,8 +12,8 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/aquilax/cooklang-go" - "github.com/nicholaswilde/cook-docs/pkg/cook" "github.com/nicholaswilde/cook-docs/pkg/util" + "github.com/nicholaswilde/cook-docs/pkg/types" log "github.com/sirupsen/logrus" ) @@ -36,7 +36,7 @@ func getHeaderTemplate() string { templateBuilder := strings.Builder{} templateBuilder.WriteString(`{{ define "cook.headerSection" }}`) - templateBuilder.WriteString("# {{ .Metadata.title }}") + templateBuilder.WriteString("# {{ .Info.RecipeName }}") templateBuilder.WriteString("{{ end }}") return templateBuilder.String() @@ -46,8 +46,8 @@ func getImageTemplate() string { templateBuilder := strings.Builder{} templateBuilder.WriteString(`{{ define "cook.imageSection" }}`) - templateBuilder.WriteString("{{ if .Metadata.ImageName }}") - templateBuilder.WriteString(`![{{ .Metadata.title }}](../assets/images/{{ lower .Metadata.ImageName | replace " " "-" }})`) + templateBuilder.WriteString("{{ if .Info.ImageFileName }}") + templateBuilder.WriteString(`![{{ .Info.RecipeName }}](../assets/images/{{ lower .Info.ImageFileName | replace " " "-" }})`) templateBuilder.WriteString("{{ end }}") templateBuilder.WriteString("{{ end }}") @@ -127,7 +127,7 @@ func getStepsTemplate() string { templateBuilder.WriteString("{{ end }}") templateBuilder.WriteString(`{{ define "cook.steps" }}`) - templateBuilder.WriteString("{{ range $i, $a := .Steps }}\n\n### Step {{add1 $i}}\n\n{{ wrap 120 .Directions }}{{- end }}") + templateBuilder.WriteString("{{ range $i, $a := .Steps }}\n\n### Step {{add1 $i}}\n\n{{ wrap $.Config.WordWrap .Directions }}{{- end }}") templateBuilder.WriteString("{{ end }}") templateBuilder.WriteString(`{{ define "cook.stepsSection" }}`) @@ -148,7 +148,7 @@ func getStepsWithQuotedCommentsTemplate() string { templateBuilder.WriteString(`{{ define "cook.stepsWithQuotedComments" }}`) templateBuilder.WriteString("{{ range $i, $a := .Steps }}") templateBuilder.WriteString("\n\n### Step {{add1 $i}}") - templateBuilder.WriteString("\n\n{{ wrap 120 .Directions }}") + templateBuilder.WriteString("\n\n{{ wrap $.Config.WordWrap .Directions }}") templateBuilder.WriteString("\n\n{{ range .Comments }}\n> {{.}}{{- end }}") templateBuilder.WriteString("{{- end }}") templateBuilder.WriteString("{{ end }}") @@ -171,7 +171,7 @@ func getStepsWithAdmonishedCommentsTemplate() string { templateBuilder.WriteString(`{{ define "cook.stepsWithAdmonishedComments" }}`) templateBuilder.WriteString("{{ range $i, $a := .Steps }}") templateBuilder.WriteString("\n\n### Step {{add1 $i}}") - templateBuilder.WriteString("\n\n{{ wrap 120 .Directions }}") + templateBuilder.WriteString("\n\n{{ wrap $.Config.WordWrap .Directions }}") templateBuilder.WriteString("\n\n{{ range .Comments }}") templateBuilder.WriteString("\n!!! note") templateBuilder.WriteString("\n{{ indent 6 . }}") @@ -319,8 +319,8 @@ func getDocumentationTemplates(recipeSearchRoot string, recipePath string, templ }, nil } -func newRecipeDocumentationTemplate(recipeSearchRoot string, recipeInfo cook.RecipeDocumentationInfo, templateFiles []string) (*template.Template, error) { - documentationTemplate := template.New(recipeInfo.RecipePath) +func newRecipeDocumentationTemplate(recipe *types.Recipe) (*template.Template, error) { + documentationTemplate := template.New(recipe.Info.RecipeFilePath) documentationTemplate.Funcs(sprig.TxtFuncMap()) documentationTemplate.Funcs(template.FuncMap{"getSource": func(source string) string { _, err := url.ParseRequestURI(source) @@ -358,7 +358,7 @@ func newRecipeDocumentationTemplate(recipeSearchRoot string, recipeInfo cook.Rec } }}) - goTemplateList, err := getDocumentationTemplates(recipeSearchRoot, recipeInfo.RecipePath, templateFiles) + goTemplateList, err := getDocumentationTemplates(recipe.Config.RecipeSearchRoot, recipe.Info.RecipeFilePath, recipe.Config.TemplateFiles) if err != nil { return nil, err } diff --git a/pkg/types/types.go b/pkg/types/types.go new file mode 100644 index 0000000..3e8247a --- /dev/null +++ b/pkg/types/types.go @@ -0,0 +1,29 @@ +package types + +import "github.com/aquilax/cooklang-go" + +type Config struct { + DryRun bool `mapstructure:"dry-run"` + Jsonify bool `mapstructure:"jsonify"` + IgnoreFile string `mapstructure:"ignore-file"` + RecipeSearchRoot string `mapstructure:"recipe-search-root"` + LogLevel string `mapstructure:"log-level"` + TemplateFiles []string `mapstructure:"template-files"` + WordWrap int `mapstructure:"word-wrap"` +} + +// Recipe contains a cooklang defined recipe +type Recipe struct { + Steps []cooklang.Step // list of steps for the recipe + Metadata cooklang.Metadata // metadata of the recipe + Config Config + Info Info +} + +type Info struct { + ImageFileName string + ImageFilePath string + NewRecipeFilePath string + RecipeName string + RecipeFilePath string +}