Created
January 3, 2017 21:41
-
-
Save henvic/9663ec0f6f51b23c171f2da51ef88f48 to your computer and use it in GitHub Desktop.
prompt.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package prompt | |
import ( | |
"bufio" | |
"errors" | |
"fmt" | |
"io" | |
"os" | |
"strconv" | |
"strings" | |
"syscall" | |
"github.com/hashicorp/errwrap" | |
"golang.org/x/crypto/ssh/terminal" | |
) | |
var ( | |
inStream io.Reader = os.Stdin | |
outStream io.Writer = os.Stdout | |
errStream io.Writer = os.Stderr | |
isTerminal = terminal.IsTerminal(int(os.Stdin.Fd())) | |
) | |
// SelectOption prompts for an option from a list | |
func SelectOption(indexLength int, equivalents map[string]int) (index int, err error) { | |
if indexLength == 0 { | |
return -1, errors.New("No options available.") | |
} | |
var option string | |
option, err = Prompt(fmt.Sprintf("\nSelect from 1..%d", indexLength)) | |
if err != nil { | |
return -1, err | |
} | |
option = strings.TrimSpace(option) | |
if equivalents != nil { | |
if index, ok := equivalents[option]; ok { | |
return getSelectOptionIndex(index, indexLength, nil) | |
} | |
} | |
index, err = strconv.Atoi(option) | |
return getSelectOptionIndex(index, indexLength, err) | |
} | |
func getSelectOptionIndex(index, indexLength int, err error) (int, error) { | |
index-- | |
if err != nil || index < 0 || index > indexLength { | |
return -1, errors.New("Invalid option.") | |
} | |
return index, nil | |
} | |
// Prompt returns a prompt to receive the value of a parameter. | |
// If the key is on a secret keys list it suppresses the feedback. | |
func Prompt(param string) (string, error) { | |
if !isTerminal { | |
return "", errors.New("Input device is not a terminal. " + | |
`Can not read "` + param + `"`) | |
} | |
fmt.Fprintf(outStream, param+": ") | |
reader := bufio.NewReader(inStream) | |
value, err := reader.ReadString('\n') | |
// on Windows the line break is \r\n | |
// let's trim \r | |
value = strings.TrimPrefix(value, "\r") | |
if err != nil { | |
return "", errwrap.Wrapf("Can not read stdin for "+param+": {{err}}", err) | |
} | |
return value[:len(value)-1], nil | |
} | |
// Hidden provides a prompt without echoing the value entered | |
func Hidden(param string) (string, error) { | |
if !isTerminal { | |
return "", errors.New("Input device is not a terminal. " + | |
`Can not read "` + param + `"`) | |
} | |
fmt.Fprintf(outStream, param+": ") | |
var b, err = terminal.ReadPassword(int(syscall.Stdin)) | |
if err != nil { | |
return "", errwrap.Wrapf("Can not read stdin for "+param+": {{err}}", err) | |
} | |
return string(b), nil | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package prompt | |
import ( | |
"bytes" | |
"os" | |
"testing" | |
) | |
var ( | |
bufInStream bytes.Buffer | |
bufErrStream bytes.Buffer | |
bufOutStream bytes.Buffer | |
defaultIsTerminal = isTerminal | |
) | |
func TestMain(m *testing.M) { | |
var defaultInStream = inStream | |
var defaultErrStream = errStream | |
var defaultOutStream = outStream | |
inStream = &bufInStream | |
errStream = &bufErrStream | |
outStream = &bufOutStream | |
ec := m.Run() | |
isTerminal = defaultIsTerminal | |
inStream = defaultInStream | |
errStream = defaultErrStream | |
outStream = defaultOutStream | |
os.Exit(ec) | |
} | |
func TestSelectOption(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("2\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(4, nil) | |
if option != 1 { | |
t.Errorf("Expected option to be 1 (index for 2), got %v instead", option) | |
} | |
if errt != nil { | |
t.Errorf("Expected option error to be nil, got %v instead", errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "\nSelect from 1..4: " { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestSelectOptionEquivalentChoosen(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("pass2\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(4, map[string]int{ | |
"fail1": 1, | |
"pass2": 2, | |
"fail3": 3, | |
"fail4": 4, | |
}) | |
if option != 1 { | |
t.Errorf("Expected option to be 1 (index for 2), got %v instead", option) | |
} | |
if errt != nil { | |
t.Errorf("Expected option error to be nil, got %v instead", errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "\nSelect from 1..4: " { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestSelectOptionEquivalentNotChoosen(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("2\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(4, map[string]int{ | |
"fail1": 1, | |
"pass2": 2, | |
"fail3": 3, | |
"fail4": 4, | |
}) | |
if option != 1 { | |
t.Errorf("Expected option to be 1 (index for 2), got %v instead", option) | |
} | |
if errt != nil { | |
t.Errorf("Expected option error to be nil, got %v instead", errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "\nSelect from 1..4: " { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestSelectOptionIsNotTerminal(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = false | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(5, nil) | |
if option != -1 { | |
t.Errorf("Expected option to be -1, got %v instead", option) | |
} | |
var wantErr = "Input device is not a terminal. Can not read \"\nSelect from 1..5\"" | |
if errt == nil || errt.Error() != wantErr { | |
t.Errorf("Expected option error to be %v, got %v instead", wantErr, errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "" { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestSelectOptionNoneAvailable(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(0, nil) | |
if option != -1 { | |
t.Errorf("Expected option to be -1, got %v instead", option) | |
} | |
var wantErr = "No options available." | |
if errt == nil || errt.Error() != wantErr { | |
t.Errorf("Expected option error to be %v, got %v instead", wantErr, errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "" { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestSelectOptionNoneAvailableEquivalent(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(0, map[string]int{ | |
"foo": 1, | |
}) | |
if option != -1 { | |
t.Errorf("Expected option to be -1, got %v instead", option) | |
} | |
var wantErr = "No options available." | |
if errt == nil || errt.Error() != wantErr { | |
t.Errorf("Expected option error to be %v, got %v instead", wantErr, errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "" { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestSelectOptionInvalidOption(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(4, nil) | |
if option != -1 { | |
t.Errorf("Expected option to be -1, got %v instead", option) | |
} | |
var wantErr = "Invalid option." | |
if errt == nil || errt.Error() != wantErr { | |
t.Errorf("Expected option error to be %v, got %v instead", wantErr, errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "\nSelect from 1..4: " { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestSelectOptionInvalidOptionEquivalent(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var option, errt = SelectOption(4, map[string]int{ | |
"foo": 1, | |
"bar": 2, | |
}) | |
if option != -1 { | |
t.Errorf("Expected option to be -1, got %v instead", option) | |
} | |
var wantErr = "Invalid option." | |
if errt == nil || errt.Error() != wantErr { | |
t.Errorf("Expected option error to be %v, got %v instead", wantErr, errt) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "\nSelect from 1..4: " { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestPrompt(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
var want = "value" | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var u, errt = Prompt("question") | |
if errt != nil { | |
t.Errorf("Expected prompt error to be nil, got %v instead", errt) | |
} | |
if u != want { | |
t.Errorf("Expected prompt value %v, got %v instead", want, u) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "question: " { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestPromptWithSpace(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
var want = "my value" | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = true | |
_, _ = bufInStream.WriteString("my value\n") | |
var u, errt = Prompt("question") | |
if errt != nil { | |
t.Errorf("Expected prompt error to be nil, got %v instead", errt) | |
} | |
if u != want { | |
t.Errorf("Expected prompt value %v, got %v instead", want, u) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "question: " { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestPromptIsNotterminal(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = false | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var u, errt = Prompt("question") | |
if errt == nil { | |
t.Errorf("Expected prompt error to be not nil, got %v instead", errt) | |
} | |
var wantErr = `Input device is not a terminal. Can not read "question"` | |
if errt.Error() != wantErr { | |
t.Errorf("Expected error message %v, got %v instead", wantErr, errt) | |
} | |
if u != "" { | |
t.Errorf("Expected prompt value empty, got %v instead", u) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "" { | |
t.Error("Unexpected output stream") | |
} | |
} | |
func TestHiddenIsNotterminal(t *testing.T) { | |
defer func() { | |
isTerminal = defaultIsTerminal | |
}() | |
bufInStream.Reset() | |
bufErrStream.Reset() | |
bufOutStream.Reset() | |
isTerminal = false | |
var _, err = bufInStream.WriteString("value\n") | |
if err != nil { | |
panic(err) | |
} | |
var u, errt = Hidden("question") | |
if errt == nil { | |
t.Errorf("Expected prompt error to be not nil, got %v instead", errt) | |
} | |
var wantErr = `Input device is not a terminal. Can not read "question"` | |
if errt.Error() != wantErr { | |
t.Errorf("Expected error message %v, got %v instead", wantErr, errt) | |
} | |
if u != "" { | |
t.Errorf("Expected prompt value empty, got %v instead", u) | |
} | |
if bufErrStream.Len() != 0 { | |
t.Error("Expected error stream to be empty") | |
} | |
if bufOutStream.String() != "" { | |
t.Error("Unexpected output stream") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment