tty-prompt 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +9 -6
  3. data/CHANGELOG.md +40 -3
  4. data/Gemfile +0 -1
  5. data/README.md +246 -65
  6. data/examples/ask.rb +7 -0
  7. data/examples/echo.rb +7 -0
  8. data/examples/in.rb +7 -0
  9. data/examples/mask.rb +9 -0
  10. data/examples/multi_select.rb +8 -0
  11. data/examples/select.rb +8 -0
  12. data/examples/validation.rb +9 -0
  13. data/examples/yes_no.rb +7 -0
  14. data/lib/tty-prompt.rb +6 -4
  15. data/lib/tty/prompt.rb +100 -25
  16. data/lib/tty/prompt/choice.rb +1 -1
  17. data/lib/tty/prompt/converter_dsl.rb +19 -0
  18. data/lib/tty/prompt/converter_registry.rb +56 -0
  19. data/lib/tty/prompt/converters.rb +77 -0
  20. data/lib/tty/prompt/evaluator.rb +29 -0
  21. data/lib/tty/prompt/list.rb +38 -36
  22. data/lib/tty/prompt/mask_question.rb +85 -0
  23. data/lib/tty/prompt/multi_list.rb +21 -32
  24. data/lib/tty/prompt/question.rb +184 -162
  25. data/lib/tty/prompt/question/checks.rb +85 -0
  26. data/lib/tty/prompt/question/modifier.rb +4 -5
  27. data/lib/tty/prompt/question/validation.rb +29 -35
  28. data/lib/tty/prompt/reader.rb +98 -52
  29. data/lib/tty/prompt/reader/codes.rb +63 -0
  30. data/lib/tty/prompt/reader/key_event.rb +67 -0
  31. data/lib/tty/prompt/reader/mode.rb +66 -0
  32. data/lib/tty/prompt/reader/mode/echo.rb +43 -0
  33. data/lib/tty/prompt/reader/mode/raw.rb +43 -0
  34. data/lib/tty/prompt/result.rb +42 -0
  35. data/lib/tty/prompt/statement.rb +9 -14
  36. data/lib/tty/prompt/suggestion.rb +4 -2
  37. data/lib/tty/prompt/symbols.rb +13 -0
  38. data/lib/tty/prompt/test.rb +3 -2
  39. data/lib/tty/prompt/utils.rb +1 -1
  40. data/lib/tty/prompt/version.rb +1 -1
  41. data/spec/unit/ask_spec.rb +31 -48
  42. data/spec/unit/choice/eql_spec.rb +0 -2
  43. data/spec/unit/choice/from_spec.rb +0 -2
  44. data/spec/unit/choices/add_spec.rb +0 -2
  45. data/spec/unit/choices/each_spec.rb +0 -2
  46. data/spec/unit/choices/new_spec.rb +0 -2
  47. data/spec/unit/choices/pluck_spec.rb +0 -2
  48. data/spec/unit/converters/convert_bool_spec.rb +58 -0
  49. data/spec/unit/{response/read_char_spec.rb → converters/convert_char_spec.rb} +2 -4
  50. data/spec/unit/converters/convert_custom_spec.rb +14 -0
  51. data/spec/unit/converters/convert_date_spec.rb +25 -0
  52. data/spec/unit/converters/convert_file_spec.rb +14 -0
  53. data/spec/unit/{response/read_number_spec.rb → converters/convert_number_spec.rb} +5 -7
  54. data/spec/unit/converters/convert_path_spec.rb +15 -0
  55. data/spec/unit/{response/read_range_spec.rb → converters/convert_range_spec.rb} +3 -5
  56. data/spec/unit/converters/convert_regex_spec.rb +12 -0
  57. data/spec/unit/converters/convert_string_spec.rb +21 -0
  58. data/spec/unit/distance/distance_spec.rb +0 -2
  59. data/spec/unit/error_spec.rb +0 -6
  60. data/spec/unit/evaluator_spec.rb +67 -0
  61. data/spec/unit/keypress_spec.rb +19 -0
  62. data/spec/unit/mask_spec.rb +95 -0
  63. data/spec/unit/multi_select_spec.rb +36 -24
  64. data/spec/unit/multiline_spec.rb +19 -0
  65. data/spec/unit/new_spec.rb +18 -0
  66. data/spec/unit/ok_spec.rb +10 -0
  67. data/spec/unit/question/default_spec.rb +17 -4
  68. data/spec/unit/question/echo_spec.rb +31 -0
  69. data/spec/unit/question/in_spec.rb +48 -16
  70. data/spec/unit/question/initialize_spec.rb +2 -9
  71. data/spec/unit/question/modifier/apply_to_spec.rb +9 -16
  72. data/spec/unit/question/modifier/letter_case_spec.rb +0 -2
  73. data/spec/unit/question/modifier/whitespace_spec.rb +12 -20
  74. data/spec/unit/question/modify_spec.rb +3 -7
  75. data/spec/unit/question/required_spec.rb +20 -14
  76. data/spec/unit/question/validate_spec.rb +20 -19
  77. data/spec/unit/question/validation/call_spec.rb +15 -6
  78. data/spec/unit/question/validation/coerce_spec.rb +17 -11
  79. data/spec/unit/reader/publish_keypress_event_spec.rb +81 -0
  80. data/spec/unit/reader/read_keypress_spec.rb +22 -0
  81. data/spec/unit/reader/read_line_spec.rb +31 -0
  82. data/spec/unit/reader/read_multiline_spec.rb +37 -0
  83. data/spec/unit/result_spec.rb +40 -0
  84. data/spec/unit/say_spec.rb +18 -23
  85. data/spec/unit/select_spec.rb +37 -32
  86. data/spec/unit/statement/initialize_spec.rb +4 -4
  87. data/spec/unit/suggest_spec.rb +0 -2
  88. data/spec/unit/warn_spec.rb +0 -5
  89. data/spec/unit/yes_no_spec.rb +70 -0
  90. data/tty-prompt.gemspec +7 -4
  91. metadata +123 -40
  92. data/lib/tty/prompt/codes.rb +0 -32
  93. data/lib/tty/prompt/cursor.rb +0 -131
  94. data/lib/tty/prompt/error.rb +0 -26
  95. data/lib/tty/prompt/mode.rb +0 -64
  96. data/lib/tty/prompt/mode/echo.rb +0 -41
  97. data/lib/tty/prompt/mode/raw.rb +0 -41
  98. data/lib/tty/prompt/response.rb +0 -247
  99. data/lib/tty/prompt/response_delegation.rb +0 -42
  100. data/spec/unit/cursor/new_spec.rb +0 -74
  101. data/spec/unit/question/character_spec.rb +0 -13
  102. data/spec/unit/reader/getc_spec.rb +0 -42
  103. data/spec/unit/response/read_bool_spec.rb +0 -58
  104. data/spec/unit/response/read_date_spec.rb +0 -16
  105. data/spec/unit/response/read_email_spec.rb +0 -45
  106. data/spec/unit/response/read_multiple_spec.rb +0 -21
  107. data/spec/unit/response/read_spec.rb +0 -69
  108. data/spec/unit/response/read_string_spec.rb +0 -14
