github.com/jingweno/gh@v2.1.1-0.20221007190738-04a7985fa9a1+incompatible/commands/runner.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/jingweno/gh/cmd"
     6  	"github.com/jingweno/gh/git"
     7  	"github.com/jingweno/gh/utils"
     8  	"github.com/kballard/go-shellquote"
     9  	flag "github.com/ogier/pflag"
    10  	"os"
    11  	"os/exec"
    12  	"strings"
    13  	"syscall"
    14  )
    15  
    16  type ExecError struct {
    17  	Err      error
    18  	ExitCode int
    19  }
    20  
    21  func (execError *ExecError) Error() string {
    22  	return execError.Err.Error()
    23  }
    24  
    25  func newExecError(err error) ExecError {
    26  	exitCode := 0
    27  	if err != nil {
    28  		exitCode = 1
    29  		if exitError, ok := err.(*exec.ExitError); ok {
    30  			if status, ok := exitError.Sys().(syscall.WaitStatus); ok {
    31  				exitCode = status.ExitStatus()
    32  			}
    33  		}
    34  	}
    35  
    36  	return ExecError{Err: err, ExitCode: exitCode}
    37  }
    38  
    39  type Runner struct {
    40  	commands map[string]*Command
    41  }
    42  
    43  func NewRunner() *Runner {
    44  	return &Runner{commands: make(map[string]*Command)}
    45  }
    46  
    47  func (r *Runner) All() map[string]*Command {
    48  	return r.commands
    49  }
    50  
    51  func (r *Runner) Use(command *Command) {
    52  	r.commands[command.Name()] = command
    53  }
    54  
    55  func (r *Runner) Lookup(name string) *Command {
    56  	return r.commands[name]
    57  }
    58  
    59  func (r *Runner) Execute() ExecError {
    60  	args := NewArgs(os.Args[1:])
    61  
    62  	if args.Command == "" {
    63  		printUsage()
    64  		return newExecError(nil)
    65  	}
    66  
    67  	updater := NewUpdater()
    68  	err := updater.PromptForUpdate()
    69  	utils.Check(err)
    70  
    71  	expandAlias(args)
    72  	slurpGlobalFlags(args)
    73  
    74  	cmd := r.Lookup(args.Command)
    75  	if cmd != nil && cmd.Runnable() {
    76  		return r.Call(cmd, args)
    77  	}
    78  
    79  	err = git.Spawn(args.Command, args.Params...)
    80  	return newExecError(err)
    81  }
    82  
    83  func (r *Runner) Call(cmd *Command, args *Args) ExecError {
    84  	err := cmd.Call(args)
    85  	if err != nil {
    86  		if err == flag.ErrHelp {
    87  			err = nil
    88  		}
    89  		return newExecError(err)
    90  	}
    91  
    92  	cmds := args.Commands()
    93  	if args.Noop {
    94  		printCommands(cmds)
    95  	} else {
    96  		err = executeCommands(cmds)
    97  	}
    98  
    99  	return newExecError(err)
   100  }
   101  
   102  func slurpGlobalFlags(args *Args) {
   103  	for i, p := range args.Params {
   104  		if p == "--noop" {
   105  			args.Noop = true
   106  			args.RemoveParam(i)
   107  		}
   108  	}
   109  }
   110  
   111  func printCommands(cmds []*cmd.Cmd) {
   112  	for _, c := range cmds {
   113  		fmt.Println(c)
   114  	}
   115  }
   116  
   117  func executeCommands(cmds []*cmd.Cmd) error {
   118  	for _, c := range cmds {
   119  		err := c.Exec()
   120  		if err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func expandAlias(args *Args) {
   129  	cmd := args.Command
   130  	expandedCmd, err := git.Alias(cmd)
   131  	if err == nil && expandedCmd != "" {
   132  		words, e := splitAliasCmd(expandedCmd)
   133  		if e == nil {
   134  			args.Command = words[0]
   135  			args.PrependParams(words[1:]...)
   136  		}
   137  	}
   138  }
   139  
   140  func splitAliasCmd(cmd string) ([]string, error) {
   141  	if cmd == "" {
   142  		return nil, fmt.Errorf("alias can't be empty")
   143  	}
   144  
   145  	if strings.HasPrefix(cmd, "!") {
   146  		return nil, fmt.Errorf("alias starting with ! can't be split")
   147  	}
   148  
   149  	words, err := shellquote.Split(cmd)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	return words, nil
   155  }