github.com/LukasHeimann/cloudfoundrycli/v8@v8.4.4/command/common/help_command.go (about) 1 package common 2 3 import ( 4 "fmt" 5 "math" 6 "strings" 7 8 "github.com/LukasHeimann/cloudfoundrycli/v8/actor/actionerror" 9 "github.com/LukasHeimann/cloudfoundrycli/v8/actor/sharedaction" 10 "github.com/LukasHeimann/cloudfoundrycli/v8/command" 11 "github.com/LukasHeimann/cloudfoundrycli/v8/command/common/internal" 12 "github.com/LukasHeimann/cloudfoundrycli/v8/command/flag" 13 "github.com/LukasHeimann/cloudfoundrycli/v8/util/configv3" 14 ) 15 16 //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . 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 244 if cmdInfo.Examples != "" { 245 examplesString := strings.Replace(cmdInfo.Examples, "CF_NAME", cmd.Config.BinaryName(), -1) 246 cmd.UI.DisplayNewline() 247 cmd.UI.DisplayText("EXAMPLES:") 248 cmd.UI.DisplayText(sharedaction.CommandIndent+"{{.Examples}}", 249 map[string]interface{}{ 250 "Examples": examplesString, 251 }, 252 ) 253 } 254 255 if cmdInfo.Resources != "" { 256 cmd.UI.DisplayNewline() 257 cmd.UI.DisplayText("RESOURCES:") 258 cmd.UI.DisplayText(sharedaction.CommandIndent+"{{.Resources}}", 259 map[string]interface{}{ 260 "Resources": cmdInfo.Resources, 261 }, 262 ) 263 } 264 265 if cmdInfo.Alias != "" { 266 cmd.UI.DisplayNewline() 267 cmd.UI.DisplayText("ALIAS:") 268 cmd.UI.DisplayText(sharedaction.CommandIndent+"{{.Alias}}", 269 map[string]interface{}{ 270 "Alias": cmdInfo.Alias, 271 }) 272 } 273 274 if len(cmdInfo.Flags) != 0 { 275 cmd.UI.DisplayNewline() 276 cmd.UI.DisplayText("OPTIONS:") 277 nameWidth := internal.LongestFlagWidth(cmdInfo.Flags) + 6 278 for _, flag := range cmdInfo.Flags { 279 name := internal.FlagWithHyphens(flag) 280 defaultText := "" 281 if flag.Default != "" { 282 defaultText = cmd.UI.TranslateText(" (Default: {{.DefaultValue}})", map[string]interface{}{ 283 "DefaultValue": flag.Default, 284 }) 285 } 286 287 cmd.UI.DisplayText(sharedaction.CommandIndent+"{{.Flags}}{{.Spaces}}{{.Description}}{{.Default}}", 288 map[string]interface{}{ 289 "Flags": name, 290 "Spaces": strings.Repeat(" ", nameWidth-len(name)), 291 "Description": cmd.UI.TranslateText(flag.Description), 292 "Default": defaultText, 293 }) 294 } 295 } 296 297 if len(cmdInfo.Environment) != 0 { 298 cmd.UI.DisplayNewline() 299 cmd.UI.DisplayText("ENVIRONMENT:") 300 for _, envVar := range cmdInfo.Environment { 301 cmd.UI.DisplayText(sharedaction.CommandIndent+"{{.EnvVar}}{{.Description}}", 302 map[string]interface{}{ 303 "EnvVar": fmt.Sprintf("%-29s", fmt.Sprintf("%s=%s", envVar.Name, envVar.DefaultValue)), 304 "Description": cmd.UI.TranslateText(envVar.Description), 305 }) 306 } 307 } 308 309 if len(cmdInfo.RelatedCommands) > 0 { 310 cmd.UI.DisplayNewline() 311 cmd.UI.DisplayText("SEE ALSO:") 312 cmd.UI.DisplayText(sharedaction.CommandIndent + strings.Join(cmdInfo.RelatedCommands, ", ")) 313 } 314 315 return nil 316 } 317 318 func (cmd HelpCommand) environmentalVariablesTableData() [][]string { 319 return [][]string{ 320 {"CF_COLOR=false", cmd.UI.TranslateText("Do not colorize output")}, 321 {"CF_DIAL_TIMEOUT=6", cmd.UI.TranslateText("Max wait time to establish a connection, including name resolution, in seconds")}, 322 {"CF_HOME=path/to/dir/", cmd.UI.TranslateText("Override path to default config directory")}, 323 {"CF_PLUGIN_HOME=path/to/dir/", cmd.UI.TranslateText("Override path to default plugin config directory")}, 324 {"CF_TRACE=true", cmd.UI.TranslateText("Print API request diagnostics to stdout")}, 325 {"CF_TRACE=path/to/trace.log", cmd.UI.TranslateText("Append API request diagnostics to a log file")}, 326 {"all_proxy=proxy.example.com:8080", cmd.UI.TranslateText("Specify a proxy server to enable proxying for all requests")}, 327 {"https_proxy=proxy.example.com:8080", cmd.UI.TranslateText("Enable proxying for HTTP requests")}, 328 } 329 } 330 331 func (cmd HelpCommand) globalOptionsTableData() [][]string { 332 return [][]string{ 333 {"--help, -h", cmd.UI.TranslateText("Show help")}, 334 {"-v", cmd.UI.TranslateText("Print API request diagnostics to stdout")}, 335 } 336 } 337 338 func (cmd HelpCommand) findPlugin() (sharedaction.CommandInfo, bool) { 339 for _, pluginConfig := range cmd.Config.Plugins() { 340 for _, command := range pluginConfig.Commands { 341 if command.Name == cmd.OptionalArgs.CommandName || 342 command.Alias == cmd.OptionalArgs.CommandName { 343 return internal.ConvertPluginToCommandInfo(command), true 344 } 345 } 346 } 347 348 return sharedaction.CommandInfo{}, false 349 } 350 351 func (cmd HelpCommand) getSortedPluginCommands() []configv3.PluginCommand { 352 plugins := cmd.Config.Plugins() 353 354 var pluginCommands []configv3.PluginCommand 355 for _, plugin := range plugins { 356 pluginCommands = append(pluginCommands, plugin.PluginCommands()...) 357 } 358 359 return pluginCommands 360 }