blob: 56e468e6a93b16929ecc026d5a4e0d317bcf0cd4 [file] [log] [blame]
package cli
import (
"bufio"
"errors"
"fmt"
"io"
"os"
"os/signal"
"strings"
)
// Ui is an interface for interacting with the terminal, or "interface"
// of a CLI. This abstraction doesn't have to be used, but helps provide
// a simple, layerable way to manage user interactions.
type Ui interface {
// Ask asks the user for input using the given query. The response is
// returned as the given string, or an error.
Ask(string) (string, error)
// Output is called for normal standard output.
Output(string)
// Info is called for information related to the previous output.
// In general this may be the exact same as Output, but this gives
// Ui implementors some flexibility with output formats.
Info(string)
// Error is used for any error messages that might appear on standard
// error.
Error(string)
// Warn is used for any warning messages that might appear on standard
// error.
Warn(string)
}
// BasicUi is an implementation of Ui that just outputs to the given
// writer. This UI is not threadsafe by default, but you can wrap it
// in a ConcurrentUi to make it safe.
type BasicUi struct {
Reader io.Reader
Writer io.Writer
ErrorWriter io.Writer
}
func (u *BasicUi) Ask(query string) (string, error) {
if _, err := fmt.Fprint(u.Writer, query+" "); err != nil {
return "", err
}
// Register for interrupts so that we can catch it and immediately
// return...
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt)
defer signal.Stop(sigCh)
// Ask for input in a go-routine so that we can ignore it.
errCh := make(chan error, 1)
lineCh := make(chan string, 1)
go func() {
r := bufio.NewReader(u.Reader)
line, err := r.ReadString('\n')
if err != nil {
errCh <- err
return
}
lineCh <- strings.TrimRight(line, "\r\n")
}()
select {
case err := <-errCh:
return "", err
case line := <-lineCh:
return line, nil
case <-sigCh:
// Print a newline so that any further output starts properly
// on a new line.
fmt.Fprintln(u.Writer)
return "", errors.New("interrupted")
}
}
func (u *BasicUi) Error(message string) {
w := u.Writer
if u.ErrorWriter != nil {
w = u.ErrorWriter
}
fmt.Fprint(w, message)
fmt.Fprint(w, "\n")
}
func (u *BasicUi) Info(message string) {
u.Output(message)
}
func (u *BasicUi) Output(message string) {
fmt.Fprint(u.Writer, message)
fmt.Fprint(u.Writer, "\n")
}
func (u *BasicUi) Warn(message string) {
u.Error(message)
}
// PrefixedUi is an implementation of Ui that prefixes messages.
type PrefixedUi struct {
AskPrefix string
OutputPrefix string
InfoPrefix string
ErrorPrefix string
WarnPrefix string
Ui Ui
}
func (u *PrefixedUi) Ask(query string) (string, error) {
if query != "" {
query = fmt.Sprintf("%s%s", u.AskPrefix, query)
}
return u.Ui.Ask(query)
}
func (u *PrefixedUi) Error(message string) {
if message != "" {
message = fmt.Sprintf("%s%s", u.ErrorPrefix, message)
}
u.Ui.Error(message)
}
func (u *PrefixedUi) Info(message string) {
if message != "" {
message = fmt.Sprintf("%s%s", u.InfoPrefix, message)
}
u.Ui.Info(message)
}
func (u *PrefixedUi) Output(message string) {
if message != "" {
message = fmt.Sprintf("%s%s", u.OutputPrefix, message)
}
u.Ui.Output(message)
}
func (u *PrefixedUi) Warn(message string) {
if message != "" {
message = fmt.Sprintf("%s%s", u.WarnPrefix, message)
}
u.Ui.Warn(message)
}