Remove ICmdObj interface

It is only implemented by *CmdObj, so use that directly in client code.
This commit is contained in:
Stefan Haller 2025-04-29 19:10:36 +02:00
parent 4e3d09e9d8
commit a400ef0079
25 changed files with 137 additions and 178 deletions

View file

@ -11,70 +11,6 @@ import (
// A command object is a general way to represent a command to be run on the
// command line.
type ICmdObj interface {
GetCmd() *exec.Cmd
// outputs string representation of command. Note that if the command was built
// using NewFromArgs, the output won't be quite the same as what you would type
// into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"'
ToString() string
// outputs args vector e.g. ["git", "commit", "-m", "my message"]
Args() []string
// Set a string to be used as stdin for the command.
SetStdin(input string) ICmdObj
AddEnvVars(...string) ICmdObj
GetEnvVars() []string
// sets the working directory
SetWd(string) ICmdObj
// runs the command and returns an error if any
Run() error
// runs the command and returns the output as a string, and an error if any
RunWithOutput() (string, error)
// runs the command and returns stdout and stderr as a string, and an error if any
RunWithOutputs() (string, string, error)
// runs the command and runs a callback function on each line of the output. If the callback returns true for the boolean value, we kill the process and return.
RunAndProcessLines(onLine func(line string) (bool, error)) error
// Be calling DontLog(), we're saying that once we call Run(), we don't want to
// log the command in the UI (it'll still be logged in the log file). The general rule
// is that if a command doesn't change the git state (e.g. read commands like `git diff`)
// then we don't want to log it. If we are changing something (e.g. `git add .`) then
// we do. The only exception is if we're running a command in the background periodically
// like `git fetch`, which technically does mutate stuff but isn't something we need
// to notify the user about.
DontLog() ICmdObj
// This returns false if DontLog() was called
ShouldLog() bool
// when you call this, then call Run(), we'll stream the output to the cmdWriter (i.e. the command log panel)
StreamOutput() ICmdObj
// returns true if StreamOutput() was called
ShouldStreamOutput() bool
// if you call this before ShouldStreamOutput we'll consider an error with no
// stderr content as a non-error. Not yet supported for Run or RunWithOutput (
// but adding support is trivial)
IgnoreEmptyError() ICmdObj
// returns true if IgnoreEmptyError() was called
ShouldIgnoreEmptyError() bool
PromptOnCredentialRequest(task gocui.Task) ICmdObj
FailOnCredentialRequest() ICmdObj
WithMutex(mutex *deadlock.Mutex) ICmdObj
Mutex() *deadlock.Mutex
GetCredentialStrategy() CredentialStrategy
GetTask() gocui.Task
Clone() ICmdObj
}
type CmdObj struct {
cmd *exec.Cmd
@ -112,12 +48,13 @@ const (
FAIL
)
var _ ICmdObj = &CmdObj{}
func (self *CmdObj) GetCmd() *exec.Cmd {
return self.cmd
}
// outputs string representation of command. Note that if the command was built
// using NewFromArgs, the output won't be quite the same as what you would type
// into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"'
func (self *CmdObj) ToString() string {
// if a given arg contains a space, we need to wrap it in quotes
quotedArgs := lo.Map(self.cmd.Args, func(arg string, _ int) string {
@ -130,17 +67,19 @@ func (self *CmdObj) ToString() string {
return strings.Join(quotedArgs, " ")
}
// outputs args vector e.g. ["git", "commit", "-m", "my message"]
func (self *CmdObj) Args() []string {
return self.cmd.Args
}
func (self *CmdObj) SetStdin(input string) ICmdObj {
// Set a string to be used as stdin for the command.
func (self *CmdObj) SetStdin(input string) *CmdObj {
self.cmd.Stdin = strings.NewReader(input)
return self
}
func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {
func (self *CmdObj) AddEnvVars(vars ...string) *CmdObj {
self.cmd.Env = append(self.cmd.Env, vars...)
return self
@ -150,75 +89,95 @@ func (self *CmdObj) GetEnvVars() []string {
return self.cmd.Env
}
func (self *CmdObj) SetWd(wd string) ICmdObj {
// sets the working directory
func (self *CmdObj) SetWd(wd string) *CmdObj {
self.cmd.Dir = wd
return self
}
func (self *CmdObj) DontLog() ICmdObj {
// By calling DontLog(), we're saying that once we call Run(), we don't want to
// log the command in the UI (it'll still be logged in the log file). The general rule
// is that if a command doesn't change the git state (e.g. read commands like `git diff`)
// then we don't want to log it. If we are changing something (e.g. `git add .`) then
// we do. The only exception is if we're running a command in the background periodically
// like `git fetch`, which technically does mutate stuff but isn't something we need
// to notify the user about.
func (self *CmdObj) DontLog() *CmdObj {
self.dontLog = true
return self
}
// This returns false if DontLog() was called
func (self *CmdObj) ShouldLog() bool {
return !self.dontLog
}
func (self *CmdObj) StreamOutput() ICmdObj {
// when you call this, then call Run(), we'll stream the output to the cmdWriter (i.e. the command log panel)
func (self *CmdObj) StreamOutput() *CmdObj {
self.streamOutput = true
return self
}
// returns true if StreamOutput() was called
func (self *CmdObj) ShouldStreamOutput() bool {
return self.streamOutput
}
func (self *CmdObj) IgnoreEmptyError() ICmdObj {
// if you call this before ShouldStreamOutput we'll consider an error with no
// stderr content as a non-error. Not yet supported for Run or RunWithOutput (
// but adding support is trivial)
func (self *CmdObj) IgnoreEmptyError() *CmdObj {
self.ignoreEmptyError = true
return self
}
// returns true if IgnoreEmptyError() was called
func (self *CmdObj) ShouldIgnoreEmptyError() bool {
return self.ignoreEmptyError
}
func (self *CmdObj) Mutex() *deadlock.Mutex {
return self.mutex
}
func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) ICmdObj {
func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) *CmdObj {
self.mutex = mutex
return self
}
func (self *CmdObj) ShouldIgnoreEmptyError() bool {
return self.ignoreEmptyError
}
// runs the command and returns an error if any
func (self *CmdObj) Run() error {
return self.runner.Run(self)
}
// runs the command and returns the output as a string, and an error if any
func (self *CmdObj) RunWithOutput() (string, error) {
return self.runner.RunWithOutput(self)
}
// runs the command and returns stdout and stderr as a string, and an error if any
func (self *CmdObj) RunWithOutputs() (string, string, error) {
return self.runner.RunWithOutputs(self)
}
// runs the command and runs a callback function on each line of the output. If the callback
// returns true for the boolean value, we kill the process and return.
func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error {
return self.runner.RunAndProcessLines(self, onLine)
}
func (self *CmdObj) PromptOnCredentialRequest(task gocui.Task) ICmdObj {
func (self *CmdObj) PromptOnCredentialRequest(task gocui.Task) *CmdObj {
self.credentialStrategy = PROMPT
self.task = task
return self
}
func (self *CmdObj) FailOnCredentialRequest() ICmdObj {
func (self *CmdObj) FailOnCredentialRequest() *CmdObj {
self.credentialStrategy = FAIL
return self
@ -232,7 +191,7 @@ func (self *CmdObj) GetTask() gocui.Task {
return self.task
}
func (self *CmdObj) Clone() ICmdObj {
func (self *CmdObj) Clone() *CmdObj {
clone := &CmdObj{}
*clone = *self
clone.cmd = cloneCmd(self.cmd)

View file

@ -11,11 +11,11 @@ import (
type ICmdObjBuilder interface {
// NewFromArgs takes a slice of strings like []string{"git", "commit"} and returns a new command object.
New(args []string) ICmdObj
New(args []string) *CmdObj
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
// shellFunctionsFile is an optional file path that will be sourced before executing the command. Callers should pass
// the value of UserConfig.OS.ShellFunctionsFile.
NewShell(commandStr string, shellFunctionsFile string) ICmdObj
NewShell(commandStr string, shellFunctionsFile string) *CmdObj
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
Quote(str string) string
}
@ -28,13 +28,13 @@ type CmdObjBuilder struct {
// poor man's version of explicitly saying that struct X implements interface Y
var _ ICmdObjBuilder = &CmdObjBuilder{}
func (self *CmdObjBuilder) New(args []string) ICmdObj {
func (self *CmdObjBuilder) New(args []string) *CmdObj {
cmdObj := self.NewWithEnviron(args, os.Environ())
return cmdObj
}
// A command with explicit environment from env
func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) *CmdObj {
cmd := exec.Command(args[0], args[1:]...)
cmd.Env = env
@ -44,7 +44,7 @@ func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
}
}
func (self *CmdObjBuilder) NewShell(commandStr string, shellFunctionsFile string) ICmdObj {
func (self *CmdObjBuilder) NewShell(commandStr string, shellFunctionsFile string) *CmdObj {
if len(shellFunctionsFile) > 0 {
commandStr = fmt.Sprintf("%ssource %s\n%s", self.platform.PrefixForShellFunctionsFile, shellFunctionsFile, commandStr)
}

View file

@ -15,10 +15,10 @@ import (
)
type ICmdObjRunner interface {
Run(cmdObj ICmdObj) error
RunWithOutput(cmdObj ICmdObj) (string, error)
RunWithOutputs(cmdObj ICmdObj) (string, string, error)
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
Run(cmdObj *CmdObj) error
RunWithOutput(cmdObj *CmdObj) (string, error)
RunWithOutputs(cmdObj *CmdObj) (string, string, error)
RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error
}
type cmdObjRunner struct {
@ -28,7 +28,7 @@ type cmdObjRunner struct {
var _ ICmdObjRunner = &cmdObjRunner{}
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
func (self *cmdObjRunner) Run(cmdObj *CmdObj) error {
if cmdObj.Mutex() != nil {
cmdObj.Mutex().Lock()
defer cmdObj.Mutex().Unlock()
@ -46,7 +46,7 @@ func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
return err
}
func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
func (self *cmdObjRunner) RunWithOutput(cmdObj *CmdObj) (string, error) {
if cmdObj.Mutex() != nil {
cmdObj.Mutex().Lock()
defer cmdObj.Mutex().Unlock()
@ -71,7 +71,7 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
return self.RunWithOutputAux(cmdObj)
}
func (self *cmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
func (self *cmdObjRunner) RunWithOutputs(cmdObj *CmdObj) (string, string, error) {
if cmdObj.Mutex() != nil {
cmdObj.Mutex().Lock()
defer cmdObj.Mutex().Unlock()
@ -96,7 +96,7 @@ func (self *cmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error)
return self.RunWithOutputsAux(cmdObj)
}
func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
func (self *cmdObjRunner) RunWithOutputAux(cmdObj *CmdObj) (string, error) {
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
if cmdObj.ShouldLog() {
@ -114,7 +114,7 @@ func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
return output, err
}
func (self *cmdObjRunner) RunWithOutputsAux(cmdObj ICmdObj) (string, string, error) {
func (self *cmdObjRunner) RunWithOutputsAux(cmdObj *CmdObj) (string, string, error) {
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
if cmdObj.ShouldLog() {
@ -139,7 +139,7 @@ func (self *cmdObjRunner) RunWithOutputsAux(cmdObj ICmdObj) (string, string, err
return stdout, stderr, err
}
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
func (self *cmdObjRunner) RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error {
if cmdObj.Mutex() != nil {
cmdObj.Mutex().Lock()
defer cmdObj.Mutex().Unlock()
@ -190,7 +190,7 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
return nil
}
func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) {
func (self *cmdObjRunner) logCmdObj(cmdObj *CmdObj) {
self.guiIO.logCommandFn(cmdObj.ToString(), true)
}
@ -213,7 +213,7 @@ type cmdHandler struct {
close func() error
}
func (self *cmdObjRunner) runAndStream(cmdObj ICmdObj) error {
func (self *cmdObjRunner) runAndStream(cmdObj *CmdObj) error {
return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) {
go func() {
_, _ = io.Copy(cmdWriter, handler.stdoutPipe)
@ -222,7 +222,7 @@ func (self *cmdObjRunner) runAndStream(cmdObj ICmdObj) error {
}
func (self *cmdObjRunner) runAndStreamAux(
cmdObj ICmdObj,
cmdObj *CmdObj,
onRun func(*cmdHandler, io.Writer),
) error {
cmdWriter := self.guiIO.newCmdWriterFn()
@ -297,7 +297,7 @@ var failPromptFn = func(CredentialType) <-chan string {
return ch
}
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj *CmdObj) error {
promptFn, err := self.getCredentialPromptFn(cmdObj)
if err != nil {
return err
@ -306,7 +306,7 @@ func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
}
func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(CredentialType) <-chan string, error) {
func (self *cmdObjRunner) getCredentialPromptFn(cmdObj *CmdObj) (func(CredentialType) <-chan string, error) {
switch cmdObj.GetCredentialStrategy() {
case PROMPT:
return self.guiIO.promptForCredentialFn, nil
@ -322,7 +322,7 @@ func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(Credential
// promptUserForCredential is a function that gets executed when this function detect you need to fill in a password or passphrase
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
func (self *cmdObjRunner) runAndDetectCredentialRequest(
cmdObj ICmdObj,
cmdObj *CmdObj,
promptUserForCredential func(CredentialType) <-chan string,
) error {
// setting the output to english so we can parse it for a username/password request

View file

@ -28,7 +28,7 @@ type FakeCmdObjRunner struct {
type CmdObjMatcher struct {
description string
// returns true if the matcher matches the command object
test func(ICmdObj) bool
test func(*CmdObj) bool
// output of the command
output string
@ -48,12 +48,12 @@ func (self *FakeCmdObjRunner) remainingExpectedCmds() []CmdObjMatcher {
})
}
func (self *FakeCmdObjRunner) Run(cmdObj ICmdObj) error {
func (self *FakeCmdObjRunner) Run(cmdObj *CmdObj) error {
_, err := self.RunWithOutput(cmdObj)
return err
}
func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
func (self *FakeCmdObjRunner) RunWithOutput(cmdObj *CmdObj) (string, error) {
self.mutex.Lock()
defer self.mutex.Unlock()
@ -78,12 +78,12 @@ func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
return "", nil
}
func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj *CmdObj) (string, string, error) {
output, err := self.RunWithOutput(cmdObj)
return output, "", err
}
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error {
output, err := self.RunWithOutput(cmdObj)
if err != nil {
return err
@ -105,7 +105,7 @@ func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(lin
return nil
}
func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj ICmdObj) bool, output string, err error) *FakeCmdObjRunner {
func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj *CmdObj) bool, output string, err error) *FakeCmdObjRunner {
self.mutex.Lock()
defer self.mutex.Unlock()
@ -121,7 +121,7 @@ func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj ICmd
func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
description := fmt.Sprintf("matches args %s", strings.Join(expectedArgs, " "))
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
self.ExpectFunc(description, func(cmdObj *CmdObj) bool {
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args)
}, output, err)
@ -130,7 +130,7 @@ func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, e
func (self *FakeCmdObjRunner) ExpectGitArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
description := fmt.Sprintf("matches git args %s", strings.Join(expectedArgs, " "))
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
self.ExpectFunc(description, func(cmdObj *CmdObj) bool {
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args[1:])
}, output, err)

View file

@ -208,13 +208,13 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
}
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
cmds := lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) *exec.Cmd {
func (c *OSCommand) PipeCommands(cmdObjs ...*CmdObj) error {
cmds := lo.Map(cmdObjs, func(cmdObj *CmdObj, _ int) *exec.Cmd {
return cmdObj.GetCmd()
})
logCmdStr := strings.Join(
lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) string {
lo.Map(cmdObjs, func(cmdObj *CmdObj, _ int) string {
return cmdObj.ToString()
}),
" | ",