2015-04-27 23:19:51 +00:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"strings"
|
2015-05-11 18:30:01 +00:00
|
|
|
|
2016-01-13 18:40:08 +00:00
|
|
|
"github.com/bgentry/speakeasy"
|
|
|
|
"github.com/mattn/go-isatty"
|
2015-04-27 23:19:51 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
2015-05-11 18:30:01 +00:00
|
|
|
// AskSecret asks the user for input using the given query, but does not echo
|
|
|
|
// the keystrokes to the terminal.
|
|
|
|
AskSecret(string) (string, error)
|
|
|
|
|
2015-04-27 23:19:51 +00:00
|
|
|
// 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) {
|
2015-05-11 18:30:01 +00:00
|
|
|
return u.ask(query, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *BasicUi) AskSecret(query string) (string, error) {
|
|
|
|
return u.ask(query, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *BasicUi) ask(query string, secret bool) (string, error) {
|
2015-04-27 23:19:51 +00:00
|
|
|
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() {
|
2015-05-11 18:30:01 +00:00
|
|
|
var line string
|
|
|
|
var err error
|
2016-01-13 18:40:08 +00:00
|
|
|
if secret && isatty.IsTerminal(os.Stdin.Fd()) {
|
|
|
|
line, err = speakeasy.Ask("")
|
2015-05-11 18:30:01 +00:00
|
|
|
} else {
|
|
|
|
r := bufio.NewReader(u.Reader)
|
|
|
|
line, err = r.ReadString('\n')
|
|
|
|
}
|
2015-04-27 23:19:51 +00:00
|
|
|
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 {
|
2015-05-11 18:30:01 +00:00
|
|
|
AskPrefix string
|
|
|
|
AskSecretPrefix string
|
|
|
|
OutputPrefix string
|
|
|
|
InfoPrefix string
|
|
|
|
ErrorPrefix string
|
|
|
|
WarnPrefix string
|
|
|
|
Ui Ui
|
2015-04-27 23:19:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (u *PrefixedUi) Ask(query string) (string, error) {
|
|
|
|
if query != "" {
|
|
|
|
query = fmt.Sprintf("%s%s", u.AskPrefix, query)
|
|
|
|
}
|
|
|
|
|
|
|
|
return u.Ui.Ask(query)
|
|
|
|
}
|
|
|
|
|
2015-05-11 18:30:01 +00:00
|
|
|
func (u *PrefixedUi) AskSecret(query string) (string, error) {
|
|
|
|
if query != "" {
|
|
|
|
query = fmt.Sprintf("%s%s", u.AskSecretPrefix, query)
|
|
|
|
}
|
|
|
|
|
|
|
|
return u.Ui.AskSecret(query)
|
|
|
|
}
|
|
|
|
|
2015-04-27 23:19:51 +00:00
|
|
|
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)
|
|
|
|
}
|