diff options
| author | Stefan Majewsky <majewsky@gmx.net> | 2017-05-09 21:32:16 +0200 |
|---|---|---|
| committer | Stefan Majewsky <majewsky@gmx.net> | 2017-05-09 21:38:24 +0200 |
| commit | a92faf7689fa01595fd613ddd3880e9c301ed558 (patch) | |
| tree | f6607fa23ac41385f9f18d8ed6ad0f970c4ba6df /pkg/cli | |
| parent | a4dfbab3b06360245f6b2fce4b56d6caf2729f8f (diff) | |
| download | gofu-a92faf7689fa01595fd613ddd3880e9c301ed558.tar.gz | |
refactor cli.Interface yet again
- Only one global instance now, so we don't need to pass it around all
the time.
- Allow to swap out the Command.Run implementation for unit tests.
- Can now use cli.Interface during func init() of packages importing
pkg/cli.
Diffstat (limited to 'pkg/cli')
| -rw-r--r-- | pkg/cli/command.go | 10 | ||||
| -rw-r--r-- | pkg/cli/interface.go | 76 | ||||
| -rw-r--r-- | pkg/cli/query.go | 6 |
3 files changed, 53 insertions, 39 deletions
diff --git a/pkg/cli/command.go b/pkg/cli/command.go index 5b3ded5..d07a120 100644 --- a/pkg/cli/command.go +++ b/pkg/cli/command.go @@ -26,7 +26,7 @@ import ( ) //Command describes a command that can be run using the methods in the -//Interface interface. +//Implementation interface. type Command struct { Program []string WorkDir string @@ -49,8 +49,14 @@ func (e commandError) Error() string { ) } -func (c Command) run(stdout, stderr io.Writer) error { +//CommandRunner is a function that can execute commands given to it. +//This interface is only useful for unit tests; the default CommandRunner +//suffices for all regular operation. +type CommandRunner func(c Command, stdin io.Reader, stdout, stderr io.Writer) error + +func DefaultCommandRunner(c Command, stdin io.Reader, stdout, stderr io.Writer) error { cmd := exec.Command(c.Program[0], c.Program[1:]...) + cmd.Stdin = stdin cmd.Stdout = stdout cmd.Stderr = stderr cmd.Dir = c.WorkDir diff --git a/pkg/cli/interface.go b/pkg/cli/interface.go index c188c77..0f1ba74 100644 --- a/pkg/cli/interface.go +++ b/pkg/cli/interface.go @@ -30,32 +30,40 @@ import ( "golang.org/x/crypto/ssh/terminal" ) -//NewInterface creates an Interface instance. -func NewInterface(stdin, stdout, stderr *os.File) *Interface { - i := &Interface{ - stdin: stdin, - stdout: stdout, - stderr: stderr, - stdinBuf: bufio.NewReader(stdin), +//Interface wraps access to the CLI, including input, output and subprocesses. +var Interface *Implementation + +func init() { + SetupInterface(os.Stdin, os.Stdout, os.Stderr, DefaultCommandRunner) +} + +//SetupInterface prepares the Interface instance with nonstandard file streams +//or a nonstandard CommandRunner. This is only required for unit tests. +func SetupInterface(stdin io.Reader, stdout, stderr io.Writer, commandRunner CommandRunner) { + Interface = &Implementation{ + stdin: stdin, + stdout: stdout, + stderr: stderr, + stdinBuf: bufio.NewReader(stdin), + commandRunner: commandRunner, } - if terminal.IsTerminal(int(stdin.Fd())) { - i.tui = &terminalTUI{i} + if stdinFile, ok := stdin.(*os.File); ok && terminal.IsTerminal(int(stdinFile.Fd())) { + Interface.tui = &terminalTUI{Interface} } else { - i.tui = &pipeTUI{i} + Interface.tui = &pipeTUI{Interface} } - - return i } -//Interface wraps access to the CLI, including input, output and subprocesses. -type Interface struct { +//Implementation wraps access to the CLI, including input, output and subprocesses. +type Implementation struct { //TODO: flag isStdinTerminal that disables color output and swaps out the TUI instance - stdin io.Reader - stdout io.Writer - stderr io.Writer - stdinBuf *bufio.Reader - tui TUI + stdin io.Reader + stdout io.Writer + stderr io.Writer + stdinBuf *bufio.Reader + tui TUI + commandRunner CommandRunner //If this flag is set, only ShowResult() will write into stdout; everything //else that usually goes to stdout goes to stderr instead. // @@ -65,7 +73,7 @@ type Interface struct { StdoutProtected bool } -//TUI provides the interactive parts of the cli.Interface, so that these can be +//TUI provides the interactive parts of the cli.Implementation, so that these can be //easily swapped out for mock implementations in unit tests. type TUI interface { //ReadLine reads a line from stdin (if tty: uses canonical mode). @@ -77,7 +85,7 @@ type TUI interface { Query(prompt string, choices ...Choice) (string, error) } -func (i *Interface) safeStdout() io.Writer { +func (i *Implementation) safeStdout() io.Writer { if i.StdoutProtected { return i.stderr } @@ -88,18 +96,18 @@ func (i *Interface) safeStdout() io.Writer { // input //ReadLine reads a line from stdin (if tty: uses canonical mode). -func (i *Interface) ReadLine(prompt string) (string, error) { +func (i *Implementation) ReadLine(prompt string) (string, error) { return i.tui.ReadLine(prompt) } //Confirm displays a yes/no question and returns whether the user answered "yes". -func (i *Interface) Confirm(question string) (bool, error) { +func (i *Implementation) Confirm(question string) (bool, error) { return i.tui.Confirm(question) } //Query displays a question and a set of answers and allows the user to select //one of the answers. Returns the Return attribute of the selected Choice. -func (i *Interface) Query(prompt string, choices ...Choice) (string, error) { +func (i *Implementation) Query(prompt string, choices ...Choice) (string, error) { return i.tui.Query(prompt, choices...) } @@ -107,14 +115,14 @@ func (i *Interface) Query(prompt string, choices ...Choice) (string, error) { // subprocesses //Run executes the given command on the same stdout and stderr. -func (i *Interface) Run(c Command) error { - return c.run(i.safeStdout(), i.stderr) +func (i *Implementation) Run(c Command) error { + return i.commandRunner(c, nil, i.safeStdout(), i.stderr) } //CaptureStdout executes the given command on the same stderr and captures its stdout. -func (i *Interface) CaptureStdout(c Command) (string, error) { +func (i *Implementation) CaptureStdout(c Command) (string, error) { var buf bytes.Buffer - err := c.run(&buf, i.stderr) + err := i.commandRunner(c, nil, &buf, i.stderr) return string(buf.Bytes()), err } @@ -122,13 +130,13 @@ func (i *Interface) CaptureStdout(c Command) (string, error) { // output //ShowResult displays the result of a computation on stdout. -func (i *Interface) ShowResult(str string) { +func (i *Implementation) ShowResult(str string) { str = strings.TrimSpace(str) + "\n" i.stdout.Write([]byte(str)) } //ShowResultsSorted calls ShowResult() on each of the results after sorting them. -func (i *Interface) ShowResultsSorted(strs []string) { +func (i *Implementation) ShowResultsSorted(strs []string) { sort.Strings(strs) for _, str := range strs { i.ShowResult(str) @@ -136,22 +144,22 @@ func (i *Interface) ShowResultsSorted(strs []string) { } //ShowProgress displays a progress message on stderr. -func (i *Interface) ShowProgress(str string) { +func (i *Implementation) ShowProgress(str string) { fmt.Fprintf(i.stderr, "\x1B[0;1;36m>>\x1B[0;36m %s\x1B[0m", strings.TrimSpace(str)) } //ShowWarning displays a warning message on stderr. -func (i *Interface) ShowWarning(str string) { +func (i *Implementation) ShowWarning(str string) { fmt.Fprintf(i.stderr, "\x1B[0;1;33m!!\x1B[0;36m %s\x1B[0m", strings.TrimSpace(str)) } //ShowError displays an error message on stderr. -func (i *Interface) ShowError(str string) { +func (i *Implementation) ShowError(str string) { fmt.Fprintf(i.stderr, "\x1B[0;1;31m!!\x1B[0;36m %s\x1B[0m", strings.TrimSpace(str)) } //ShowUsage displays a usage synopsis on stderr. -func (i *Interface) ShowUsage(str string) { +func (i *Implementation) ShowUsage(str string) { str = strings.TrimSpace(str) + "\n" i.stderr.Write([]byte(str)) } diff --git a/pkg/cli/query.go b/pkg/cli/query.go index ed9e913..d9aa9a2 100644 --- a/pkg/cli/query.go +++ b/pkg/cli/query.go @@ -41,7 +41,7 @@ func (e errInterrupted) Error() string { // TUI implementation for when stdin is a terminal type terminalTUI struct { - i *Interface + i *Implementation } func (t terminalTUI) ReadLine(prompt string) (string, error) { @@ -78,7 +78,7 @@ type Choice struct { Shortcut byte //The display string that describes this choice. Text string - //The string to return from Interface.Query(). + //The string to return from Implementation.Query(). Return string } @@ -228,7 +228,7 @@ func (b *buffer) getNextInput() []byte { // TUI implementation for when stdin is a pipe type pipeTUI struct { - i *Interface + i *Implementation } func (t *pipeTUI) ReadLine(prompt string) (string, error) { |
