Skip to content

Commit d84b849

Browse files
authored
fixes: multiple fixes (#1)
1 parent c5186c0 commit d84b849

File tree

3 files changed

+281
-59
lines changed

3 files changed

+281
-59
lines changed

cobra2snooty.go

Lines changed: 11 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ const (
3434
defaultExtension = ".txt"
3535
)
3636

37-
// GenSnootyTree generates the docs for the full tree of commands.
38-
func GenSnootyTree(cmd *cobra.Command, dir string) error {
37+
// GenTreeDocs generates the docs for the full tree of commands.
38+
func GenTreeDocs(cmd *cobra.Command, dir string) error {
3939
for _, c := range cmd.Commands() {
4040
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() {
4141
continue
4242
}
43-
if err := GenSnootyTree(c, dir); err != nil {
43+
if err := GenTreeDocs(c, dir); err != nil {
4444
return err
4545
}
4646
}
@@ -53,7 +53,7 @@ func GenSnootyTree(cmd *cobra.Command, dir string) error {
5353
}
5454
defer f.Close()
5555

56-
return GenSnootyCustom(cmd, f)
56+
return GenDocs(cmd, f)
5757
}
5858

5959
const toc = `
@@ -83,9 +83,9 @@ const tocHeader = `
8383
:titlesonly:
8484
`
8585

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

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

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

154156
if !cmd.DisableAutoGenTag {
155-
buf.WriteString("*Auto generated by MongoDB CLI on " + time.Now().Format("2-Jan-2006") + "*\n")
157+
buf.WriteString("*Auto generated by cobra2snooty on " + time.Now().Format("2-Jan-2006") + "*\n")
156158
}
157159
_, err := buf.WriteTo(w)
158160
return err
@@ -176,53 +178,3 @@ type byName []*cobra.Command
176178
func (s byName) Len() int { return len(s) }
177179
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
178180
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() }
179-
180-
const optionsHeader = `.. list-table::
181-
:header-rows: 1
182-
:widths: 20 10 10 60
183-
184-
* - Name
185-
- Type
186-
- Required
187-
- Description
188-
`
189-
190-
func printArgsSnooty(buf *bytes.Buffer, cmd *cobra.Command) {
191-
if args, ok := cmd.Annotations["args"]; ok {
192-
buf.WriteString("Arguments\n")
193-
buf.WriteString("---------\n\n")
194-
buf.WriteString(optionsHeader)
195-
var requiredSlice []string
196-
if requiredArgs, hasRequired := cmd.Annotations["requiredArgs"]; hasRequired {
197-
requiredSlice = strings.Split(requiredArgs, ",")
198-
}
199-
200-
for _, arg := range strings.Split(args, ",") {
201-
required := stringInSlice(requiredSlice, arg)
202-
description := cmd.Annotations[arg+"Desc"]
203-
line := fmt.Sprintf(" * - %s\n - string\n - %v\n - %s", arg, required, description)
204-
buf.WriteString(line)
205-
}
206-
buf.WriteString("\n\n")
207-
}
208-
}
209-
210-
func printOptionsSnooty(buf *bytes.Buffer, cmd *cobra.Command) {
211-
flags := cmd.NonInheritedFlags()
212-
if flags.HasAvailableFlags() {
213-
buf.WriteString("Options\n")
214-
buf.WriteString("-------\n\n")
215-
buf.WriteString(optionsHeader)
216-
buf.WriteString(indentString(FlagUsages(flags), " "))
217-
buf.WriteString("\n")
218-
}
219-
220-
parentFlags := cmd.InheritedFlags()
221-
if parentFlags.HasAvailableFlags() {
222-
buf.WriteString("Inherited Options\n")
223-
buf.WriteString("-----------------\n\n")
224-
buf.WriteString(optionsHeader)
225-
buf.WriteString(indentString(FlagUsages(parentFlags), " "))
226-
buf.WriteString("\n")
227-
}
228-
}

cobra2snooty_test.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package cobra2snooty
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func emptyRun(*cobra.Command, []string) {}
15+
16+
var rootCmd *cobra.Command
17+
var echoCmd *cobra.Command
18+
19+
func Root() *cobra.Command {
20+
if rootCmd != nil {
21+
return rootCmd
22+
}
23+
rootCmd = &cobra.Command{
24+
Use: "root",
25+
Short: "Root short description",
26+
Long: "Root long description",
27+
Run: emptyRun,
28+
}
29+
rootCmd.PersistentFlags().StringP("rootflag", "r", "two", "")
30+
rootCmd.PersistentFlags().StringP("strtwo", "t", "two", "help message for parent flag strtwo")
31+
32+
printCmd := &cobra.Command{
33+
Use: "print [string to print]",
34+
Short: "Print anything to the screen",
35+
Long: `an absolutely utterly useless command for testing.`,
36+
}
37+
printCmd.PersistentFlags().StringP("strthree", "s", "three", "help message for flag strthree")
38+
printCmd.Flags().IntP("intthree", "i", 345, "help message for flag intthree")
39+
printCmd.Flags().BoolP("boolthree", "b", true, "help message for flag boolthree")
40+
41+
dummyCmd := &cobra.Command{
42+
Use: "dummy [action]",
43+
Short: "Performs a dummy action",
44+
}
45+
46+
rootCmd.AddCommand(printCmd, Echo(), dummyCmd)
47+
return rootCmd
48+
}
49+
50+
func Echo() *cobra.Command {
51+
if echoCmd != nil {
52+
return echoCmd
53+
}
54+
echoCmd = &cobra.Command{
55+
Use: "echo [string to echo]",
56+
Aliases: []string{"say"},
57+
Short: "Echo anything to the screen",
58+
Long: "an utterly useless command for testing",
59+
Example: "Just run root echo",
60+
}
61+
echoCmd.PersistentFlags().StringP("strone", "s", "one", "help message for flag strone")
62+
echoCmd.PersistentFlags().BoolP("persistentbool", "p", false, "help message for flag persistentbool")
63+
echoCmd.Flags().IntP("intone", "i", 123, "help message for flag intone")
64+
echoCmd.Flags().BoolP("boolone", "b", true, "help message for flag boolone")
65+
66+
timesCmd := &cobra.Command{
67+
Use: "times [# times] [string to echo]",
68+
SuggestFor: []string{"counts"},
69+
Short: "Echo anything to the screen more times",
70+
Long: `a slightly useless command for testing.`,
71+
Run: emptyRun,
72+
}
73+
timesCmd.PersistentFlags().StringP("strtwo", "t", "2", "help message for child flag strtwo")
74+
timesCmd.Flags().IntP("inttwo", "j", 234, "help message for flag inttwo")
75+
timesCmd.Flags().BoolP("booltwo", "c", false, "help message for flag booltwo")
76+
77+
echoCmd.AddCommand(timesCmd, EchoSubCmd(), deprecatedCmd)
78+
return echoCmd
79+
}
80+
81+
var echoSubCmd *cobra.Command
82+
83+
func EchoSubCmd() *cobra.Command {
84+
if echoSubCmd != nil {
85+
return echoSubCmd
86+
}
87+
echoSubCmd = &cobra.Command{
88+
Use: "echosub [string to print]",
89+
Short: "second sub command for echo",
90+
Long: "an absolutely utterly useless command for testing gendocs!.",
91+
Run: emptyRun,
92+
}
93+
return echoSubCmd
94+
}
95+
96+
var deprecatedCmd = &cobra.Command{
97+
Use: "deprecated [can't do anything here]",
98+
Short: "A command which is deprecated",
99+
Long: `an absolutely utterly useless command for testing deprecation!.`,
100+
Deprecated: "Please use echo instead",
101+
}
102+
103+
func TestGenDocs(t *testing.T) {
104+
// We generate on a subcommand so we have both subcommands and parents
105+
buf := new(bytes.Buffer)
106+
Root() // init root
107+
if err := GenDocs(Echo(), buf); err != nil {
108+
t.Fatal(err)
109+
}
110+
output := buf.String()
111+
112+
checkStringContains(t, output, Echo().Long)
113+
checkStringContains(t, output, Echo().Example)
114+
checkStringContains(t, output, "boolone")
115+
checkStringContains(t, output, "rootflag")
116+
checkStringOmits(t, output, Root().Short)
117+
checkStringContains(t, output, EchoSubCmd().Short)
118+
checkStringOmits(t, output, deprecatedCmd.Short)
119+
}
120+
121+
func TestGenDocsNoHiddenParents(t *testing.T) {
122+
// We generate on a subcommand so we have both subcommands and parents
123+
for _, name := range []string{"rootflag", "strtwo"} {
124+
f := Root().PersistentFlags().Lookup(name)
125+
f.Hidden = true
126+
defer func() { f.Hidden = false }()
127+
}
128+
buf := new(bytes.Buffer)
129+
if err := GenDocs(Echo(), buf); err != nil {
130+
t.Fatal(err)
131+
}
132+
output := buf.String()
133+
134+
checkStringContains(t, output, Echo().Long)
135+
checkStringContains(t, output, Echo().Example)
136+
checkStringContains(t, output, "boolone")
137+
checkStringOmits(t, output, "rootflag")
138+
checkStringOmits(t, output, Root().Short)
139+
checkStringContains(t, output, Echo().Short)
140+
checkStringOmits(t, output, deprecatedCmd.Short)
141+
checkStringOmits(t, output, "Options inherited from parent commands")
142+
}
143+
144+
func TestGenDocsNoTag(t *testing.T) {
145+
Root().DisableAutoGenTag = true
146+
defer func() { Root().DisableAutoGenTag = false }()
147+
148+
buf := new(bytes.Buffer)
149+
if err := GenDocs(Root(), buf); err != nil {
150+
t.Fatal(err)
151+
}
152+
output := buf.String()
153+
154+
unexpected := "Auto generated"
155+
checkStringOmits(t, output, unexpected)
156+
}
157+
158+
func TestGenTreeDocs(t *testing.T) {
159+
c := &cobra.Command{Use: "do [OPTIONS] arg1 arg2"}
160+
161+
tmpdir, err := ioutil.TempDir("", "test-gen-rst-tree")
162+
if err != nil {
163+
t.Fatalf("Failed to create tmpdir: %s", err.Error())
164+
}
165+
defer os.RemoveAll(tmpdir)
166+
167+
if err := GenTreeDocs(c, tmpdir); err != nil {
168+
t.Fatalf("GenReSTTree failed: %s", err.Error())
169+
}
170+
171+
if _, err := os.Stat(filepath.Join(tmpdir, "do.txt")); err != nil {
172+
t.Fatalf("Expected file 'do.rst' to exist")
173+
}
174+
}
175+
176+
func BenchmarkGenDocsToFile(b *testing.B) {
177+
file, err := ioutil.TempFile("", "")
178+
if err != nil {
179+
b.Fatal(err)
180+
}
181+
defer os.Remove(file.Name())
182+
defer file.Close()
183+
184+
b.ResetTimer()
185+
for i := 0; i < b.N; i++ {
186+
if err := GenDocs(Root(), file); err != nil {
187+
b.Fatal(err)
188+
}
189+
}
190+
}
191+
192+
func checkStringContains(t *testing.T, got, expected string) {
193+
t.Helper()
194+
if !strings.Contains(got, expected) {
195+
t.Errorf("Expected to contain: \n %v\nGot:\n %v\n", expected, got)
196+
}
197+
}
198+
199+
func checkStringOmits(t *testing.T, got, expected string) {
200+
t.Helper()
201+
if strings.Contains(got, expected) {
202+
t.Errorf("Expected to not contain: \n %v\nGot: %v", expected, got)
203+
}
204+
}

options.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cobra2snooty
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
const optionsHeader = `.. list-table::
13+
:header-rows: 1
14+
:widths: 20 10 10 60
15+
16+
* - Name
17+
- Type
18+
- Required
19+
- Description
20+
`
21+
22+
var ErrMissingDescription = errors.New("missing description")
23+
24+
func printArgs(buf *bytes.Buffer, cmd *cobra.Command) error {
25+
if args, ok := cmd.Annotations["args"]; ok {
26+
buf.WriteString("Arguments\n")
27+
buf.WriteString("---------\n\n")
28+
buf.WriteString(optionsHeader)
29+
var requiredSlice []string
30+
if requiredArgs, hasRequired := cmd.Annotations["requiredArgs"]; hasRequired {
31+
requiredSlice = strings.Split(requiredArgs, ",")
32+
}
33+
34+
for _, arg := range strings.Split(args, ",") {
35+
required := stringInSlice(requiredSlice, arg)
36+
if description, hasDescription := cmd.Annotations[arg+"Desc"]; hasDescription {
37+
line := fmt.Sprintf(" * - %s\n - string\n - %v\n - %s", arg, required, description)
38+
buf.WriteString(line)
39+
} else {
40+
return fmt.Errorf("%w: %s", ErrMissingDescription, arg)
41+
}
42+
}
43+
buf.WriteString("\n\n")
44+
}
45+
return nil
46+
}
47+
48+
func printOptions(buf *bytes.Buffer, cmd *cobra.Command) {
49+
flags := cmd.NonInheritedFlags()
50+
if flags.HasAvailableFlags() {
51+
buf.WriteString("Options\n")
52+
buf.WriteString("-------\n\n")
53+
buf.WriteString(optionsHeader)
54+
buf.WriteString(indentString(FlagUsages(flags), " "))
55+
buf.WriteString("\n")
56+
}
57+
58+
parentFlags := cmd.InheritedFlags()
59+
if parentFlags.HasAvailableFlags() {
60+
buf.WriteString("Inherited Options\n")
61+
buf.WriteString("-----------------\n\n")
62+
buf.WriteString(optionsHeader)
63+
buf.WriteString(indentString(FlagUsages(parentFlags), " "))
64+
buf.WriteString("\n")
65+
}
66+
}

0 commit comments

Comments
 (0)