Skip to content

Instantly share code, notes, and snippets.

@henvic
Created January 3, 2017 21:41
Show Gist options
  • Save henvic/9663ec0f6f51b23c171f2da51ef88f48 to your computer and use it in GitHub Desktop.
Save henvic/9663ec0f6f51b23c171f2da51ef88f48 to your computer and use it in GitHub Desktop.
prompt.go
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
}
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