summaryrefslogtreecommitdiff
path: root/pkg/cli
diff options
context:
space:
mode:
authorStefan Majewsky <majewsky@gmx.net>2020-03-28 13:53:10 +0100
committerStefan Majewsky <majewsky@gmx.net>2020-03-28 13:53:10 +0100
commited2b9374599cc00467b1c27121318748cc0d3f49 (patch)
tree65e3200a0b69817b24ae772223d1d852a52458af /pkg/cli
parent1ff86053ddef8de3247909434e0bd7000c060f2a (diff)
downloadgofu-ed2b9374599cc00467b1c27121318748cc0d3f49.tar.gz
rename pkg/ to internal/
Diffstat (limited to 'pkg/cli')
-rw-r--r--pkg/cli/command.go70
-rw-r--r--pkg/cli/interface.go168
-rw-r--r--pkg/cli/query.go295
3 files changed, 0 insertions, 533 deletions
diff --git a/pkg/cli/command.go b/pkg/cli/command.go
deleted file mode 100644
index a9938d1..0000000
--- a/pkg/cli/command.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*******************************************************************************
-*
-* Copyright 2017 Stefan Majewsky <majewsky@gmx.net>
-*
-* This program is free software: you can redistribute it and/or modify it under
-* the terms of the GNU General Public License as published by the Free Software
-* Foundation, either version 3 of the License, or (at your option) any later
-* version.
-*
-* This program is distributed in the hope that it will be useful, but WITHOUT ANY
-* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License along with
-* this program. If not, see <http://www.gnu.org/licenses/>.
-*
-*******************************************************************************/
-
-package cli
-
-import (
- "fmt"
- "io"
- "os/exec"
- "strings"
-)
-
-//Command describes a command that can be run using the methods in the
-//Implementation interface.
-type Command struct {
- Program []string
- WorkDir string
-}
-
-type commandError struct {
- Cmd Command
- Err error
-}
-
-func (e commandError) Error() string {
- cmdline := strings.Join(e.Cmd.Program, " ")
- if e.Cmd.WorkDir == "" {
- return fmt.Sprintf("exec `%s`: %s",
- cmdline, e.Err.Error(),
- )
- }
- return fmt.Sprintf("exec `%s` in %s: %s",
- cmdline, e.Cmd.WorkDir, e.Err.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
-
-//DefaultCommandRunner is a CommandRunner that actually executes the command.
-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
-
- err := cmd.Run()
- if err != nil {
- err = commandError{c, err}
- }
- return err
-}
diff --git a/pkg/cli/interface.go b/pkg/cli/interface.go
deleted file mode 100644
index a88212b..0000000
--- a/pkg/cli/interface.go
+++ /dev/null
@@ -1,168 +0,0 @@
-/*******************************************************************************
-*
-* Copyright 2017 Stefan Majewsky <majewsky@gmx.net>
-*
-* This program is free software: you can redistribute it and/or modify it under
-* the terms of the GNU General Public License as published by the Free Software
-* Foundation, either version 3 of the License, or (at your option) any later
-* version.
-*
-* This program is distributed in the hope that it will be useful, but WITHOUT ANY
-* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License along with
-* this program. If not, see <http://www.gnu.org/licenses/>.
-*
-*******************************************************************************/
-
-package cli
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "os"
- "sort"
- "strings"
-
- "golang.org/x/crypto/ssh/terminal"
-)
-
-//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 stdinFile, ok := stdin.(*os.File); ok && terminal.IsTerminal(int(stdinFile.Fd())) {
- Interface.tui = &terminalTUI{Interface}
- } else {
- Interface.tui = &pipeTUI{Interface}
- }
-}
-
-//Implementation wraps access to the CLI, including input, output and subprocesses.
-type Implementation struct {
- 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.
- //
- //This is useful when gofu is expected to output a certain value to stdout
- //which is used by the next program in the pipe, and additional output from
- //subprocesses could confuse the stdout handler.
- StdoutProtected bool
-}
-
-//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).
- ReadLine(prompt string) (string, error)
- //Confirm displays a yes/no question and returns whether the user answered "yes".
- Confirm(question string) (bool, error)
- //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.
- Query(prompt string, choices ...Choice) (string, error)
- //Print writes the given string (potentially including ANSI escape codes) to
- //the given writer. At this point, it can be decided whether to strip out the
- //ANSI escape codes.
- Print(w io.Writer, msg string)
-}
-
-func (i *Implementation) safeStdout() io.Writer {
- if i.StdoutProtected {
- return i.stderr
- }
- return i.stdout
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// input
-
-//ReadLine reads a line from stdin (if tty: uses canonical mode).
-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 *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 *Implementation) Query(prompt string, choices ...Choice) (string, error) {
- return i.tui.Query(prompt, choices...)
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// subprocesses
-
-//Run executes the given command on the same stdout and 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 *Implementation) CaptureStdout(c Command) (string, error) {
- var buf bytes.Buffer
- err := i.commandRunner(c, nil, &buf, i.stderr)
- return string(buf.Bytes()), err
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// output
-
-//ShowResult displays the result of a computation on stdout.
-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 *Implementation) ShowResultsSorted(strs []string) {
- sort.Strings(strs)
- for _, str := range strs {
- i.ShowResult(str)
- }
-}
-
-//ShowProgress displays a progress message on stderr.
-func (i *Implementation) ShowProgress(str string) {
- i.tui.Print(i.stderr, fmt.Sprintf("\x1B[0;1;36m>>\x1B[0;36m %s\x1B[0m\n", strings.TrimSpace(str)))
-}
-
-//ShowWarning displays a warning message on stderr.
-func (i *Implementation) ShowWarning(str string) {
- i.tui.Print(i.stderr, fmt.Sprintf("\x1B[0;1;33m!!\x1B[0;33m %s\x1B[0m\n", strings.TrimSpace(str)))
-}
-
-//ShowError displays an error message on stderr.
-func (i *Implementation) ShowError(str string) {
- i.tui.Print(i.stderr, fmt.Sprintf("\x1B[0;1;31m!!\x1B[0;31m %s\x1B[0m\n", strings.TrimSpace(str)))
-}
-
-//ShowUsage displays a usage synopsis on stderr.
-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
deleted file mode 100644
index add170c..0000000
--- a/pkg/cli/query.go
+++ /dev/null
@@ -1,295 +0,0 @@
-/*******************************************************************************
-*
-* Copyright 2017 Stefan Majewsky <majewsky@gmx.net>
-*
-* This program is free software: you can redistribute it and/or modify it under
-* the terms of the GNU General Public License as published by the Free Software
-* Foundation, either version 3 of the License, or (at your option) any later
-* version.
-*
-* This program is distributed in the hope that it will be useful, but WITHOUT ANY
-* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-* A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License along with
-* this program. If not, see <http://www.gnu.org/licenses/>.
-*
-*******************************************************************************/
-
-package cli
-
-import (
- "errors"
- "fmt"
- "io"
- "regexp"
- "strconv"
- "strings"
-
- terminal "golang.org/x/crypto/ssh/terminal"
-)
-
-//cannot use `var errInterrupted = errors.New("Interrupted!")` because golint
-//complains about the formatting of the error message
-type errInterrupted struct{}
-
-func (e errInterrupted) Error() string {
- return "Interrupted!"
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// TUI implementation for when stdin is a terminal
-
-type terminalTUI struct {
- i *Implementation
-}
-
-func (t terminalTUI) Print(w io.Writer, msg string) {
- w.Write([]byte(msg))
-}
-
-func (t terminalTUI) ReadLine(prompt string) (string, error) {
- if prompt != "" {
- t.i.safeStdout().Write([]byte(strings.TrimSpace(prompt) + " "))
- }
- str, err := t.i.stdinBuf.ReadString('\n')
- return strings.TrimSpace(str), err
-}
-
-func (t terminalTUI) Confirm(question string) (bool, error) {
- out := t.i.safeStdout()
- out.Write([]byte(strings.TrimSpace(question) + " [y/n] "))
-
- buf := buffer{Input: t.i.stdin}
- for {
- switch string(buf.getNextInput()) {
- case "y", "Y":
- out.Write([]byte("-> yes\n"))
- return true, nil
- case "n", "N":
- out.Write([]byte("-> no\n"))
- return false, nil
- case "\x03": // Ctrl-C
- return false, errInterrupted{}
- }
- }
-}
-
-//Choice is a thing that the user can choose during Query().
-type Choice struct {
- //If given, the choice can be selected by pressing the key that
- //produces this character.
- Shortcut byte
- //The display string that describes this choice.
- Text string
- //The string to return from Implementation.Query().
- Return string
-}
-
-func (c Choice) hasShortcut() bool {
- return c.Shortcut != '\000'
-}
-
-func (t terminalTUI) Query(prompt string, choices ...Choice) (string, error) {
- if len(choices) == 0 {
- panic("no choices")
- }
-
- //disable line wrap; unexpected wrapping would confuse our cursor-moving code
- out := t.i.safeStdout()
- out.Write([]byte("\x1B[?7l"))
- defer out.Write([]byte("\x1B[?7h"))
-
- //display question
- out.Write([]byte(strings.TrimSuffix(prompt, "\n") + "\n"))
- selected := 0
-
- buf := buffer{Input: t.i.stdin}
-OUTER:
- for {
- displayChoices(out, choices, selected)
-
- input := buf.getNextInput()
- if len(input) == 1 {
- //single character entered - check if a shortcut matches
- for idx, choice := range choices {
- if choice.Shortcut == input[0] {
- selected = idx
- break OUTER
- }
- }
- }
-
- switch string(input) {
- case "\r", "\n":
- break OUTER
- case "\x1B[A": // Up arrow key
- selected--
- if selected < 0 {
- selected = 0
- }
- case "\x1B[B": // Down arrow key
- selected++
- if selected >= len(choices) {
- selected = len(choices) - 1
- }
- case "\x03": // Ctrl-C
- return "", errInterrupted{}
- }
-
- //prepare to re-render choices
- removeDisplayLines(out, len(choices))
- }
-
- //clear query display
- removeDisplayLines(out, len(choices)+1)
-
- //display question + chosen answer
- fmt.Fprintf(out, "%s -> %s\n",
- strings.TrimSuffix(prompt, "\n"),
- strings.TrimSpace(choices[selected].Text),
- )
-
- return choices[selected].Return, nil
-}
-
-func removeDisplayLines(stdout io.Writer, n int) {
- for idx := 0; idx < n; idx++ {
- stdout.Write([]byte("\x1B[A\x1B[2K"))
- }
-}
-
-func displayChoices(out io.Writer, choices []Choice, selectedIndex int) {
- hasShortcuts := false
- for _, choice := range choices {
- if choice.hasShortcut() {
- hasShortcuts = true
- break
- }
- }
-
- for idx, choice := range choices {
- text := " " + strings.TrimSpace(choice.Text) + " \n"
- if hasShortcuts {
- shortcut := choice.Shortcut
- if !choice.hasShortcut() {
- shortcut = ' '
- }
- text = fmt.Sprintf(" [%c]%s", shortcut, text)
- }
-
- if idx == selectedIndex {
- fmt.Fprintf(out, "\x1B[0;7m%s\x1B[0m", text)
- } else {
- out.Write([]byte(text))
- }
- }
-}
-
-var ansiEscapeRx = regexp.MustCompile(`^\x1B\[[\x20-\x3F]*[\x40-\x7E]`)
-
-type buffer struct {
- Input io.Reader
- buf [128]byte
- fill int
-}
-
-func (b *buffer) getNextInput() []byte {
- //do we have a simple input character?
- if b.fill > 0 && b.buf[0] != '\x1B' {
- result := append([]byte(nil), b.buf[0])
- copy(b.buf[0:], b.buf[1:])
- b.fill--
- return result
- }
-
- //do we have a full ANSI escape sequence?
- match := ansiEscapeRx.Find(b.buf[0:b.fill])
- if match != nil {
- result := append([]byte(nil), match...)
- copy(b.buf[0:], b.buf[len(match):])
- b.fill -= len(match)
- return result
- }
-
- oldState, err := terminal.MakeRaw(0)
- if err != nil {
- panic(err)
- }
- defer terminal.Restore(0, oldState)
-
- //fill buffer some more
- n, err := b.Input.Read(b.buf[b.fill:])
- if err != nil {
- panic(err)
- }
- b.fill += n
-
- return b.getNextInput() //restart
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// TUI implementation for when stdin is a pipe (also used in unit tests)
-
-type pipeTUI struct {
- i *Implementation
-}
-
-//AnsiColorCodeRx is a regexp that matches ANSI escape sequences of the type SGR.
-var AnsiColorCodeRx = regexp.MustCompile("\x1B" + `\[[0-9;]*m`)
-
-func (t *pipeTUI) Print(w io.Writer, msg string) {
- w.Write([]byte(AnsiColorCodeRx.ReplaceAllString(msg, "")))
-}
-
-func (t *pipeTUI) ReadLine(prompt string) (string, error) {
- str, err := t.i.stdinBuf.ReadString('\n')
- str = strings.TrimSpace(str)
- if err == nil {
- fmt.Fprintf(t.i.stderr, "%s %s\n", strings.TrimSpace(prompt), str)
- }
- return str, err
-}
-
-func (t *pipeTUI) Confirm(question string) (bool, error) {
- question = strings.TrimSpace(question)
- str, err := t.ReadLine(question)
- if err != nil {
- return false, err
- }
- //recognize /[01tTfF]|true|false/
- ok, err := strconv.ParseBool(str)
- if err != nil {
- //recognize /[yY]|yes|no/
- ok = strings.HasPrefix(question, "y") || strings.HasPrefix(question, "Y")
- }
- fmt.Fprintf(t.i.stderr, "%s -> %v (%s)\n", question, ok, str)
- return ok, nil
-}
-
-//Query for the pipe TUI is limited to exact matches of the choice text, or
-//matching by choice shortcut.
-func (t *pipeTUI) Query(prompt string, choices ...Choice) (string, error) {
- str, err := t.i.stdinBuf.ReadString('\n')
- if err != nil {
- return str, err
- }
- str = strings.TrimSpace(str)
-
- //prefer exact match on choice.Text
- for _, choice := range choices {
- if choice.Text == str {
- fmt.Fprintf(t.i.stderr, "%s -> %s\n", prompt, choice.Text)
- return choice.Return, nil
- }
- }
- //allow match on choice.Shortcut
- for _, choice := range choices {
- if string(choice.Shortcut) == str {
- fmt.Fprintf(t.i.stderr, "%s -> %s\n", prompt, choice.Text)
- return choice.Return, nil
- }
- }
- fmt.Fprintf(t.i.stderr, "%s -> [%s]\n", prompt, str)
- return "", errors.New("cannot match input with available choices")
-}