data/examples/ask.rb ADDED
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ prompt.ask('What is your name?', default: ENV['USER'])
data/examples/echo.rb ADDED
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ puts prompt.ask('Password?', echo: false)
data/examples/in.rb ADDED
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ prompt.ask('How do you like it on scale 1 - 10?', in: '1-10')
data/examples/mask.rb ADDED
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ prompt.mask('What is your secret?') do |q|
8
+ q.validate /[a-z]{5,8}/
9
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ drinks = %w(vodka beer wine whisky bourbon)
8
+ prompt.multi_select('Choose your favourite drink?', drinks)
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ warriors = %w(Scorpion Kano Jax)
8
+ prompt.select('Choose your destiny?', warriors)
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ prompt.ask('What is your username?') do |q|
8
+ q.validate(/^[^\.]+\.[^\.]+/)
9
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new
6
+
7
+ prompt.yes?('Do you like Ruby?')
data/lib/tty-prompt.rb CHANGED
@@ -2,25 +2,27 @@
2
2
 
3
3
  require 'necromancer'
4
4
  require 'pastel'
5
+ require 'tty-cursor'
5
6
  require 'tty-platform'
6
7
 
7
8
  require 'tty/prompt'
8
9
  require 'tty/prompt/choice'
9
10
  require 'tty/prompt/choices'
10
- require 'tty/prompt/codes'
11
- require 'tty/prompt/cursor'
11
+ require 'tty/prompt/evaluator'
12
12
  require 'tty/prompt/list'
13
13
  require 'tty/prompt/multi_list'
