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 `<Num>_corso_command1_command2` where `<Num>` 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:
- [ ] 🌻 Feature
- [ ] 🐛 Bugfix
- [x] 🗺️ Documentation
- [ ] 🤖 Test
- [ ] 🐹 Trivial/Minor

## Issue(s)
<!-- Can reference multiple issues. Use one of the following "magic words" - "closes, fixes" to auto-close the Github issue. -->

* https://github.com/alcionai/corso/issues/536
* https://github.com/alcionai/corso/issues/528

## Test Plan

<!-- How will this be tested prior to merging.-->

- [x] 💪 Manual
- [ ]  Unit test
- [ ] 💚 E2E
This commit is contained in:
Georgi Matev 2022-09-08 23:15:24 -04:00 committed by GitHub
parent ca6331b66e
commit f509543856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -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")
})
}