tty-prompt 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+
3
+ require 'tty-prompt'
4
+ require 'benchmark/ips'
5
+ require 'stringio'
6
+
7
+ input = ::StringIO.new
8
+ output = ::StringIO.new
9
+ prompt = TTY::Prompt.new(input: input, output: output)
10
+
11
+ Benchmark.ips do |r|
12
+
13
+ r.report("Ruby #puts") do
14
+ output.puts "What is your name?"
15
+ end
16
+
17
+ r.report("TTY::Prompt #ask") do
18
+ prompt.ask("What is your name?")
19
+ end
20
+ end
21
+
22
+ # Calculating -------------------------------------
23
+ # Ruby #puts 34601 i/100ms
24
+ # TTY::Prompt #ask 12 i/100ms
25
+ # -------------------------------------------------
26
+ # Ruby #puts 758640.5 (±14.9%) i/s - 3736908 in 5.028562s
27
+ # TTY::Prompt #ask 63.1 (±7.9%) i/s - 324 in 5.176857s
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ prompt = TTY::Prompt.new(prefix: '[?] ')
6
+
7
+ result = prompt.collect do
8
+ key(:name).ask('Name?')
9
+
10
+ key(:age).ask('Age?', convert: :int)
11
+
12
+ key(:address) do
13
+ key(:street).ask('Street?', required: true)
14
+ key(:city).ask('City?')
15
+ key(:zip).ask('Zip?', validate: /\A\d{3}\Z/)
16
+ end
17
+ end
18
+
19
+ puts result
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tty-prompt'
4
+
5
+ choices = [{
6
+ key: 'y',
7
+ name: 'overwrite this file',
8
+ value: :yes
9
+ }, {
10
+ key: 'n',
11
+ name: 'do not overwrite this file',
12
+ value: :no
13
+ }, {
14
+ key: 'a',
15
+ name: 'overwrite this file and all later files',
16
+ value: :all
17
+ }, {
18
+ key: 'd',
19
+ name: 'show diff',
20
+ value: :diff
21
+ }, {
22
+ key: 'q',
23
+ name: 'quit; do not overwrite this file ',
24
+ value: :quit
25
+ }]
26
+
27
+ prompt = TTY::Prompt.new
28
+
29
+ prompt.expand('Overwrite Gemfile?', choices, default: 3)
@@ -9,15 +9,18 @@ require 'tty/prompt'
9
9
  require 'tty/prompt/choice'
10
10
  require 'tty/prompt/choices'
11
11
  require 'tty/prompt/enum_list'
12
+ require 'tty/prompt/expander'
12
13
  require 'tty/prompt/evaluator'
13
14
  require 'tty/prompt/list'
14
15
  require 'tty/prompt/multi_list'
15
16
  require 'tty/prompt/question'
16
17
  require 'tty/prompt/mask_question'
18
+ require 'tty/prompt/confirm_question'
17
19
  require 'tty/prompt/reader'
18
20
  require 'tty/prompt/slider'
19
21
  require 'tty/prompt/statement'
20
22
  require 'tty/prompt/suggestion'
23
+ require 'tty/prompt/answers_collector'
21
24
  require 'tty/prompt/symbols'
22
25
  require 'tty/prompt/test'
23
26
  require 'tty/prompt/utils'
@@ -31,10 +31,20 @@ module TTY
31
31
 
32
32
  # Prompt prefix
33
33
  #
34
+ # @example
35
+ # prompt = TTY::Prompt.new(prefix: [?])
36
+ #
37
+ # @return [String]
38
+ #
34
39
  # @api private
35
40
  attr_reader :prefix
36
41
 
37
- def_delegators :@pastel, :decorate
42
+ # Theme colors
43
+ #
44
+ # @api private
45
+ attr_reader :active_color, :help_color, :error_color
46
+
47
+ def_delegators :@pastel, :decorate, :strip
38
48
 
