tty-prompt 0.2.0 → 0.3.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 +4 -4
- data/.travis.yml +9 -6
- data/CHANGELOG.md +40 -3
- data/Gemfile +0 -1
- data/README.md +246 -65
- data/examples/ask.rb +7 -0
- data/examples/echo.rb +7 -0
- data/examples/in.rb +7 -0
- data/examples/mask.rb +9 -0
- data/examples/multi_select.rb +8 -0
- data/examples/select.rb +8 -0
- data/examples/validation.rb +9 -0
- data/examples/yes_no.rb +7 -0
- data/lib/tty-prompt.rb +6 -4
- data/lib/tty/prompt.rb +100 -25
- data/lib/tty/prompt/choice.rb +1 -1
- data/lib/tty/prompt/converter_dsl.rb +19 -0
- data/lib/tty/prompt/converter_registry.rb +56 -0
- data/lib/tty/prompt/converters.rb +77 -0
- data/lib/tty/prompt/evaluator.rb +29 -0
- data/lib/tty/prompt/list.rb +38 -36
- data/lib/tty/prompt/mask_question.rb +85 -0
- data/lib/tty/prompt/multi_list.rb +21 -32
- data/lib/tty/prompt/question.rb +184 -162
- data/lib/tty/prompt/question/checks.rb +85 -0
- data/lib/tty/prompt/question/modifier.rb +4 -5
- data/lib/tty/prompt/question/validation.rb +29 -35
- data/lib/tty/prompt/reader.rb +98 -52
- data/lib/tty/prompt/reader/codes.rb +63 -0
- data/lib/tty/prompt/reader/key_event.rb +67 -0
- data/lib/tty/prompt/reader/mode.rb +66 -0
- data/lib/tty/prompt/reader/mode/echo.rb +43 -0
- data/lib/tty/prompt/reader/mode/raw.rb +43 -0
- data/lib/tty/prompt/result.rb +42 -0
- data/lib/tty/prompt/statement.rb +9 -14
- data/lib/tty/prompt/suggestion.rb +4 -2
- data/lib/tty/prompt/symbols.rb +13 -0
- data/lib/tty/prompt/test.rb +3 -2
- data/lib/tty/prompt/utils.rb +1 -1
- data/lib/tty/prompt/version.rb +1 -1
- data/spec/unit/ask_spec.rb +31 -48
- data/spec/unit/choice/eql_spec.rb +0 -2
- data/spec/unit/choice/from_spec.rb +0 -2
- data/spec/unit/choices/add_spec.rb +0 -2
- data/spec/unit/choices/each_spec.rb +0 -2
- data/spec/unit/choices/new_spec.rb +0 -2
- data/spec/unit/choices/pluck_spec.rb +0 -2
- data/spec/unit/converters/convert_bool_spec.rb +58 -0
- data/spec/unit/{response/read_char_spec.rb → converters/convert_char_spec.rb} +2 -4
- data/spec/unit/converters/convert_custom_spec.rb +14 -0
- data/spec/unit/converters/convert_date_spec.rb +25 -0
- data/spec/unit/converters/convert_file_spec.rb +14 -0
- data/spec/unit/{response/read_number_spec.rb → converters/convert_number_spec.rb} +5 -7
- data/spec/unit/converters/convert_path_spec.rb +15 -0
- data/spec/unit/{response/read_range_spec.rb → converters/convert_range_spec.rb} +3 -5
- data/spec/unit/converters/convert_regex_spec.rb +12 -0
- data/spec/unit/converters/convert_string_spec.rb +21 -0
- data/spec/unit/distance/distance_spec.rb +0 -2
- data/spec/unit/error_spec.rb +0 -6
- data/spec/unit/evaluator_spec.rb +67 -0
- data/spec/unit/keypress_spec.rb +19 -0
- data/spec/unit/mask_spec.rb +95 -0
- data/spec/unit/multi_select_spec.rb +36 -24
- data/spec/unit/multiline_spec.rb +19 -0
- data/spec/unit/new_spec.rb +18 -0
- data/spec/unit/ok_spec.rb +10 -0
- data/spec/unit/question/default_spec.rb +17 -4
- data/spec/unit/question/echo_spec.rb +31 -0
- data/spec/unit/question/in_spec.rb +48 -16
- data/spec/unit/question/initialize_spec.rb +2 -9
- data/spec/unit/question/modifier/apply_to_spec.rb +9 -16
- data/spec/unit/question/modifier/letter_case_spec.rb +0 -2
- data/spec/unit/question/modifier/whitespace_spec.rb +12 -20
- data/spec/unit/question/modify_spec.rb +3 -7
- data/spec/unit/question/required_spec.rb +20 -14
- data/spec/unit/question/validate_spec.rb +20 -19
- data/spec/unit/question/validation/call_spec.rb +15 -6
- data/spec/unit/question/validation/coerce_spec.rb +17 -11
- data/spec/unit/reader/publish_keypress_event_spec.rb +81 -0
- data/spec/unit/reader/read_keypress_spec.rb +22 -0
- data/spec/unit/reader/read_line_spec.rb +31 -0
- data/spec/unit/reader/read_multiline_spec.rb +37 -0
- data/spec/unit/result_spec.rb +40 -0
- data/spec/unit/say_spec.rb +18 -23
- data/spec/unit/select_spec.rb +37 -32
- data/spec/unit/statement/initialize_spec.rb +4 -4
- data/spec/unit/suggest_spec.rb +0 -2
- data/spec/unit/warn_spec.rb +0 -5
- data/spec/unit/yes_no_spec.rb +70 -0
- data/tty-prompt.gemspec +7 -4
- metadata +123 -40
- data/lib/tty/prompt/codes.rb +0 -32
- data/lib/tty/prompt/cursor.rb +0 -131
- data/lib/tty/prompt/error.rb +0 -26
- data/lib/tty/prompt/mode.rb +0 -64
- data/lib/tty/prompt/mode/echo.rb +0 -41
- data/lib/tty/prompt/mode/raw.rb +0 -41
- data/lib/tty/prompt/response.rb +0 -247
- data/lib/tty/prompt/response_delegation.rb +0 -42
- data/spec/unit/cursor/new_spec.rb +0 -74
- data/spec/unit/question/character_spec.rb +0 -13
- data/spec/unit/reader/getc_spec.rb +0 -42
- data/spec/unit/response/read_bool_spec.rb +0 -58
- data/spec/unit/response/read_date_spec.rb +0 -16
- data/spec/unit/response/read_email_spec.rb +0 -45
- data/spec/unit/response/read_multiple_spec.rb +0 -21
- data/spec/unit/response/read_spec.rb +0 -69
- data/spec/unit/response/read_string_spec.rb +0 -14
data/examples/ask.rb
ADDED
data/examples/echo.rb
ADDED
data/examples/in.rb
ADDED
data/examples/mask.rb
ADDED
data/examples/select.rb
ADDED
data/examples/yes_no.rb
ADDED
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/
|
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(
|
32
|
-
|
33
|
-
@
|
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
|
-
#
|
41
|
-
#
|
64
|
+
# propmt = TTY::Prompt.new
|
65
|
+
# prompt.ask("What is your name?")
|
42
66
|
#
|
43
|
-
# @param [String]
|
44
|
-
#
|
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?(
|
132
|
-
|
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?(
|
142
|
-
!yes?(
|
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.
|
232
|
+
message = message.to_s
|
158
233
|
return unless message.length > 0
|
159
234
|
|
160
235
|
statement = Statement.new(self, options)
|
161
|
-
statement.
|
236
|
+
statement.call(message)
|
162
237
|
end
|
163
238
|
|
164
239
|
# Print statement(s) out in red green.
|
165
240
|
#
|
166
241
|
# @example
|
167
|
-
#
|
168
|
-
#
|
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
|
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
|
-
#
|
184
|
-
#
|
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
|
-
#
|
200
|
-
#
|
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
|
-
#
|
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
|
314
|
+
# Check if outputing to terminal
|
240
315
|
#
|
241
316
|
# @return [Boolean]
|
242
317
|
#
|
data/lib/tty/prompt/choice.rb
CHANGED
@@ -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
|