package main import ( "bytes" "fmt" "html/template" "os" "path/filepath" "regexp" "strings" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" "knative.dev/func/cmd" fn "knative.dev/func/pkg/functions" ) var ( // Helper function for indenting template values correctly fm = template.FuncMap{ "indent": func(i int, c string, v string) string { indentation := strings.Repeat(c, i) return indentation + strings.ReplaceAll(v, "\n", "\n"+indentation) }, "rootCmdUse": func() string { return rootName }, } rootName = "func" targetDir = "docs/reference" ) // String substitutions in command help docs type TemplateOptions struct { Name string Options string Use string } // This helper application generates markdown help documents func main() { // Ignore global config.yaml if it exists defer ignoreConfigEnv()() // Create a new client so that we can get builtin repo options client := fn.New() // Generate options for templates opts, err := cmd.RuntimeTemplateOptions(client) if err != nil { fmt.Printf("%s\n", err) } // Initialize an options struct templateOptions := TemplateOptions{ Name: rootName, Options: opts, Use: rootName, } // Create the root command var root = cmd.NewRootCmd(cmd.RootCommandConfig{Name: rootName}) // Write the markdown for the root command if err := writeMarkdown(root, rootName, templateOptions); err != nil { fmt.Fprintf(os.Stderr, "Error writing help markdown %s", err) } // Recurse all subcommands and write the markdown for them if err := processSubCommands(root, rootName, templateOptions); err != nil { fmt.Fprintf(os.Stderr, "Error processing subcommands %s", err) } } // ignoreConfigEnv sets FUNC_CONFIG_FILE to a nonexistent path and // returns a function which undoes this change. func ignoreConfigEnv() (done func()) { var ( env = "FUNC_CONFIG_FILE" v, ok = os.LookupEnv(env) ) os.Setenv(env, filepath.Join(os.TempDir(), "nonexistent.yaml")) return func() { if ok { os.Setenv(env, v) } else { os.Unsetenv(env) } } } // processSubCommands is a recursive function which writes the markdown text // for all subcommands of the provided cobra command, prepending the parent // string to the file name, and recursively calls itself for each subcommand. func processSubCommands(c *cobra.Command, parent string, opts TemplateOptions) error { for _, cc := range c.Commands() { if cc.Hidden { continue } name := cc.Name() if name == "help" { continue } if parent != "" { name = parent + "_" + name } opts.Use = cc.Use if err := writeMarkdown(cc, name, opts); err != nil { return err } if err := processSubCommands(cc, name, opts); err != nil { return err } } return nil } // writeMarkdown generates the untemplated markdown string for the given // command, then does standard template substitution on the generated markdown, // ultimately writing the markdown file to docs/reference/[command_name].md func writeMarkdown(c *cobra.Command, name string, opts TemplateOptions) error { out := new(bytes.Buffer) c.DisableAutoGenTag = true err := doc.GenMarkdown(c, out) if err != nil { return err } t := template.New("help") t.Funcs(fm) tpl := template.Must(t.Parse(out.String())) out.Reset() if err := tpl.Execute(out, opts); err != nil { fmt.Fprintf(os.Stderr, "unable to process help text: %v", err) } re := regexp.MustCompile(`[^\S\r\n]+\n`) data := re.ReplaceAll(out.Bytes(), []byte{'\n'}) // trim white spaces before EOL if err := os.WriteFile(targetDir+"/"+name+".md", data, 0666); err != nil { return err } return nil }