-
Notifications
You must be signed in to change notification settings - Fork 0
/
input.go
153 lines (129 loc) · 4.6 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
// Package input contains identifiers used in getting TunaQuest command input
// from CLI or other sources of input.
package input
import (
"bufio"
"fmt"
"io"
"strings"
"github.com/chzyer/readline"
)
// DirectCommandReader implements command.Reader and reads commands from any
// generic input stream directly. It can be used generically with any io.Reader
// but does not sanitize the input of control and escape sequences.
//
// DirectCommandReader should not be used directly; instead, create one with
// [NewDirectReader].
type DirectCommandReader struct {
r *bufio.Reader
blanksAllowed bool
}
// InteractiveCommandReader implements command.Reader and reads commands from
// stdin using a go implementation of the GNU Readline library. This keeps input
// clear of all typing and editing escape sequences and enables the use of
// command history. This should in general probably only be used when directly
// connecting to a TTY for input.
//
// InteractiveCommandReader should not be used directly; instead, create one
// with [NewInteractiveReader].
type InteractiveCommandReader struct {
rl *readline.Instance
blanksAllowed bool
prompt string
}
// Create a new DirectCommandReader and initialize a buffered reader on the
// provided reader. The returned CommandReader must have Close() called on it
// before disposal to properly teardown readline resources.
func NewDirectReader(r io.Reader) *DirectCommandReader {
return &DirectCommandReader{
r: bufio.NewReader(r),
}
}
// Create a new InteractiveCommandReader and initialize readline. The returned
// InteractiveCommandReader must have Close() called on it before disposal to
// properly teardown readline resources.
func NewInteractiveReader() (*InteractiveCommandReader, error) {
rl, err := readline.NewEx(&readline.Config{
Prompt: "> ",
})
if err != nil {
return nil, fmt.Errorf("create readline config: %w", err)
}
return &InteractiveCommandReader{
rl: rl,
prompt: "> ",
}, nil
}
// Close cleans up resources associated with the DirectCommandReader.
func (dcr *DirectCommandReader) Close() error {
// this function is here so DirectCommandReader implements
// game.CommandReader. For now it doesn't really do anything as the
// DirectCommandReader does not create resources but it may in the future
// and callers should treat it as though it must have Close called on it.
return nil
}
// Close cleans up readline resources and other resources associated with the
// InteractiveCommandReader.
func (icr *InteractiveCommandReader) Close() error {
return icr.rl.Close()
}
// ReadCommand reads the next line from stdin. The returned string will only
// be empty if there is an error reading input, otherwise this function is
// blocked on until a line containing non-space characters is read.
//
// If at end of input, the returned string will be empty and error will be
// io.EOF. If any other error occurs, the returned string will be empty and
// error will be that error.
func (dcr *DirectCommandReader) ReadCommand() (string, error) {
var line string
var err error
for line == "" {
line, err = dcr.r.ReadString('\n')
if err != nil && (err != io.EOF || line == "") {
return "", err
}
line = strings.TrimSpace(line)
if line == "" && dcr.blanksAllowed {
return line, nil
}
}
return line, nil
}
// ReadCommand reads the next command from stdin. The returned string will only
// be empty if there is an error, otherwise this function is blocked on until a
// line consisting of more than empty or whitespace-only input is read.
//
// If at end of input, the returned string will be empty and error will be
// io.EOF. If any other error occurs, the returned string will be empty and
// error will be that error.
func (icr *InteractiveCommandReader) ReadCommand() (string, error) {
var line string
var err error
for line == "" {
line, err = icr.rl.Readline()
if err != nil && (err != io.EOF || line == "") {
return "", err
}
line = strings.TrimSpace(line)
if line == "" && icr.blanksAllowed {
return line, nil
}
}
return line, nil
}
// AllowBlank sets whether blank output is allowed. By default it is not.
func (dcr *DirectCommandReader) AllowBlank(allow bool) {
dcr.blanksAllowed = allow
}
// AllowBlank sets whether blank output is allowed. By default it is not.
func (icr *InteractiveCommandReader) AllowBlank(allow bool) {
icr.blanksAllowed = allow
}
// SetPrompt updates the prompt to the given text.
func (icr *InteractiveCommandReader) SetPrompt(p string) {
icr.rl.SetPrompt(p)
}
// GetPrompt gets the current prompt.
func (icr *InteractiveCommandReader) GetPrompt() string {
return icr.prompt
}