spf13--cobra/command.go

1259 lines
34 KiB
Go
Raw Normal View History

2013-09-24 16:47:29 +00:00
// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2014-12-13 05:05:43 +00:00
//Package cobra is a commander providing a simple interface to create powerful modern CLI interfaces.
//In addition to providing an interface, Cobra simultaneously provides a controller to organize your application code.
2013-09-24 16:47:29 +00:00
package cobra
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"sort"
2013-09-24 16:47:29 +00:00
"strings"
flag "github.com/spf13/pflag"
2013-09-24 16:47:29 +00:00
)
// Command is just that, a command for your application.
// eg. 'go run' ... 'run' is the command. Cobra requires
// you to define the usage and description as part of your command
// definition to ensure usability.
type Command struct {
// Name is the command name, usually the executable's name.
name string
// The one-line usage message.
Use string
2014-09-25 23:03:34 +00:00
// 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
2013-09-24 16:47:29 +00:00
// The short description shown in the 'help' output.
Short string
// The long message shown in the 'help <this-command>' output.
Long string
// Examples of how to use the command
Example string
// List of all valid non-flag arguments that are accepted in bash completions
ValidArgs []string
// List of aliases for ValidArgs. These are not suggested to the user in the bash
// completion, but accepted if entered manually.
ArgAliases []string
// Custom functions used by the bash autocompletion generator
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
2013-09-24 16:47:29 +00:00
flags *flag.FlagSet
// Set of flags childrens of this command will inherit
2013-09-24 16:47:29 +00:00
pflags *flag.FlagSet
// Flags that are declared specifically by this command (not inherited).
lflags *flag.FlagSet
// SilenceErrors is an option to quiet errors down stream
SilenceErrors bool
2015-10-26 23:38:08 +00:00
// Silence Usage is an option to silence usage when an error occurs.
SilenceUsage bool
// The *Run functions are executed in the following order:
// * PersistentPreRun()
// * PreRun()
// * Run()
// * PostRun()
// * PersistentPostRun()
// All functions get the same args, the arguments after the command name
// PersistentPreRun: children of this command will inherit and execute
PersistentPreRun func(cmd *Command, args []string)
// PersistentPreRunE: PersistentPreRun but returns an error
PersistentPreRunE func(cmd *Command, args []string) error
// PreRun: children of this command will not inherit.
2015-02-09 22:44:14 +00:00
PreRun func(cmd *Command, args []string)
// PreRunE: PreRun but returns an error
PreRunE func(cmd *Command, args []string) error
// Run: Typically the actual work function. Most commands will only implement this
Run func(cmd *Command, args []string)
// RunE: Run but returns an error
RunE func(cmd *Command, args []string) error
// PostRun: run after the Run command.
2015-02-09 22:44:14 +00:00
PostRun func(cmd *Command, args []string)
// PostRunE: PostRun but returns an error
PostRunE func(cmd *Command, args []string) error
// PersistentPostRun: children of this command will inherit and execute after PostRun
2015-02-17 21:09:49 +00:00
PersistentPostRun func(cmd *Command, args []string)
// PersistentPostRunE: PersistentPostRun but returns an error
PersistentPostRunE func(cmd *Command, args []string) error
// DisableAutoGenTag remove
DisableAutoGenTag bool
// Commands is the list of commands supported by this program.
2013-09-24 16:47:29 +00:00
commands []*Command
// Parent Command for this command
parent *Command
// max lengths of commands' string lengths for use in padding
commandsMaxUseLen int
commandsMaxCommandPathLen int
commandsMaxNameLen int
// is commands slice are sorted or not
commandsAreSorted bool
2013-09-24 16:47:29 +00:00
flagErrorBuf *bytes.Buffer
2014-12-13 05:05:43 +00:00
args []string // actual args parsed from flags
2014-04-11 14:11:09 +00:00
output *io.Writer // nil means stderr; use Out() method instead
usageFunc func(*Command) error // Usage can be defined by application
usageTemplate string // Can be defined by Application
helpTemplate string // Can be defined by Application
helpFunc func(*Command, []string) // Help can be defined by application
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
2016-06-04 00:25:52 +00:00
// Disable the flag parsing. If this is true all flags will be passed to the command as arguments.
DisableFlagParsing bool
2013-09-24 16:47:29 +00:00
}
// os.Args[1:] by default, if desired, can be overridden
// particularly useful when testing.
func (c *Command) SetArgs(a []string) {
c.args = a
}
// SetOutput sets the destination for usage and error messages.
// If output is nil, os.Stderr is used.
func (c *Command) SetOutput(output io.Writer) {
c.output = &output
}
// Usage can be defined by application
func (c *Command) SetUsageFunc(f func(*Command) error) {
c.usageFunc = f
}
// Can be defined by Application
func (c *Command) SetUsageTemplate(s string) {
c.usageTemplate = s
}
// Can be defined by Application
func (c *Command) SetHelpFunc(f func(*Command, []string)) {
c.helpFunc = f
}
func (c *Command) SetHelpCommand(cmd *Command) {
c.helpCommand = cmd
}
// Can be defined by Application
func (c *Command) SetHelpTemplate(s string) {
c.helpTemplate = s
}
// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
// The user should not have a cyclic dependency on commands.
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
c.Flags().SetNormalizeFunc(n)
c.PersistentFlags().SetNormalizeFunc(n)
c.globNormFunc = n
for _, command := range c.commands {
command.SetGlobalNormalizationFunc(n)
}
}
func (c *Command) getOutOrStdout() io.Writer {
return c.getOut(os.Stdout)
}
func (c *Command) getOutOrStderr() io.Writer {
return c.getOut(os.Stderr)
}
func (c *Command) getOut(def io.Writer) io.Writer {
if c.output != nil {
return *c.output
}
if c.HasParent() {
return c.parent.getOut(def)
}
return def
}
// UsageFunc returns either the function set by SetUsageFunc for this command
// or a parent, or it returns a default usage function
func (c *Command) UsageFunc() (f func(*Command) error) {
if c.usageFunc != nil {
return c.usageFunc
}
if c.HasParent() {
return c.parent.UsageFunc()
}
return func(c *Command) error {
err := c.Usage()
if err != nil {
c.Println(err)
}
return err
}
}
// HelpFunc returns either the function set by SetHelpFunc for this command
// or a parent, or it returns a function which calls c.Help()
func (c *Command) HelpFunc() func(*Command, []string) {
cmd := c
for cmd != nil {
if cmd.helpFunc != nil {
return cmd.helpFunc
}
cmd = cmd.parent
}
return func(*Command, []string) {
err := c.Help()
if err != nil {
c.Println(err)
}
}
}
// Output the usage for the command
// Used when a user provides invalid input
// Can be defined by user by overriding UsageFunc
func (c *Command) Usage() error {
c.mergePersistentFlags()
err := tmpl(c.getOutOrStderr(), c.UsageTemplate(), c)
return err
}
// Output the help for the command
// Used when a user calls help [command]
// by the default HelpFunc in the commander
func (c *Command) Help() error {
c.mergePersistentFlags()
err := tmpl(c.getOutOrStdout(), c.HelpTemplate(), c)
return err
}
func (c *Command) UsageString() string {
tmpOutput := c.output
bb := new(bytes.Buffer)
c.SetOutput(bb)
c.Usage()
c.output = tmpOutput
return bb.String()
}
var minUsagePadding = 25
func (c *Command) UsagePadding() int {
if c.parent == nil || minUsagePadding > c.parent.commandsMaxUseLen {
return minUsagePadding
}
return c.parent.commandsMaxUseLen
}
var minCommandPathPadding = 11
2014-12-13 05:05:43 +00:00
//
func (c *Command) CommandPathPadding() int {
if c.parent == nil || minCommandPathPadding > c.parent.commandsMaxCommandPathLen {
return minCommandPathPadding
}
return c.parent.commandsMaxCommandPathLen
}
var minNamePadding = 11
func (c *Command) NamePadding() int {
if c.parent == nil || minNamePadding > c.parent.commandsMaxNameLen {
return minNamePadding
}
return c.parent.commandsMaxNameLen
}
func (c *Command) UsageTemplate() string {
if c.usageTemplate != "" {
return c.usageTemplate
}
if c.HasParent() {
return c.parent.UsageTemplate()
}
return `Usage:{{if .Runnable}}
{{if .HasAvailableFlags}}{{appendIfNotPresent .UseLine "[flags]"}}{{else}}{{.UseLine}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
2014-09-25 23:03:34 +00:00
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
Aliases:
{{.NameAndAliases}}
{{end}}{{if .HasExample}}
Examples:
{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}}
2015-09-11 17:25:15 +00:00
Available Commands:{{range .Commands}}{{if .IsAvailableCommand}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasAvailableInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}}
2015-09-11 17:25:15 +00:00
Additional help topics:{{range .Commands}}{{if .IsHelpCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasAvailableSubCommands }}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`
}
func (c *Command) HelpTemplate() string {
if c.helpTemplate != "" {
return c.helpTemplate
}
if c.HasParent() {
return c.parent.HelpTemplate()
}
return `{{with or .Long .Short }}{{. | trim}}
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
}
// Really only used when casting a command to a commander
func (c *Command) resetChildrensParents() {
for _, x := range c.commands {
x.parent = c
}
}
// Test if the named flag is a boolean flag.
func isBooleanFlag(name string, f *flag.FlagSet) bool {
flag := f.Lookup(name)
if flag == nil {
return false
}
return flag.Value.Type() == "bool"
}
// Test if the named flag is a boolean flag.
func isBooleanShortFlag(name string, f *flag.FlagSet) bool {
result := false
f.VisitAll(func(f *flag.Flag) {
if f.Shorthand == name && f.Value.Type() == "bool" {
result = true
}
})
return result
}
func stripFlags(args []string, c *Command) []string {
2014-06-17 16:32:27 +00:00
if len(args) < 1 {
return args
}
c.mergePersistentFlags()
2014-06-17 16:32:27 +00:00
commands := []string{}
inQuote := false
inFlag := false
2014-06-17 16:32:27 +00:00
for _, y := range args {
if !inQuote {
switch {
case strings.HasPrefix(y, "\""):
inQuote = true
case strings.Contains(y, "=\""):
inQuote = true
case strings.HasPrefix(y, "--") && !strings.Contains(y, "="):
// TODO: this isn't quite right, we should really check ahead for 'true' or 'false'
inFlag = !isBooleanFlag(y[2:], c.Flags())
case strings.HasPrefix(y, "-") && !strings.Contains(y, "=") && len(y) == 2 && !isBooleanShortFlag(y[1:], c.Flags()):
inFlag = true
case inFlag:
inFlag = false
case y == "":
// strip empty commands, as the go tests expect this to be ok....
2014-06-17 16:32:27 +00:00
case !strings.HasPrefix(y, "-"):
commands = append(commands, y)
inFlag = false
2014-06-17 16:32:27 +00:00
}
}
if strings.HasSuffix(y, "\"") && !strings.HasSuffix(y, "\\\"") {
inQuote = false
}
}
return commands
}
2015-04-24 15:39:11 +00:00
// argsMinusFirstX removes only the first x from args. Otherwise, commands that look like
// openshift admin policy add-role-to-user admin my-user, lose the admin argument (arg[4]).
func argsMinusFirstX(args []string, x string) []string {
for i, y := range args {
if x == y {
ret := []string{}
ret = append(ret, args[:i]...)
ret = append(ret, args[i+1:]...)
return ret
2014-06-17 16:32:27 +00:00
}
}
2015-04-24 15:39:11 +00:00
return args
2014-06-17 16:32:27 +00:00
}
2013-09-24 16:47:29 +00:00
// find the target command given the args and command tree
2013-09-24 20:03:22 +00:00
// Meant to be run on the highest node. Only searches down.
func (c *Command) Find(args []string) (*Command, []string, error) {
2013-09-24 16:47:29 +00:00
if c == nil {
return nil, nil, fmt.Errorf("Called find() on a nil Command")
}
var innerfind func(*Command, []string) (*Command, []string)
innerfind = func(c *Command, innerArgs []string) (*Command, []string) {
argsWOflags := stripFlags(innerArgs, c)
if len(argsWOflags) == 0 {
return c, innerArgs
}
nextSubCmd := argsWOflags[0]
matches := make([]*Command, 0)
for _, cmd := range c.commands {
if cmd.Name() == nextSubCmd || cmd.HasAlias(nextSubCmd) { // exact name or alias match
return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
}
if EnablePrefixMatching {
if strings.HasPrefix(cmd.Name(), nextSubCmd) { // prefix match
matches = append(matches, cmd)
}
for _, x := range cmd.Aliases {
if strings.HasPrefix(x, nextSubCmd) {
matches = append(matches, cmd)
}
}
}
2013-09-24 16:47:29 +00:00
}
// only accept a single prefix match - multiple matches would be ambiguous
if len(matches) == 1 {
return innerfind(matches[0], argsMinusFirstX(innerArgs, argsWOflags[0]))
}
return c, innerArgs
2013-09-24 16:47:29 +00:00
}
commandFound, a := innerfind(c, args)
argsWOflags := stripFlags(a, commandFound)
// no subcommand, always take args
if !commandFound.HasSubCommands() {
return commandFound, a, nil
}
// root command with subcommands, do subcommand checking
if commandFound == c && len(argsWOflags) > 0 {
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)
2013-09-24 16:47:29 +00:00
}
return commandFound, a, nil
2013-09-24 16:47:29 +00:00
}
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) VisitParents(fn func(*Command)) {
var traverse func(*Command) *Command
traverse = func(x *Command) *Command {
if x != c {
fn(x)
}
if x.HasParent() {
return traverse(x.parent)
}
return x
}
traverse(c)
}
2013-09-24 20:03:22 +00:00
func (c *Command) Root() *Command {
2013-09-24 16:47:29 +00:00
var findRoot func(*Command) *Command
findRoot = func(x *Command) *Command {
if x.HasParent() {
return findRoot(x.parent)
}
return x
2013-09-24 16:47:29 +00:00
}
2013-09-24 20:03:22 +00:00
return findRoot(c)
}
2015-10-13 22:37:14 +00:00
// ArgsLenAtDash will return the length of f.Args at the moment when a -- was
// found during arg parsing. This allows your program to know which args were
// before the -- and which came after. (Description from
// https://godoc.org/github.com/spf13/pflag#FlagSet.ArgsLenAtDash).
func (c *Command) ArgsLenAtDash() int {
return c.Flags().ArgsLenAtDash()
}
2013-10-01 01:54:46 +00:00
func (c *Command) execute(a []string) (err error) {
2013-09-24 16:47:29 +00:00
if c == nil {
return fmt.Errorf("Called Execute() on a nil Command")
}
if len(c.Deprecated) > 0 {
c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
}
2015-09-01 04:16:41 +00:00
// initialize help flag as the last point possible to allow for user
// overriding
c.initHelpFlag()
2013-10-01 01:54:46 +00:00
err = c.ParseFlags(a)
if err != nil {
return err
}
// If help is called, regardless of other flags, return we want help
// Also say we need help if the command isn't runnable.
2015-09-01 04:16:41 +00:00
helpVal, err := c.Flags().GetBool("help")
if err != nil {
// should be impossible to get here as we always declare a help
// flag in initHelpFlag()
c.Println("\"help\" flag declared as non-bool. Please correct your code")
return err
}
2015-09-01 04:16:41 +00:00
if helpVal || !c.Runnable() {
return flag.ErrHelp
2013-09-24 16:47:29 +00:00
}
c.preRun()
2016-06-04 00:25:52 +00:00
argWoFlags := c.Flags().Args()
2016-06-04 00:25:52 +00:00
if c.DisableFlagParsing {
argWoFlags = a
}
2015-02-09 22:44:14 +00:00
for p := c; p != nil; p = p.Parent() {
if p.PersistentPreRunE != nil {
2015-09-30 07:09:17 +00:00
if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
return err
}
break
} else if p.PersistentPreRun != nil {
p.PersistentPreRun(c, argWoFlags)
break
}
2015-02-17 21:09:49 +00:00
}
if c.PreRunE != nil {
if err := c.PreRunE(c, argWoFlags); err != nil {
return err
}
} else if c.PreRun != nil {
2015-02-09 22:44:14 +00:00
c.PreRun(c, argWoFlags)
}
if c.RunE != nil {
if err := c.RunE(c, argWoFlags); err != nil {
return err
}
} else {
c.Run(c, argWoFlags)
}
if c.PostRunE != nil {
if err := c.PostRunE(c, argWoFlags); err != nil {
return err
}
} else if c.PostRun != nil {
2015-02-09 22:44:14 +00:00
c.PostRun(c, argWoFlags)
}
for p := c; p != nil; p = p.Parent() {
if p.PersistentPostRunE != nil {
if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
return err
}
break
} else if p.PersistentPostRun != nil {
p.PersistentPostRun(c, argWoFlags)
break
}
2015-02-17 21:09:49 +00:00
}
2015-02-09 22:44:14 +00:00
return nil
2013-09-24 16:47:29 +00:00
}
func (c *Command) preRun() {
for _, x := range initializers {
x()
}
}
2014-06-17 16:32:27 +00:00
func (c *Command) errorMsgFromParse() string {
s := c.flagErrorBuf.String()
x := strings.Split(s, "\n")
if len(x) > 0 {
return x[0]
}
return ""
2014-06-17 16:32:27 +00:00
}
// Call execute to use the args (os.Args[1:] by default)
// and run through the command tree finding appropriate matches
// for commands and then corresponding flags.
func (c *Command) Execute() error {
_, err := c.ExecuteC()
return err
}
func (c *Command) ExecuteC() (cmd *Command, err error) {
// Regardless of what command execute is called on, run on Root only
if c.HasParent() {
return c.Root().ExecuteC()
}
// windows hook
if preExecHookFn != nil {
preExecHookFn(c)
}
// initialize help as the last point possible to allow for user
// overriding
2015-09-01 04:16:41 +00:00
c.initHelpCmd()
var args []string
// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
args = os.Args[1:]
} else {
args = c.args
}
cmd, flags, err := c.Find(args)
if err != nil {
// If found parse to a subcommand and then failed, talk about the subcommand
if cmd != nil {
c = cmd
}
if !c.SilenceErrors {
c.Println("Error:", err.Error())
c.Printf("Run '%v --help' for usage.\n", c.CommandPath())
}
return c, err
}
err = cmd.execute(flags)
if err != nil {
// Always show help if requested, even if SilenceErrors is in
// effect
if err == flag.ErrHelp {
cmd.HelpFunc()(cmd, args)
return cmd, nil
}
// If root command has SilentErrors flagged,
// all subcommands should respect it
2015-10-26 03:17:39 +00:00
if !cmd.SilenceErrors && !c.SilenceErrors {
c.Println("Error:", err.Error())
}
// If root command has SilentUsage flagged,
// all subcommands should respect it
if !cmd.SilenceUsage && !c.SilenceUsage {
c.Println(cmd.UsageString())
}
return cmd, err
}
return cmd, nil
}
2015-09-01 04:16:41 +00:00
func (c *Command) initHelpFlag() {
if c.Flags().Lookup("help") == nil {
2015-09-01 04:16:41 +00:00
c.Flags().BoolP("help", "h", false, "help for "+c.Name())
}
2015-09-01 04:16:41 +00:00
}
func (c *Command) initHelpCmd() {
if c.helpCommand == nil {
if !c.HasSubCommands() {
return
}
c.helpCommand = &Command{
Use: "help [command]",
Short: "Help about any command",
Long: `Help provides help for any command in the application.
Simply type ` + c.Name() + ` help [path to command] for full details.`,
2015-02-17 21:09:49 +00:00
PersistentPreRun: func(cmd *Command, args []string) {},
PersistentPostRun: func(cmd *Command, args []string) {},
Run: func(c *Command, args []string) {
cmd, _, e := c.Root().Find(args)
if cmd == nil || e != nil {
c.Printf("Unknown help topic %#q.", args)
c.Root().Usage()
} else {
helpFunc := cmd.HelpFunc()
helpFunc(cmd, args)
}
},
}
}
c.AddCommand(c.helpCommand)
}
2013-09-24 16:47:29 +00:00
// Used for testing
func (c *Command) ResetCommands() {
c.commands = nil
c.helpCommand = nil
2013-09-24 16:47:29 +00:00
}
// Sorts commands by their names
type commandSorterByName []*Command
func (c commandSorterByName) Len() int { return len(c) }
func (c commandSorterByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c commandSorterByName) Less(i, j int) bool { return c[i].Name() < c[j].Name() }
// Commands returns a sorted slice of child commands.
2013-09-24 16:47:29 +00:00
func (c *Command) Commands() []*Command {
// do not sort commands if it already sorted or sorting was disabled
if EnableCommandSorting && !c.commandsAreSorted {
sort.Sort(commandSorterByName(c.commands))
c.commandsAreSorted = true
}
2013-09-24 16:47:29 +00:00
return c.commands
}
2014-12-13 05:05:43 +00:00
// AddCommand adds one or more commands to this parent command.
2013-09-24 16:47:29 +00:00
func (c *Command) AddCommand(cmds ...*Command) {
for i, x := range cmds {
if cmds[i] == c {
panic("Command can't be a child of itself")
}
cmds[i].parent = c
// update max lengths
usageLen := len(x.Use)
if usageLen > c.commandsMaxUseLen {
c.commandsMaxUseLen = usageLen
}
commandPathLen := len(x.CommandPath())
if commandPathLen > c.commandsMaxCommandPathLen {
c.commandsMaxCommandPathLen = commandPathLen
}
nameLen := len(x.Name())
if nameLen > c.commandsMaxNameLen {
c.commandsMaxNameLen = nameLen
}
// If global normalization function exists, update all children
if c.globNormFunc != nil {
x.SetGlobalNormalizationFunc(c.globNormFunc)
}
2013-09-24 16:47:29 +00:00
c.commands = append(c.commands, x)
c.commandsAreSorted = false
2013-09-24 16:47:29 +00:00
}
}
// RemoveCommand removes one or more commands from a parent command.
func (c *Command) RemoveCommand(cmds ...*Command) {
commands := []*Command{}
main:
for _, command := range c.commands {
for _, cmd := range cmds {
if command == cmd {
command.parent = nil
continue main
}
}
commands = append(commands, command)
}
c.commands = commands
// recompute all lengths
c.commandsMaxUseLen = 0
c.commandsMaxCommandPathLen = 0
c.commandsMaxNameLen = 0
for _, command := range c.commands {
usageLen := len(command.Use)
if usageLen > c.commandsMaxUseLen {
c.commandsMaxUseLen = usageLen
}
commandPathLen := len(command.CommandPath())
if commandPathLen > c.commandsMaxCommandPathLen {
c.commandsMaxCommandPathLen = commandPathLen
}
nameLen := len(command.Name())
if nameLen > c.commandsMaxNameLen {
c.commandsMaxNameLen = nameLen
}
}
}
// Print is a convenience method to Print to the defined output
2013-09-24 16:47:29 +00:00
func (c *Command) Print(i ...interface{}) {
fmt.Fprint(c.getOutOrStderr(), i...)
2013-09-24 16:47:29 +00:00
}
// Println is a convenience method to Println to the defined output
2013-09-24 16:47:29 +00:00
func (c *Command) Println(i ...interface{}) {
str := fmt.Sprintln(i...)
c.Print(str)
}
// Printf is a convenience method to Printf to the defined output
2013-09-24 16:47:29 +00:00
func (c *Command) Printf(format string, i ...interface{}) {
str := fmt.Sprintf(format, i...)
c.Print(str)
}
2014-12-13 05:05:43 +00:00
// CommandPath returns the full path to this command.
2013-09-24 16:47:29 +00:00
func (c *Command) CommandPath() string {
str := c.Name()
x := c
for x.HasParent() {
str = x.parent.Name() + " " + str
x = x.parent
}
return str
}
//The full usage for a given command (including parents)
func (c *Command) UseLine() string {
str := ""
if c.HasParent() {
str = c.parent.CommandPath() + " "
}
return str + c.Use
}
// For use in determining which flags have been assigned to which commands
// and which persist
func (c *Command) DebugFlags() {
c.Println("DebugFlags called on", c.Name())
var debugflags func(*Command)
debugflags = func(x *Command) {
if x.HasFlags() || x.HasPersistentFlags() {
c.Println(x.Name())
}
if x.HasFlags() {
x.flags.VisitAll(func(f *flag.Flag) {
if x.HasPersistentFlags() {
if x.persistentFlag(f.Name) == nil {
c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [L]")
} else {
c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [LP]")
}
} else {
c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [L]")
}
})
}
if x.HasPersistentFlags() {
x.pflags.VisitAll(func(f *flag.Flag) {
if x.HasFlags() {
if x.flags.Lookup(f.Name) == nil {
c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [P]")
}
} else {
c.Println(" -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, " [P]")
}
})
}
c.Println(x.flagErrorBuf)
if x.HasSubCommands() {
for _, y := range x.commands {
debugflags(y)
}
}
}
debugflags(c)
}
// Name returns the command's name: the first word in the use line.
func (c *Command) Name() string {
if c.name != "" {
return c.name
}
name := c.Use
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
// HasAlias determines if a given string is an alias of the command.
2014-09-25 23:03:34 +00:00
func (c *Command) HasAlias(s string) bool {
for _, a := range c.Aliases {
if a == s {
return true
}
}
return false
}
func (c *Command) NameAndAliases() string {
return strings.Join(append([]string{c.Name()}, c.Aliases...), ", ")
}
func (c *Command) HasExample() bool {
return len(c.Example) > 0
}
// Runnable determines if the command is itself runnable
2013-09-24 16:47:29 +00:00
func (c *Command) Runnable() bool {
return c.Run != nil || c.RunE != nil
2013-09-24 16:47:29 +00:00
}
// HasSubCommands determines if the command has children commands
2013-09-24 16:47:29 +00:00
func (c *Command) HasSubCommands() bool {
return len(c.commands) > 0
}
// 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
}
2015-09-11 22:18:36 +00:00
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 {
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
return false
}
// if any non-help sub commands are found, the command is not a 'help' command
for _, sub := range c.commands {
if !sub.IsHelpCommand() {
return false
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
}
}
// the command either has no sub commands, or no non-help sub commands
return true
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
}
// 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'
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
func (c *Command) HasHelpSubCommands() bool {
// return true on the first found available 'help' sub command
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
for _, sub := range c.commands {
if sub.IsHelpCommand() {
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
return true
}
}
// the command either has no sub commands, or no available 'help' sub commands
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
return false
}
// 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
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
for _, sub := range c.commands {
if sub.IsAvailableCommand() {
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
return true
}
}
// the command either has no sub comamnds, or no available (non deprecated/help/hidden)
// sub commands
Fix additional help topics template The additional help topics were really hard to ever get to show. The required conditionals were difficult to meet and did not seem to really be logical. Problems I see: 1) the top level command could never have additional topics. 2) you must have at least one sibling command AND one subcommand 3) we had the AND above, but then test both conditionals a second time 4) if the sub command was runnable we wouldn't print anything 5) if the sibling commands were not runnable we wouldn't print anything 4+5) it's possible that we printed "Additional help topics:" with nothing following it 6) We always printed ourselves as a sibling in the additional info Whew, I think I fixed all of those! Again, using https://github.com/eparis/readable-golang-template I'm actually able to visualize the template and see this craziness. The conditionals BEFORE this change: {{if .HasParent}} {{if and (gt .Commands 0) (gt .Parent.Commands 1) }} Additional help topics: {{if gt .Commands 0 }} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if gt .Parent.Commands 1 }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{end}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}} The conditionals AFTER this change: {{if or (.HasHelpSubCommands) (.HasRunnableSiblings)}} Additional help topics: {{if .HasHelpSubCommands}} {{range .Commands}} {{if not .Runnable}} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{if .HasRunnableSiblings }} {{range .Parent.Commands}} {{if .Runnable}} {{if not (eq .Name $cmd.Name) }} {{rpad .CommandPath .CommandPathPadding}} {{.Short}} {{end}} {{end}} {{end}} {{end}} {{end}}
2015-03-20 18:47:56 +00:00
return false
}
2013-09-24 16:47:29 +00:00
// Determine if the command is a child command
func (c *Command) HasParent() bool {
return c.parent != nil
}
// GlobalNormalizationFunc returns the global normalization function or nil if doesn't exists
func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName {
return c.globNormFunc
}
// Get the complete FlagSet that applies to this command (local and persistent declared here and by all parents)
2013-09-24 16:47:29 +00:00
func (c *Command) Flags() *flag.FlagSet {
if c.flags == nil {
c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
c.flags.SetOutput(c.flagErrorBuf)
}
return c.flags
}
// LocalNonPersistentFlags are flags specific to this command which will NOT persist to subcommands
func (c *Command) LocalNonPersistentFlags() *flag.FlagSet {
persistentFlags := c.PersistentFlags()
out := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.LocalFlags().VisitAll(func(f *flag.Flag) {
if persistentFlags.Lookup(f.Name) == nil {
out.AddFlag(f)
}
})
return out
}
// Get the local FlagSet specifically set in the current command
func (c *Command) LocalFlags() *flag.FlagSet {
c.mergePersistentFlags()
local := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.lflags.VisitAll(func(f *flag.Flag) {
local.AddFlag(f)
})
if !c.HasParent() {
flag.CommandLine.VisitAll(func(f *flag.Flag) {
if local.Lookup(f.Name) == nil {
local.AddFlag(f)
}
})
}
return local
}
// All Flags which were inherited from parents commands
func (c *Command) InheritedFlags() *flag.FlagSet {
c.mergePersistentFlags()
inherited := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
local := c.LocalFlags()
var rmerge func(x *Command)
rmerge = func(x *Command) {
if x.HasPersistentFlags() {
x.PersistentFlags().VisitAll(func(f *flag.Flag) {
if inherited.Lookup(f.Name) == nil && local.Lookup(f.Name) == nil {
inherited.AddFlag(f)
}
})
}
if x.HasParent() {
rmerge(x.parent)
}
}
if c.HasParent() {
rmerge(c.parent)
}
return inherited
}
// All Flags which were not inherited from parent commands
func (c *Command) NonInheritedFlags() *flag.FlagSet {
return c.LocalFlags()
}
// Get the Persistent FlagSet specifically set in the current command
2013-09-24 16:47:29 +00:00
func (c *Command) PersistentFlags() *flag.FlagSet {
if c.pflags == nil {
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
c.pflags.SetOutput(c.flagErrorBuf)
}
return c.pflags
}
// For use in testing
func (c *Command) ResetFlags() {
c.flagErrorBuf = new(bytes.Buffer)
c.flagErrorBuf.Reset()
c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.flags.SetOutput(c.flagErrorBuf)
c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
c.pflags.SetOutput(c.flagErrorBuf)
}
// Does the command contain any flags (local plus persistent from the entire structure)
2013-09-24 16:47:29 +00:00
func (c *Command) HasFlags() bool {
return c.Flags().HasFlags()
}
// Does the command contain persistent flags
func (c *Command) HasPersistentFlags() bool {
return c.PersistentFlags().HasFlags()
}
// Does the command has flags specifically declared locally
func (c *Command) HasLocalFlags() bool {
return c.LocalFlags().HasFlags()
}
// Does the command have flags inherited from its parent command
func (c *Command) HasInheritedFlags() bool {
return c.InheritedFlags().HasFlags()
}
// Does the command contain any flags (local plus persistent from the entire
// structure) which are not hidden or deprecated
func (c *Command) HasAvailableFlags() bool {
return c.Flags().HasAvailableFlags()
}
// Does the command contain persistent flags which are not hidden or deprecated
func (c *Command) HasAvailablePersistentFlags() bool {
return c.PersistentFlags().HasAvailableFlags()
}
// Does the command has flags specifically declared locally which are not hidden
// or deprecated
func (c *Command) HasAvailableLocalFlags() bool {
return c.LocalFlags().HasAvailableFlags()
}
// Does the command have flags inherited from its parent command which are
// not hidden or deprecated
func (c *Command) HasAvailableInheritedFlags() bool {
return c.InheritedFlags().HasAvailableFlags()
}
// Flag climbs up the command tree looking for matching flag
2013-09-24 16:47:29 +00:00
func (c *Command) Flag(name string) (flag *flag.Flag) {
flag = c.Flags().Lookup(name)
if flag == nil {
flag = c.persistentFlag(name)
}
return
}
// recursively find matching persistent flag
func (c *Command) persistentFlag(name string) (flag *flag.Flag) {
if c.HasPersistentFlags() {
flag = c.PersistentFlags().Lookup(name)
}
if flag == nil && c.HasParent() {
flag = c.parent.persistentFlag(name)
}
return
}
// ParseFlags parses persistent flag tree & local flags
2013-09-24 16:47:29 +00:00
func (c *Command) ParseFlags(args []string) (err error) {
2016-06-04 00:25:52 +00:00
if c.DisableFlagParsing {
return nil
}
2013-09-24 16:47:29 +00:00
c.mergePersistentFlags()
err = c.Flags().Parse(args)
return
2013-09-24 16:47:29 +00:00
}
// Parent returns a commands parent command
func (c *Command) Parent() *Command {
return c.parent
}
2013-09-24 16:47:29 +00:00
func (c *Command) mergePersistentFlags() {
var rmerge func(x *Command)
// Save the set of local flags
if c.lflags == nil {
c.lflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
c.lflags.SetOutput(c.flagErrorBuf)
addtolocal := func(f *flag.Flag) {
c.lflags.AddFlag(f)
}
c.Flags().VisitAll(addtolocal)
c.PersistentFlags().VisitAll(addtolocal)
}
2013-09-24 16:47:29 +00:00
rmerge = func(x *Command) {
2015-02-09 22:44:14 +00:00
if !x.HasParent() {
flag.CommandLine.VisitAll(func(f *flag.Flag) {
if x.PersistentFlags().Lookup(f.Name) == nil {
x.PersistentFlags().AddFlag(f)
}
})
}
2013-09-24 16:47:29 +00:00
if x.HasPersistentFlags() {
x.PersistentFlags().VisitAll(func(f *flag.Flag) {
if c.Flags().Lookup(f.Name) == nil {
c.Flags().AddFlag(f)
}
})
}
if x.HasParent() {
rmerge(x.parent)
}
}
rmerge(c)
}