github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/alias/set/set.go (about)

     1  package set
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"strings"
     7  
     8  	"github.com/MakeNowJust/heredoc"
     9  	"github.com/cli/cli/internal/config"
    10  	"github.com/cli/cli/pkg/cmdutil"
    11  	"github.com/cli/cli/pkg/iostreams"
    12  	"github.com/google/shlex"
    13  	"github.com/spf13/cobra"
    14  )
    15  
    16  type SetOptions struct {
    17  	Config func() (config.Config, error)
    18  	IO     *iostreams.IOStreams
    19  
    20  	Name      string
    21  	Expansion string
    22  	IsShell   bool
    23  
    24  	validCommand func(string) bool
    25  }
    26  
    27  func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command {
    28  	opts := &SetOptions{
    29  		IO:     f.IOStreams,
    30  		Config: f.Config,
    31  	}
    32  
    33  	cmd := &cobra.Command{
    34  		Use:   "set <alias> <expansion>",
    35  		Short: "Create a shortcut for a gh command",
    36  		Long: heredoc.Doc(`
    37  			Define a word that will expand to a full gh command when invoked.
    38  
    39  			The expansion may specify additional arguments and flags. If the expansion includes
    40  			positional placeholders such as "$1", extra arguments that follow the alias will be
    41  			inserted appropriately. Otherwise, extra arguments will be appended to the expanded
    42  			command.
    43  
    44  			Use "-" as expansion argument to read the expansion string from standard input. This
    45  			is useful to avoid quoting issues when defining expansions.
    46  
    47  			If the expansion starts with "!" or if "--shell" was given, the expansion is a shell
    48  			expression that will be evaluated through the "sh" interpreter when the alias is
    49  			invoked. This allows for chaining multiple commands via piping and redirection.
    50  		`),
    51  		Example: heredoc.Doc(`
    52  			# note: Command Prompt on Windows requires using double quotes for arguments
    53  			$ gh alias set pv 'pr view'
    54  			$ gh pv -w 123  #=> gh pr view -w 123
    55  
    56  			$ gh alias set bugs 'issue list --label=bugs'
    57  			$ gh bugs
    58  
    59  			$ gh alias set homework 'issue list --assignee @me'
    60  			$ gh homework
    61  
    62  			$ gh alias set epicsBy 'issue list --author="$1" --label="epic"'
    63  			$ gh epicsBy vilmibm  #=> gh issue list --author="vilmibm" --label="epic"
    64  
    65  			$ gh alias set --shell igrep 'gh issue list --label="$1" | grep "$2"'
    66  			$ gh igrep epic foo  #=> gh issue list --label="epic" | grep "foo"
    67  		`),
    68  		Args: cobra.ExactArgs(2),
    69  		RunE: func(cmd *cobra.Command, args []string) error {
    70  			opts.Name = args[0]
    71  			opts.Expansion = args[1]
    72  
    73  			opts.validCommand = func(args string) bool {
    74  				split, err := shlex.Split(args)
    75  				if err != nil {
    76  					return false
    77  				}
    78  
    79  				rootCmd := cmd.Root()
    80  				cmd, _, err := rootCmd.Traverse(split)
    81  				if err == nil && cmd != rootCmd {
    82  					return true
    83  				}
    84  
    85  				for _, ext := range f.ExtensionManager.List(false) {
    86  					if ext.Name() == split[0] {
    87  						return true
    88  					}
    89  				}
    90  				return false
    91  			}
    92  
    93  			if runF != nil {
    94  				return runF(opts)
    95  			}
    96  			return setRun(opts)
    97  		},
    98  	}
    99  
   100  	cmd.Flags().BoolVarP(&opts.IsShell, "shell", "s", false, "Declare an alias to be passed through a shell interpreter")
   101  
   102  	return cmd
   103  }
   104  
   105  func setRun(opts *SetOptions) error {
   106  	cs := opts.IO.ColorScheme()
   107  	cfg, err := opts.Config()
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	aliasCfg, err := cfg.Aliases()
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	expansion, err := getExpansion(opts)
   118  	if err != nil {
   119  		return fmt.Errorf("did not understand expansion: %w", err)
   120  	}
   121  
   122  	isTerminal := opts.IO.IsStdoutTTY()
   123  	if isTerminal {
   124  		fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(expansion))
   125  	}
   126  
   127  	isShell := opts.IsShell
   128  	if isShell && !strings.HasPrefix(expansion, "!") {
   129  		expansion = "!" + expansion
   130  	}
   131  	isShell = strings.HasPrefix(expansion, "!")
   132  
   133  	if opts.validCommand(opts.Name) {
   134  		return fmt.Errorf("could not create alias: %q is already a gh command", opts.Name)
   135  	}
   136  
   137  	if !isShell && !opts.validCommand(expansion) {
   138  		return fmt.Errorf("could not create alias: %s does not correspond to a gh command", expansion)
   139  	}
   140  
   141  	successMsg := fmt.Sprintf("%s Added alias.", cs.SuccessIcon())
   142  	if oldExpansion, ok := aliasCfg.Get(opts.Name); ok {
   143  		successMsg = fmt.Sprintf("%s Changed alias %s from %s to %s",
   144  			cs.SuccessIcon(),
   145  			cs.Bold(opts.Name),
   146  			cs.Bold(oldExpansion),
   147  			cs.Bold(expansion),
   148  		)
   149  	}
   150  
   151  	err = aliasCfg.Add(opts.Name, expansion)
   152  	if err != nil {
   153  		return fmt.Errorf("could not create alias: %s", err)
   154  	}
   155  
   156  	if isTerminal {
   157  		fmt.Fprintln(opts.IO.ErrOut, successMsg)
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  func getExpansion(opts *SetOptions) (string, error) {
   164  	if opts.Expansion == "-" {
   165  		stdin, err := ioutil.ReadAll(opts.IO.In)
   166  		if err != nil {
   167  			return "", fmt.Errorf("failed to read from STDIN: %w", err)
   168  		}
   169  
   170  		return string(stdin), nil
   171  	}
   172  
   173  	return opts.Expansion, nil
   174  }