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  }