Skip to content

Commit

Permalink
fixes: multiple fixes (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
gssbzn authored Jul 12, 2021
1 parent c5186c0 commit d84b849
Show file tree
Hide file tree
Showing 3 changed files with 281 additions and 59 deletions.
70 changes: 11 additions & 59 deletions cobra2snooty.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ const (
defaultExtension = ".txt"
)

// GenSnootyTree generates the docs for the full tree of commands.
func GenSnootyTree(cmd *cobra.Command, dir string) error {
// GenTreeDocs generates the docs for the full tree of commands.
func GenTreeDocs(cmd *cobra.Command, dir string) error {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
continue
}
if err := GenSnootyTree(c, dir); err != nil {
if err := GenTreeDocs(c, dir); err != nil {
return err
}
}
Expand All @@ -53,7 +53,7 @@ func GenSnootyTree(cmd *cobra.Command, dir string) error {
}
defer f.Close()

return GenSnootyCustom(cmd, f)
return GenDocs(cmd, f)
}

const toc = `
Expand Down Expand Up @@ -83,9 +83,9 @@ const tocHeader = `
:titlesonly:
`

// GenSnootyCustom creates custom reStructured Text output.
// GenDocs creates snooty help output.
// Adapted from https://github.com/spf13/cobra/tree/master/doc to match MongoDB tooling and style.
func GenSnootyCustom(cmd *cobra.Command, w io.Writer) error {
func GenDocs(cmd *cobra.Command, w io.Writer) error {
cmd.InitDefaultHelpCmd()
cmd.InitDefaultHelpFlag()

Expand All @@ -109,8 +109,10 @@ func GenSnootyCustom(cmd *cobra.Command, w io.Writer) error {
buf.WriteString(syntaxHeader)
buf.WriteString(fmt.Sprintf("\n %s\n\n", strings.ReplaceAll(cmd.UseLine(), "[flags]", "[options]")))
}
printArgsSnooty(buf, cmd)
printOptionsSnooty(buf, cmd)
if err := printArgs(buf, cmd); err != nil {
return err
}
printOptions(buf, cmd)

if len(cmd.Example) > 0 {
buf.WriteString(examplesHeader)
Expand Down Expand Up @@ -152,7 +154,7 @@ func GenSnootyCustom(cmd *cobra.Command, w io.Writer) error {
}

if !cmd.DisableAutoGenTag {
buf.WriteString("*Auto generated by MongoDB CLI on " + time.Now().Format("2-Jan-2006") + "*\n")
buf.WriteString("*Auto generated by cobra2snooty on " + time.Now().Format("2-Jan-2006") + "*\n")
}
_, err := buf.WriteTo(w)
return err
Expand All @@ -176,53 +178,3 @@ type byName []*cobra.Command
func (s byName) Len() int { return len(s) }
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }

const optionsHeader = `.. list-table::
:header-rows: 1
:widths: 20 10 10 60
* - Name
- Type
- Required
- Description
`

func printArgsSnooty(buf *bytes.Buffer, cmd *cobra.Command) {
if args, ok := cmd.Annotations["args"]; ok {
buf.WriteString("Arguments\n")
buf.WriteString("---------\n\n")
buf.WriteString(optionsHeader)
var requiredSlice []string
if requiredArgs, hasRequired := cmd.Annotations["requiredArgs"]; hasRequired {
requiredSlice = strings.Split(requiredArgs, ",")
}

for _, arg := range strings.Split(args, ",") {
required := stringInSlice(requiredSlice, arg)
description := cmd.Annotations[arg+"Desc"]
line := fmt.Sprintf(" * - %s\n - string\n - %v\n - %s", arg, required, description)
buf.WriteString(line)
}
buf.WriteString("\n\n")
}
}

func printOptionsSnooty(buf *bytes.Buffer, cmd *cobra.Command) {
flags := cmd.NonInheritedFlags()
if flags.HasAvailableFlags() {
buf.WriteString("Options\n")
buf.WriteString("-------\n\n")
buf.WriteString(optionsHeader)
buf.WriteString(indentString(FlagUsages(flags), " "))
buf.WriteString("\n")
}

parentFlags := cmd.InheritedFlags()
if parentFlags.HasAvailableFlags() {
buf.WriteString("Inherited Options\n")
buf.WriteString("-----------------\n\n")
buf.WriteString(optionsHeader)
buf.WriteString(indentString(FlagUsages(parentFlags), " "))
buf.WriteString("\n")
}
}
204 changes: 204 additions & 0 deletions cobra2snooty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package cobra2snooty

import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"github.com/spf13/cobra"
)

func emptyRun(*cobra.Command, []string) {}

var rootCmd *cobra.Command
var echoCmd *cobra.Command

func Root() *cobra.Command {
if rootCmd != nil {
return rootCmd
}
rootCmd = &cobra.Command{
Use: "root",
Short: "Root short description",
Long: "Root long description",
Run: emptyRun,
}
rootCmd.PersistentFlags().StringP("rootflag", "r", "two", "")
rootCmd.PersistentFlags().StringP("strtwo", "t", "two", "help message for parent flag strtwo")

printCmd := &cobra.Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
Long: `an absolutely utterly useless command for testing.`,
}
printCmd.PersistentFlags().StringP("strthree", "s", "three", "help message for flag strthree")
printCmd.Flags().IntP("intthree", "i", 345, "help message for flag intthree")
printCmd.Flags().BoolP("boolthree", "b", true, "help message for flag boolthree")

dummyCmd := &cobra.Command{
Use: "dummy [action]",
Short: "Performs a dummy action",
}

rootCmd.AddCommand(printCmd, Echo(), dummyCmd)
return rootCmd
}

func Echo() *cobra.Command {
if echoCmd != nil {
return echoCmd
}
echoCmd = &cobra.Command{
Use: "echo [string to echo]",
Aliases: []string{"say"},
Short: "Echo anything to the screen",
Long: "an utterly useless command for testing",
Example: "Just run root echo",
}
echoCmd.PersistentFlags().StringP("strone", "s", "one", "help message for flag strone")
echoCmd.PersistentFlags().BoolP("persistentbool", "p", false, "help message for flag persistentbool")
echoCmd.Flags().IntP("intone", "i", 123, "help message for flag intone")
echoCmd.Flags().BoolP("boolone", "b", true, "help message for flag boolone")

timesCmd := &cobra.Command{
Use: "times [# times] [string to echo]",
SuggestFor: []string{"counts"},
Short: "Echo anything to the screen more times",
Long: `a slightly useless command for testing.`,
Run: emptyRun,
}
timesCmd.PersistentFlags().StringP("strtwo", "t", "2", "help message for child flag strtwo")
timesCmd.Flags().IntP("inttwo", "j", 234, "help message for flag inttwo")
timesCmd.Flags().BoolP("booltwo", "c", false, "help message for flag booltwo")

echoCmd.AddCommand(timesCmd, EchoSubCmd(), deprecatedCmd)
return echoCmd
}

var echoSubCmd *cobra.Command

func EchoSubCmd() *cobra.Command {
if echoSubCmd != nil {
return echoSubCmd
}
echoSubCmd = &cobra.Command{
Use: "echosub [string to print]",
Short: "second sub command for echo",
Long: "an absolutely utterly useless command for testing gendocs!.",
Run: emptyRun,
}
return echoSubCmd
}

var deprecatedCmd = &cobra.Command{
Use: "deprecated [can't do anything here]",
Short: "A command which is deprecated",
Long: `an absolutely utterly useless command for testing deprecation!.`,
Deprecated: "Please use echo instead",
}

func TestGenDocs(t *testing.T) {
// We generate on a subcommand so we have both subcommands and parents
buf := new(bytes.Buffer)
Root() // init root
if err := GenDocs(Echo(), buf); err != nil {
t.Fatal(err)
}
output := buf.String()

checkStringContains(t, output, Echo().Long)
checkStringContains(t, output, Echo().Example)
checkStringContains(t, output, "boolone")
checkStringContains(t, output, "rootflag")
checkStringOmits(t, output, Root().Short)
checkStringContains(t, output, EchoSubCmd().Short)
checkStringOmits(t, output, deprecatedCmd.Short)
}

func TestGenDocsNoHiddenParents(t *testing.T) {
// We generate on a subcommand so we have both subcommands and parents
for _, name := range []string{"rootflag", "strtwo"} {
f := Root().PersistentFlags().Lookup(name)
f.Hidden = true
defer func() { f.Hidden = false }()
}
buf := new(bytes.Buffer)
if err := GenDocs(Echo(), buf); err != nil {
t.Fatal(err)
}
output := buf.String()

checkStringContains(t, output, Echo().Long)
checkStringContains(t, output, Echo().Example)
checkStringContains(t, output, "boolone")
checkStringOmits(t, output, "rootflag")
checkStringOmits(t, output, Root().Short)
checkStringContains(t, output, Echo().Short)
checkStringOmits(t, output, deprecatedCmd.Short)
checkStringOmits(t, output, "Options inherited from parent commands")
}

func TestGenDocsNoTag(t *testing.T) {
Root().DisableAutoGenTag = true
defer func() { Root().DisableAutoGenTag = false }()

buf := new(bytes.Buffer)
if err := GenDocs(Root(), buf); err != nil {
t.Fatal(err)
}
output := buf.String()

unexpected := "Auto generated"
checkStringOmits(t, output, unexpected)
}

func TestGenTreeDocs(t *testing.T) {
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}

tmpdir, err := ioutil.TempDir("", "test-gen-rst-tree")
if err != nil {
t.Fatalf("Failed to create tmpdir: %s", err.Error())
}
defer os.RemoveAll(tmpdir)

if err := GenTreeDocs(c, tmpdir); err != nil {
t.Fatalf("GenReSTTree failed: %s", err.Error())
}

if _, err := os.Stat(filepath.Join(tmpdir, "do.txt")); err != nil {
t.Fatalf("Expected file 'do.rst' to exist")
}
}

func BenchmarkGenDocsToFile(b *testing.B) {
file, err := ioutil.TempFile("", "")
if err != nil {
b.Fatal(err)
}
defer os.Remove(file.Name())
defer file.Close()

b.ResetTimer()
for i := 0; i < b.N; i++ {
if err := GenDocs(Root(), file); err != nil {
b.Fatal(err)
}
}
}

func checkStringContains(t *testing.T, got, expected string) {
t.Helper()
if !strings.Contains(got, expected) {
t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
}
}

func checkStringOmits(t *testing.T, got, expected string) {
t.Helper()
if strings.Contains(got, expected) {
t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got)
}
}
66 changes: 66 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package cobra2snooty

import (
"bytes"
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"
)

const optionsHeader = `.. list-table::
:header-rows: 1
:widths: 20 10 10 60
* - Name
- Type
- Required
- Description
`

var ErrMissingDescription = errors.New("missing description")

func printArgs(buf *bytes.Buffer, cmd *cobra.Command) error {
if args, ok := cmd.Annotations["args"]; ok {
buf.WriteString("Arguments\n")
buf.WriteString("---------\n\n")
buf.WriteString(optionsHeader)
var requiredSlice []string
if requiredArgs, hasRequired := cmd.Annotations["requiredArgs"]; hasRequired {
requiredSlice = strings.Split(requiredArgs, ",")
}

for _, arg := range strings.Split(args, ",") {
required := stringInSlice(requiredSlice, arg)
if description, hasDescription := cmd.Annotations[arg+"Desc"]; hasDescription {
line := fmt.Sprintf(" * - %s\n - string\n - %v\n - %s", arg, required, description)
buf.WriteString(line)
} else {
return fmt.Errorf("%w: %s", ErrMissingDescription, arg)
}
}
buf.WriteString("\n\n")
}
return nil
}

func printOptions(buf *bytes.Buffer, cmd *cobra.Command) {
flags := cmd.NonInheritedFlags()
if flags.HasAvailableFlags() {
buf.WriteString("Options\n")
buf.WriteString("-------\n\n")
buf.WriteString(optionsHeader)
buf.WriteString(indentString(FlagUsages(flags), " "))
buf.WriteString("\n")
}

parentFlags := cmd.InheritedFlags()
if parentFlags.HasAvailableFlags() {
buf.WriteString("Inherited Options\n")
buf.WriteString("-----------------\n\n")
buf.WriteString(optionsHeader)
buf.WriteString(indentString(FlagUsages(parentFlags), " "))
buf.WriteString("\n")
}
}

0 comments on commit d84b849

Please sign in to comment.