| 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) |
| } |