forked from tcnksm/go-input
-
Notifications
You must be signed in to change notification settings - Fork 2
/
select.go
141 lines (117 loc) · 3.3 KB
/
select.go
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
}