39
49
  def_delegators :@cursor, :clear_lines, :clear_line,
40
50
  :show, :hide
@@ -60,6 +70,9 @@ module TTY
60
70
  @input = options.fetch(:input) { $stdin }
61
71
  @output = options.fetch(:output) { $stdout }
62
72
  @prefix = options.fetch(:prefix) { '' }
73
+ @active_color = options.fetch(:active_color) { :green }
74
+ @help_color = options.fetch(:help_color) { :bright_black }
75
+ @error_color = options.fetch(:error_color) { :red }
63
76
 
64
77
  @cursor = TTY::Cursor
65
78
  @pastel = Pastel.new
@@ -223,16 +236,62 @@ module TTY
223
236
  #
224
237
  # @example
225
238
  # prompt = TTY::Prompt.new
226
- # prompt.yes?('Are you human? (Y/n)') # => true
239
+ # prompt.yes?('Are you human?')
240
+ # # => Are you human? (Y/n)
227
241
  #
228
242
  # @return [Boolean]
229
243
  #
230
244
  # @api public
231
- def yes?(question, *args, &block)
232
- options = Utils.extract_options!(args)
233
- options.merge!(convert: :bool)
234
- args << options
235
- ask(question, *args, &block)
245
+ def yes?(message, *args, &block)
246
+ defaults = { default: true }
247
+ options = Utils.extract_options!(args)
248
+ options.merge!(defaults.reject { |k, _| options.key?(k) })
249
+
250
+ question = ConfirmQuestion.new(self, options)
251
+ question.call(message, &block)
252
+ end
253
+
254
+ # A shortcut method to ask the user negative question and return
255
+ # true for 'no' reply.
256
+ #
257
+ # @example
258
+ # prompt = TTY::Prompt.new
259
+ # prompt.no?('Are you alien?') # => true
260
+ # # => Are you human? (y/N)
261
+ #
262
+ # @return [Boolean]
263
+ #
264
+ # @api public
265
+ def no?(message, *args, &block)
266
+ defaults = { default: false, type: :no }
267
+ options = Utils.extract_options!(args)
268
+ options.merge!(defaults.reject { |k, _| options.key?(k) })
269
+
270
+ question = ConfirmQuestion.new(self, options)
271
+ !question.call(message, &block)
272
+ end
273
+
274
+ # Expand available options
275
+ #
276
+ # @example
277
+ # prompt = TTY::Prompt.new
278
+ # choices = [{
279
+ # key: 'Y',
280
+ # name: 'Overwrite',
281
+ # value: :yes
282
+ # }, {
283
+ # key: 'n',
284
+ # name: 'Skip',
285
+ # value: :no
286
+ # }]
287
+ # prompt.expand('Overwirte Gemfile?', choices)
288
+ #
289
+ # @return [Object]
290
+ # the user specified value
291
+ #
292
+ # @api public
293
+ def expand(message, *args, &block)
294
+ invoke_select(Expander, message, *args, &block)
236
295
  end
237
296
 
238
297
  # Ask a question with a range slider
@@ -253,20 +312,6 @@ module TTY
253
312
  slider.call(question, &block)
254
313
  end
255
314
 
256
- # A shortcut method to ask the user negative question and return
257
- # true for 'no' reply.
258
- #
259
- # @example
260
- # prompt = TTY::Prompt.new
261
- # prompt.no?('Are you alien? (y/N)') # => true
262
- #
263
- # @return [Boolean]
264
- #
265
- # @api public
266
- def no?(question, *args, &block)
267
- !yes?(question, *args, &block)
268
- end
269
-
270
315
  # Print statement out. If the supplied message ends with a space or
271
316
  # tab character, a new line will not be appended.
272
317
  #
@@ -361,6 +406,22 @@ module TTY
361
406
  say(suggestion.suggest(message, possibilities))
362
407
  end
363
408
 
