diff --git a/cmd/root.go b/cmd/root.go index c7b043a3f..b1ad251be 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -138,18 +138,25 @@ func init() { func initConfig() { styles := boa.DefaultStyles() b := boa.New(boa.WithStyles(styles)) - + oldUsageFunc := RootCmd.UsageFunc() RootCmd.SetUsageFunc(b.UsageFunc) RootCmd.SetHelpFunc(func(command *cobra.Command, strings []string) { // Print a styled Atmos logo to the terminal fmt.Println() - err := tuiUtils.PrintStyledText("ATMOS") - if err != nil { - u.LogErrorAndExit(schema.CliConfiguration{}, err) + if command.Use != "terraform" { + err := tuiUtils.PrintStyledText("ATMOS") + if err != nil { + u.LogErrorAndExit(schema.CliConfiguration{}, err) + } + } + // TODO: find a better way to do this if possible + if command.Use == "terraform" { + oldUsageFunc(command) + return + } else { + b.HelpFunc(command, strings) } - - b.HelpFunc(command, strings) command.Usage() }) } diff --git a/cmd/terraform.go b/cmd/terraform.go index 586e79d22..74d34b9bf 100644 --- a/cmd/terraform.go +++ b/cmd/terraform.go @@ -5,8 +5,10 @@ import ( "github.com/spf13/cobra" e "github.com/cloudposse/atmos/internal/exec" + "github.com/cloudposse/atmos/internal/tui/templates" "github.com/cloudposse/atmos/pkg/schema" u "github.com/cloudposse/atmos/pkg/utils" + cc "github.com/ivanpirog/coloredcobra" ) // terraformCmd represents the base command for all terraform sub-commands @@ -16,42 +18,69 @@ var terraformCmd = &cobra.Command{ Short: "Execute Terraform commands", Long: `This command executes Terraform commands`, FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: true}, - Run: func(cmd *cobra.Command, args []string) { - // Check Atmos configuration - //checkAtmosConfig() - - var argsAfterDoubleDash []string - var finalArgs = args - - doubleDashIndex := lo.IndexOf(args, "--") - if doubleDashIndex > 0 { - finalArgs = lo.Slice(args, 0, doubleDashIndex) - argsAfterDoubleDash = lo.Slice(args, doubleDashIndex+1, len(args)) - } - info, err := e.ProcessCommandLineArgs("terraform", cmd, finalArgs, argsAfterDoubleDash) - if err != nil { - u.LogErrorAndExit(schema.CliConfiguration{}, err) - } - - // Exit on help - if info.NeedHelp { - // Check for the latest Atmos release on GitHub and print update message - CheckForAtmosUpdateAndPrintMessage(cliConfig) - return - } - // Check Atmos configuration - checkAtmosConfig() - - err = e.ExecuteTerraform(info) - if err != nil { - u.LogErrorAndExit(schema.CliConfiguration{}, err) - } - }, + Run: terraformRun, +} + +func terraformRun(cmd *cobra.Command, args []string) { + // Check Atmos configuration + //checkAtmosConfig() + + var argsAfterDoubleDash []string + var finalArgs = args + + doubleDashIndex := lo.IndexOf(args, "--") + if doubleDashIndex > 0 { + finalArgs = lo.Slice(args, 0, doubleDashIndex) + argsAfterDoubleDash = lo.Slice(args, doubleDashIndex+1, len(args)) + } + info, err := e.ProcessCommandLineArgs("terraform", cmd, finalArgs, argsAfterDoubleDash) + if err != nil { + u.LogErrorAndExit(schema.CliConfiguration{}, err) + } + + // Exit on help + if info.NeedHelp { + // Check for the latest Atmos release on GitHub and print update message + template := templates.GenerateFromBaseTemplate(cmd.Use, []templates.HelpTemplateSections{ + templates.LongDescription, + templates.Usage, + templates.Aliases, + templates.Examples, + templates.AvailableCommands, + templates.Flags, + templates.GlobalFlags, + templates.NativeCommands, + templates.Footer, + }) + + cmd.SetUsageTemplate(template) + cc.Init(&cc.Config{ + RootCmd: cmd, + Headings: cc.HiCyan + cc.Bold + cc.Underline, + Commands: cc.HiGreen + cc.Bold, + Example: cc.Italic, + ExecName: cc.Bold, + Flags: cc.Bold, + }) + + cmd.Help() + CheckForAtmosUpdateAndPrintMessage(cliConfig) + return + } + // Check Atmos configuration + checkAtmosConfig() + + err = e.ExecuteTerraform(info) + if err != nil { + u.LogErrorAndExit(schema.CliConfiguration{}, err) + } + } func init() { // https://github.com/spf13/cobra/issues/739 terraformCmd.DisableFlagParsing = true terraformCmd.PersistentFlags().StringP("stack", "s", "", "atmos terraform -s ") + attachTerraformCommands(terraformCmd) RootCmd.AddCommand(terraformCmd) } diff --git a/cmd/terraform_generate.go b/cmd/terraform_generate.go index 1c51e9acf..4bbbec664 100644 --- a/cmd/terraform_generate.go +++ b/cmd/terraform_generate.go @@ -10,6 +10,7 @@ var terraformGenerateCmd = &cobra.Command{ Short: "Execute 'terraform generate' commands", Long: "This command generates configurations for terraform components", FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, + Run: terraformRun, } func init() { diff --git a/cmd/terraform_native_commands.go b/cmd/terraform_native_commands.go new file mode 100644 index 000000000..7ec6ea963 --- /dev/null +++ b/cmd/terraform_native_commands.go @@ -0,0 +1,238 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +// getTerraformCommands returns an array of statically defined Terraform commands with flags +func getTerraformCommands() []*cobra.Command { + // List of Terraform commands + return []*cobra.Command{ + { + Use: "plan", + Short: "Show changes required by the current configuration", + Long: "Generate an execution plan, which shows what actions Terraform will take to reach the desired state of the configuration.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "apply", + Short: "Apply changes to infrastructure", + Long: "Apply the changes required to reach the desired state of the configuration. This will prompt for confirmation before making changes.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "workspace", + Short: "Manage Terraform workspaces", + Long: "Create, list, select, or delete Terraform workspaces, which allow for separate states within the same configuration.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "clean", + Short: "Clean up resources", + Long: "Remove unused or outdated resources to keep the infrastructure clean and reduce costs.", + Run: terraformRun, + }, + { + Use: "deploy", + Short: "Deploy the specified infrastructure using Terraform", + Long: `Deploy the specified infrastructure by running the Terraform plan and apply commands. +This command automates the deployment process, integrates configuration, and ensures streamlined execution.`, + Run: terraformRun, + }, + { + Use: "shell", + Short: "Configures an 'atmos' environment and starts a shell for native Terraform commands.", + Long: "command configures an environment for an 'atmos' component in a stack and starts a new shell allowing executing all native terraform commands inside the shell without using atmos-specific arguments and flag", + Run: terraformRun, + }, + { + Use: "version", + Short: "Show the current Terraform version", + Long: "Displays the current version of Terraform installed on the system.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "varfile", + Short: "Load variables from a file", + Long: "Load variable definitions from a specified file and use them in the configuration.", + Run: terraformRun, + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "write varfile", + Short: "Write variables to a file", + Long: "Write the variables used in the configuration to a specified file for later use or modification.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "destroy", + Short: "Destroy previously-created infrastructure", + Long: "Destroy all the infrastructure managed by Terraform, removing resources as defined in the state file.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "refresh", + Short: "Update the state to match remote systems", + Long: "Refresh the Terraform state, reconciling the local state with the actual infrastructure state.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "init", + Short: "Prepare your working directory for other commands", + Long: "Initialize the working directory containing Terraform configuration files. It will download necessary provider plugins and set up the backend.", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "validate", + Short: "Check whether the configuration is valid", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "console", + Short: "Try Terraform expressions at an interactive command prompt", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "fmt", + Short: "Reformat your configuration in the standard style", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "force-unlock", + Short: "Release a stuck lock on the current workspace", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "get", + Short: "Install or upgrade remote Terraform modules", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "graph", + Short: "Generate a Graphviz graph of the steps in an operation", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "import", + Short: "Associate existing infrastructure with a Terraform resource", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "login", + Short: "Obtain and save credentials for a remote host", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "logout", + Short: "Remove locally-stored credentials for a remote host", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "metadata", + Short: "Metadata related commands", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "modules", + Short: "Show all declared modules in a working directory", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "output", + Short: "Show output values from your root module", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "providers", + Short: "Show the providers required for this configuration", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "show", + Short: "Show the current state or a saved plan", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "state", + Short: "Advanced state management", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "taint", + Short: "Mark a resource instance as not fully functional", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "test", + Short: "Execute integration tests for Terraform modules", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + { + Use: "untaint", + Short: "Remove the 'tainted' state from a resource instance", + Annotations: map[string]string{ + "nativeCommand": "true", + }, + }, + } +} + +// attachTerraformCommands attaches static Terraform commands to a provided parent command +func attachTerraformCommands(parentCmd *cobra.Command) { + commands := getTerraformCommands() + for _, cmd := range commands { + parentCmd.AddCommand(cmd) + } +} diff --git a/internal/exec/help.go b/internal/exec/help.go index df79ec5ee..20594a0b0 100644 --- a/internal/exec/help.go +++ b/internal/exec/help.go @@ -18,61 +18,6 @@ func processHelp( u.PrintMessage("In addition, the 'component' argument and 'stack' flag are required to generate the variables and backend config for the component in the stack.\n") u.PrintMessage(fmt.Sprintf("atmos %s -s [options]", componentType)) u.PrintMessage(fmt.Sprintf("atmos %s --stack [options]", componentType)) - - if componentType == "terraform" { - u.PrintMessage(` -Usage: atmos terraform [global options] [args] - -The available commands for execution are listed below. -The primary workflow commands are given first, followed by -less common or more advanced commands. - -Atmos commands: - generate backend Command generates a backend config file for an 'atmos' component in a stack - generate backends Command generates backend config files for all 'atmos' components in all stacks - generate varfile Command generates a varfile for an 'atmos' component in a stack - generate varfiles Command generates varfiles for all 'atmos' components in all stacks - shell Command configures an environment for an 'atmos' component in a stack and starts a new shell allowing executing all native terraform commands inside the shell without using atmos-specific arguments and flags - double-dash '--' Can be used to signify the end of the options for Atmos and the start of the additional native arguments and flags for the 'terraform' commands. For example: atmos terraform plan -s -- -refresh=false -lock=false - '--append-user-agent' Flag sets the TF_APPEND_USER_AGENT environment variable to customize the User-Agent string in Terraform provider requests. Example: 'Atmos/0.0.1 (Cloud Posse; +https://atmos.tools)'. If not specified, defaults to 'atmos 0.0.1' - -Main commands: - init Prepare your working directory for other commands - validate Check whether the configuration is valid - plan Show changes required by the current configuration - apply Create or update infrastructure - destroy Destroy previously-created infrastructure - -All other commands: - console Try Terraform expressions at an interactive command prompt - fmt Reformat your configuration in the standard style - force-unlock Release a stuck lock on the current workspace - get Install or upgrade remote Terraform modules - graph Generate a Graphviz graph of the steps in an operation - import Associate existing infrastructure with a Terraform resource - login Obtain and save credentials for a remote host - logout Remove locally-stored credentials for a remote host - metadata Metadata related commands - modules Show all declared modules in a working directory - output Show output values from your root module - providers Show the providers required for this configuration - refresh Update the state to match remote systems - show Show the current state or a saved plan - state Advanced state management - taint Mark a resource instance as not fully functional - test Execute integration tests for Terraform modules - untaint Remove the 'tainted' state from a resource instance - version Show the current Terraform version - workspace Workspace management - -Global options (use these before the subcommand, if any): - -chdir=DIR Switch to a different working directory before executing the - given subcommand. - -help Show this help output, or the help for a specified subcommand. - -version An alias for the "version" subcommand. -`) - } - if componentType == "helmfile" { u.PrintMessage("\nAdditions and differences from native helmfile:") u.PrintMessage(" - 'atmos helmfile generate varfile' command generates a varfile for the component in the stack") diff --git a/internal/exec/utils.go b/internal/exec/utils.go index 46693a4ba..a4737513e 100644 --- a/internal/exec/utils.go +++ b/internal/exec/utils.go @@ -220,7 +220,7 @@ func ProcessCommandLineArgs( configAndStacksInfo.SettingsListMergeStrategy = argsAndFlagsInfo.SettingsListMergeStrategy // Check if `-h` or `--help` flags are specified - if argsAndFlagsInfo.NeedHelp { + if argsAndFlagsInfo.NeedHelp && configAndStacksInfo.ComponentType != "terraform" { // If we're dealing with `-h` or `--help`, // then the SubCommand should be empty. if argsAndFlagsInfo.SubCommand == "-h" || argsAndFlagsInfo.SubCommand == "--help" { diff --git a/internal/tui/templates/base_template.go b/internal/tui/templates/base_template.go new file mode 100644 index 000000000..5281a46aa --- /dev/null +++ b/internal/tui/templates/base_template.go @@ -0,0 +1,156 @@ +package templates + +import "fmt" + +// + +// MainUsageTemplate returns the usage template for the root command and wrap cobra flag usages to the terminal width +// func MainUsageTemplate() string { +// return ` +// {{ .Long }} +// Usage:{{if .Runnable}} +// {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} +// {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} + +// Aliases: +// {{.NameAndAliases}}{{end}}{{if .HasExample}} + +// Examples: +// {{.Example}}{{end}}{{if .HasAvailableSubCommands}} + +// Available Commands: +// {{formatCommands .Commands false}}{{end}}{{if .HasAvailableLocalFlags}} + +// Flags: +// {{wrappedFlagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +// Global Flags: +// {{wrappedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +// Additional help topics: +// {{formatCommands .Commands true}}{{end}}{{if .HasAvailableSubCommands}} + +// Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +// ` +// } + +// MainUsageTemplate returns the usage template for the root command and wrap cobra flag usages to the terminal width +func MainUsageTemplate(isLongRequired bool) string { + template := ` +Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}} + + +{{if gt (len .Aliases) 0}} +Aliases: + {{.NameAndAliases}} +{{end}} + +{{if .HasExample}} +Examples: +{{.Example}}{{end}} + +{{if .HasAvailableSubCommands}} +Available Commands: +{{formatCommands .Commands false}}{{end}} + +{{if .HasAvailableLocalFlags}} +Flags: +{{wrappedFlagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}} + + +{{if .HasAvailableInheritedFlags}} +Global Flags: +{{wrappedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}{{end}} + +{{if .HasHelpSubCommands}} +Additional help topics: +{{formatCommands .Commands true}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` + if isLongRequired { + template = ` +{{ .Long }} +` + template + } + return template +} + +type HelpTemplateSections int + +const ( + LongDescription HelpTemplateSections = iota + Usage + Aliases + Examples + AvailableCommands + Flags + GlobalFlags + AdditionalHelpTopics + NativeCommands + Footer +) + +func GenerateFromBaseTemplate(commandName string, parts []HelpTemplateSections) string { + template := "" + for _, value := range parts { + template += getSection(commandName, value) + } + return template +} + +func getSection(commandName string, section HelpTemplateSections) string { + switch section { + case LongDescription: + return `{{ .Long }} +` + case AdditionalHelpTopics: + return `{{if .HasHelpSubCommands}} + +Additional help topics: +{{formatCommands .Commands "additionalHelpTopics"}}{{end}}` + case Aliases: + return `{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}` + case AvailableCommands: + return `{{if .HasAvailableSubCommands}} + +Available Commands: +{{formatCommands .Commands "availableCommands"}}{{end}}` + case Examples: + return `{{if .HasExample}} + +Examples: +{{.Example}}{{end}}` + case Flags: + return `{{if .HasAvailableLocalFlags}} + +Flags: +{{wrappedFlagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}` + case GlobalFlags: + return `{{if .HasAvailableInheritedFlags}} + +Global Flags: +{{wrappedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}{{end}}` + case NativeCommands: + return fmt.Sprintf(` +{{HeadingStyle "Native %s Commands:"}} + +{{formatCommands .Commands "native"}} +`, commandName) + case Usage: + return `Usage:{{if .Runnable}} + {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} + {{.CommandPath}} [command]{{end}}` + case Footer: + return `{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}` + default: + return "" + } +} diff --git a/internal/tui/templates/templater.go b/internal/tui/templates/templater.go index 3d5781ac9..bfab72c3d 100644 --- a/internal/tui/templates/templater.go +++ b/internal/tui/templates/templater.go @@ -3,6 +3,7 @@ package templates import ( "fmt" "os" + "sort" "strings" "github.com/charmbracelet/lipgloss" @@ -24,34 +25,80 @@ var ( commandDescStyle = lipgloss.NewStyle(). Foreground(lipgloss.Color("7")) // White color for description + + commandUnsupportedNameStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("8")). + Bold(true) + commandUnsupportedDescStyle = lipgloss.NewStyle(). + Foreground(lipgloss.Color("8")) ) // formatCommand returns a styled string for a command and its description -func formatCommand(name string, desc string, padding int) string { +func formatCommand(name string, desc string, padding int, IsNotSupported bool) string { paddedName := fmt.Sprintf("%-*s", padding, name) + if IsNotSupported { + styledName := commandUnsupportedNameStyle.Render(paddedName) + styledDesc := commandUnsupportedDescStyle.Render(desc + " [unsupported]") + return fmt.Sprintf(" %-30s %s", styledName, styledDesc) + } styledName := commandNameStyle.Render(paddedName) styledDesc := commandDescStyle.Render(desc) - return fmt.Sprintf(" %s %s", styledName, styledDesc) + return fmt.Sprintf(" %-30s %s", styledName, styledDesc) } // formatCommands formats a slice of cobra commands with proper styling -func formatCommands(cmds []*cobra.Command) string { +func formatCommands(cmds []*cobra.Command, listType string) string { var maxLen int availableCmds := make([]*cobra.Command, 0) // First pass: collect available commands and find max length for _, cmd := range cmds { - if cmd.IsAvailableCommand() || cmd.Name() == "help" { - availableCmds = append(availableCmds, cmd) - if len(cmd.Name()) > maxLen { - maxLen = len(cmd.Name()) + switch listType { + case "additionalHelpTopics": + if cmd.IsAdditionalHelpTopicCommand() { + availableCmds = append(availableCmds, cmd) + if len(cmd.Name()) > maxLen { + maxLen = len(cmd.Name()) + } + continue + } + case "native": + if cmd.Annotations["nativeCommand"] == "true" { + availableCmds = append(availableCmds, cmd) + if len(cmd.Name()) > maxLen { + maxLen = len(cmd.Name()) + } + continue + } + default: + if cmd.IsAvailableCommand() || cmd.Name() == "help" { + availableCmds = append(availableCmds, cmd) + if len(cmd.Name()) > maxLen { + maxLen = len(cmd.Name()) + } } } } var lines []string + // Sorting by whether "IsNotSupported" is present in the Annotations map + sort.Slice(availableCmds, func(i, j int) bool { + // Check if "IsNotSupported" is present for commands[i] and commands[j] + iHasKey := availableCmds[i].Annotations["IsNotSupported"] != "true" + jHasKey := availableCmds[j].Annotations["IsNotSupported"] != "true" + + // Place commands with "IsNotSupported" at the top + if iHasKey && !jHasKey { + return true + } + if !iHasKey && jHasKey { + return false + } + // If both or neither have the key, maintain original order + return i < j + }) for _, cmd := range availableCmds { - lines = append(lines, formatCommand(cmd.Name(), cmd.Short, maxLen)) + lines = append(lines, formatCommand(cmd.Name(), cmd.Short, maxLen, cmd.Annotations["IsNotSupported"] == "true")) } return strings.Join(lines, "\n") @@ -64,7 +111,16 @@ func SetCustomUsageFunc(cmd *cobra.Command) error { return fmt.Errorf("command cannot be nil") } t := &Templater{ - UsageTemplate: MainUsageTemplate(), + UsageTemplate: GenerateFromBaseTemplate(cmd.Use, []HelpTemplateSections{ + Usage, + Aliases, + Examples, + AvailableCommands, + Flags, + GlobalFlags, + AdditionalHelpTopics, + Footer, + }), } cmd.SetUsageTemplate(t.UsageTemplate) @@ -88,34 +144,6 @@ func getTerminalWidth() int { return screenWidth } -// MainUsageTemplate returns the usage template for the root command and wrap cobra flag usages to the terminal width -func MainUsageTemplate() string { - return `Usage:{{if .Runnable}} - {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} - {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: -{{.Example}}{{end}}{{if .HasAvailableSubCommands}} - -Available Commands: -{{formatCommands .Commands}}{{end}}{{if .HasAvailableLocalFlags}} - -Flags: -{{wrappedFlagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} - -Global Flags: -{{wrappedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} - -Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} - -Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} -` -} - // WrappedFlagUsages formats the flag usage string to fit within the terminal width func WrappedFlagUsages(f *pflag.FlagSet) string { var builder strings.Builder