tty-prompt 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +22 -0
- data/README.md +199 -0
- data/Rakefile +8 -0
- data/lib/tty-prompt.rb +15 -0
- data/lib/tty/prompt.rb +206 -0
- data/lib/tty/prompt/distance.rb +49 -0
- data/lib/tty/prompt/error.rb +26 -0
- data/lib/tty/prompt/history.rb +16 -0
- data/lib/tty/prompt/mode.rb +64 -0
- data/lib/tty/prompt/mode/echo.rb +40 -0
- data/lib/tty/prompt/mode/raw.rb +40 -0
- data/lib/tty/prompt/question.rb +338 -0
- data/lib/tty/prompt/question/modifier.rb +93 -0
- data/lib/tty/prompt/question/validation.rb +92 -0
- data/lib/tty/prompt/reader.rb +113 -0
- data/lib/tty/prompt/response.rb +252 -0
- data/lib/tty/prompt/response_delegation.rb +41 -0
- data/lib/tty/prompt/statement.rb +60 -0
- data/lib/tty/prompt/suggestion.rb +113 -0
- data/lib/tty/prompt/utils.rb +16 -0
- data/lib/tty/prompt/version.rb +7 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/unit/ask_spec.rb +77 -0
- data/spec/unit/distance/distance_spec.rb +75 -0
- data/spec/unit/error_spec.rb +30 -0
- data/spec/unit/question/argument_spec.rb +30 -0
- data/spec/unit/question/character_spec.rb +24 -0
- data/spec/unit/question/default_spec.rb +25 -0
- data/spec/unit/question/in_spec.rb +23 -0
- data/spec/unit/question/initialize_spec.rb +24 -0
- data/spec/unit/question/modifier/apply_to_spec.rb +31 -0
- data/spec/unit/question/modifier/letter_case_spec.rb +22 -0
- data/spec/unit/question/modifier/whitespace_spec.rb +33 -0
- data/spec/unit/question/modify_spec.rb +44 -0
- data/spec/unit/question/valid_spec.rb +46 -0
- data/spec/unit/question/validate_spec.rb +30 -0
- data/spec/unit/question/validation/coerce_spec.rb +24 -0
- data/spec/unit/question/validation/valid_value_spec.rb +22 -0
- data/spec/unit/reader/getc_spec.rb +42 -0
- data/spec/unit/response/read_bool_spec.rb +47 -0
- data/spec/unit/response/read_char_spec.rb +16 -0
- data/spec/unit/response/read_date_spec.rb +20 -0
- data/spec/unit/response/read_email_spec.rb +42 -0
- data/spec/unit/response/read_multiple_spec.rb +23 -0
- data/spec/unit/response/read_number_spec.rb +28 -0
- data/spec/unit/response/read_range_spec.rb +26 -0
- data/spec/unit/response/read_spec.rb +68 -0
- data/spec/unit/response/read_string_spec.rb +19 -0
- data/spec/unit/say_spec.rb +66 -0
- data/spec/unit/statement/initialize_spec.rb +19 -0
- data/spec/unit/suggest_spec.rb +33 -0
- data/spec/unit/warn_spec.rb +30 -0
- data/tasks/console.rake +10 -0
- data/tasks/coverage.rake +11 -0
- data/tasks/spec.rake +29 -0
- data/tty-prompt.gemspec +26 -0
- metadata +194 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
# A class responsible for string comparison
|
6
|
+
class Distance
|
7
|
+
# Calculate the optimal string alignment distance
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
def distance(first, second)
|
11
|
+
distances = []
|
12
|
+
rows = first.to_s.length
|
13
|
+
cols = second.to_s.length
|
14
|
+
|
15
|
+
0.upto(rows) do |index|
|
16
|
+
distances << [index] + [0] * cols
|
17
|
+
end
|
18
|
+
distances[0] = 0.upto(cols).to_a
|
19
|
+
|
20
|
+
1.upto(rows) do |first_index|
|
21
|
+
1.upto(cols) do |second_index|
|
22
|
+
first_char = first[first_index - 1]
|
23
|
+
second_char = second[second_index - 1]
|
24
|
+
cost = first_char == second_char ? 0 : 1
|
25
|
+
|
26
|
+
distances[first_index][second_index] = [
|
27
|
+
distances[first_index - 1][second_index], # deletion
|
28
|
+
distances[first_index][second_index - 1], # insertion
|
29
|
+
distances[first_index - 1][second_index - 1] # substitution
|
30
|
+
].min + cost
|
31
|
+
|
32
|
+
if first_index > 1 && second_index > 1
|
33
|
+
first_previous_char = first[first_index - 2]
|
34
|
+
second_previous_char = second[second_index - 2]
|
35
|
+
if first_char == second_previous_char && second_char == first_previous_char
|
36
|
+
distances[first_index][second_index] = [
|
37
|
+
distances[first_index][second_index],
|
38
|
+
distances[first_index - 2][second_index - 2] + 1 # transposition
|
39
|
+
].min
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
distances[rows][cols]
|
46
|
+
end
|
47
|
+
end # Distance
|
48
|
+
end # Prompt
|
49
|
+
end # TTY
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Error
|
4
|
+
|
5
|
+
attr_reader :errors
|
6
|
+
|
7
|
+
def initialize(question, errors=[])
|
8
|
+
@question = question
|
9
|
+
@errors = errors
|
10
|
+
end
|
11
|
+
|
12
|
+
def <<(type, message)
|
13
|
+
@errors << [type, message]
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# Handle exception
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
def error_wrapping(&block)
|
21
|
+
yield
|
22
|
+
rescue
|
23
|
+
question.error? ? block.call : raise
|
24
|
+
end
|
25
|
+
|
26
|
+
end # Error
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
# A class responsible for storing shell interactions
|
6
|
+
class History
|
7
|
+
|
8
|
+
attr_reader :max_size
|
9
|
+
|
10
|
+
def initialize(max_size=nil)
|
11
|
+
@max_size = max_size
|
12
|
+
end
|
13
|
+
|
14
|
+
end # History
|
15
|
+
end # Prompt
|
16
|
+
end # TTY
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tty/prompt/mode/echo'
|
4
|
+
require 'tty/prompt/mode/raw'
|
5
|
+
|
6
|
+
module TTY
|
7
|
+
class Prompt
|
8
|
+
class Mode
|
9
|
+
# Initialize a Terminal
|
10
|
+
#
|
11
|
+
# @api public
|
12
|
+
def initialize(options = {})
|
13
|
+
@echo = TTY::Prompt::Mode::Echo.new
|
14
|
+
@raw = TTY::Prompt::Mode::Raw.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Switch echo on
|
18
|
+
#
|
19
|
+
# @api public
|
20
|
+
def echo_on
|
21
|
+
@echo.on
|
22
|
+
end
|
23
|
+
|
24
|
+
# Switch echo off
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def echo_off
|
28
|
+
@echo.off
|
29
|
+
end
|
30
|
+
|
31
|
+
# Echo given block
|
32
|
+
#
|
33
|
+
# @param [Boolean] is_on
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
def echo(is_on = true, &block)
|
37
|
+
@echo.echo(is_on, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Switch raw mode on
|
41
|
+
#
|
42
|
+
# @api public
|
43
|
+
def raw_on
|
44
|
+
@raw.on
|
45
|
+
end
|
46
|
+
|
47
|
+
# Switch raw mode off
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def raw_off
|
51
|
+
@raw.off
|
52
|
+
end
|
53
|
+
|
54
|
+
# Use raw mode in the given block
|
55
|
+
#
|
56
|
+
# @param [Boolean] is_on
|
57
|
+
#
|
58
|
+
# @api public
|
59
|
+
def raw(is_on = true, &block)
|
60
|
+
@raw.raw(is_on, &block)
|
61
|
+
end
|
62
|
+
end # Mode
|
63
|
+
end # Prompt
|
64
|
+
end # TTY
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Mode
|
6
|
+
# A class responsible for toggling echo.
|
7
|
+
class Echo
|
8
|
+
# Turn echo on
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
def on
|
12
|
+
%x{stty echo} if TTY::Platform.unix?
|
13
|
+
end
|
14
|
+
|
15
|
+
# Turn echo off
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def off
|
19
|
+
%x{stty -echo} if TTY::Platform.unix?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Wrap code block inside echo
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def echo(is_on=true, &block)
|
26
|
+
value = nil
|
27
|
+
begin
|
28
|
+
off unless is_on
|
29
|
+
value = block.call if block_given?
|
30
|
+
on
|
31
|
+
return value
|
32
|
+
rescue NoMethodError, Interrupt
|
33
|
+
on
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end # Echo
|
38
|
+
end # Mode
|
39
|
+
end # Prompt
|
40
|
+
end # TTY
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module TTY
|
4
|
+
class Prompt
|
5
|
+
class Mode
|
6
|
+
# A class responsible for toggling raw mode.
|
7
|
+
class Raw
|
8
|
+
# Turn raw mode on
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
def on
|
12
|
+
%x{stty raw} if TTY::Platform.unix?
|
13
|
+
end
|
14
|
+
|
15
|
+
# Turn raw mode off
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def off
|
19
|
+
%x{stty -raw} if TTY::Platform.unix?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Wrap code block inside raw mode
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
def raw(is_on=true, &block)
|
26
|
+
value = nil
|
27
|
+
begin
|
28
|
+
on if is_on
|
29
|
+
value = block.call if block_given?
|
30
|
+
off
|
31
|
+
return value
|
32
|
+
rescue NoMethodError, Interrupt
|
33
|
+
off
|
34
|
+
exit
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end # Raw
|
38
|
+
end # Mode
|
39
|
+
end # Prompt
|
40
|
+
end # TTY
|
@@ -0,0 +1,338 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'tty/prompt/question/modifier'
|
4
|
+
require 'tty/prompt/question/validation'
|
5
|
+
|
6
|
+
require 'tty/prompt/response_delegation'
|
7
|
+
|
8
|
+
module TTY
|
9
|
+
# A class responsible for shell prompt interactions.
|
10
|
+
class Prompt
|
11
|
+
# A class representing a command line question
|
12
|
+
class Question
|
13
|
+
include ResponseDelegation
|
14
|
+
|
15
|
+
PREFIX = ' + '
|
16
|
+
MULTIPLE_PREFIX = ' * '
|
17
|
+
ERROR_PREFIX = ' ERROR:'
|
18
|
+
|
19
|
+
# Store statement.
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
attr_accessor :statement
|
23
|
+
|
24
|
+
# Store default value.
|
25
|
+
#
|
26
|
+
# @api private
|
27
|
+
attr_reader :default_value
|
28
|
+
|
29
|
+
attr_reader :required
|
30
|
+
private :required
|
31
|
+
|
32
|
+
attr_reader :validation
|
33
|
+
|
34
|
+
# Controls character processing of the answer
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
attr_reader :modifier
|
38
|
+
|
39
|
+
# Returns valid answers
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
attr_reader :valid_values
|
43
|
+
|
44
|
+
attr_reader :error
|
45
|
+
|
46
|
+
# Returns character mode
|
47
|
+
#
|
48
|
+
# @api public
|
49
|
+
attr_reader :character
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
attr_reader :shell
|
53
|
+
private :shell
|
54
|
+
|
55
|
+
# Initialize a Question
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
def initialize(shell, options = {})
|
59
|
+
@shell = shell || Prompt.new
|
60
|
+
@required = options.fetch(:required) { false }
|
61
|
+
@echo = options.fetch(:echo) { true }
|
62
|
+
@raw = options.fetch(:raw) { false }
|
63
|
+
@mask = options.fetch(:mask) { false }
|
64
|
+
@character = options.fetch(:character) { false }
|
65
|
+
@in = options.fetch(:in) { false }
|
66
|
+
@modifier = Modifier.new options.fetch(:modifier) { [] }
|
67
|
+
@valid_values = options.fetch(:valid) { [] }
|
68
|
+
@validation = Validation.new options.fetch(:validation) { nil }
|
69
|
+
@default_value = nil
|
70
|
+
@error = false
|
71
|
+
@converter = Necromancer.new
|
72
|
+
end
|
73
|
+
|
74
|
+
# Set a new prompt
|
75
|
+
#
|
76
|
+
# @param [String] message
|
77
|
+
#
|
78
|
+
# @return [self]
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
def prompt(message)
|
82
|
+
self.statement = message
|
83
|
+
shell.say shell.prefix + statement
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Set default value.
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
def default(value)
|
91
|
+
return self if value == ''
|
92
|
+
@default_value = value
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
# Check if default value is set
|
97
|
+
#
|
98
|
+
# @return [Boolean]
|
99
|
+
#
|
100
|
+
# @api public
|
101
|
+
def default?
|
102
|
+
!!@default_value
|
103
|
+
end
|
104
|
+
|
105
|
+
# Ensure that passed argument is present if required option
|
106
|
+
#
|
107
|
+
# @return [Question]
|
108
|
+
#
|
109
|
+
# @api public
|
110
|
+
def argument(value)
|
111
|
+
case value
|
112
|
+
when :required
|
113
|
+
@required = true
|
114
|
+
when :optional
|
115
|
+
@required = false
|
116
|
+
end
|
117
|
+
self
|
118
|
+
end
|
119
|
+
|
120
|
+
# Check if required argument present.
|
121
|
+
#
|
122
|
+
# @return [Boolean]
|
123
|
+
#
|
124
|
+
# @api private
|
125
|
+
def required?
|
126
|
+
required
|
127
|
+
end
|
128
|
+
|
129
|
+
# Set validation rule for an argument
|
130
|
+
#
|
131
|
+
# @param [Object] value
|
132
|
+
#
|
133
|
+
# @return [Question]
|
134
|
+
#
|
135
|
+
# @api public
|
136
|
+
def validate(value = nil, &block)
|
137
|
+
@validation = Validation.new(value || block)
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
# Set expected values
|
142
|
+
#
|
143
|
+
# @param [Array] values
|
144
|
+
#
|
145
|
+
# @return [self]
|
146
|
+
#
|
147
|
+
# @api public
|
148
|
+
def valid(values)
|
149
|
+
@valid_values = values
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
# Reset question object.
|
154
|
+
#
|
155
|
+
# @api public
|
156
|
+
def clean
|
157
|
+
@statement = nil
|
158
|
+
@default_value = nil
|
159
|
+
@required = false
|
160
|
+
@modifier = nil
|
161
|
+
end
|
162
|
+
|
163
|
+
# Modify string according to the rule given.
|
164
|
+
#
|
165
|
+
# @param [Symbol] rule
|
166
|
+
#
|
167
|
+
# @api public
|
168
|
+
def modify(*rules)
|
169
|
+
@modifier = Modifier.new(*rules)
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
# Setup behaviour when error(s) occur
|
174
|
+
#
|
175
|
+
# @api public
|
176
|
+
def on_error(action = nil)
|
177
|
+
@error = action
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
# Check if error behaviour is set
|
182
|
+
#
|
183
|
+
# @api public
|
184
|
+
def error?
|
185
|
+
!!@error
|
186
|
+
end
|
187
|
+
|
188
|
+
# Turn terminal echo on or off. This is used to secure the display so
|
189
|
+
# that the entered characters are not echoed back to the screen.
|
190
|
+
#
|
191
|
+
# @api public
|
192
|
+
def echo(value = nil)
|
193
|
+
return @echo if value.nil?
|
194
|
+
@echo = value
|
195
|
+
self
|
196
|
+
end
|
197
|
+
|
198
|
+
# Chec if echo is set
|
199
|
+
#
|
200
|
+
# @api public
|
201
|
+
def echo?
|
202
|
+
!!@echo
|
203
|
+
end
|
204
|
+
|
205
|
+
# Turn raw mode on or off. This enables character-based input.
|
206
|
+
#
|
207
|
+
# @api public
|
208
|
+
def raw(value = nil)
|
209
|
+
return @raw if value.nil?
|
210
|
+
@raw = value
|
211
|
+
self
|
212
|
+
end
|
213
|
+
|
214
|
+
# Check if raw mode is set
|
215
|
+
#
|
216
|
+
# @api public
|
217
|
+
def raw?
|
218
|
+
!!@raw
|
219
|
+
end
|
220
|
+
|
221
|
+
# Set character for masking the STDIN input
|
222
|
+
#
|
223
|
+
# @param [String] character
|
224
|
+
#
|
225
|
+
# @return [self]
|
226
|
+
#
|
227
|
+
# @api public
|
228
|
+
def mask(char = nil)
|
229
|
+
return @mask if char.nil?
|
230
|
+
@mask = char
|
231
|
+
self
|
232
|
+
end
|
233
|
+
|
234
|
+
# Check if character mask is set
|
235
|
+
#
|
236
|
+
# @return [Boolean]
|
237
|
+
#
|
238
|
+
# @api public
|
239
|
+
def mask?
|
240
|
+
!!@mask
|
241
|
+
end
|
242
|
+
|
243
|
+
# Set if the input is character based or not
|
244
|
+
#
|
245
|
+
# @param [Boolean] value
|
246
|
+
#
|
247
|
+
# @return [self]
|
248
|
+
#
|
249
|
+
# @api public
|
250
|
+
def char(value = nil)
|
251
|
+
return @character if value.nil?
|
252
|
+
@character = value
|
253
|
+
self
|
254
|
+
end
|
255
|
+
|
256
|
+
# Check if character intput is set
|
257
|
+
#
|
258
|
+
# @return [Boolean]
|
259
|
+
#
|
260
|
+
# @api public
|
261
|
+
def character?
|
262
|
+
!!@character
|
263
|
+
end
|
264
|
+
|
265
|
+
# Set expect range of values
|
266
|
+
#
|
267
|
+
# @param [String] value
|
268
|
+
#
|
269
|
+
# @api public
|
270
|
+
def in(value = nil)
|
271
|
+
return @in if value.nil?
|
272
|
+
@in = @converter.convert(value).to(:range, strict: true)
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
# Check if range is set
|
277
|
+
#
|
278
|
+
# @return [Boolean]
|
279
|
+
#
|
280
|
+
# @api public
|
281
|
+
def in?
|
282
|
+
!!@in
|
283
|
+
end
|
284
|
+
|
285
|
+
# Check if response matches all the requirements set by the question
|
286
|
+
#
|
287
|
+
# @param [Object] value
|
288
|
+
#
|
289
|
+
# @return [Object]
|
290
|
+
#
|
291
|
+
# @api private
|
292
|
+
def evaluate_response(value)
|
293
|
+
return default_value if !value && default?
|
294
|
+
check_required(value)
|
295
|
+
return if value.nil?
|
296
|
+
|
297
|
+
check_valid(value) unless valid_values.empty?
|
298
|
+
within?(value)
|
299
|
+
validation.valid_value?(value)
|
300
|
+
modifier.apply_to(value)
|
301
|
+
end
|
302
|
+
|
303
|
+
private
|
304
|
+
|
305
|
+
# Check if value is present
|
306
|
+
#
|
307
|
+
# @api private
|
308
|
+
def check_required(value)
|
309
|
+
if required? && !default? && value.nil?
|
310
|
+
fail ArgumentRequired, 'No value provided for required'
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Check if value matches any of the expected values
|
315
|
+
#
|
316
|
+
# @api private
|
317
|
+
def check_valid(value)
|
318
|
+
if Array(value).all? { |val| valid_values.include? val }
|
319
|
+
return value
|
320
|
+
else
|
321
|
+
fail InvalidArgument, "Valid values are: #{valid_values.join(', ')}"
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
# Check if value is within expected range
|
326
|
+
#
|
327
|
+
# @api private
|
328
|
+
def within?(value)
|
329
|
+
if in? && value
|
330
|
+
if @in.include?(value)
|
331
|
+
else
|
332
|
+
fail InvalidArgument, "Value #{value} is not included in the range #{@in}"
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end # Question
|
337
|
+
end # Prompt
|
338
|
+
end # TTY
|