1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
package input
import (
"bytes"
"fmt"
"strconv"
)
// Select asks the user to select a item from the given list by the number.
// It shows the given query and list to user. The response is returned as string
// from the list. By default, it checks the input is the number and is not
// out of range of the list and if not returns error. If Loop is true, it continue to
// ask until it receives valid input.
//
// If the user sends SIGINT (Ctrl+C) while reading input, it catches
// it and return it as a error.
func (i *UI) Select(query string, list []string, opts *Options) (string, error) {
// Set default val
i.once.Do(i.setDefault)
// Input must not be empty if no default is specified.
// Because Select ask user to input by number.
// If empty, can not transform it to int.
opts.Required = true
// Find default index which opts.Default indicates
defaultIndex := -1
defaultVal := opts.Default
if defaultVal != "" {
for i, item := range list {
if item == defaultVal {
defaultIndex = i
}
}
// DefaultVal is set but doesn't exist in list
if defaultIndex == -1 {
// This error message is not for user
// Should be found while development
return "", fmt.Errorf("opt.Default is specified but item does not exist in list")
}
}
// Construct the query & display it to user
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%s\n\n", query))
for i, item := range list {
buf.WriteString(fmt.Sprintf("%d. %s\n", i+1, item))
}
buf.WriteString("\n")
fmt.Fprintf(i.Writer, buf.String())
// resultStr and resultErr are return val of this function
var resultStr string
var resultErr error
for {
// Construct the asking line to input
var buf bytes.Buffer
buf.WriteString("Enter a number")
// Add default val if provided
if defaultIndex >= 0 && !opts.HideDefault {
buf.WriteString(fmt.Sprintf(" (Default is %d)", defaultIndex+1))
}
buf.WriteString(": ")
fmt.Fprintf(i.Writer, buf.String())
// Read user input from reader.
line, err := i.read(opts.readOpts())
if err != nil {
resultErr = err
break
}
// line is empty but default is provided returns it
if line == "" && defaultIndex >= 0 {
resultStr = list[defaultIndex]
break
}
if line == "" && opts.Required {
if !opts.Loop {
resultErr = ErrEmpty
break
}
fmt.Fprintf(i.Writer, "Input must not be empty. Answer by a number.\n\n")
continue
}
// Convert user input string to int val
n, err := strconv.Atoi(line)
if err != nil {
if !opts.Loop {
resultErr = ErrNotNumber
break
}
fmt.Fprintf(i.Writer,
"%q is not a valid input. Answer by a number.\n\n", line)
continue
}
// Check answer is in range of list
if n < 1 || len(list) < n {
if !opts.Loop {
resultErr = ErrOutOfRange
break
}
fmt.Fprintf(i.Writer,
"%q is not a valid choice. Choose a number from 1 to %d.\n\n",
line, len(list))
continue
}
// validate input by custom function
validate := opts.validateFunc()
if err := validate(line); err != nil {
if !opts.Loop {
resultErr = err
break
}
fmt.Fprintf(i.Writer, "Failed to validate input string: %s\n\n", err)
continue
}
// Reach here means it gets ideal input.
resultStr = list[n-1]
break
}
// Insert the new line for next output
fmt.Fprintf(i.Writer, "\n")
return resultStr, resultErr
}
|