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
This commit is contained in:
Frank Schroeder 2015-11-24 00:19:16 +01:00
parent b167d9beaa
commit 90aa777933
9 changed files with 203 additions and 81 deletions

145
doc/cmd_test.go Normal file
View file

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

View file

@ -11,36 +11,33 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package cobra package doc
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"sort" "sort"
"strings" "strings"
"time" "time"
mangen "github.com/cpuguy83/go-md2man/md2man" mangen "github.com/cpuguy83/go-md2man/md2man"
"github.com/spf13/cobra"
"github.com/spf13/pflag" "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 // 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 // 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 // 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` // 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`. // 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 { if header == nil {
header = &GenManHeader{} header = &GenManHeader{}
} }
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() || c.IsHelpCommand() {
continue continue
} }
GenManTree(c, header, dir) GenManTree(c, header, dir)
@ -49,7 +46,7 @@ func (cmd *Command) GenManTree(header *GenManHeader, dir string) {
needToResetTitle := header.Title == "" needToResetTitle := header.Title == ""
cmd.GenMan(header, out) GenMan(cmd, header, out)
if needToResetTitle { if needToResetTitle {
header.Title = "" header.Title = ""
@ -83,18 +80,13 @@ type GenManHeader struct {
Manual string 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. // 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. // 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 { if header == nil {
header = &GenManHeader{} header = &GenManHeader{}
} }
buf := genMarkdown(cmd, header) buf := genMan(cmd, header)
final := mangen.Render(buf) final := mangen.Render(buf)
out.Write(final) 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) dashName := strings.Replace(name, " ", "-", -1)
fmt.Fprintf(out, `%% %s(%s)%s fmt.Fprintf(out, `%% %s(%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) 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) { flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden { if len(flag.Deprecated) > 0 || flag.Hidden {
return 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() flags := command.NonInheritedFlags()
if flags.HasFlags() { if flags.HasFlags() {
fmt.Fprintf(out, "# OPTIONS\n") 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` // something like `rootcmd subcmd1 subcmd2`
commandName := cmd.CommandPath() commandName := cmd.CommandPath()
// something like `rootcmd-subcmd1-subcmd2` // something like `rootcmd-subcmd1-subcmd2`
@ -195,13 +187,13 @@ func genMarkdown(cmd *Command, header *GenManHeader) []byte {
fmt.Fprintf(buf, "# EXAMPLE\n") fmt.Fprintf(buf, "# EXAMPLE\n")
fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example) fmt.Fprintf(buf, "```\n%s\n```\n", cmd.Example)
} }
if cmd.hasSeeAlso() { if hasSeeAlso(cmd) {
fmt.Fprintf(buf, "# SEE ALSO\n") fmt.Fprintf(buf, "# SEE ALSO\n")
if cmd.HasParent() { if cmd.HasParent() {
parentPath := cmd.Parent().CommandPath() parentPath := cmd.Parent().CommandPath()
dashParentPath := strings.Replace(parentPath, " ", "-", -1) dashParentPath := strings.Replace(parentPath, " ", "-", -1)
fmt.Fprintf(buf, "**%s(%s)**", dashParentPath, header.Section) fmt.Fprintf(buf, "**%s(%s)**", dashParentPath, header.Section)
cmd.VisitParents(func(c *Command) { cmd.VisitParents(func(c *cobra.Command) {
if c.DisableAutoGenTag { if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag cmd.DisableAutoGenTag = c.DisableAutoGenTag
} }
@ -210,7 +202,7 @@ func genMarkdown(cmd *Command, header *GenManHeader) []byte {
children := cmd.Commands() children := cmd.Commands()
sort.Sort(byName(children)) sort.Sort(byName(children))
for i, c := range children { for i, c := range children {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() || c.IsHelpCommand() {
continue continue
} }
if cmd.HasParent() || i > 0 { if cmd.HasParent() || i > 0 {

View file

@ -1,4 +1,4 @@
package cobra package doc
import ( import (
"bytes" "bytes"
@ -29,7 +29,7 @@ func TestGenManDoc(t *testing.T) {
Section: "2", Section: "2",
} }
// We generate on a subcommand so we have both subcommands and parents // We generate on a subcommand so we have both subcommands and parents
cmdEcho.GenMan(header, out) GenMan(cmdEcho, header, out)
found := out.String() found := out.String()
// Make sure parent has - in CommandPath() in SEE ALSO: // Make sure parent has - in CommandPath() in SEE ALSO:
@ -72,7 +72,6 @@ func TestGenManDoc(t *testing.T) {
} }
func TestGenManNoGenTag(t *testing.T) { func TestGenManNoGenTag(t *testing.T) {
c := initializeWithRootCmd() c := initializeWithRootCmd()
// Need two commands to run the command alphabetical sort // Need two commands to run the command alphabetical sort
cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated) cmdEcho.AddCommand(cmdTimes, cmdEchoSub, cmdDeprecated)
@ -86,7 +85,7 @@ func TestGenManNoGenTag(t *testing.T) {
Section: "2", Section: "2",
} }
// We generate on a subcommand so we have both subcommands and parents // We generate on a subcommand so we have both subcommands and parents
cmdEcho.GenMan(header, out) GenMan(cmdEcho, header, out)
found := out.String() found := out.String()
unexpected := translate("#HISTORY") unexpected := translate("#HISTORY")

View file

@ -1,10 +1,11 @@
package cobra_test package doc_test
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
) )
func ExampleCommand_GenManTree() { func ExampleCommand_GenManTree() {
@ -12,11 +13,11 @@ func ExampleCommand_GenManTree() {
Use: "test", Use: "test",
Short: "my test program", Short: "my test program",
} }
header := &cobra.GenManHeader{ header := &doc.GenManHeader{
Title: "MINE", Title: "MINE",
Section: "3", Section: "3",
} }
cmd.GenManTree(header, "/tmp") doc.GenManTree(cmd, header, "/tmp")
} }
func ExampleCommand_GenMan() { func ExampleCommand_GenMan() {
@ -24,11 +25,11 @@ func ExampleCommand_GenMan() {
Use: "test", Use: "test",
Short: "my test program", Short: "my test program",
} }
header := &cobra.GenManHeader{ header := &doc.GenManHeader{
Title: "MINE", Title: "MINE",
Section: "3", Section: "3",
} }
out := new(bytes.Buffer) out := new(bytes.Buffer)
cmd.GenMan(header, out) doc.GenMan(cmd, header, out)
fmt.Print(out.String()) fmt.Print(out.String())
} }

View file

@ -11,18 +11,21 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package cobra package doc
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"os" "os"
"sort" "sort"
"strings" "strings"
"time" "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 := cmd.NonInheritedFlags()
flags.SetOutput(out) flags.SetOutput(out)
if flags.HasFlags() { if flags.HasFlags() {
@ -40,25 +43,11 @@ func printOptions(out *bytes.Buffer, cmd *Command, name string) {
} }
} }
type byName []*Command func GenMarkdown(cmd *cobra.Command, out io.Writer) {
GenMarkdownCustom(cmd, out, func(s string) string { return s })
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 (cmd *Command) GenMarkdown(out *bytes.Buffer) { func GenMarkdownCustom(cmd *cobra.Command, out io.Writer, linkHandler func(string) string) {
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) {
name := cmd.CommandPath() name := cmd.CommandPath()
short := cmd.Short short := cmd.Short
@ -82,7 +71,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string
} }
printOptions(out, cmd, name) printOptions(out, cmd, name)
if cmd.hasSeeAlso() { if hasSeeAlso(cmd) {
fmt.Fprintf(out, "### SEE ALSO\n") fmt.Fprintf(out, "### SEE ALSO\n")
if cmd.HasParent() { if cmd.HasParent() {
parent := cmd.Parent() parent := cmd.Parent()
@ -90,7 +79,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string
link := pname + ".md" link := pname + ".md"
link = strings.Replace(link, " ", "_", -1) link = strings.Replace(link, " ", "_", -1)
fmt.Fprintf(out, "* [%s](%s)\t - %s\n", pname, linkHandler(link), parent.Short) 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 { if c.DisableAutoGenTag {
cmd.DisableAutoGenTag = c.DisableAutoGenTag cmd.DisableAutoGenTag = c.DisableAutoGenTag
} }
@ -101,7 +90,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string
sort.Sort(byName(children)) sort.Sort(byName(children))
for _, child := range children { for _, child := range children {
if !child.IsAvailableCommand() || child == cmd.helpCommand { if !child.IsAvailableCommand() || child.IsHelpCommand() {
continue continue
} }
cname := name + " " + child.Name() cname := name + " " + child.Name()
@ -116,30 +105,22 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string
} }
} }
func GenMarkdownTree(cmd *Command, dir string) { func GenMarkdownTree(cmd *cobra.Command, dir string) {
cmd.GenMarkdownTree(dir)
}
func (cmd *Command) GenMarkdownTree(dir string) {
identity := func(s string) string { return s } identity := func(s string) string { return s }
emptyStr := func(s string) string { return "" } 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) { func GenMarkdownTreeCustom(cmd *cobra.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) {
for _, c := range cmd.Commands() { for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand { if !c.IsAvailableCommand() || c.IsHelpCommand() {
continue continue
} }
c.GenMarkdownTreeCustom(dir, filePrepender, linkHandler) GenMarkdownTreeCustom(c, dir, filePrepender, linkHandler)
} }
out := new(bytes.Buffer) out := new(bytes.Buffer)
cmd.GenMarkdownCustom(out, linkHandler) GenMarkdownCustom(cmd, out, linkHandler)
filename := cmd.CommandPath() filename := cmd.CommandPath()
filename = dir + strings.Replace(filename, " ", "_", -1) + ".md" filename = dir + strings.Replace(filename, " ", "_", -1) + ".md"

View file

@ -12,12 +12,12 @@ import (
"os" "os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd"
"github.com/spf13/cobra" "github.com/spf13/cobra/cobra"
) )
func main() { func main() {
kubectl := cmd.NewFactory(nil).NewKubectlCommand(os.Stdin, ioutil.Discard, ioutil.Discard) 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 ```go
out := new(bytes.Buffer) 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. This will write the markdown doc for ONLY "cmd" into the out, buffer.

View file

@ -1,4 +1,4 @@
package cobra package doc
import ( import (
"bytes" "bytes"

View file

@ -11,24 +11,28 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 // 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 // Basically this is a test for a parent commend or a subcommand which is
// both not deprecated and not the autogenerated help command. // both not deprecated and not the autogenerated help command.
func (cmd *Command) hasSeeAlso() bool { func hasSeeAlso(cmd *cobra.Command) bool {
if cmd.HasParent() { if cmd.HasParent() {
return true return true
} }
children := cmd.Commands() for _, c := range cmd.Commands() {
if len(children) == 0 { if !c.IsAvailableCommand() || c.IsHelpCommand() {
return false
}
for _, c := range children {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue continue
} }
return true return true
} }
return false 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() }