github.com/abdfnx/gh-api@v0.0.0-20210414084727-f5432eec23b8/pkg/cmd/alias/set/set.go (about)

     1  package set
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/MakeNowJust/heredoc"
     8  	"github.com/abdfnx/gh-api/internal/config"
     9  	"github.com/abdfnx/gh-api/pkg/cmdutil"
    10  	"github.com/abdfnx/gh-api/pkg/iostreams"
    11  	"github.com/google/shlex"
    12  	"github.com/spf13/cobra"
    13  )
    14  
    15  type SetOptions struct {
    16  	Config func() (config.Config, error)
    17  	IO     *iostreams.IOStreams
    18  
    19  	Name      string
    20  	Expansion string
    21  	IsShell   bool
    22  	RootCmd   *cobra.Command
    23  }
    24  
    25  func NewCmdSet(f *cmdutil.Factory, runF func(*SetOptions) error) *cobra.Command {
    26  	opts := &SetOptions{
    27  		IO:     f.IOStreams,
    28  		Config: f.Config,
    29  	}
    30  
    31  	cmd := &cobra.Command{
    32  		Use:   "set <alias> <expansion>",
    33  		Short: "Create a shortcut for a gh command",
    34  		Long: heredoc.Doc(`
    35  			Declare a word as a command alias that will expand to the specified command(s).
    36  
    37  			The expansion may specify additional arguments and flags. If the expansion
    38  			includes positional placeholders such as '$1', '$2', etc., any extra arguments
    39  			that follow the invocation of an alias will be inserted appropriately.
    40  
    41  			If '--shell' is specified, the alias will be run through a shell interpreter (sh). This allows you
    42  			to compose commands with "|" or redirect with ">". Note that extra arguments following the alias
    43  			will not be automatically passed to the expanded expression. To have a shell alias receive
    44  			arguments, you must explicitly accept them using "$1", "$2", etc., or "$@" to accept all of them.
    45  
    46  			Platform note: on Windows, shell aliases are executed via "sh" as installed by Git For Windows. If
    47  			you have installed git on Windows in some other way, shell aliases may not work for you.
    48  
    49  			Quotes must always be used when defining a command as in the examples.
    50  		`),
    51  		Example: heredoc.Doc(`
    52  			$ gh alias set pv 'pr view'
    53  			$ gh pv -w 123
    54  			#=> 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
    64  			#=> gh issue list --author="vilmibm" --label="epic"
    65  
    66  			$ gh alias set --shell igrep 'gh issue list --label="$1" | grep $2'
    67  			$ gh igrep epic foo
    68  			#=> gh issue list --label="epic" | grep "foo"
    69  		`),
    70  		Args: cobra.ExactArgs(2),
    71  		RunE: func(cmd *cobra.Command, args []string) error {
    72  			opts.RootCmd = cmd.Root()
    73  
    74  			opts.Name = args[0]
    75  			opts.Expansion = args[1]
    76  
    77  			if runF != nil {
    78  				return runF(opts)
    79  			}
    80  			return setRun(opts)
    81  		},
    82  	}
    83  
    84  	cmd.Flags().BoolVarP(&opts.IsShell, "shell", "s", false, "Declare an alias to be passed through a shell interpreter")
    85  
    86  	return cmd
    87  }
    88  
    89  func setRun(opts *SetOptions) error {
    90  	cs := opts.IO.ColorScheme()
    91  	cfg, err := opts.Config()
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	aliasCfg, err := cfg.Aliases()
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	isTerminal := opts.IO.IsStdoutTTY()
   102  	if isTerminal {
   103  		fmt.Fprintf(opts.IO.ErrOut, "- Adding alias for %s: %s\n", cs.Bold(opts.Name), cs.Bold(opts.Expansion))
   104  	}
   105  
   106  	expansion := opts.Expansion
   107  	isShell := opts.IsShell
   108  	if isShell && !strings.HasPrefix(expansion, "!") {
   109  		expansion = "!" + expansion
   110  	}
   111  	isShell = strings.HasPrefix(expansion, "!")
   112  
   113  	if validCommand(opts.RootCmd, opts.Name) {
   114  		return fmt.Errorf("could not create alias: %q is already a gh command", opts.Name)
   115  	}
   116  
   117  	if !isShell && !validCommand(opts.RootCmd, expansion) {
   118  		return fmt.Errorf("could not create alias: %s does not correspond to a gh command", expansion)
   119  	}
   120  
   121  	successMsg := fmt.Sprintf("%s Added alias.", cs.SuccessIcon())
   122  	if oldExpansion, ok := aliasCfg.Get(opts.Name); ok {
   123  		successMsg = fmt.Sprintf("%s Changed alias %s from %s to %s",
   124  			cs.SuccessIcon(),
   125  			cs.Bold(opts.Name),
   126  			cs.Bold(oldExpansion),
   127  			cs.Bold(expansion),
   128  		)
   129  	}
   130  
   131  	err = aliasCfg.Add(opts.Name, expansion)
   132  	if err != nil {
   133  		return fmt.Errorf("could not create alias: %s", err)
   134  	}
   135  
   136  	if isTerminal {
   137  		fmt.Fprintln(opts.IO.ErrOut, successMsg)
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  func validCommand(rootCmd *cobra.Command, expansion string) bool {
   144  	split, err := shlex.Split(expansion)
   145  	if err != nil {
   146  		return false
   147  	}
   148  
   149  	cmd, _, err := rootCmd.Traverse(split)
   150  	return err == nil && cmd != rootCmd
   151  }