409
+ # Gathers more than one aswer
410
+ #
411
+ # @example
412
+ # prompt.collect do
413
+ # key(:name).ask('Name?')
414
+ # end
415
+ #
416
+ # @return [Hash]
417
+ # the collection of answers
418
+ #
419
+ # @api public
420
+ def collect(options = {}, &block)
421
+ collector = AnswersCollector.new(self, options)
422
+ collector.call(&block)
423
+ end
424
+
364
425
  # Check if outputing to terminal
365
426
  #
366
427
  # @return [Boolean]
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class AnswersCollector
6
+ # Initialize answer collector
7
+ #
8
+ # @api public
9
+ def initialize(prompt, options = {})
10
+ @prompt = prompt
11
+ @answers = options.fetch(:answers) { {} }
12
+ end
13
+
14
+ # Start gathering answers
15
+ #
16
+ # @return [Hash]
17
+ # the collection of all answers
18
+ #
19
+ # @api public
20
+ def call(&block)
21
+ instance_eval(&block)
22
+ @answers
23
+ end
24
+
25
+ # Create answer entry
26
+ #
27
+ # @example
28
+ # key(:name).ask('Name?')
29
+ #
30
+ # @api public
31
+ def key(name, &block)
32
+ @name = name
33
+ if block
34
+ answer = create_collector.(&block)
35
+ add_answer(answer)
36
+ end
37
+ self
38
+ end
39
+
40
+ # @api public
41
+ def create_collector
42
+ self.class.new(@prompt)
43
+ end
44
+
45
+ # @api public
46
+ def add_answer(answer)
47
+ @answers[@name] = answer
48
+ end
49
+
50
+ private
51
+
52
+ # @api private
53
+ def method_missing(method, *args, &block)
54
+ answer = @prompt.public_send(method, *args, &block)
55
+ add_answer(answer)
56
+ end
57
+ end # AnswersCollector
58
+ end # Prompt
59
+ end # TTY
@@ -11,12 +11,15 @@ module TTY
11
11
  # @api public
12
12
  attr_reader :name
13
13
 
14
+ attr_reader :key
15
+
14
16
  # Create a Choice instance
15
17
  #
16
18
  # @api public
17
- def initialize(name, value)
19
+ def initialize(name, value, key = nil)
18
20
  @name = name
19
21
  @value = value
22
+ @key = key
20
23
  end
21
24
 
22
25
  # Create choice from value
@@ -42,7 +45,11 @@ module TTY
42
45
  when Array
43
46
  new("#{val.first}", val.last)
44
47
  when Hash
45
- new("#{val.keys.first}", val.values.first)
48
+ if val.key?(:name)
49
+ new("#{val[:name]}", val[:value], val[:key])
50
+ else
51
+ new("#{val.keys.first}", val.values.first)
52
+ end
46
53
  else
47
54
  raise ArgumentError, "#{val} cannot be coerced into Choice"
48
55
  end
@@ -85,7 +85,23 @@ module TTY
85
85
  #
86
86
  # @api public
87
87
  def pluck(name)
88
- find { |choice| choice.name == name }
88
+ map { |choice| choice.public_send(name) }
89
+ end
90
+
91
+ # Find a matching choice
92
+ #
93
+ # @exmaple
94
+ # choices.find_by(:name, 'small')
95
+ #
96
+ # @param [Symbol] attr
97
+ # the attribute name
98
+ # @param [Object] value
99
+ #
100
+ # @return [Choice]
101
+ #
102
+ # @api public
103
+ def find_by(attr, value)
104
+ find { |choice| choice.public_send(attr) == value }
89
105
  end
90
106
  end # Choices
91
107
  end # Prompt
