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