forked from ukautz/clif
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathinput.go
163 lines (142 loc) · 4.53 KB
/
input.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package clif
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"sort"
"strings"
)
// Input is an interface for input helping. It provides shorthand methods for
// often used CLI interactions.
type Input interface {
// Ask prints question to user and then reads user input and returns as soon
// as it's non empty or queries again until it is
Ask(question string, check func(string) error) string
// AskRegex prints question to user and then reads user input, compares it
// against regex and return if matching or queries again until it does
AskRegex(question string, rx *regexp.Regexp) string
// Choose renders choices for user and returns what was choosen
Choose(question string, choices map[string]string) string
// Confirm prints question to user until she replies with "yes", "y", "no" or "n"
Confirm(question string) bool
}
// DefaultInput is the default used input implementation
type DefaultInput struct {
in io.Reader
out Output
}
// NewDefaultInput constructs a new default input implementation on given
// io reader (if nil, fall back to `os.Stdin`). Requires Output for issuing
// questions to user.
func NewDefaultInput(in io.Reader, out Output) *DefaultInput {
if in == nil {
in = os.Stdin
}
return &DefaultInput{in, out}
}
var RenderAskQuestion = func(question string) string {
return "<query>"+ strings.TrimRight(question, " ")+ "<reset> "
}
func (this *DefaultInput) Ask(question string, check func(string) error) string {
if check == nil {
check = func(in string) error {
if len(in) > 0 {
return nil
} else {
return fmt.Errorf("Input required")
}
}
}
reader := bufio.NewReader(this.in)
for {
this.out.Printf(RenderAskQuestion(question))
if line, _, err := reader.ReadLine(); err != nil {
this.out.Printf("<warn>%s<reset>\n\n", err)
} else if err := check(string(line)); err != nil {
this.out.Printf("<warn>%s<reset>\n\n", err)
} else {
return string(line)
}
}
}
func (this *DefaultInput) AskRegex(question string, rx *regexp.Regexp) string {
return this.Ask(question, func(in string) error {
if rx.MatchString(in) {
return nil
} else {
return fmt.Errorf("Input does not match criteria")
}
})
}
// RenderChooseQuestion is the method used by default input `Choose()` method to
// to render the question (displayed before listing the choices) into a string.
// Can be overwritten at users discretion.
var RenderChooseQuestion = func(question string) string {
return question + "\n"
}
// RenderChooseOption is the method used by default input `Choose()` method to
// to render a singular choice into a string. Can be overwritten at users discretion.
var RenderChooseOption = func(key, value string, size int) string {
return fmt.Sprintf(" <query>%-"+fmt.Sprintf("%d", size+1)+"s<reset> %s\n", key+")", value)
}
// RenderChooseQuery is the method used by default input `Choose()` method to
// to render the query prompt choice (after the choices) into a string. Can be
// overwritten at users discretion.
var RenderChooseQuery = func() string {
return "Choose: "
}
func (this *DefaultInput) Choose(question string, choices map[string]string) string {
options := RenderChooseQuestion(question)
keys := []string{}
max := 0
for k, _ := range choices {
if l := len(k); l > max {
max = l
}
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
options += RenderChooseOption(k, choices[k], max)
}
options += RenderChooseQuery()
return this.Ask(options, func(in string) error {
if _, ok := choices[in]; ok {
return nil
} else {
return fmt.Errorf("Choose one of: %s", strings.Join(keys, ", "))
}
})
}
// ConfirmRejection is the message replied to the user if she does not answer
// with "yes", "y", "no" or "n" (case insensitive)
var ConfirmRejection = "<warn>Please respond with \"yes\" or \"no\"<reset>\n\n"
// ConfirmYesRegex is the regular expression used to check if the user replied positive
var ConfirmYesRegex = regexp.MustCompile(`^(?i)y(es)?$`)
// ConfirmNoRegex is the regular expression used to check if the user replied negative
var ConfirmNoRegex = regexp.MustCompile(`^(?i)no?$`)
func (this *DefaultInput) Confirm(question string) bool {
cb := func(value string) error {return nil}
for {
res := this.Ask(question, cb)
if ConfirmYesRegex.MatchString(res) {
return true
} else if ConfirmNoRegex.MatchString(res) {
return false
} else {
this.out.Printf(ConfirmRejection)
}
}
}
func InputEmptyOk(s string) error {
return nil
}
func InputAny(s string) error {
if len(s) == 0 {
return fmt.Errorf("No input provided")
} else {
return nil
}
}