@@ -0,0 +1,140 @@
1
+ # encoding: utf-8
2
+
3
+ module TTY
4
+ class Prompt
5
+ class ConfirmQuestion < Question
6
+ # Create confirmation question
7
+ #
8
+ # @param [Hash] options
9
+ # @option options [String] :suffix
10
+ # @option options [String] :positive
11
+ # @option options [String] :negative
12
+ #
13
+ # @api public
14
+ def initialize(prompt, options = {})
15
+ super
16
+
17
+ @suffix = options.fetch(:suffix) { UndefinedSetting }
18
+ @positive = options.fetch(:positive) { UndefinedSetting }
19
+ @negative = options.fetch(:negative) { UndefinedSetting }
20
+ @type = options.fetch(:type) { :yes }
21
+ end
22
+
23
+ def positive?
24
+ @positive != UndefinedSetting
25
+ end
26
+
27
+ def negative?
28
+ @negative != UndefinedSetting
29
+ end
30
+
31
+ def suffix?
32
+ @suffix != UndefinedSetting
33
+ end
34
+
35
+ # Set question suffix
36
+ #
37
+ # @api public
38
+ def suffix(value)
39
+ @suffix = value
40
+ end
41
+
42
+ # Set value for matching positive choice
43
+ #
44
+ # @api public
45
+ def positive(value)
46
+ @positive = value
47
+ end
48
+
49
+ # Set value for matching negative choice
50
+ #
51
+ # @api public
52
+ def negative(value)
53
+ @negative = value
54
+ end
55
+
56
+ def call(message, &block)
57
+ return if Utils.blank?(message)
58
+ @message = message
59
+ block.call(self) if block
60
+ setup_defaults
61
+ render
62
+ end
63
+
64
+ # Render confirmation question
65
+ #
66
+ # @api private
67
+ def render_question
68
+ header = "#{@prefix}#{message} "
69
+
70
+ if !@done
71
+ header += @prompt.decorate("(#{@suffix})", @help_color) + ' '
72
+ else
73
+ answer = convert_result(@input)
74
+ label = answer ? @positive : @negative
75
+ header += @prompt.decorate(label, @active_color)
76
+ end
77
+ @prompt.print(header)
78
+ @prompt.print("\n") if @done
79
+ end
80
+
81
+ protected
82
+
83
+ # @api private
84
+ def is?(type)
85
+ @type == type
86
+ end
87
+
88
+ # @api private
89
+ def setup_defaults
90
+ return if suffix? && positive?
91
+
92
+ if suffix? && !positive?
93
+ parts = @suffix.split('/')
94
+ @positive = parts[0]
95
+ @negative = parts[1]
96
+ @convert = conversion
97
+ elsif !suffix? && positive?
98
+ @suffix = create_suffix
99
+ @convert = conversion
100
+ else
101
+ create_default_labels
102
+ @convert = :bool
103
+ end
104
+ end
105
+
106
+ def create_default_labels
107
+ if is?(:yes)
108
+ @suffix = default? ? 'Y/n' : 'y/N'
109
+ @positive = default? ? 'Yes' : 'yes'
110
+ @negative = default? ? 'no' : 'No'
111
+ else
112
+ @suffix = default? ? 'y/N' : 'Y/n'
113
+ @positive = default? ? 'Yes' : 'yes'
114
+ @negative = default? ? 'No' : 'no'
115
+ end
116
+ end
117
+
118
+ # @api private
119
+ def create_suffix
120
+ result = ''
121
+ if is?(:yes)
122
+ result << "#{default? ? @positive.capitalize : @positive.downcase}"
123
+ result << '/'
124
+ result << "#{default? ? @negative.downcase : @negative.capitalize}"
125
+ else
126
+ result << "#{default? ? @positive.downcase : @positive.capitalize}"
127
+ result << '/'
128
+ result << "#{default? ? @negative.capitalize : @negative.downcase}"
129
+ end
130
+ end
131
+
132
+ # Create custom conversion
133
+ #
134
+ # @api private
135
+ def conversion
136
+ proc { |input| !input.match(/^#{@positive}|#{@positive[0]}$/i).nil? }
137
+ end
138
+ end # ConfirmQuestion
139
+ end # Prompt
140
+ end # TTY