From b3f29e98e63a3618988a231fd02b45c1e6369d4f Mon Sep 17 00:00:00 2001 From: Frank Schroeder Date: Tue, 24 Nov 2015 00:19:16 +0100 Subject: [PATCH] Issue #195: Move doc generation into separate pkg * Move man_docs and md_docs into new doc pkg * Replace *bytes.Buffer with io.Writer * Replace c == cmd.helpCommand with c.IsHelpCommand() * Remove redundant len(children) == 0 check in HasSeeAlso * Duplicate test setup for doc generation --- doc/cmd_test.go | 145 +++++++++++++++++++ man_docs.go => doc/man_docs.go | 38 ++--- man_docs.md => doc/man_docs.md | 0 man_docs_test.go => doc/man_docs_test.go | 7 +- examples_test.go => doc/man_examples_test.go | 11 +- md_docs.go => doc/md_docs.go | 53 +++---- md_docs.md => doc/md_docs.md | 8 +- md_docs_test.go => doc/md_docs_test.go | 2 +- doc_util.go => doc/util.go | 20 ++- 9 files changed, 203 insertions(+), 81 deletions(-) create mode 100644 doc/cmd_test.go rename man_docs.go => doc/man_docs.go (84%) rename man_docs.md => doc/man_docs.md (100%) rename man_docs_test.go => doc/man_docs_test.go (97%) rename examples_test.go => doc/man_examples_test.go (69%) rename md_docs.go => doc/md_docs.go (66%) rename md_docs.md => doc/md_docs.md (95%) rename md_docs_test.go => doc/md_docs_test.go (99%) rename doc_util.go => doc/util.go (69%) diff --git a/doc/cmd_test.go b/doc/cmd_test.go new file mode 100644 index 0000000..d49d186 --- /dev/null +++ b/doc/cmd_test.go @@ -0,0 +1,145 @@ +package doc + +import ( + "bytes" + "fmt" + "runtime" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +var flagb1, flagb2, flagb3, flagbr, flagbp bool +var flags1, flags2a, flags2b, flags3, outs string +var flagi1, flagi2, flagi3, flagir int + +const strtwoParentHelp = "help message for parent flag strtwo" +const strtwoChildHelp = "help message for child flag strtwo" + +var cmdEcho = &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 cobra-test echo", +} + +var cmdEchoSub = &cobra.Command{ + Use: "echosub [string to print]", + Short: "second sub command for echo", + Long: `an absolutely utterly useless command for testing gendocs!.`, + Run: func(cmd *cobra.Command, args []string) {}, +} + +var cmdDeprecated = &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", +} + +var cmdTimes = &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.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) {}, + Run: func(cmd *cobra.Command, args []string) {}, +} + +var cmdPrint = &cobra.Command{ + Use: "print [string to print]", + Short: "Print anything to the screen", + Long: `an absolutely utterly useless command for testing.`, +} + +var cmdRootNoRun = &cobra.Command{ + Use: "cobra-test", + Short: "The root can run its own function", + Long: "The root description for help", +} + +var cmdRootSameName = &cobra.Command{ + Use: "print", + Short: "Root with the same name as a subcommand", + Long: "The root description for help", +} + +var cmdRootWithRun = &cobra.Command{ + Use: "cobra-test", + Short: "The root can run its own function", + Long: "The root description for help", +} + +var cmdSubNoRun = &cobra.Command{ + Use: "subnorun", + Short: "A subcommand without a Run function", + Long: "A long output about a subcommand without a Run function", +} + +var cmdVersion1 = &cobra.Command{ + Use: "version", + Short: "Print the version number", + Long: `First version of the version command`, +} + +var cmdVersion2 = &cobra.Command{ + Use: "version", + Short: "Print the version number", + Long: `Second version of the version command`, +} + +func flagInit() { + cmdEcho.ResetFlags() + cmdPrint.ResetFlags() + cmdTimes.ResetFlags() + cmdRootNoRun.ResetFlags() + cmdRootSameName.ResetFlags() + cmdRootWithRun.ResetFlags() + cmdSubNoRun.ResetFlags() + cmdRootNoRun.PersistentFlags().StringVarP(&flags2a, "strtwo", "t", "two", strtwoParentHelp) + cmdEcho.Flags().IntVarP(&flagi1, "intone", "i", 123, "help message for flag intone") + cmdTimes.Flags().IntVarP(&flagi2, "inttwo", "j", 234, "help message for flag inttwo") + cmdPrint.Flags().IntVarP(&flagi3, "intthree", "i", 345, "help message for flag intthree") + cmdEcho.PersistentFlags().StringVarP(&flags1, "strone", "s", "one", "help message for flag strone") + cmdEcho.PersistentFlags().BoolVarP(&flagbp, "persistentbool", "p", false, "help message for flag persistentbool") + cmdTimes.PersistentFlags().StringVarP(&flags2b, "strtwo", "t", "2", strtwoChildHelp) + cmdPrint.PersistentFlags().StringVarP(&flags3, "strthree", "s", "three", "help message for flag strthree") + cmdEcho.Flags().BoolVarP(&flagb1, "boolone", "b", true, "help message for flag boolone") + cmdTimes.Flags().BoolVarP(&flagb2, "booltwo", "c", false, "help message for flag booltwo") + cmdPrint.Flags().BoolVarP(&flagb3, "boolthree", "b", true, "help message for flag boolthree") + cmdVersion1.ResetFlags() + cmdVersion2.ResetFlags() +} + +func initializeWithRootCmd() *cobra.Command { + cmdRootWithRun.ResetCommands() + flagInit() + cmdRootWithRun.Flags().BoolVarP(&flagbr, "boolroot", "b", false, "help message for flag boolroot") + cmdRootWithRun.Flags().IntVarP(&flagir, "introot", "i", 321, "help message for flag introot") + return cmdRootWithRun +} + +func checkStringContains(t *testing.T, found, expected string) { + if !strings.Contains(found, expected) { + logErr(t, found, expected) + } +} + +func checkStringOmits(t *testing.T, found, expected string) { + if strings.Contains(found, expected) { + logErr(t, found, expected) + } +} + +func logErr(t *testing.T, found, expected string) { + out := new(bytes.Buffer) + + _, _, line, ok := runtime.Caller(2) + if ok { + fmt.Fprintf(out, "Line: %d ", line) + } + fmt.Fprintf(out, "Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) + t.Errorf(out.String()) +} diff --git a/man_docs.go b/doc/man_docs.go similarity index 84% rename from man_docs.go rename to doc/man_docs.go index c168b62..71e020d 100644 --- a/man_docs.go +++ b/doc/man_docs.go @@ -11,36 +11,33 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cobra +package doc import ( "bytes" "fmt" + "io" "os" "sort" "strings" "time" mangen "github.com/cpuguy83/go-md2man/md2man" + "github.com/spf13/cobra" "github.com/spf13/pflag" ) -// GenManTree will call cmd.GenManTree(header, dir) -func GenManTree(cmd *Command, header *GenManHeader, dir string) { - cmd.GenManTree(header, dir) -} - // GenManTree will generate a man page for this command and all decendants // in the directory given. The header may be nil. This function may not work // correctly if your command names have - in them. If you have `cmd` with two // subcmds, `sub` and `sub-third`. And `sub` has a subcommand called `third` // it is undefined which help output will be in the file `cmd-sub-third.1`. -func (cmd *Command) GenManTree(header *GenManHeader, dir string) { +func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) { if header == nil { header = &GenManHeader{} } for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + if !c.IsAvailableCommand() || c.IsHelpCommand() { continue } GenManTree(c, header, dir) @@ -49,7 +46,7 @@ func (cmd *Command) GenManTree(header *GenManHeader, dir string) { needToResetTitle := header.Title == "" - cmd.GenMan(header, out) + GenMan(cmd, header, out) if needToResetTitle { header.Title = "" @@ -83,18 +80,13 @@ type GenManHeader struct { Manual string } -// GenMan will call cmd.GenMan(header, out) -func GenMan(cmd *Command, header *GenManHeader, out *bytes.Buffer) { - cmd.GenMan(header, out) -} - // GenMan will generate a man page for the given command in the out buffer. // The header argument may be nil, however obviously out may not. -func (cmd *Command) GenMan(header *GenManHeader, out *bytes.Buffer) { +func GenMan(cmd *cobra.Command, header *GenManHeader, out io.Writer) { if header == nil { header = &GenManHeader{} } - buf := genMarkdown(cmd, header) + buf := genMan(cmd, header) final := mangen.Render(buf) out.Write(final) } @@ -116,7 +108,7 @@ func fillHeader(header *GenManHeader, name string) { } } -func manPreamble(out *bytes.Buffer, header *GenManHeader, name, short, long string) { +func manPreamble(out io.Writer, header *GenManHeader, name, short, long string) { dashName := strings.Replace(name, " ", "-", -1) fmt.Fprintf(out, `%% %s(%s)%s %% %s @@ -130,7 +122,7 @@ func manPreamble(out *bytes.Buffer, header *GenManHeader, name, short, long stri fmt.Fprintf(out, "%s\n\n", long) } -func manPrintFlags(out *bytes.Buffer, flags *pflag.FlagSet) { +func manPrintFlags(out io.Writer, flags *pflag.FlagSet) { flags.VisitAll(func(flag *pflag.Flag) { if len(flag.Deprecated) > 0 || flag.Hidden { return @@ -158,7 +150,7 @@ func manPrintFlags(out *bytes.Buffer, flags *pflag.FlagSet) { }) } -func manPrintOptions(out *bytes.Buffer, command *Command) { +func manPrintOptions(out io.Writer, command *cobra.Command) { flags := command.NonInheritedFlags() if flags.HasFlags() { fmt.Fprintf(out, "# OPTIONS\n") @@ -173,7 +165,7 @@ func manPrintOptions(out *bytes.Buffer, command *Command) { } } -func genMarkdown(cmd *Command, header *GenManHeader) []byte { +func genMan(cmd *cobra.Command, header *GenManHeader) []byte { // something like `rootcmd subcmd1 subcmd2` commandName := cmd.CommandPath() // something like `rootcmd-subcmd1-subcmd2` @@ -195,13 +187,13 @@ func genMarkdown(cmd *Command, header *GenManHeader) []byte { fmt.Fprintf(buf, "# EXAMPLE\n") fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example) } - if cmd.hasSeeAlso() { + if hasSeeAlso(cmd) { fmt.Fprintf(buf, "# SEE ALSO\n") if cmd.HasParent() { parentPath := cmd.Parent().CommandPath() dashParentPath := strings.Replace(parentPath, " ", "-", -1) fmt.Fprintf(buf, "**%s(%s)**", dashParentPath, header.Section) - cmd.VisitParents(func(c *Command) { + cmd.VisitParents(func(c *cobra.Command) { if c.DisableAutoGenTag { cmd.DisableAutoGenTag = c.DisableAutoGenTag } @@ -210,7 +202,7 @@ func genMarkdown(cmd *Command, header *GenManHeader) []byte { children := cmd.Commands() sort.Sort(byName(children)) for i, c := range children { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + if !c.IsAvailableCommand() || c.IsHelpCommand() { continue } if cmd.HasParent() || i > 0 { diff --git a/man_docs.md b/doc/man_docs.md similarity index 100% rename from man_docs.md rename to doc/man_docs.md diff --git a/man_docs_test.go b/doc/man_docs_test.go similarity index 97% rename from man_docs_test.go rename to doc/man_docs_test.go index ab4030c..4e8f706 100644 --- a/man_docs_test.go +++ b/doc/man_docs_test.go @@ -1,4 +1,4 @@ -package cobra +package doc import ( "bytes" @@ -29,7 +29,7 @@ func TestGenManDoc(t *testing.T) { Section: "2", } // We generate on a subcommand so we have both subcommands and parents - cmdEcho.GenMan(header, out) + GenMan(cmdEcho, header, out) found := out.String() // Make sure parent has - in CommandPath() in SEE ALSO: @@ -72,7 +72,6 @@ func TestGenManDoc(t *testing.T) { } func TestGenManNoGenTag(t *testing.T) { - c := initializeWithRootCmd() // Need two commands to run the command alphabetical sort cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) @@ -86,7 +85,7 @@ func TestGenManNoGenTag(t *testing.T) { Section: "2", } // We generate on a subcommand so we have both subcommands and parents - cmdEcho.GenMan(header, out) + GenMan(cmdEcho, header, out) found := out.String() unexpected := translate("#HISTORY") diff --git a/examples_test.go b/doc/man_examples_test.go similarity index 69% rename from examples_test.go rename to doc/man_examples_test.go index 10da985..3593853 100644 --- a/examples_test.go +++ b/doc/man_examples_test.go @@ -1,10 +1,11 @@ -package cobra_test +package doc_test import ( "bytes" "fmt" "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" ) func ExampleCommand_GenManTree() { @@ -12,11 +13,11 @@ func ExampleCommand_GenManTree() { Use: "test", Short: "my test program", } - header := &cobra.GenManHeader{ + header := &doc.GenManHeader{ Title: "MINE", Section: "3", } - cmd.GenManTree(header, "/tmp") + doc.GenManTree(cmd, header, "/tmp") } func ExampleCommand_GenMan() { @@ -24,11 +25,11 @@ func ExampleCommand_GenMan() { Use: "test", Short: "my test program", } - header := &cobra.GenManHeader{ + header := &doc.GenManHeader{ Title: "MINE", Section: "3", } out := new(bytes.Buffer) - cmd.GenMan(header, out) + doc.GenMan(cmd, header, out) fmt.Print(out.String()) } diff --git a/md_docs.go b/doc/md_docs.go similarity index 66% rename from md_docs.go rename to doc/md_docs.go index e940ba1..79bb687 100644 --- a/md_docs.go +++ b/doc/md_docs.go @@ -11,18 +11,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cobra +package doc import ( "bytes" "fmt" + "io" "os" "sort" "strings" "time" + + "github.com/spf13/cobra" ) -func printOptions(out *bytes.Buffer, cmd *Command, name string) { +func printOptions(out io.Writer, cmd *cobra.Command, name string) { flags := cmd.NonInheritedFlags() flags.SetOutput(out) if flags.HasFlags() { @@ -40,25 +43,11 @@ func printOptions(out *bytes.Buffer, cmd *Command, name string) { } } -type byName []*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() } - -func GenMarkdown(cmd *Command, out *bytes.Buffer) { - cmd.GenMarkdown(out) +func GenMarkdown(cmd *cobra.Command, out io.Writer) { + GenMarkdownCustom(cmd, out, func(s string) string { return s }) } -func (cmd *Command) GenMarkdown(out *bytes.Buffer) { - cmd.GenMarkdownCustom(out, func(s string) string { return s }) -} - -func GenMarkdownCustom(cmd *Command, out *bytes.Buffer, linkHandler func(string) string) { - cmd.GenMarkdownCustom(out, linkHandler) -} - -func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string) string) { +func GenMarkdownCustom(cmd *cobra.Command, out io.Writer, linkHandler func(string) string) { name := cmd.CommandPath() short := cmd.Short @@ -82,7 +71,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string } printOptions(out, cmd, name) - if cmd.hasSeeAlso() { + if hasSeeAlso(cmd) { fmt.Fprintf(out, "### SEE ALSO\n") if cmd.HasParent() { parent := cmd.Parent() @@ -90,7 +79,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string link := pname + ".md" link = strings.Replace(link, " ", "_", -1) fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short) - cmd.VisitParents(func(c *Command) { + cmd.VisitParents(func(c *cobra.Command) { if c.DisableAutoGenTag { cmd.DisableAutoGenTag = c.DisableAutoGenTag } @@ -101,7 +90,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string sort.Sort(byName(children)) for _, child := range children { - if !child.IsAvailableCommand() || child == cmd.helpCommand { + if !child.IsAvailableCommand() || child.IsHelpCommand() { continue } cname := name + " " + child.Name() @@ -116,30 +105,22 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string } } -func GenMarkdownTree(cmd *Command, dir string) { - cmd.GenMarkdownTree(dir) -} - -func (cmd *Command) GenMarkdownTree(dir string) { +func GenMarkdownTree(cmd *cobra.Command, dir string) { identity := func(s string) string { return s } emptyStr := func(s string) string { return "" } - cmd.GenMarkdownTreeCustom(dir, emptyStr, identity) + GenMarkdownTreeCustom(cmd, dir, emptyStr, identity) } -func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender func(string) string, linkHandler func(string) string) { - cmd.GenMarkdownTreeCustom(dir, filePrepender, linkHandler) -} - -func (cmd *Command) GenMarkdownTreeCustom(dir string, filePrepender func(string) string, linkHandler func(string) string) { +func GenMarkdownTreeCustom(cmd *cobra.Command, dir string, filePrepender func(string) string, linkHandler func(string) string) { for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + if !c.IsAvailableCommand() || c.IsHelpCommand() { continue } - c.GenMarkdownTreeCustom(dir, filePrepender, linkHandler) + GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler) } out := new(bytes.Buffer) - cmd.GenMarkdownCustom(out, linkHandler) + GenMarkdownCustom(cmd, out, linkHandler) filename := cmd.CommandPath() filename = dir + strings.Replace(filename, " ", "_", -1) + ".md" diff --git a/md_docs.md b/doc/md_docs.md similarity index 95% rename from md_docs.md rename to doc/md_docs.md index 3a0d55a..41259d2 100644 --- a/md_docs.md +++ b/doc/md_docs.md @@ -12,12 +12,12 @@ import ( "os" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" - "github.com/spf13/cobra" + "github.com/spf13/cobra/cobra" ) func main() { kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) - cobra.GenMarkdownTree(kubectl, "./") + doc.GenMarkdownTree(kubectl, "./") } ``` @@ -29,7 +29,7 @@ You may wish to have more control over the output, or only generate for a single ```go out := new(bytes.Buffer) - cobra.GenMarkdown(cmd, out) + doc.GenMarkdown(cmd, out) ``` This will write the markdown doc for ONLY "cmd" into the out, buffer. @@ -78,4 +78,4 @@ linkHandler := func(name string) string { return "/commands/" + strings.ToLower(base) + "/" } ``` - + diff --git a/md_docs_test.go b/doc/md_docs_test.go similarity index 99% rename from md_docs_test.go rename to doc/md_docs_test.go index 82f5452..4e9d6a9 100644 --- a/md_docs_test.go +++ b/doc/md_docs_test.go @@ -1,4 +1,4 @@ -package cobra +package doc import ( "bytes" diff --git a/doc_util.go b/doc/util.go similarity index 69% rename from doc_util.go rename to doc/util.go index e248216..a1c6b89 100644 --- a/doc_util.go +++ b/doc/util.go @@ -11,24 +11,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cobra +package doc + +import "github.com/spf13/cobra" // Test to see if we have a reason to print See Also information in docs // Basically this is a test for a parent commend or a subcommand which is // both not deprecated and not the autogenerated help command. -func (cmd *Command) hasSeeAlso() bool { +func hasSeeAlso(cmd *cobra.Command) bool { if cmd.HasParent() { return true } - children := cmd.Commands() - if len(children) == 0 { - return false - } - for _, c := range children { - if !c.IsAvailableCommand() || c == cmd.helpCommand { + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsHelpCommand() { continue } return true } return false } + +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() }