14
- require 'tty/prompt/mode'
15
14
  require 'tty/prompt/question'
15
+ require 'tty/prompt/mask_question'
16
16
  require 'tty/prompt/reader'
17
- require 'tty/prompt/response'
18
17
  require 'tty/prompt/statement'
19
18
  require 'tty/prompt/suggestion'
19
+ require 'tty/prompt/symbols'
20
20
  require 'tty/prompt/test'
21
21
  require 'tty/prompt/utils'
22
22
  require 'tty/prompt/version'
23
23
 
24
+ # A collection of small libraries for building CLI apps,
25
+ # each following unix philosophy of focused task
24
26
  module TTY
25
27
  PromptConfigurationError = Class.new(StandardError)
26
28
  end
data/lib/tty/prompt.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'forwardable'
4
+
3
5
  module TTY
6
+ # A main entry for asking prompt questions.
4
7
  class Prompt
8
+ extend Forwardable
9
+
5
10
  # Raised when the passed in validation argument is of wrong type
6
11
  class ValidationCoercion < TypeError; end
7
12
 
@@ -20,35 +25,54 @@ module TTY
20
25
  # @api private
21
26
  attr_reader :output
22
27
 
28
+ attr_reader :reader
29
+
30
+ attr_reader :cursor
31
+
23
32
  # Prompt prefix
24
33
  #
25
34
  # @api private
26
35
  attr_reader :prefix
27
36
 
37
+ def_delegators :@pastel, :decorate
38
+
39
+ def_delegators :@cursor, :clear_lines, :clear_line,
40
+ :show, :hide
41
+
42
+ def_delegators :@reader, :read_line, :read_keypress,
43
+ :read_multiline, :on, :subscribe
44
+
45
+ def_delegators :@output, :print, :puts, :flush
46
+
28
47
  # Initialize a Prompt
29
48
  #
30
49
  # @api public
31
- def initialize(input = stdin, output = stdout, options = {})
32
- @input = input
33
- @output = output
50
+ def initialize(*args)
51
+ options = Utils.extract_options!(args)
52
+ @input = options.fetch(:input) { $stdin }
53
+ @output = options.fetch(:output) { $stdout }
34
54
  @prefix = options.fetch(:prefix) { '' }
55
+
56
+ @cursor = TTY::Cursor
57
+ @pastel = Pastel.new
58
+ @reader = Reader.new(@input, @output)
35
59
  end
36
60
 
37
61
  # Ask a question.
38
62
  #
39
63
  # @example
40
- # shell = TTY::Prompt.new
41
- # shell.ask("What is your name?")
64
+ # propmt = TTY::Prompt.new
65
+ # prompt.ask("What is your name?")
42
66
  #
43
- # @param [String] statement
44
- # string question to be asked
67
+ # @param [String] message
68
+ # the question to be asked
45
69
  #
46
- # @yieldparam [TTY::Question] question
70
+ # @yieldparam [TTY::Prompt::Question] question
47
71
  # further configure the question
48
72
  #
49
73
  # @yield [question]
50
74
  #
51
- # @return [TTY::Question]
75
+ # @return [TTY::Prompt::Question]
52
76
  #
53
77
  # @api public
54
78
  def ask(message, *args, &block)
@@ -58,6 +82,46 @@ module TTY
58
82
  question.call(message, &block)
59
83
  end
60
84
 
85
+ # Ask a question with a keypress answer
86
+ #
87
+ # @see #ask
88
+ #
89
+ # @api public
90
+ def keypress(message, *args, &block)
91
+ options = Utils.extract_options!(args)
92
+ options.merge!(read: :keypress)
93
+ args << options
94
+ ask(message, *args, &block)
95
+ end
96
+
97
+ # Ask a question with a multiline answer
98
+ #
99
+ # @see @ask
100
+ #
101
+ # @api public
102
+ def multiline(message, *args, &block)
103
+ options = Utils.extract_options!(args)
104
+ options.merge!(read: :multiline)
105
+ args << options
106
+ ask(message, *args, &block)
107
+ end
108
+
109
+ # Ask masked question
110
+ #
111
+ # @example
112
+ # propmt = TTY::Prompt.new
113
+ # prompt.mask("What is your secret?")
114
+ #
115
+ # @return [TTY::Prompt::MaskQuestion]
116
+ #
117
+ # @api public
118
+ def mask(message, *args, &block)
119
+ options = Utils.extract_options!(args)
120
+
121
+ question = MaskQuestion.new(self, options)
122
+ question.call(message, &block)
123
+ end
124
+
61
125
  # Ask a question with a list of options
