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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +24 -0
  6. data/Gemfile +16 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +199 -0
  9. data/Rakefile +8 -0
  10. data/lib/tty-prompt.rb +15 -0
  11. data/lib/tty/prompt.rb +206 -0
  12. data/lib/tty/prompt/distance.rb +49 -0
  13. data/lib/tty/prompt/error.rb +26 -0
  14. data/lib/tty/prompt/history.rb +16 -0
  15. data/lib/tty/prompt/mode.rb +64 -0
  16. data/lib/tty/prompt/mode/echo.rb +40 -0
  17. data/lib/tty/prompt/mode/raw.rb +40 -0
  18. data/lib/tty/prompt/question.rb +338 -0
  19. data/lib/tty/prompt/question/modifier.rb +93 -0
  20. data/lib/tty/prompt/question/validation.rb +92 -0
  21. data/lib/tty/prompt/reader.rb +113 -0
  22. data/lib/tty/prompt/response.rb +252 -0
  23. data/lib/tty/prompt/response_delegation.rb +41 -0
  24. data/lib/tty/prompt/statement.rb +60 -0
  25. data/lib/tty/prompt/suggestion.rb +113 -0
  26. data/lib/tty/prompt/utils.rb +16 -0
  27. data/lib/tty/prompt/version.rb +7 -0
  28. data/spec/spec_helper.rb +45 -0
  29. data/spec/unit/ask_spec.rb +77 -0
  30. data/spec/unit/distance/distance_spec.rb +75 -0
  31. data/spec/unit/error_spec.rb +30 -0
  32. data/spec/unit/question/argument_spec.rb +30 -0
  33. data/spec/unit/question/character_spec.rb +24 -0
  34. data/spec/unit/question/default_spec.rb +25 -0
  35. data/spec/unit/question/in_spec.rb +23 -0
  36. data/spec/unit/question/initialize_spec.rb +24 -0
  37. data/spec/unit/question/modifier/apply_to_spec.rb +31 -0
  38. data/spec/unit/question/modifier/letter_case_spec.rb +22 -0
  39. data/spec/unit/question/modifier/whitespace_spec.rb +33 -0
  40. data/spec/unit/question/modify_spec.rb +44 -0
  41. data/spec/unit/question/valid_spec.rb +46 -0
  42. data/spec/unit/question/validate_spec.rb +30 -0
  43. data/spec/unit/question/validation/coerce_spec.rb +24 -0
  44. data/spec/unit/question/validation/valid_value_spec.rb +22 -0
  45. data/spec/unit/reader/getc_spec.rb +42 -0
  46. data/spec/unit/response/read_bool_spec.rb +47 -0
  47. data/spec/unit/response/read_char_spec.rb +16 -0
  48. data/spec/unit/response/read_date_spec.rb +20 -0
  49. data/spec/unit/response/read_email_spec.rb +42 -0
  50. data/spec/unit/response/read_multiple_spec.rb +23 -0
  51. data/spec/unit/response/read_number_spec.rb +28 -0
  52. data/spec/unit/response/read_range_spec.rb +26 -0
  53. data/spec/unit/response/read_spec.rb +68 -0
  54. data/spec/unit/response/read_string_spec.rb +19 -0
  55. data/spec/unit/say_spec.rb +66 -0
  56. data/spec/unit/statement/initialize_spec.rb +19 -0
  57. data/spec/unit/suggest_spec.rb +33 -0
  58. data/spec/unit/warn_spec.rb +30 -0
  59. data/tasks/console.rake +10 -0
  60. data/tasks/coverage.rake +11 -0
  61. data/tasks/spec.rake +29 -0
  62. data/tty-prompt.gemspec +26 -0
  63. 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