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 }