From f5095438569a67d921d310fec06dea1807fb3065 Mon Sep 17 00:00:00 2001 From: Georgi Matev Date: Thu, 8 Sep 2022 23:15:24 -0400 Subject: [PATCH] Custom output template for auto-generated docs (#785) ## Description Introduces a Corso specific (more desirable) formatting for the auto generated documentation. More or less an adaptation or https://github.com/spf13/cobra/blob/main/doc/md_docs.go. Unfortunately the original package does not have sufficient hooks to allow for plugging in a new renderer. Other approaches considered: * [Override the UsageTemplate ](https://github.com/spf13/cobra/blob/main/user_guide.md#defining-your-own-help) - unfortunately does not apply to Markdown generation * Use a golang template for the docs output - unfortunately the `pflags` package does not have a suitable way of iterating over the flags that can be easily fed into a template. Follow on work: * ~~Styling of flags tables as part of docs rendering - Done~~ * Opens up possibility for flag ordering - need to set `pflags.SortFlags = false` before parsing the flags - https://github.com/alcionai/corso/issues/783 * Can simplify sidebar command maintenance by naming the rendered docs files line similar to `_corso_command1_command2` where `` can be set as annotation on the commands. Order in the navbar can be set as part of command authoring - https://github.com/alcionai/corso/issues/784 ## Type of change Please check the type of change your PR introduces: - [ ] :sunflower: Feature - [ ] :bug: Bugfix - [x] :world_map: Documentation - [ ] :robot: Test - [ ] :hamster: Trivial/Minor ## Issue(s) * https://github.com/alcionai/corso/issues/536 * https://github.com/alcionai/corso/issues/528 ## Test Plan - [x] :muscle: Manual - [ ] :zap: Unit test - [ ] :green_heart: E2E --- src/cmd/mdgen/mdgen.go | 134 +++++++++++++++++++++++++++++++++++------ 1 file changed, 117 insertions(+), 17 deletions(-) diff --git a/src/cmd/mdgen/mdgen.go b/src/cmd/mdgen/mdgen.go index c735523d0..9786e1e9e 100644 --- a/src/cmd/mdgen/mdgen.go +++ b/src/cmd/mdgen/mdgen.go @@ -1,14 +1,16 @@ package main import ( + "bytes" "fmt" + "io" "os" "path/filepath" "strings" "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" + "github.com/spf13/pflag" "github.com/alcionai/corso/src/cli" ) @@ -27,12 +29,6 @@ var cmd = &cobra.Command{ Run: genDocs, } -const fmTemplate = `--- -title: "%s" -hide_title: true ---- -` - func main() { cmd. PersistentFlags(). @@ -48,22 +44,13 @@ func main() { } func genDocs(cmd *cobra.Command, args []string) { - identity := func(s string) string { return s } - filePrepender := func(filename string) string { - name := filepath.Base(filename) - base := strings.TrimSuffix(name, filepath.Ext(name)) - - return fmt.Sprintf(fmTemplate, strings.Replace(base, "_", " ", -1)) - } - if err := makeDir(cliMarkdownDir); err != nil { fatal(errors.Wrap(err, "preparing directory for markdown generation")) } corsoCmd := cli.CorsoCommand() - corsoCmd.DisableAutoGenTag = true - err := doc.GenMarkdownTreeCustom(corsoCmd, cliMarkdownDir, filePrepender, identity) + err := genMarkdownCorso(corsoCmd, cliMarkdownDir) if err != nil { fatal(errors.Wrap(err, "generating the Corso CLI markdown")) } @@ -97,3 +84,116 @@ func fatal(err error) { fmt.Fprintf(os.Stderr, "ERR: %v\n", err) os.Exit(1) } + +// Adapted from https://github.com/spf13/cobra/blob/main/doc/md_docs.go for Corso specific formatting +func genMarkdownCorso(cmd *cobra.Command, dir string) error { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { + continue + } + + if err := genMarkdownCorso(c, dir); err != nil { + return err + } + } + + // Skip docs for non-leaf commands + if !cmd.Runnable() || cmd.HasSubCommands() { + return nil + } + + basename := strings.ReplaceAll(cmd.CommandPath(), " ", "_") + ".md" + filename := filepath.Join(dir, basename) + + f, err := os.Create(filename) + if err != nil { + return err + } + + defer f.Close() + + return genMarkdownCustomCorso(cmd, f) +} + +func genMarkdownCustomCorso(cmd *cobra.Command, w io.Writer) error { + cmd.InitDefaultHelpCmd() + cmd.InitDefaultHelpFlag() + + buf := new(bytes.Buffer) + name := cmd.CommandPath() + + // frontMatter section + buf.WriteString("---\n") + buf.WriteString(fmt.Sprintf("title: %s\n", name)) + buf.WriteString("hide_title: true\n") + buf.WriteString("---\n") + + // actual markdown + buf.WriteString("## " + name + "\n\n") + + if len(cmd.Long) > 0 { + buf.WriteString(cmd.Long + "\n\n") + } else { + buf.WriteString(cmd.Short + "\n\n") + } + + if cmd.Runnable() { + buf.WriteString(fmt.Sprintf("```bash\n%s\n```\n", cmd.UseLine())) + } + + flags := cmd.NonInheritedFlags() + if flags.HasAvailableFlags() { + buf.WriteString("\n") + buf.WriteString("### Flags\n\n") + printFlags(buf, flags) + } + + parentFlags := cmd.InheritedFlags() + if parentFlags.HasAvailableFlags() { + buf.WriteString("\n") + buf.WriteString("### Global and inherited flags\n\n") + printFlags(buf, parentFlags) + } + + if len(cmd.Example) > 0 { + buf.WriteString("\n### Examples\n\n") + buf.WriteString(fmt.Sprintf("```\n%s\n```\n\n", cmd.Example)) + } + + _, err := buf.WriteTo(w) + + return err +} + +func printFlags(buf *bytes.Buffer, flags *pflag.FlagSet) { + if !flags.HasAvailableFlags() { + return + } + + buf.WriteString("|Flag|Short|Default|Help\n") + buf.WriteString("|:----|:-----|:-------|:----\n") + + flags.VisitAll(func(flag *pflag.Flag) { + if flag.Hidden { + return + } + + buf.WriteString("|") + buf.WriteString(fmt.Sprintf("`--%s`", flag.Name)) + buf.WriteString("|") + + if flag.Shorthand != "" && flag.ShorthandDeprecated == "" { + buf.WriteString(fmt.Sprintf("`-%s`", flag.Shorthand)) + } + + buf.WriteString("|") + + if flag.DefValue != "" { + buf.WriteString(fmt.Sprintf("`%s`", flag.DefValue)) + } + + buf.WriteString("|") + buf.WriteString(flag.Usage) + buf.WriteString("\n") + }) +}