github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+incompatible/command/common/help_command.go (about)

     1  package common
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"strings"
     7  
     8  	"code.cloudfoundry.org/cli/actor/actionerror"
     9  	"code.cloudfoundry.org/cli/actor/sharedaction"
    10  	"code.cloudfoundry.org/cli/command"
    11  	"code.cloudfoundry.org/cli/command/common/internal"
    12  	"code.cloudfoundry.org/cli/command/flag"
    13  	"code.cloudfoundry.org/cli/util/configv3"
    14  )
    15  
    16  const (
    17  	commonCommandsIndent string = "  "
    18  	allCommandsIndent    string = "   "
    19  	commandIndent        string = "   "
    20  )
    21  
    22  //go:generate counterfeiter . HelpActor
    23  
    24  // HelpActor handles the business logic of the help command
    25  type HelpActor interface {
    26  	// CommandInfoByName returns back a help command information for the given
    27  	// command
    28  	CommandInfoByName(interface{}, string) (sharedaction.CommandInfo, error)
    29  
    30  	// CommandInfos returns a list of all commands
    31  	CommandInfos(interface{}) map[string]sharedaction.CommandInfo
    32  }
    33  
    34  type HelpCommand struct {
    35  	UI     command.UI
    36  	Actor  HelpActor
    37  	Config command.Config
    38  
    39  	OptionalArgs flag.CommandName `positional-args:"yes"`
    40  	AllCommands  bool             `short:"a" description:"All available CLI commands"`
    41  	usage        interface{}      `usage:"CF_NAME help [COMMAND]"`
    42  }
    43  
    44  func (cmd *HelpCommand) Setup(config command.Config, ui command.UI) error {
    45  	cmd.Actor = sharedaction.NewActor(config)
    46  	cmd.Config = config
    47  	cmd.UI = ui
    48  
    49  	return nil
    50  }
    51  
    52  func (cmd HelpCommand) Execute(args []string) error {
    53  	var err error
    54  	if cmd.OptionalArgs.CommandName == "" {
    55  		cmd.displayFullHelp()
    56  	} else {
    57  		err = cmd.displayCommand()
    58  	}
    59  
    60  	return err
    61  }
    62  
    63  func (cmd HelpCommand) displayFullHelp() {
    64  	if cmd.AllCommands {
    65  		pluginCommands := cmd.getSortedPluginCommands()
    66  		cmdInfo := cmd.Actor.CommandInfos(Commands)
    67  		longestCmd := internal.LongestCommandName(cmdInfo, pluginCommands)
    68  
    69  		cmd.displayHelpPreamble()
    70  		cmd.displayAllCommands(pluginCommands, cmdInfo, longestCmd)
    71  		cmd.displayHelpFooter(cmdInfo)
    72  	} else {
    73  		cmd.displayCommonCommands()
    74  	}
    75  }
    76  
    77  func (cmd HelpCommand) displayHelpPreamble() {
    78  	cmd.UI.DisplayHeader("NAME:")
    79  	cmd.UI.DisplayText(allCommandsIndent+"{{.CommandName}} - {{.CommandDescription}}",
    80  		map[string]interface{}{
    81  			"CommandName":        cmd.Config.BinaryName(),
    82  			"CommandDescription": cmd.UI.TranslateText("A command line tool to interact with Cloud Foundry"),
    83  		})
    84  	cmd.UI.DisplayNewline()
    85  
    86  	cmd.UI.DisplayHeader("USAGE:")
    87  	cmd.UI.DisplayText(allCommandsIndent+"{{.CommandName}} {{.CommandUsage}}",
    88  		map[string]interface{}{
    89  			"CommandName":  cmd.Config.BinaryName(),
    90  			"CommandUsage": cmd.UI.TranslateText("[global options] command [arguments...] [command options]"),
    91  		})
    92  	cmd.UI.DisplayNewline()
    93  
    94  	cmd.UI.DisplayHeader("VERSION:")
    95  	cmd.UI.DisplayText(allCommandsIndent + cmd.Config.BinaryVersion())
    96  	cmd.UI.DisplayNewline()
    97  }
    98  
    99  func (cmd HelpCommand) displayAllCommands(pluginCommands []configv3.PluginCommand, cmdInfo map[string]sharedaction.CommandInfo, longestCmd int) {
   100  	cmd.displayCommandGroups(internal.HelpCategoryList, cmdInfo, longestCmd)
   101  	cmd.UI.DisplayNewline()
   102  
   103  	cmd.UI.DisplayHeader("INSTALLED PLUGIN COMMANDS:")
   104  	for _, pluginCommand := range pluginCommands {
   105  		cmd.UI.DisplayText(allCommandsIndent+"{{.CommandName}}{{.Gap}}{{.CommandDescription}}", map[string]interface{}{
   106  			"CommandName":        pluginCommand.Name,
   107  			"CommandDescription": pluginCommand.HelpText,
   108  			"Gap":                strings.Repeat(" ", longestCmd+1-len(pluginCommand.Name)),
   109  		})
   110  	}
   111  	cmd.UI.DisplayNewline()
   112  }
   113  
   114  func (cmd HelpCommand) displayCommandGroups(commandGroupList []internal.HelpCategory, cmdInfo map[string]sharedaction.CommandInfo, longestCmd int) {
   115  	for i, category := range commandGroupList {
   116  		cmd.UI.DisplayHeader(category.CategoryName)
   117  
   118  		for j, row := range category.CommandList {
   119  			for _, command := range row {
   120  				cmd.UI.DisplayText(allCommandsIndent+"{{.CommandName}}{{.Gap}}{{.CommandDescription}}",
   121  					map[string]interface{}{
   122  						"CommandName":        cmdInfo[command].Name,
   123  						"CommandDescription": cmd.UI.TranslateText(cmdInfo[command].Description),
   124  						"Gap":                strings.Repeat(" ", longestCmd+1-len(command)),
   125  					})
   126  			}
   127  
   128  			if j < len(category.CommandList)-1 || i < len(commandGroupList)-1 {
   129  				cmd.UI.DisplayNewline()
   130  			}
   131  		}
   132  	}
   133  }
   134  
   135  func (cmd HelpCommand) displayHelpFooter(cmdInfo map[string]sharedaction.CommandInfo) {
   136  	cmd.UI.DisplayHeader("ENVIRONMENT VARIABLES:")
   137  	cmd.UI.DisplayNonWrappingTable(allCommandsIndent, cmd.environmentalVariablesTableData(), 1)
   138  
   139  	cmd.UI.DisplayNewline()
   140  
   141  	cmd.UI.DisplayHeader("GLOBAL OPTIONS:")
   142  	cmd.UI.DisplayNonWrappingTable(allCommandsIndent, cmd.globalOptionsTableData(), 25)
   143  
   144  	cmd.UI.DisplayNewline()
   145  
   146  	cmd.displayCommandGroups(internal.ExperimentalHelpCategoryList, cmdInfo, 34)
   147  }
   148  
   149  func (cmd HelpCommand) displayCommonCommands() {
   150  	cmdInfo := cmd.Actor.CommandInfos(Commands)
   151  
   152  	cmd.UI.DisplayText("{{.CommandName}} {{.VersionCommand}} {{.Version}}, {{.CLI}}",
   153  		map[string]interface{}{
   154  			"CommandName":    cmd.Config.BinaryName(),
   155  			"VersionCommand": cmd.UI.TranslateText("version"),
   156  			"Version":        cmd.Config.BinaryVersion(),
   157  			"CLI":            cmd.UI.TranslateText("Cloud Foundry command line tool"),
   158  		})
   159  	cmd.UI.DisplayText("{{.Usage}} {{.CommandName}} {{.CommandUsage}}",
   160  		map[string]interface{}{
   161  			"Usage":        cmd.UI.TranslateText("Usage:"),
   162  			"CommandName":  cmd.Config.BinaryName(),
   163  			"CommandUsage": cmd.UI.TranslateText("[global options] command [arguments...] [command options]"),
   164  		})
   165  	cmd.UI.DisplayNewline()
   166  
   167  	for _, category := range internal.CommonHelpCategoryList {
   168  		cmd.UI.DisplayHeader(category.CategoryName)
   169  		table := [][]string{}
   170  
   171  		for _, row := range category.CommandList {
   172  			finalRow := []string{}
   173  
   174  			for _, command := range row {
   175  				separator := ""
   176  				if info, ok := cmdInfo[command]; ok {
   177  					if len(info.Alias) > 0 {
   178  						separator = ","
   179  					}
   180  					finalRow = append(finalRow, fmt.Sprint(info.Name, separator, info.Alias))
   181  				}
   182  			}
   183  
   184  			table = append(table, finalRow)
   185  		}
   186  
   187  		cmd.UI.DisplayNonWrappingTable(commonCommandsIndent, table, 4)
   188  		cmd.UI.DisplayNewline()
   189  	}
   190  
   191  	pluginCommands := cmd.getSortedPluginCommands()
   192  
   193  	size := int(math.Ceil(float64(len(pluginCommands)) / 3))
   194  	table := make([][]string, size)
   195  	for i := 0; i < size; i++ {
   196  		table[i] = make([]string, 3)
   197  		for j := 0; j < 3; j++ {
   198  			index := i + j*size
   199  			if index < len(pluginCommands) {
   200  				pluginName := pluginCommands[index].Name
   201  				if pluginCommands[index].Alias != "" {
   202  					pluginName = pluginName + "," + pluginCommands[index].Alias
   203  				}
   204  				table[i][j] = pluginName
   205  			}
   206  		}
   207  	}
   208  
   209  	cmd.UI.DisplayHeader("Commands offered by installed plugins:")
   210  	cmd.UI.DisplayNonWrappingTable(commonCommandsIndent, table, 4)
   211  	cmd.UI.DisplayNewline()
   212  
   213  	cmd.UI.DisplayHeader("Global options:")
   214  	cmd.UI.DisplayNonWrappingTable(commonCommandsIndent, cmd.globalOptionsTableData(), 25)
   215  	cmd.UI.DisplayNewline()
   216  
   217  	cmd.UI.DisplayText("Use 'cf help -a' to see all commands.")
   218  }
   219  
   220  func (cmd HelpCommand) displayCommand() error {
   221  	cmdInfo, err := cmd.Actor.CommandInfoByName(Commands, cmd.OptionalArgs.CommandName)
   222  	if err != nil {
   223  		if err, ok := err.(actionerror.InvalidCommandError); ok {
   224  			var found bool
   225  			if cmdInfo, found = cmd.findPlugin(); !found {
   226  				return err
   227  			}
   228  		} else {
   229  			return err
   230  		}
   231  	}
   232  
   233  	cmd.UI.DisplayText("NAME:")
   234  	cmd.UI.DisplayText(commandIndent+"{{.CommandName}} - {{.CommandDescription}}",
   235  		map[string]interface{}{
   236  			"CommandName":        cmdInfo.Name,
   237  			"CommandDescription": cmd.UI.TranslateText(cmdInfo.Description),
   238  		})
   239  
   240  	cmd.UI.DisplayNewline()
   241  	usageString := strings.Replace(cmdInfo.Usage, "CF_NAME", cmd.Config.BinaryName(), -1)
   242  	cmd.UI.DisplayText("USAGE:")
   243  	cmd.UI.DisplayText(commandIndent+"{{.CommandUsage}}",
   244  		map[string]interface{}{
   245  			"CommandUsage": cmd.UI.TranslateText(usageString),
   246  		})
   247  
   248  	if cmdInfo.Alias != "" {
   249  		cmd.UI.DisplayNewline()
   250  		cmd.UI.DisplayText("ALIAS:")
   251  		cmd.UI.DisplayText(commandIndent+"{{.Alias}}",
   252  			map[string]interface{}{
   253  				"Alias": cmdInfo.Alias,
   254  			})
   255  	}
   256  
   257  	if len(cmdInfo.Flags) != 0 {
   258  		cmd.UI.DisplayNewline()
   259  		cmd.UI.DisplayText("OPTIONS:")
   260  		nameWidth := internal.LongestFlagWidth(cmdInfo.Flags) + 6
   261  		for _, flag := range cmdInfo.Flags {
   262  			var name string
   263  			if flag.Short != "" && flag.Long != "" {
   264  				name = fmt.Sprintf("--%s, -%s", flag.Long, flag.Short)
   265  			} else if flag.Short != "" {
   266  				name = "-" + flag.Short
   267  			} else {
   268  				name = "--" + flag.Long
   269  			}
   270  
   271  			defaultText := ""
   272  			if flag.Default != "" {
   273  				defaultText = cmd.UI.TranslateText(" (Default: {{.DefaultValue}})", map[string]interface{}{
   274  					"DefaultValue": flag.Default,
   275  				})
   276  			}
   277  
   278  			cmd.UI.DisplayText(commandIndent+"{{.Flags}}{{.Spaces}}{{.Description}}{{.Default}}",
   279  				map[string]interface{}{
   280  					"Flags":       name,
   281  					"Spaces":      strings.Repeat(" ", nameWidth-len(name)),
   282  					"Description": cmd.UI.TranslateText(flag.Description),
   283  					"Default":     defaultText,
   284  				})
   285  		}
   286  	}
   287  
   288  	if len(cmdInfo.Environment) != 0 {
   289  		cmd.UI.DisplayNewline()
   290  		cmd.UI.DisplayText("ENVIRONMENT:")
   291  		for _, envVar := range cmdInfo.Environment {
   292  			cmd.UI.DisplayText(commandIndent+"{{.EnvVar}}{{.Description}}",
   293  				map[string]interface{}{
   294  					"EnvVar":      fmt.Sprintf("%-29s", fmt.Sprintf("%s=%s", envVar.Name, envVar.DefaultValue)),
   295  					"Description": cmd.UI.TranslateText(envVar.Description),
   296  				})
   297  		}
   298  	}
   299  
   300  	if len(cmdInfo.RelatedCommands) > 0 {
   301  		cmd.UI.DisplayNewline()
   302  		cmd.UI.DisplayText("SEE ALSO:")
   303  		cmd.UI.DisplayText(commandIndent + strings.Join(cmdInfo.RelatedCommands, ", "))
   304  	}
   305  
   306  	return nil
   307  }
   308  
   309  func (cmd HelpCommand) environmentalVariablesTableData() [][]string {
   310  	return [][]string{
   311  		{"CF_COLOR=false", cmd.UI.TranslateText("Do not colorize output")},
   312  		{"CF_DIAL_TIMEOUT=5", cmd.UI.TranslateText("Max wait time to establish a connection, including name resolution, in seconds")},
   313  		{"CF_HOME=path/to/dir/", cmd.UI.TranslateText("Override path to default config directory")},
   314  		{"CF_PLUGIN_HOME=path/to/dir/", cmd.UI.TranslateText("Override path to default plugin config directory")},
   315  		{"CF_TRACE=true", cmd.UI.TranslateText("Print API request diagnostics to stdout")},
   316  		{"CF_TRACE=path/to/trace.log", cmd.UI.TranslateText("Append API request diagnostics to a log file")},
   317  		{"all_proxy=proxy.example.com:8080", cmd.UI.TranslateText("Specify a proxy server to enable proxying for all requests")},
   318  		{"https_proxy=proxy.example.com:8080", cmd.UI.TranslateText("Enable proxying for HTTP requests")},
   319  	}
   320  }
   321  
   322  func (cmd HelpCommand) globalOptionsTableData() [][]string {
   323  	return [][]string{
   324  		{"--help, -h", cmd.UI.TranslateText("Show help")},
   325  		{"-v", cmd.UI.TranslateText("Print API request diagnostics to stdout")},
   326  	}
   327  }
   328  
   329  func (cmd HelpCommand) findPlugin() (sharedaction.CommandInfo, bool) {
   330  	for _, pluginConfig := range cmd.Config.Plugins() {
   331  		for _, command := range pluginConfig.Commands {
   332  			if command.Name == cmd.OptionalArgs.CommandName ||
   333  				command.Alias == cmd.OptionalArgs.CommandName {
   334  				return internal.ConvertPluginToCommandInfo(command), true
   335  			}
   336  		}
   337  	}
   338  
   339  	return sharedaction.CommandInfo{}, false
   340  }
   341  
   342  func (cmd HelpCommand) getSortedPluginCommands() []configv3.PluginCommand {
   343  	plugins := cmd.Config.Plugins()
   344  
   345  	var pluginCommands []configv3.PluginCommand
   346  	for _, plugin := range plugins {
   347  		pluginCommands = append(pluginCommands, plugin.PluginCommands()...)
   348  	}
   349  
   350  	return pluginCommands
   351  }