summaryrefslogtreecommitdiff
path: root/pkg/cli
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/cli')
-rw-r--r--pkg/cli/command.go10
-rw-r--r--pkg/cli/interface.go76
-rw-r--r--pkg/cli/query.go6
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) {