62
126
  #
63
127
  # @example
@@ -125,28 +189,39 @@ module TTY
125
189
  # A shortcut method to ask the user positive question and return
126
190
  # true for 'yes' reply, false for 'no'.
127
191
  #
192
+ # @example
193
+ # prompt = TTY::Prompt.new
194
+ # prompt.yes?('Are you human? (Y/n)') # => true
195
+ #
128
196
  # @return [Boolean]
129
197
  #
130
198
  # @api public
131
- def yes?(statement, *args, &block)
132
- ask(statement, {read: :bool}, &block)
199
+ def yes?(question, *args, &block)
200
+ options = Utils.extract_options!(args)
201
+ options.merge!(convert: :bool)
202
+ args << options
203
+ ask(question, *args, &block)
133
204
  end
134
205
 
135
206
  # A shortcut method to ask the user negative question and return
136
207
  # true for 'no' reply.
137
208
  #
209
+ # @example
210
+ # prompt = TTY::Prompt.new
211
+ # prompt.no?('Are you alien? (y/N)') # => true
212
+ #
138
213
  # @return [Boolean]
139
214
  #
140
215
  # @api public
141
- def no?(statement, *args, &block)
142
- !yes?(statement, *args, &block)
216
+ def no?(question, *args, &block)
217
+ !yes?(question, *args, &block)
143
218
  end
144
219
 
145
220
  # Print statement out. If the supplied message ends with a space or
146
221
  # tab character, a new line will not be appended.
147
222
  #
148
223
  # @example
149
- # say("Simple things.")
224
+ # say("Simple things.", color: :red)
150
225
  #
151
226
  # @param [String] message
152
227
  #
@@ -154,25 +229,25 @@ module TTY
154
229
  #
155
230
  # @api public
156
231
  def say(message = '', options = {})
157
- message = message.to_str
232
+ message = message.to_s
158
233
  return unless message.length > 0
159
234
 
160
235
  statement = Statement.new(self, options)
161
- statement.declare message
236
+ statement.call(message)
162
237
  end
163
238
 
164
239
  # Print statement(s) out in red green.
165
240
  #
166
241
  # @example
167
- # shell.confirm "Are you sure?"
168
- # shell.confirm "All is fine!", "This is fine too."
242
+ # prompt.ok "Are you sure?"
243
+ # prompt.ok "All is fine!", "This is fine too."
169
244
  #
170
245
  # @param [Array] messages
171
246
  #
172
247
  # @return [Array] messages
173
248
  #
174
249
  # @api public
175
- def confirm(*args)
250
+ def ok(*args)
176
251
  options = Utils.extract_options!(args)
177
252
  args.each { |message| say message, options.merge(color: :green) }
178
253
  end
@@ -180,8 +255,8 @@ module TTY
180
255
  # Print statement(s) out in yellow color.
181
256
  #
182
257
  # @example
183
- # shell.warn "This action can have dire consequences"
184
- # shell.warn "Carefull young apprentice", "This is potentially dangerous"
258
+ # prompt.warn "This action can have dire consequences"
259
+ # prompt.warn "Carefull young apprentice", "This is potentially dangerous"
185
260
  #
186
261
  # @param [Array] messages
187
262
  #
@@ -196,8 +271,8 @@ module TTY
196
271
  # Print statement(s) out in red color.
197
272
  #
198
273
  # @example
199
- # shell.error "Shutting down all systems!"
200
- # shell.error "Nothing is fine!", "All is broken!"
274
+ # prompt.error "Shutting down all systems!"
275
+ # prompt.error "Nothing is fine!", "All is broken!"
201
276
  #
202
277
  # @param [Array] messages
203
278
  #
@@ -213,7 +288,7 @@ module TTY
213
288
  # matches to suggest an unambigous string
214
289
  #
215
290
  # @example
216
- # shell.suggest('sta', ['status', 'stage', 'commit', 'branch'])
291
+ # prompt.suggest('sta', ['status', 'stage', 'commit', 'branch'])
217
292
  # # => "status, stage"
218
293
  #
219
294
  # @param [String] message
@@ -236,7 +311,7 @@ module TTY
236
311
  say(suggestion.suggest(message, possibilities))
