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 }