From cfecf1379c6d9e380520c2d1f2a2dd759dcc80bf Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Tue, 8 Sep 2015 12:32:15 -0400 Subject: [PATCH 1/2] manpage generation: Make sure parent in SEE ALSO has dashes The CommandPath() for a parent might have a space (if .Parent() != .Root()) so we need to replace those spaces with `-` --- cobra_test.go | 26 +++++++++++++++++--------- man_docs.go | 4 +++- man_docs_test.go | 37 +++++++++++++++---------------------- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/cobra_test.go b/cobra_test.go index 3aed7dd6..01632691 100644 --- a/cobra_test.go +++ b/cobra_test.go @@ -255,16 +255,24 @@ func logErr(t *testing.T, found, expected string) { t.Errorf(out.String()) } +func checkStringContains(t *testing.T, found, expected string) { + if !strings.Contains(found, expected) { + logErr(t, found, expected) + } +} + func checkResultContains(t *testing.T, x resulter, check string) { - if !strings.Contains(x.Output, check) { - logErr(t, x.Output, check) + checkStringContains(t, x.Output, check) +} + +func checkStringOmits(t *testing.T, found, expected string) { + if strings.Contains(found, expected) { + logErr(t, found, expected) } } func checkResultOmits(t *testing.T, x resulter, check string) { - if strings.Contains(x.Output, check) { - logErr(t, x.Output, check) - } + checkStringOmits(t, x.Output, check) } func checkOutputContains(t *testing.T, c *Command, check string) { @@ -976,15 +984,15 @@ func TestFlagOnPflagCommandLine(t *testing.T) { func TestAddTemplateFunctions(t *testing.T) { AddTemplateFunc("t", func() bool { return true }) AddTemplateFuncs(template.FuncMap{ - "f": func() bool { return false }, - "h": func() string { return "Hello," }, + "f": func() bool { return false }, + "h": func() string { return "Hello," }, "w": func() string { return "world." }}) const usage = "Hello, world." - + c := &Command{} c.SetUsageTemplate(`{{if t}}{{h}}{{end}}{{if f}}{{h}}{{end}} {{w}}`) - + if us := c.UsageString(); us != usage { t.Errorf("c.UsageString() != \"%s\", is \"%s\"", usage, us) } diff --git a/man_docs.go b/man_docs.go index f260990e..894e5ecf 100644 --- a/man_docs.go +++ b/man_docs.go @@ -145,7 +145,9 @@ func genMarkdown(cmd *Command, projectName string) []byte { if cmd.hasSeeAlso() { fmt.Fprintf(buf, "# SEE ALSO\n") if cmd.HasParent() { - fmt.Fprintf(buf, "**%s(1)**, ", cmd.Parent().CommandPath()) + parentPath := cmd.Parent().CommandPath() + dashParentPath := strings.Replace(parentPath, " ", "-", -1) + fmt.Fprintf(buf, "**%s(1)**, ", dashParentPath) } children := cmd.Commands() diff --git a/man_docs_test.go b/man_docs_test.go index 6e31ab3b..cb86edb9 100644 --- a/man_docs_test.go +++ b/man_docs_test.go @@ -28,44 +28,37 @@ func TestGenManDoc(t *testing.T) { cmdEcho.GenMan("PROJECT", out) found := out.String() + // Make sure parent has - in CommandPath() in SEE ALSO: + parentPath := cmdEcho.Parent().CommandPath() + dashParentPath := strings.Replace(parentPath, " ", "-", -1) + expected := translate(dashParentPath) + expected = expected + "(1)" + checkStringContains(t, found, expected) + // Our description - expected := translate(cmdEcho.Name()) - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } + expected = translate(cmdEcho.Name()) + checkStringContains(t, found, expected) // Better have our example expected = translate(cmdEcho.Name()) - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } + checkStringContains(t, found, expected) // A local flag expected = "boolone" - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } + checkStringContains(t, found, expected) // persistent flag on parent expected = "rootflag" - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } + checkStringContains(t, found, expected) // We better output info about our parent expected = translate(cmdRootWithRun.Name()) - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } + checkStringContains(t, found, expected) // And about subcommands expected = translate(cmdEchoSub.Name()) - if !strings.Contains(found, expected) { - t.Errorf("Unexpected response.\nExpecting to contain: \n %q\nGot:\n %q\n", expected, found) - } + checkStringContains(t, found, expected) unexpected := translate(cmdDeprecated.Name()) - if strings.Contains(found, unexpected) { - t.Errorf("Unexpected response.\nFound: %v\nBut should not have!!\n", unexpected) - } + checkStringOmits(t, found, unexpected) } From edcf765d9fb36cbc5382ea5bf7e33f1ad931588c Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Tue, 8 Sep 2015 16:02:02 -0400 Subject: [PATCH 2/2] Mangen: allow callers to specify header information We previously had this weak argument called projectName which let you set a single part of a man page header. Instead do the best we can if the caller doesn't pass us anything, but let the caller specify anything they want. --- examples_test.go | 34 ++++++++++++++++++++ man_docs.go | 81 ++++++++++++++++++++++++++++++++++++++---------- man_docs.md | 6 +++- man_docs_test.go | 8 +++-- md_docs.go | 2 +- 5 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 examples_test.go diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 00000000..10da9850 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,34 @@ +package cobra_test + +import ( + "bytes" + "fmt" + + "github.com/spf13/cobra" +) + +func ExampleCommand_GenManTree() { + cmd := &cobra.Command{ + Use: "test", + Short: "my test program", + } + header := &cobra.GenManHeader{ + Title: "MINE", + Section: "3", + } + cmd.GenManTree(header, "/tmp") +} + +func ExampleCommand_GenMan() { + cmd := &cobra.Command{ + Use: "test", + Short: "my test program", + } + header := &cobra.GenManHeader{ + Title: "MINE", + Section: "3", + } + out := new(bytes.Buffer) + cmd.GenMan(header, out) + fmt.Print(out.String()) +} diff --git a/man_docs.go b/man_docs.go index 894e5ecf..e5edbfdd 100644 --- a/man_docs.go +++ b/man_docs.go @@ -25,20 +25,29 @@ import ( "github.com/spf13/pflag" ) -func GenManTree(cmd *Command, projectName, dir string) { - cmd.GenManTree(projectName, dir) +// GenManTree will call cmd.GenManTree(header, dir) +func GenManTree(cmd *Command, header *GenManHeader, dir string) { + cmd.GenManTree(header, dir) } -func (cmd *Command) GenManTree(projectName, dir string) { +// 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) { + if header == nil { + header = &GenManHeader{} + } for _, c := range cmd.Commands() { if len(c.Deprecated) != 0 || c == cmd.helpCommand { continue } - GenManTree(c, projectName, dir) + GenManTree(c, header, dir) } out := new(bytes.Buffer) - cmd.GenMan(projectName, out) + cmd.GenMan(header, out) filename := cmd.CommandPath() filename = dir + strings.Replace(filename, " ", "-", -1) + ".1" @@ -55,21 +64,58 @@ func (cmd *Command) GenManTree(projectName, dir string) { } } -func GenMan(cmd *Command, projectName string, out *bytes.Buffer) { - cmd.GenMan(projectName, out) +// GenManHeader is a lot like the .TH header at the start of man pages. These +// include the title, section, date, source, and manual. We will use the +// current time if Date if unset and will use "Auto generated by spf13/cobra" +// if the Source is unset. +type GenManHeader struct { + Title string + Section string + Date *time.Time + date string + Source string + Manual string } -func (cmd *Command) GenMan(projectName string, out *bytes.Buffer) { +// GenMan will call cmd.GenMan(header, out) +func GenMan(cmd *Command, header *GenManHeader, out *bytes.Buffer) { + cmd.GenMan(header, out) +} - buf := genMarkdown(cmd, projectName) +// 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) { + if header == nil { + header = &GenManHeader{} + } + buf := genMarkdown(cmd, header) final := mangen.Render(buf) out.Write(final) } -func manPreamble(out *bytes.Buffer, projectName, name, short, long string) { - fmt.Fprintf(out, `%% %s(1) +func fillHeader(header *GenManHeader, name string) { + if header.Title == "" { + header.Title = name + } + if header.Section == "" { + header.Section = "1" + } + if header.Date == nil { + now := time.Now() + header.Date = &now + } + header.date = (*header.Date).Format("Jan 2006") + if header.Source == "" { + header.Source = "Auto generated by spf13/cobra" + } +} + +func manPreamble(out *bytes.Buffer, header *GenManHeader, name, short, long string) { + fmt.Fprintf(out, `%% %s(%s)%s +%% %s +%% %s # NAME -`, projectName) +`, header.Title, header.Section, header.date, header.Source, header.Manual) fmt.Fprintf(out, "%s \\- %s\n\n", name, short) fmt.Fprintf(out, "# SYNOPSIS\n") fmt.Fprintf(out, "**%s** [OPTIONS]\n\n", name) @@ -120,7 +166,8 @@ func manPrintOptions(out *bytes.Buffer, command *Command) { } } -func genMarkdown(cmd *Command, projectName string) []byte { +func genMarkdown(cmd *Command, header *GenManHeader) []byte { + fillHeader(header, cmd.Name()) // something like `rootcmd subcmd1 subcmd2` commandName := cmd.CommandPath() // something like `rootcmd-subcmd1-subcmd2` @@ -134,7 +181,7 @@ func genMarkdown(cmd *Command, projectName string) []byte { long = short } - manPreamble(buf, projectName, commandName, short, long) + manPreamble(buf, header, commandName, short, long) manPrintOptions(buf, cmd) if len(cmd.Example) > 0 { @@ -147,7 +194,7 @@ func genMarkdown(cmd *Command, projectName string) []byte { if cmd.HasParent() { parentPath := cmd.Parent().CommandPath() dashParentPath := strings.Replace(parentPath, " ", "-", -1) - fmt.Fprintf(buf, "**%s(1)**, ", dashParentPath) + fmt.Fprintf(buf, "**%s(%s)**, ", dashParentPath, header.Section) } children := cmd.Commands() @@ -156,11 +203,11 @@ func genMarkdown(cmd *Command, projectName string) []byte { if len(c.Deprecated) != 0 || c == cmd.helpCommand { continue } - fmt.Fprintf(buf, "**%s-%s(1)**, ", dashCommandName, c.Name()) + fmt.Fprintf(buf, "**%s-%s(%s)**, ", dashCommandName, c.Name(), header.Section) } fmt.Fprintf(buf, "\n") } - fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", time.Now().UTC()) + fmt.Fprintf(buf, "# HISTORY\n%s Auto generated by spf13/cobra\n", header.Date.Format("2-Jan-2006")) return buf.Bytes() } diff --git a/man_docs.md b/man_docs.md index 3516d37c..a764099e 100644 --- a/man_docs.md +++ b/man_docs.md @@ -14,7 +14,11 @@ func main() { Use: "test", Short: "my test program", } - cmd.GenManTree("/tmp") + header := &cobra.GenManHeader{ + Title: "MINE", + Section: "3", + } + cmd.GenManTree(header, "/tmp") } ``` diff --git a/man_docs_test.go b/man_docs_test.go index cb86edb9..1c121d6c 100644 --- a/man_docs_test.go +++ b/man_docs_test.go @@ -24,15 +24,19 @@ func TestGenManDoc(t *testing.T) { out := new(bytes.Buffer) + header := &GenManHeader{ + Title: "Project", + Section: "2", + } // We generate on a subcommand so we have both subcommands and parents - cmdEcho.GenMan("PROJECT", out) + cmdEcho.GenMan(header, out) found := out.String() // Make sure parent has - in CommandPath() in SEE ALSO: parentPath := cmdEcho.Parent().CommandPath() dashParentPath := strings.Replace(parentPath, " ", "-", -1) expected := translate(dashParentPath) - expected = expected + "(1)" + expected = expected + "(" + header.Section + ")" checkStringContains(t, found, expected) // Our description diff --git a/md_docs.go b/md_docs.go index dde5b114..4e5b23a1 100644 --- a/md_docs.go +++ b/md_docs.go @@ -108,7 +108,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string fmt.Fprintf(out, "\n") } - fmt.Fprintf(out, "###### Auto generated by spf13/cobra at %s\n", time.Now().UTC()) + fmt.Fprintf(out, "###### Auto generated by spf13/cobra on %s\n", time.Now().Format("2-Jan-2006")) } func GenMarkdownTree(cmd *Command, dir string) {