237
312
  end
238
313
 
239
- # Check if outputing to shell
314
+ # Check if outputing to terminal
240
315
  #
241
316
  # @return [Boolean]
242
317
  #
@@ -36,7 +36,7 @@ module TTY
36
36
  def self.from(val)
37
37
  case val
38
38
  when Choice
39
- return val
39
+ val
40
40
  when String, Symbol
41
41
  new(val, val)
42
42
  when Array
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty/prompt/converter_registry'
4
+
5
+ module TTY
6
+ class Prompt
7
+ module ConverterDSL
8
+ def self.extended(base)
9
+ attr_reader :converter_registry
10
+
11
+ base.instance_variable_set(:@converter_registry, ConverterRegistry.new)
12
+ end
13
+
14
+ def converter(name, &block)
15
+ converter_registry.register(name, &block)
16
+ end
17
+ end # ConverterDSL
18
+ end # Prompt
19
+ end # TTY
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class ConverterRegistry
6
+ def initialize
7
+ @_registry = {}
8
+ end
9
+
10
+ # Register converter
11
+ #
12
+ # @api public
13
+ def register(key, contents = nil, &block)
14
+ if block_given?
15
+ item = block
16
+ else
17
+ item = contents
18
+ end
19
+
20
+ if key?(key)
21
+ fail ArgumentError, "Converter for #{key.inspect} already registered"
22
+ else
23
+ @_registry[key] = item
24
+ end
25
+ self
26
+ end
27
+
28
+ # Check if converter is registered
29
+ #
30
+ # @return [Boolean]
31
+ #
32
+ # @api public
33
+ def key?(key)
34
+ @_registry.key?(key)
35
+ end
36
+
37
+ # Execute converter
38
+ #
39
+ # @api public
40
+ def call(key, input)
41
+ if key.respond_to?(:call)
42
+ converter = key
43
+ else
44
+ converter = @_registry.fetch(key) do
45
+ fail ArgumentError, "#{key.inspect} is not registered"
46
+ end
47
+ end
48
+ converter.call(input)
49
+ end
50
+
51
+ def inspect
52
+ @_registry.inspect
53
+ end
54
+ end # ConverterRegistry
55
+ end # Prompt
56
+ end # TTY
@@ -0,0 +1,77 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pathname'
4
+ require 'necromancer'
5
+ require 'tty/prompt/converter_dsl'
6
+
7
+ module TTY
8
+ class Prompt
9
+ module Converters
10
+ extend ConverterDSL
11
+
12
+ def self.included(base)
13
+ base.class_eval do
14
+ def converter_registry
15
+ Converters.converter_registry
16
+ end
17
+ end
18
+ end
19
+
20
+ converter(:bool) do |input|
21
+ converter = Necromancer.new
22
+ converter.convert(input).to(:boolean, strict: true)
23
+ end
24
+
25
+ converter(:string) do |input|
26
+ String(input).chomp
27
+ end
28
+
29
+ converter(:symbol) do |input|
30
+ input.to_sym
31
+ end
32
+
33
+ converter(:date) do |input|
34
+ converter = Necromancer.new
35
+ converter.convert(input).to(:date)
36
+ end
37
+
38
+ converter(:datetime) do |input|
39
+ converter = Necromancer.new
40
+ converter.convert(input).to(:datetime)
41
+ end
42
+
43
+ converter(:int) do |input|
44
+ converter = Necromancer.new
45
+ converter.convert(input).to(:integer)
46
+ end
47
+
48
+ converter(:float) do |input|
49
+ converter = Necromancer.new
50
+ converter.convert(input).to(:float)
51
+ end
52
+
53
+ converter(:range) do |input|
54
+ converter = Necromancer.new
55
+ converter.convert(input).to(:range, strict: true)
56
+ end
57
+
58
+ converter(:regexp) do |input|
59
+ Regexp.new(input)
60
+ end
61
+
62
+ converter(:file) do |input|
63
+ directory = File.expand_path(File.dirname($0))
64
+ File.open(File.join(directory, input))
65
+ end
66
+
67
+ converter(:path) do |input|
68
+ directory = File.expand_path(File.dirname($0))
69
+ Pathname.new(File.join(directory, input))
70
+ end
71
+
72
+ converter(:char) do |input|
73
+ String(input).chars.to_a[0]
74
+ end
75
+ end # Converters
76
+ end # Prompt
77
+ end # TTY