Bump spf13/cobra

This commit is contained in:
Fabiano Franz 2015-10-01 14:03:44 -03:00
parent 64d1e14833
commit c8f817225e
14 changed files with 436 additions and 103 deletions

2
Godeps/Godeps.json generated
View File

@ -531,7 +531,7 @@
},
{
"ImportPath": "github.com/spf13/cobra",
"Rev": "68f5a81a722d56241bd70faf6860ceb05eb27d64"
"Rev": "d732ab3a34e6e9e6b5bdac80707c2b6bad852936"
},
{
"ImportPath": "github.com/spf13/pflag",

View File

@ -1,7 +1,8 @@
language: go
go:
- 1.3
- 1.3.3
- 1.4.2
- 1.5.1
- tip
script:
- go test ./...

View File

@ -418,6 +418,42 @@ func main() {
}
```
## Suggestions when "unknown command" happens
Cobra will print automatic suggestions when "unknown command" errors happen. This allows Cobra to behavior similarly to the `git` command when a typo happens. For example:
```
$ hugo srever
unknown command "srever" for "hugo"
Did you mean this?
server
Run 'hugo --help' for usage.
```
Suggestions are automatic based on every subcommand registered and use an implementation of Levenshtein distance. Every registered command that matches a minimum distance of 2 (ignoring case) will be displayed as a suggestion.
If you need to disable suggestions or tweak the string distance in your command, use:
command.DisableSuggestions = true
or
command.SuggestionsMinimumDistance = 1
You can also explicitly set names for which a given command will be suggested using the `SuggestFor` attribute. This allows suggestions for strings that are not close in terms of string distance, but makes sense in your set of commands and for some which you don't want aliases. Example:
```
$ hugo delete
unknown command "delete" for "hugo"
Did you mean this?
remove
Run 'hugo --help' for usage.
```
## Generating markdown formatted documentation for your command
Cobra can generate a markdown formatted document based on the subcommands, flags, etc. A simple example of how to do this for your command can be found in [Markdown Docs](md_docs.md)

View File

@ -223,7 +223,7 @@ func postscript(out *bytes.Buffer, name string) {
func writeCommands(cmd *Command, out *bytes.Buffer) {
fmt.Fprintf(out, " commands=()\n")
for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 || c == cmd.helpCommand {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
fmt.Fprintf(out, " commands+=(%q)\n", c.Name())
@ -332,7 +332,7 @@ func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
func gen(cmd *Command, out *bytes.Buffer) {
for _, c := range cmd.Commands() {
if len(c.Deprecated) > 0 || c == cmd.helpCommand {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
gen(c, out)

View File

@ -23,13 +23,15 @@ import (
"strconv"
"strings"
"text/template"
"unicode"
)
var templateFuncs template.FuncMap = template.FuncMap{
"trim": strings.TrimSpace,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
"trim": strings.TrimSpace,
"trimRightSpace": trimRightSpace,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
}
var initializers []func()
@ -113,6 +115,10 @@ func Eq(a interface{}, b interface{}) bool {
return false
}
func trimRightSpace(s string) string {
return strings.TrimRightFunc(s, unicode.IsSpace)
}
//rpad adds padding to the right of a string
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds", padding)
@ -126,3 +132,39 @@ func tmpl(w io.Writer, text string, data interface{}) error {
template.Must(t.Parse(text))
return t.Execute(w, data)
}
// ld compares two strings and returns the levenshtein distance between them
func ld(s, t string, ignoreCase bool) int {
if ignoreCase {
s = strings.ToLower(s)
t = strings.ToLower(t)
}
d := make([][]int, len(s)+1)
for i := range d {
d[i] = make([]int, len(t)+1)
}
for i := range d {
d[i][0] = i
}
for j := range d[0] {
d[0][j] = j
}
for j := 1; j <= len(t); j++ {
for i := 1; i <= len(s); i++ {
if s[i-1] == t[j-1] {
d[i][j] = d[i-1][j-1]
} else {
min := d[i-1][j]
if d[i][j-1] < min {
min = d[i][j-1]
}
if d[i-1][j-1] < min {
min = d[i-1][j-1]
}
d[i][j] = min + 1
}
}
}
return d[len(s)][len(t)]
}

View File

@ -19,7 +19,7 @@ var _ = os.Stderr
var tp, te, tt, t1, tr []string
var rootPersPre, echoPre, echoPersPre, timesPersPre []string
var flagb1, flagb2, flagb3, flagbr, flagbp bool
var flags1, flags2a, flags2b, flags3 string
var flags1, flags2a, flags2b, flags3, outs string
var flagi1, flagi2, flagi3, flagir int
var globalFlag1 bool
var flagEcho, rootcalled bool
@ -28,6 +28,16 @@ var versionUsed int
const strtwoParentHelp = "help message for parent flag strtwo"
const strtwoChildHelp = "help message for child flag strtwo"
var cmdHidden = &Command{
Use: "hide [secret string to print]",
Short: "Print anything to screen (if command is known)",
Long: `an absolutely utterly useless command for testing.`,
Run: func(cmd *Command, args []string) {
outs = "hidden"
},
Hidden: true,
}
var cmdPrint = &Command{
Use: "print [string to print]",
Short: "Print anything to the screen",
@ -72,9 +82,10 @@ var cmdDeprecated = &Command{
}
var cmdTimes = &Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `a slightly useless command for testing.`,
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 *Command, args []string) {
timesPersPre = args
},
@ -255,16 +266,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) {
@ -398,8 +417,11 @@ func TestGrandChildSameName(t *testing.T) {
}
func TestFlagLong(t *testing.T) {
noRRSetupTest("echo --intone=13 something here")
noRRSetupTest("echo --intone=13 something -- here")
if cmdEcho.ArgsLenAtDash() != 1 {
t.Errorf("expected argsLenAtDash: %d but got %d", 1, cmdRootNoRun.ArgsLenAtDash())
}
if strings.Join(te, " ") != "something here" {
t.Errorf("flags didn't leave proper args remaining..%s given", te)
}
@ -412,8 +434,11 @@ func TestFlagLong(t *testing.T) {
}
func TestFlagShort(t *testing.T) {
noRRSetupTest("echo -i13 something here")
noRRSetupTest("echo -i13 -- something here")
if cmdEcho.ArgsLenAtDash() != 0 {
t.Errorf("expected argsLenAtDash: %d but got %d", 0, cmdRootNoRun.ArgsLenAtDash())
}
if strings.Join(te, " ") != "something here" {
t.Errorf("flags didn't leave proper args remaining..%s given", te)
}
@ -614,10 +639,10 @@ func TestNonRunChildHelp(t *testing.T) {
}
func TestRunnableRootCommand(t *testing.T) {
fullSetupTest("")
x := fullSetupTest("")
if rootcalled != true {
t.Errorf("Root Function was not called")
t.Errorf("Root Function was not called\n out:%v", x.Error)
}
}
@ -632,7 +657,10 @@ func TestRunnableRootCommandNilInput(t *testing.T) {
c.AddCommand(cmdPrint, cmdEcho)
c.SetArgs(empty_arg)
c.Execute()
err := c.Execute()
if err != nil {
t.Errorf("Execute() failed with %v", err)
}
if rootcalled != true {
t.Errorf("Root Function was not called")
@ -781,6 +809,45 @@ func TestRootUnknownCommand(t *testing.T) {
}
}
func TestRootSuggestions(t *testing.T) {
outputWithSuggestions := "Error: unknown command \"%s\" for \"cobra-test\"\n\nDid you mean this?\n\t%s\n\nRun 'cobra-test --help' for usage.\n"
outputWithoutSuggestions := "Error: unknown command \"%s\" for \"cobra-test\"\nRun 'cobra-test --help' for usage.\n"
cmd := initializeWithRootCmd()
cmd.AddCommand(cmdTimes)
tests := map[string]string{
"time": "times",
"tiems": "times",
"tims": "times",
"timeS": "times",
"rimes": "times",
"ti": "times",
"t": "times",
"timely": "times",
"ri": "",
"timezone": "",
"foo": "",
"counts": "times",
}
for typo, suggestion := range tests {
for _, suggestionsDisabled := range []bool{false, true} {
cmd.DisableSuggestions = suggestionsDisabled
result := simpleTester(cmd, typo)
expected := ""
if len(suggestion) == 0 || suggestionsDisabled {
expected = fmt.Sprintf(outputWithoutSuggestions, typo)
} else {
expected = fmt.Sprintf(outputWithSuggestions, typo, suggestion)
}
if result.Output != expected {
t.Errorf("Unexpected response.\nExpecting to be:\n %q\nGot:\n %q\n", expected, result.Output)
}
}
}
}
func TestFlagsBeforeCommand(t *testing.T) {
// short without space
x := fullSetupTest("-i10 echo")
@ -976,15 +1043,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)
}

View File

@ -39,6 +39,8 @@ type Command struct {
Use string
// An array of aliases that can be used instead of the first word in Use.
Aliases []string
// An array of command names for which this command will be suggested - similar to aliases but only suggests.
SuggestFor []string
// The short description shown in the 'help' output.
Short string
// The long message shown in the 'help <this-command>' output.
@ -51,6 +53,8 @@ type Command struct {
BashCompletionFunction string
// Is this command deprecated and should print this string when used?
Deprecated string
// Is this command hidden and should NOT show up in the list of available commands?
Hidden bool
// Full set of flags
flags *flag.FlagSet
// Set of flags childrens of this command will inherit
@ -104,6 +108,11 @@ type Command struct {
helpCommand *Command // The help command
// The global normalization function that we can use on every pFlag set and children commands
globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName
// Disable the suggestions based on Levenshtein distance that go along with 'unknown command' messages
DisableSuggestions bool
// If displaying suggestions, allows to set the minimum levenshtein distance to display, must be > 0
SuggestionsMinimumDistance int
}
// os.Args[1:] by default, if desired, can be overridden
@ -184,6 +193,9 @@ func (c *Command) UsageFunc() (f func(*Command) error) {
} else {
return func(c *Command) error {
err := tmpl(c.Out(), c.UsageTemplate(), c)
if err != nil {
fmt.Print(err)
}
return err
}
}
@ -246,8 +258,7 @@ func (c *Command) UsageTemplate() string {
if c.HasParent() {
return c.parent.UsageTemplate()
} else {
return `{{ $cmd := . }}
Usage: {{if .Runnable}}
return `Usage:{{if .Runnable}}
{{.UseLine}}{{if .HasFlags}} [flags]{{end}}{{end}}{{if .HasSubCommands}}
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
@ -256,22 +267,22 @@ Aliases:
{{end}}{{if .HasExample}}
Examples:
{{ .Example }}{{end}}{{ if .HasNonHelpSubCommands}}
{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}}
Available Commands: {{range .Commands}}{{if (not .IsHelpCommand)}}
Available Commands:{{range .Commands}}{{if .IsAvailableCommand}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages}}{{end}}{{ if .HasInheritedFlags}}
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages}}{{end}}{{if .HasHelpSubCommands}}
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics: {{range .Commands}}{{if .IsHelpCommand}}
Additional help topics:{{range .Commands}}{{if .IsHelpCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
Use "{{.CommandPath}} [command] --help" for more information about a command.
{{end}}`
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`
}
}
@ -283,9 +294,9 @@ func (c *Command) HelpTemplate() string {
if c.HasParent() {
return c.parent.HelpTemplate()
} else {
return `{{with or .Long .Short }}{{. | trim}}{{end}}
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}
`
return `{{with or .Long .Short }}{{. | trim}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
}
}
@ -417,14 +428,47 @@ func (c *Command) Find(args []string) (*Command, []string, error) {
if !commandFound.HasSubCommands() {
return commandFound, a, nil
}
// root command with subcommands, do subcommand checking
if commandFound == c && len(argsWOflags) > 0 {
return commandFound, a, fmt.Errorf("unknown command %q for %q", argsWOflags[0], commandFound.CommandPath())
suggestionsString := ""
if !c.DisableSuggestions {
if c.SuggestionsMinimumDistance <= 0 {
c.SuggestionsMinimumDistance = 2
}
if suggestions := c.SuggestionsFor(argsWOflags[0]); len(suggestions) > 0 {
suggestionsString += "\n\nDid you mean this?\n"
for _, s := range suggestions {
suggestionsString += fmt.Sprintf("\t%v\n", s)
}
}
}
return commandFound, a, fmt.Errorf("unknown command %q for %q%s", argsWOflags[0], commandFound.CommandPath(), suggestionsString)
}
return commandFound, a, nil
}
func (c *Command) SuggestionsFor(typedName string) []string {
suggestions := []string{}
for _, cmd := range c.commands {
if cmd.IsAvailableCommand() {
levenshteinDistance := ld(typedName, cmd.Name(), true)
suggestByLevenshtein := levenshteinDistance <= c.SuggestionsMinimumDistance
suggestByPrefix := strings.HasPrefix(strings.ToLower(cmd.Name()), strings.ToLower(typedName))
if suggestByLevenshtein || suggestByPrefix {
suggestions = append(suggestions, cmd.Name())
}
for _, explicitSuggestion := range cmd.SuggestFor {
if strings.EqualFold(typedName, explicitSuggestion) {
suggestions = append(suggestions, cmd.Name())
}
}
}
}
return suggestions
}
func (c *Command) Root() *Command {
var findRoot func(*Command) *Command
@ -439,6 +483,10 @@ func (c *Command) Root() *Command {
return findRoot(c)
}
func (c *Command) ArgsLenAtDash() int {
return c.Flags().ArgsLenAtDash()
}
func (c *Command) execute(a []string) (err error) {
if c == nil {
return fmt.Errorf("Called Execute() on a nil Command")
@ -474,7 +522,7 @@ func (c *Command) execute(a []string) (err error) {
for p := c; p != nil; p = p.Parent() {
if p.PersistentPreRunE != nil {
if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
return err
}
break
@ -850,42 +898,75 @@ func (c *Command) HasSubCommands() bool {
return len(c.commands) > 0
}
func (c *Command) IsHelpCommand() bool {
if c.Runnable() {
// IsAvailableCommand determines if a command is available as a non-help command
// (this includes all non deprecated/hidden commands)
func (c *Command) IsAvailableCommand() bool {
if len(c.Deprecated) != 0 || c.Hidden {
return false
}
if c.HasParent() && c.Parent().helpCommand == c {
return false
}
if c.Runnable() || c.HasAvailableSubCommands() {
return true
}
return false
}
// IsHelpCommand determines if a command is a 'help' command; a help command is
// determined by the fact that it is NOT runnable/hidden/deprecated, and has no
// sub commands that are runnable/hidden/deprecated
func (c *Command) IsHelpCommand() bool {
// if a command is runnable, deprecated, or hidden it is not a 'help' command
if c.Runnable() || len(c.Deprecated) != 0 || c.Hidden {
return false
}
// if any non-help sub commands are found, the command is not a 'help' command
for _, sub := range c.commands {
if len(sub.Deprecated) != 0 {
continue
}
if !sub.IsHelpCommand() {
return false
}
}
// the command either has no sub commands, or no non-help sub commands
return true
}
// HasHelpSubCommands determines if a command has any avilable 'help' sub commands
// that need to be shown in the usage/help default template under 'additional help
// topics'
func (c *Command) HasHelpSubCommands() bool {
// return true on the first found available 'help' sub command
for _, sub := range c.commands {
if len(sub.Deprecated) != 0 {
continue
}
if sub.IsHelpCommand() {
return true
}
}
// the command either has no sub commands, or no available 'help' sub commands
return false
}
func (c *Command) HasNonHelpSubCommands() bool {
// HasAvailableSubCommands determines if a command has available sub commands that
// need to be shown in the usage/help default template under 'available commands'
func (c *Command) HasAvailableSubCommands() bool {
// return true on the first found available (non deprecated/help/hidden)
// sub command
for _, sub := range c.commands {
if len(sub.Deprecated) != 0 {
continue
}
if !sub.IsHelpCommand() {
if sub.IsAvailableCommand() {
return true
}
}
// the command either has no sub comamnds, or no available (non deprecated/help/hidden)
// sub commands
return false
}

View File

@ -5,6 +5,30 @@ import (
"testing"
)
// test to ensure hidden commands run as intended
func TestHiddenCommandExecutes(t *testing.T) {
// ensure that outs does not already equal what the command will be setting it
// to, if it did this test would not actually be testing anything...
if outs == "hidden" {
t.Errorf("outs should NOT EQUAL hidden")
}
cmdHidden.Execute()
// upon running the command, the value of outs should now be 'hidden'
if outs != "hidden" {
t.Errorf("Hidden command failed to run!")
}
}
// test to ensure hidden commands do not show up in usage/help text
func TestHiddenCommandIsHidden(t *testing.T) {
if cmdHidden.IsAvailableCommand() {
t.Errorf("Hidden command found!")
}
}
func TestStripFlags(t *testing.T) {
tests := []struct {
input []string

View File

@ -13,8 +13,6 @@
package cobra
import ()
// 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.
@ -27,7 +25,7 @@ func (cmd *Command) hasSeeAlso() bool {
return false
}
for _, c := range children {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
return true

View File

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

View File

@ -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 {
if !c.IsAvailableCommand() || 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)
@ -79,7 +125,7 @@ func manPreamble(out *bytes.Buffer, projectName, name, short, long string) {
func manPrintFlags(out *bytes.Buffer, flags *pflag.FlagSet) {
flags.VisitAll(func(flag *pflag.Flag) {
if len(flag.Deprecated) > 0 {
if len(flag.Deprecated) > 0 || flag.Hidden {
return
}
format := ""
@ -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 {
@ -145,20 +192,22 @@ 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(%s)**, ", dashParentPath, header.Section)
}
children := cmd.Commands()
sort.Sort(byName(children))
for _, c := range children {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
if !c.IsAvailableCommand() || 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()
}

View File

@ -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")
}
```

View File

@ -24,48 +24,45 @@ 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 + "(" + header.Section + ")"
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)
}

View File

@ -97,7 +97,7 @@ func (cmd *Command) GenMarkdownCustom(out *bytes.Buffer, linkHandler func(string
sort.Sort(byName(children))
for _, child := range children {
if len(child.Deprecated) > 0 || child == cmd.helpCommand {
if !child.IsAvailableCommand() || child == cmd.helpCommand {
continue
}
cname := name + " " + child.Name()
@ -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) {
@ -127,7 +127,7 @@ func GenMarkdownTreeCustom(cmd *Command, dir string, filePrepender func(string)
func (cmd *Command) GenMarkdownTreeCustom(dir string, filePrepender func(string) string, linkHandler func(string) string) {
for _, c := range cmd.Commands() {
if len(c.Deprecated) != 0 || c == cmd.helpCommand {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
c.GenMarkdownTreeCustom(dir, filePrepender, linkHandler)