-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrline.go
229 lines (212 loc) · 5.09 KB
/
rline.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// Package rline provides a readline implementation for usql.
package rline
import (
"errors"
"io"
"os"
"github.com/gohxs/readline"
isatty "github.com/mattn/go-isatty"
)
var (
// ErrInterrupt is the interrupt error.
ErrInterrupt = readline.ErrInterrupt
// ErrPasswordNotAvailable is the password not available error.
ErrPasswordNotAvailable = errors.New("password not available")
)
// IO is the common input/output interface.
type IO interface {
// Next returns the next line of runes (excluding '\n') from the input.
Next() ([]rune, error)
// Close closes the IO.
Close() error
// Stdout is the IO's standard out.
Stdout() io.Writer
// Stderr is the IO's standard error out.
Stderr() io.Writer
// Interactive determines if the IO is an interactive terminal.
Interactive() bool
// Cygwin determines if the IO is a Cygwin interactive terminal.
Cygwin() bool
// Prompt sets the prompt for the next interactive line read.
Prompt(string)
// Completer sets the auto-completer.
Completer(readline.AutoCompleter)
// Save saves a line of history.
Save(string) error
// Password prompts for a password.
Password(string) (string, error)
// SetOutput sets the output filter func.
SetOutput(func(string) string)
}
// Rline provides a type compatible with the IO interface.
type Rline struct {
Inst *readline.Instance
N func() ([]rune, error)
C func() error
Out io.Writer
Err io.Writer
Int bool
Cyg bool
P func(string)
A func(readline.AutoCompleter)
S func(string) error
Pw func(string) (string, error)
}
// Next returns the next line of runes (excluding '\n') from the input.
func (l *Rline) Next() ([]rune, error) {
if l.N != nil {
return l.N()
}
return nil, io.EOF
}
// Close closes the IO.
func (l *Rline) Close() error {
if l.C != nil {
return l.C()
}
return nil
}
// Stdout is the IO's standard out.
func (l *Rline) Stdout() io.Writer {
return l.Out
}
// Stderr is the IO's standard error out.
func (l *Rline) Stderr() io.Writer {
return l.Err
}
// Interactive determines if the IO is an interactive terminal.
func (l *Rline) Interactive() bool {
return l.Int
}
// Cygwin determines if the IO is a Cygwin interactive terminal.
func (l *Rline) Cygwin() bool {
return l.Cyg
}
// Prompt sets the prompt for the next interactive line read.
func (l *Rline) Prompt(s string) {
if l.P != nil {
l.P(s)
}
}
// Completer sets the auto-completer.
func (l *Rline) Completer(a readline.AutoCompleter) {
if l.A != nil {
l.A(a)
}
}
// Save saves a line of history.
func (l *Rline) Save(s string) error {
if l.S != nil {
return l.S(s)
}
return nil
}
// Password prompts for a password.
func (l *Rline) Password(prompt string) (string, error) {
if l.Pw != nil {
return l.Pw(prompt)
}
return "", ErrPasswordNotAvailable
}
// SetOutput sets the output format func.
func (l *Rline) SetOutput(f func(string) string) {
l.Inst.Config.Output = f
}
// New creates a new readline input/output handler.
func New(forceNonInteractive bool, out, histfile string) (IO, error) {
// determine if interactive
interactive := isatty.IsTerminal(os.Stdout.Fd()) && isatty.IsTerminal(os.Stdin.Fd())
cygwin := isatty.IsCygwinTerminal(os.Stdout.Fd()) && isatty.IsCygwinTerminal(os.Stdin.Fd())
var closers []func() error
// configure stdin
var stdin io.ReadCloser
switch {
case forceNonInteractive:
interactive, cygwin = false, false
case cygwin:
stdin = os.Stdin
default:
stdin = readline.Stdin
}
// configure stdout
var stdout io.WriteCloser
switch {
case out != "":
var err error
stdout, err = os.OpenFile(out, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return nil, err
}
closers = append(closers, stdout.Close)
interactive = false
case cygwin:
stdout = os.Stdout
default:
stdout = readline.Stdout
}
// configure stderr
var stderr io.Writer = os.Stderr
if !cygwin {
stderr = readline.Stderr
}
if interactive {
// wrap it with cancelable stdin
stdin = readline.NewCancelableStdin(stdin)
}
// create readline instance
l, err := readline.NewEx(&readline.Config{
HistoryFile: histfile,
DisableAutoSaveHistory: true,
InterruptPrompt: "^C",
HistorySearchFold: true,
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
FuncIsTerminal: func() bool {
return interactive || cygwin
},
FuncFilterInputRune: func(r rune) (rune, bool) {
if r == readline.CharCtrlZ {
return r, false
}
return r, true
},
})
if err != nil {
return nil, err
}
closers = append(closers, l.Close)
n := l.Operation.Runes
pw := func(prompt string) (string, error) {
buf, err := l.ReadPassword(prompt)
if err != nil {
return "", err
}
return string(buf), nil
}
if forceNonInteractive {
n, pw = nil, nil
}
return &Rline{
Inst: l,
N: n,
C: func() error {
for _, f := range closers {
_ = f()
}
return nil
},
Out: stdout,
Err: stderr,
Int: interactive || cygwin,
Cyg: cygwin,
P: l.SetPrompt,
A: func(a readline.AutoCompleter) {
cfg := l.Config.Clone()
cfg.AutoComplete = a
l.SetConfig(cfg)
},
S: l.SaveHistory,
Pw: pw,
}, nil
}