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  }