github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/alias/set/set.go (about) 1 package set 2 3 import ( 4 "fmt" 5 "io" 6 "strings" 7 8 "github.com/MakeNowJust/heredoc" 9 "github.com/ungtb10d/cli/v2/internal/config" 10 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 11 "github.com/ungtb10d/cli/v2/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() { 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 := cfg.Aliases() 113 114 expansion, err := getExpansion(opts) 115 if err != nil { 116 return fmt.Errorf("did not understand expansion: %w", err) 117 } 118 119 isTerminal := opts.IO.IsStdoutTTY() 120 if isTerminal { 121 fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(expansion)) 122 } 123 124 isShell := opts.IsShell 125 if isShell && !strings.HasPrefix(expansion, "!") { 126 expansion = "!" + expansion 127 } 128 isShell = strings.HasPrefix(expansion, "!") 129 130 if opts.validCommand(opts.Name) { 131 return fmt.Errorf("could not create alias: %q is already a gh command", opts.Name) 132 } 133 134 if !isShell && !opts.validCommand(expansion) { 135 return fmt.Errorf("could not create alias: %s does not correspond to a gh command", expansion) 136 } 137 138 successMsg := fmt.Sprintf("%s Added alias.", cs.SuccessIcon()) 139 if oldExpansion, err := aliasCfg.Get(opts.Name); err == nil { 140 successMsg = fmt.Sprintf("%s Changed alias %s from %s to %s", 141 cs.SuccessIcon(), 142 cs.Bold(opts.Name), 143 cs.Bold(oldExpansion), 144 cs.Bold(expansion), 145 ) 146 } 147 148 aliasCfg.Add(opts.Name, expansion) 149 150 err = cfg.Write() 151 if err != nil { 152 return err 153 } 154 155 if isTerminal { 156 fmt.Fprintln(opts.IO.ErrOut, successMsg) 157 } 158 159 return nil 160 } 161 162 func getExpansion(opts *SetOptions) (string, error) { 163 if opts.Expansion == "-" { 164 stdin, err := io.ReadAll(opts.IO.In) 165 if err != nil { 166 return "", fmt.Errorf("failed to read from STDIN: %w", err) 167 } 168 169 return string(stdin), nil 170 } 171 172 return opts.Expansion, nil 173 }