github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/pkg/cmd/root/help.go (about) 1 package root 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 8 "github.com/abdfnx/gh-api/pkg/cmdutil" 9 "github.com/abdfnx/gh-api/pkg/iostreams" 10 "github.com/abdfnx/gh-api/pkg/text" 11 "github.com/spf13/cobra" 12 "github.com/spf13/pflag" 13 ) 14 15 func rootUsageFunc(command *cobra.Command) error { 16 command.Printf("Usage: %s", command.UseLine()) 17 18 subcommands := command.Commands() 19 if len(subcommands) > 0 { 20 command.Print("\n\nAvailable commands:\n") 21 for _, c := range subcommands { 22 if c.Hidden { 23 continue 24 } 25 command.Printf(" %s\n", c.Name()) 26 } 27 return nil 28 } 29 30 flagUsages := command.LocalFlags().FlagUsages() 31 if flagUsages != "" { 32 command.Println("\n\nFlags:") 33 command.Print(text.Indent(dedent(flagUsages), " ")) 34 } 35 return nil 36 } 37 38 func rootFlagErrorFunc(cmd *cobra.Command, err error) error { 39 if err == pflag.ErrHelp { 40 return err 41 } 42 return &cmdutil.FlagError{Err: err} 43 } 44 45 var hasFailed bool 46 47 // HasFailed signals that the main process should exit with non-zero status 48 func HasFailed() bool { 49 return hasFailed 50 } 51 52 // Display helpful error message in case subcommand name was mistyped. 53 // This matches Cobra's behavior for root command, which Cobra 54 // confusingly doesn't apply to nested commands. 55 func nestedSuggestFunc(command *cobra.Command, arg string) { 56 command.Printf("unknown command %q for %q\n", arg, command.CommandPath()) 57 58 var candidates []string 59 if arg == "help" { 60 candidates = []string{"--help"} 61 } else { 62 if command.SuggestionsMinimumDistance <= 0 { 63 command.SuggestionsMinimumDistance = 2 64 } 65 candidates = command.SuggestionsFor(arg) 66 } 67 68 if len(candidates) > 0 { 69 command.Print("\nDid you mean this?\n") 70 for _, c := range candidates { 71 command.Printf("\t%s\n", c) 72 } 73 } 74 75 command.Print("\n") 76 _ = rootUsageFunc(command) 77 } 78 79 func isRootCmd(command *cobra.Command) bool { 80 return command != nil && !command.HasParent() 81 } 82 83 func rootHelpFunc(cs *iostreams.ColorScheme, command *cobra.Command, args []string) { 84 if isRootCmd(command.Parent()) && len(args) >= 2 && args[1] != "--help" && args[1] != "-h" { 85 nestedSuggestFunc(command, args[1]) 86 hasFailed = true 87 return 88 } 89 90 coreCommands := []string{} 91 actionsCommands := []string{} 92 additionalCommands := []string{} 93 for _, c := range command.Commands() { 94 if c.Short == "" { 95 continue 96 } 97 if c.Hidden { 98 continue 99 } 100 101 s := rpad(c.Name()+":", c.NamePadding()) + c.Short 102 if _, ok := c.Annotations["IsCore"]; ok { 103 coreCommands = append(coreCommands, s) 104 } else if _, ok := c.Annotations["IsActions"]; ok { 105 actionsCommands = append(actionsCommands, s) 106 } else { 107 additionalCommands = append(additionalCommands, s) 108 } 109 } 110 111 // If there are no core commands, assume everything is a core command 112 if len(coreCommands) == 0 { 113 coreCommands = additionalCommands 114 additionalCommands = []string{} 115 } 116 117 type helpEntry struct { 118 Title string 119 Body string 120 } 121 122 helpEntries := []helpEntry{} 123 if command.Long != "" { 124 helpEntries = append(helpEntries, helpEntry{"", command.Long}) 125 } else if command.Short != "" { 126 helpEntries = append(helpEntries, helpEntry{"", command.Short}) 127 } 128 helpEntries = append(helpEntries, helpEntry{"USAGE", command.UseLine()}) 129 if len(coreCommands) > 0 { 130 helpEntries = append(helpEntries, helpEntry{"CORE COMMANDS", strings.Join(coreCommands, "\n")}) 131 } 132 if len(actionsCommands) > 0 { 133 helpEntries = append(helpEntries, helpEntry{"ACTIONS COMMANDS", strings.Join(actionsCommands, "\n")}) 134 } 135 if len(additionalCommands) > 0 { 136 helpEntries = append(helpEntries, helpEntry{"ADDITIONAL COMMANDS", strings.Join(additionalCommands, "\n")}) 137 } 138 139 flagUsages := command.LocalFlags().FlagUsages() 140 if flagUsages != "" { 141 helpEntries = append(helpEntries, helpEntry{"FLAGS", dedent(flagUsages)}) 142 } 143 inheritedFlagUsages := command.InheritedFlags().FlagUsages() 144 if inheritedFlagUsages != "" { 145 helpEntries = append(helpEntries, helpEntry{"INHERITED FLAGS", dedent(inheritedFlagUsages)}) 146 } 147 if _, ok := command.Annotations["help:arguments"]; ok { 148 helpEntries = append(helpEntries, helpEntry{"ARGUMENTS", command.Annotations["help:arguments"]}) 149 } 150 if command.Example != "" { 151 helpEntries = append(helpEntries, helpEntry{"EXAMPLES", command.Example}) 152 } 153 if _, ok := command.Annotations["help:environment"]; ok { 154 helpEntries = append(helpEntries, helpEntry{"ENVIRONMENT VARIABLES", command.Annotations["help:environment"]}) 155 } 156 helpEntries = append(helpEntries, helpEntry{"LEARN MORE", ` 157 Use 'secman <command> <subcommand> --help' for more information about a command. 158 Read docs at https://secman.vercel.app/docs`}) 159 if _, ok := command.Annotations["help:feedback"]; ok { 160 helpEntries = append(helpEntries, helpEntry{"FEEDBACK", command.Annotations["help:feedback"]}) 161 } 162 163 out := command.OutOrStdout() 164 for _, e := range helpEntries { 165 if e.Title != "" { 166 // If there is a title, add indentation to each line in the body 167 fmt.Fprintln(out, cs.Bold(e.Title)) 168 fmt.Fprintln(out, text.Indent(strings.Trim(e.Body, "\r\n"), " ")) 169 } else { 170 // If there is no title print the body as is 171 fmt.Fprintln(out, e.Body) 172 } 173 fmt.Fprintln(out) 174 } 175 } 176 177 // rpad adds padding to the right of a string. 178 func rpad(s string, padding int) string { 179 template := fmt.Sprintf("%%-%ds ", padding) 180 return fmt.Sprintf(template, s) 181 } 182 183 func dedent(s string) string { 184 lines := strings.Split(s, "\n") 185 minIndent := -1 186 187 for _, l := range lines { 188 if len(l) == 0 { 189 continue 190 } 191 192 indent := len(l) - len(strings.TrimLeft(l, " ")) 193 if minIndent == -1 || indent < minIndent { 194 minIndent = indent 195 } 196 } 197 198 if minIndent <= 0 { 199 return s 200 } 201 202 var buf bytes.Buffer 203 for _, l := range lines { 204 fmt.Fprintln(&buf, strings.TrimPrefix(l, strings.Repeat(" ", minIndent))) 205 } 206 return strings.TrimSuffix(buf.String(), "\n") 207 }