github.com/dcarley/cf-cli@v6.24.1-0.20170220111324-4225ff346898+incompatible/command/common/help_command.go (about)

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