File: set.go

package info (click to toggle)
gh 2.46.0-4
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,548 kB
  • sloc: sh: 227; makefile: 117
file content (176 lines) | stat: -rw-r--r-- 4,832 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package set

import (
	"fmt"
	"io"
	"strings"

	"github.com/MakeNowJust/heredoc"
	"github.com/cli/cli/v2/internal/config"
	"github.com/cli/cli/v2/pkg/cmd/alias/shared"
	"github.com/cli/cli/v2/pkg/cmdutil"
	"github.com/cli/cli/v2/pkg/iostreams"
	"github.com/spf13/cobra"
)

type SetOptions struct {
	Config func() (config.Config, error)
	IO     *iostreams.IOStreams

	Name              string
	Expansion         string
	IsShell           bool
	OverwriteExisting bool

	validAliasName      func(string) bool
	validAliasExpansion func(string) bool
}

func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command {
	opts := &SetOptions{
		IO:     f.IOStreams,
		Config: f.Config,
	}

	cmd := &cobra.Command{
		Use:   "set <alias> <expansion>",
		Short: "Create a shortcut for a gh command",
		Long: heredoc.Docf(`
			Define a word that will expand to a full gh command when invoked.

			The expansion may specify additional arguments and flags. If the expansion includes
			positional placeholders such as %[1]s$1%[1]s, extra arguments that follow the alias will be
			inserted appropriately. Otherwise, extra arguments will be appended to the expanded
			command.

			Use %[1]s-%[1]s as expansion argument to read the expansion string from standard input. This
			is useful to avoid quoting issues when defining expansions.

			If the expansion starts with %[1]s!%[1]s or if %[1]s--shell%[1]s was given, the expansion is a shell
			expression that will be evaluated through the %[1]ssh%[1]s interpreter when the alias is
			invoked. This allows for chaining multiple commands via piping and redirection.
		`, "`"),
		Example: heredoc.Doc(`
			# note: Command Prompt on Windows requires using double quotes for arguments
			$ gh alias set pv 'pr view'
			$ gh pv -w 123  #=> gh pr view -w 123

			$ gh alias set bugs 'issue list --label=bugs'
			$ gh bugs

			$ gh alias set homework 'issue list --assignee @me'
			$ gh homework

			$ gh alias set 'issue mine' 'issue list --mention @me'
			$ gh issue mine

			$ gh alias set epicsBy 'issue list --author="$1" --label="epic"'
			$ gh epicsBy vilmibm  #=> gh issue list --author="vilmibm" --label="epic"

			$ gh alias set --shell igrep 'gh issue list --label="$1" | grep "$2"'
			$ gh igrep epic foo  #=> gh issue list --label="epic" | grep "foo"
		`),
		Args: cobra.ExactArgs(2),
		RunE: func(cmd *cobra.Command, args []string) error {
			opts.Name = args[0]
			opts.Expansion = args[1]

			opts.validAliasName = shared.ValidAliasNameFunc(cmd)
			opts.validAliasExpansion = shared.ValidAliasExpansionFunc(cmd)

			if runF != nil {
				return runF(opts)
			}

			return setRun(opts)
		},
	}

	cmd.Flags().BoolVarP(&opts.IsShell, "shell", "s", false, "Declare an alias to be passed through a shell interpreter")
	cmd.Flags().BoolVar(&opts.OverwriteExisting, "clobber", false, "Overwrite existing aliases of the same name")

	return cmd
}

func setRun(opts *SetOptions) error {
	cs := opts.IO.ColorScheme()
	cfg, err := opts.Config()
	if err != nil {
		return err
	}

	aliasCfg := cfg.Aliases()

	expansion, err := getExpansion(opts)
	if err != nil {
		return fmt.Errorf("did not understand expansion: %w", err)
	}

	if opts.IsShell && !strings.HasPrefix(expansion, "!") {
		expansion = "!" + expansion
	}

	isTerminal := opts.IO.IsStdoutTTY()
	if isTerminal {
		fmt.Fprintf(opts.IO.ErrOut, "- Creating alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(expansion))
	}

	var existingAlias bool
	if _, err := aliasCfg.Get(opts.Name); err == nil {
		existingAlias = true
	}

	if !opts.validAliasName(opts.Name) {
		if !existingAlias {
			return fmt.Errorf("%s Could not create alias %s: already a gh command or extension",
				cs.FailureIcon(),
				cs.Bold(opts.Name))
		}

		if existingAlias && !opts.OverwriteExisting {
			return fmt.Errorf("%s Could not create alias %s: name already taken, use the --clobber flag to overwrite it",
				cs.FailureIcon(),
				cs.Bold(opts.Name),
			)
		}
	}

	if !opts.validAliasExpansion(expansion) {
		return fmt.Errorf("%s Could not create alias %s: expansion does not correspond to a gh command, extension, or alias",
			cs.FailureIcon(),
			cs.Bold(opts.Name))
	}

	aliasCfg.Add(opts.Name, expansion)

	err = cfg.Write()
	if err != nil {
		return err
	}

	successMsg := fmt.Sprintf("%s Added alias %s", cs.SuccessIcon(), cs.Bold(opts.Name))
	if existingAlias && opts.OverwriteExisting {
		successMsg = fmt.Sprintf("%s Changed alias %s",
			cs.WarningIcon(),
			cs.Bold(opts.Name))
	}

	if isTerminal {
		fmt.Fprintln(opts.IO.ErrOut, successMsg)
	}

	return nil
}

func getExpansion(opts *SetOptions) (string, error) {
	if opts.Expansion == "-" {
		stdin, err := io.ReadAll(opts.IO.In)
		if err != nil {
			return "", fmt.Errorf("failed to read from STDIN: %w", err)
		}

		return string(stdin), nil
	}

	return opts.Expansion, nil
}