tty-prompt 0.18.0 → 0.22.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/README.md +549 -248
  4. data/lib/tty-prompt.rb +1 -2
  5. data/lib/tty/prompt.rb +187 -143
  6. data/lib/tty/prompt/answers_collector.rb +5 -5
  7. data/lib/tty/prompt/{enum_paginator.rb → block_paginator.rb} +20 -19
  8. data/lib/tty/prompt/choice.rb +5 -7
  9. data/lib/tty/prompt/choices.rb +29 -11
  10. data/lib/tty/prompt/confirm_question.rb +38 -16
  11. data/lib/tty/prompt/const.rb +17 -0
  12. data/lib/tty/prompt/converter_dsl.rb +6 -7
  13. data/lib/tty/prompt/converter_registry.rb +31 -26
  14. data/lib/tty/prompt/converters.rb +139 -32
  15. data/lib/tty/prompt/enum_list.rb +57 -27
  16. data/lib/tty/prompt/errors.rb +31 -0
  17. data/lib/tty/prompt/evaluator.rb +1 -1
  18. data/lib/tty/prompt/expander.rb +39 -13
  19. data/lib/tty/prompt/keypress.rb +31 -36
  20. data/lib/tty/prompt/list.rb +175 -65
  21. data/lib/tty/prompt/mask_question.rb +4 -5
  22. data/lib/tty/prompt/multi_list.rb +124 -33
  23. data/lib/tty/prompt/multiline.rb +7 -6
  24. data/lib/tty/prompt/paginator.rb +38 -26
  25. data/lib/tty/prompt/question.rb +83 -34
  26. data/lib/tty/prompt/question/checks.rb +18 -0
  27. data/lib/tty/prompt/question/validation.rb +3 -3
  28. data/lib/tty/prompt/selected_choices.rb +76 -0
  29. data/lib/tty/prompt/slider.rb +83 -16
  30. data/lib/tty/prompt/statement.rb +3 -3
  31. data/lib/tty/prompt/suggestion.rb +6 -6
  32. data/lib/tty/prompt/symbols.rb +58 -34
  33. data/lib/tty/prompt/test.rb +36 -0
  34. data/lib/tty/prompt/timer.rb +75 -0
  35. data/lib/tty/prompt/utils.rb +1 -3
  36. data/lib/tty/prompt/version.rb +1 -1
  37. metadata +29 -227
  38. data/Rakefile +0 -8
  39. data/examples/ask.rb +0 -7
  40. data/examples/ask_valid.rb +0 -12
  41. data/examples/collect.rb +0 -21
  42. data/examples/echo.rb +0 -11
  43. data/examples/enum_select.rb +0 -7
  44. data/examples/enum_select_disabled.rb +0 -16
  45. data/examples/enum_select_paged.rb +0 -9
  46. data/examples/enum_select_wrapped.rb +0 -15
  47. data/examples/expand.rb +0 -29
  48. data/examples/in.rb +0 -9
  49. data/examples/inputs.rb +0 -10
  50. data/examples/key_events.rb +0 -15
  51. data/examples/keypress.rb +0 -9
  52. data/examples/mask.rb +0 -13
  53. data/examples/multi_select.rb +0 -8
  54. data/examples/multi_select_disabled.rb +0 -17
  55. data/examples/multi_select_paged.rb +0 -9
  56. data/examples/multi_select_wrapped.rb +0 -15
  57. data/examples/multiline.rb +0 -9
  58. data/examples/pause.rb +0 -9
  59. data/examples/select.rb +0 -20
  60. data/examples/select_disabled.rb +0 -18
  61. data/examples/select_enum.rb +0 -8
  62. data/examples/select_filtered.rb +0 -11
  63. data/examples/select_paginated.rb +0 -11
  64. data/examples/select_wrapped.rb +0 -15
  65. data/examples/slider.rb +0 -6
  66. data/examples/validation.rb +0 -9
  67. data/examples/yes_no.rb +0 -7
  68. data/lib/tty/prompt/messages.rb +0 -49
  69. data/lib/tty/prompt/timeout.rb +0 -78
  70. data/lib/tty/test_prompt.rb +0 -20
  71. data/spec/spec_helper.rb +0 -45
  72. data/spec/unit/ask_spec.rb +0 -132
  73. data/spec/unit/choice/eql_spec.rb +0 -22
  74. data/spec/unit/choice/from_spec.rb +0 -96
  75. data/spec/unit/choices/add_spec.rb +0 -12
  76. data/spec/unit/choices/each_spec.rb +0 -13
  77. data/spec/unit/choices/find_by_spec.rb +0 -10
  78. data/spec/unit/choices/new_spec.rb +0 -10
  79. data/spec/unit/choices/pluck_spec.rb +0 -9
  80. data/spec/unit/collect_spec.rb +0 -96
  81. data/spec/unit/converters/convert_bool_spec.rb +0 -58
  82. data/spec/unit/converters/convert_char_spec.rb +0 -11
  83. data/spec/unit/converters/convert_custom_spec.rb +0 -14
  84. data/spec/unit/converters/convert_date_spec.rb +0 -34
  85. data/spec/unit/converters/convert_file_spec.rb +0 -18
  86. data/spec/unit/converters/convert_number_spec.rb +0 -39
  87. data/spec/unit/converters/convert_path_spec.rb +0 -15
  88. data/spec/unit/converters/convert_range_spec.rb +0 -22
  89. data/spec/unit/converters/convert_regex_spec.rb +0 -12
  90. data/spec/unit/converters/convert_string_spec.rb +0 -21
  91. data/spec/unit/converters/on_error_spec.rb +0 -9
  92. data/spec/unit/distance/distance_spec.rb +0 -73
  93. data/spec/unit/enum_paginator_spec.rb +0 -75
  94. data/spec/unit/enum_select_spec.rb +0 -446
  95. data/spec/unit/error_spec.rb +0 -20
  96. data/spec/unit/evaluator_spec.rb +0 -67
  97. data/spec/unit/expand_spec.rb +0 -198
  98. data/spec/unit/keypress_spec.rb +0 -72
  99. data/spec/unit/mask_spec.rb +0 -132
  100. data/spec/unit/multi_select_spec.rb +0 -495
  101. data/spec/unit/multiline_spec.rb +0 -77
  102. data/spec/unit/new_spec.rb +0 -20
  103. data/spec/unit/ok_spec.rb +0 -10
  104. data/spec/unit/paginator_spec.rb +0 -73
  105. data/spec/unit/question/checks_spec.rb +0 -97
  106. data/spec/unit/question/default_spec.rb +0 -31
  107. data/spec/unit/question/echo_spec.rb +0 -38
  108. data/spec/unit/question/in_spec.rb +0 -115
  109. data/spec/unit/question/initialize_spec.rb +0 -12
  110. data/spec/unit/question/modifier/apply_to_spec.rb +0 -24
  111. data/spec/unit/question/modifier/letter_case_spec.rb +0 -41
  112. data/spec/unit/question/modifier/whitespace_spec.rb +0 -51
  113. data/spec/unit/question/modify_spec.rb +0 -41
  114. data/spec/unit/question/required_spec.rb +0 -92
  115. data/spec/unit/question/validate_spec.rb +0 -115
  116. data/spec/unit/question/validation/call_spec.rb +0 -31
  117. data/spec/unit/question/validation/coerce_spec.rb +0 -30
  118. data/spec/unit/result_spec.rb +0 -40
  119. data/spec/unit/say_spec.rb +0 -67
  120. data/spec/unit/select_spec.rb +0 -643
  121. data/spec/unit/slider_spec.rb +0 -100
  122. data/spec/unit/statement/initialize_spec.rb +0 -15
  123. data/spec/unit/subscribe_spec.rb +0 -22
  124. data/spec/unit/suggest_spec.rb +0 -28
  125. data/spec/unit/warn_spec.rb +0 -21
  126. data/spec/unit/yes_no_spec.rb +0 -251
  127. data/tasks/console.rake +0 -11
  128. data/tasks/coverage.rake +0 -11
  129. data/tasks/spec.rake +0 -29
  130. data/tty-prompt.gemspec +0 -33
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'question'
4
- require_relative 'symbols'
3
+ require_relative "question"
5
4
 
6
5
  module TTY
7
6
  class Prompt
@@ -12,9 +11,9 @@ module TTY
12
11
  # @option options [String] :mask
13
12
  #
14
13
  # @api public
15
- def initialize(prompt, options = {})
14
+ def initialize(prompt, **options)
16
15
  super
17
- @mask = options.fetch(:mask) { Symbols.symbols[:dot] }
16
+ @mask = options.fetch(:mask) { @prompt.symbols[:dot] }
18
17
  @done_masked = false
19
18
  @failure = false
20
19
  end
@@ -76,7 +75,7 @@ module TTY
76
75
  def read_input(question)
77
76
  @done_masked = false
78
77
  @failure = false
79
- @input = ''
78
+ @input = ""
80
79
  @prompt.print(question)
81
80
  until @done_masked
82
81
  @prompt.read_keypress
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'list'
3
+ require_relative "list"
4
+ require_relative "selected_choices"
4
5
 
5
6
  module TTY
6
7
  class Prompt
@@ -9,34 +10,80 @@ module TTY
9
10
  #
10
11
  # @api private
11
12
  class MultiList < List
12
- HELP = '(Use arrow%s keys, press Space to select and Enter to finish%s)'.freeze
13
-
14
13
  # Create instance of TTY::Prompt::MultiList menu.
15
14
  #
16
15
  # @param [Prompt] :prompt
17
16
  # @param [Hash] options
18
17
  #
19
18
  # @api public
20
- def initialize(prompt, options)
19
+ def initialize(prompt, **options)
21
20
  super
22
- @selected = []
23
- @help = options[:help]
24
- @default = Array(options[:default])
21
+ @selected = SelectedChoices.new
22
+ @help = options[:help]
25
23
  @echo = options.fetch(:echo, true)
24
+ @min = options[:min]
25
+ @max = options[:max]
26
+ end
27
+
28
+ # Set a minimum number of choices
29
+ #
30
+ # @api public
31
+ def min(value)
32
+ @min = value
26
33
  end
27
34
 
35
+ # Set a maximum number of choices
36
+ #
37
+ # @api public
38
+ def max(value)
39
+ @max = value
40
+ end
41
+
42
+ # Callback fired when enter/return key is pressed
43
+ #
44
+ # @api private
45
+ def keyenter(*)
46
+ valid = true
47
+ valid = @min <= @selected.size if @min
48
+ valid = @selected.size <= @max if @max
49
+
50
+ super if valid
51
+ end
52
+ alias keyreturn keyenter
53
+
28
54
  # Callback fired when space key is pressed
29
55
  #
30
56
  # @api private
31
57
  def keyspace(*)
32
58
  active_choice = choices[@active - 1]
33
59
  if @selected.include?(active_choice)
34
- @selected.delete(active_choice)
60
+ @selected.delete_at(@active - 1)
35
61
  else
36
- @selected << active_choice
62
+ return if @max && @selected.size >= @max
63
+ @selected.insert(@active - 1, active_choice)
37
64
  end
38
65
  end
39
66
 
67
+ # Selects all choices when Ctrl+A is pressed
68
+ #
69
+ # @api private
70
+ def keyctrl_a(*)
71
+ return if @max && @max < choices.size
72
+ @selected = SelectedChoices.new(choices.enabled, choices.enabled_indexes)
73
+ end
74
+
75
+ # Revert currently selected choices when Ctrl+I is pressed
76
+ #
77
+ # @api private
78
+ def keyctrl_r(*)
79
+ return if @max && @max < choices.size
80
+ indexes = choices.each_with_index.reduce([]) do |acc, (choice, idx)|
81
+ acc << idx if !choice.disabled? && !@selected.include?(choice)
82
+ acc
83
+ end
84
+ @selected = SelectedChoices.new(choices.enabled - @selected.to_a, indexes)
85
+ end
86
+
40
87
  private
41
88
 
42
89
  # Setup default options and active selection
@@ -45,11 +92,14 @@ module TTY
45
92
  def setup_defaults
46
93
  validate_defaults
47
94
  # At this stage, @choices matches all the visible choices.
48
- @selected = @choices.values_at(*@default.map { |d| d - 1 })
49
- @active = @default.last unless @selected.empty?
50
- if choices[@active - 1] && choices[@active - 1].disabled?
51
- raise ConfigurationError,
52
- "active choice '#{choices[@active - 1]}' matches disabled item"
95
+ default_indexes = @default.map { |d| d - 1 }
96
+ @selected = SelectedChoices.new(@choices.values_at(*default_indexes),
97
+ default_indexes)
98
+
99
+ if !@default.empty?
100
+ @active = @default.last
101
+ else
102
+ @active = @choices.index { |choice| !choice.disabled? } + 1
53
103
  end
54
104
  end
55
105
 
@@ -59,23 +109,63 @@ module TTY
59
109
  #
60
110
  # @api private
61
111
  def selected_names
62
- @selected.map(&:name).join(', ')
112
+ @selected.map(&:name).join(", ")
113
+ end
114
+
115
+ # Header part showing the minimum/maximum number of choices
116
+ #
117
+ # @return [String]
118
+ #
119
+ # @api private
120
+ def minmax_help
121
+ help = []
122
+ help << "min. #{@min}" if @min
123
+ help << "max. #{@max}" if @max
124
+ "(%s) " % [help.join(", ")]
125
+ end
126
+
127
+ # Build a default help text
128
+ #
129
+ # @return [String]
130
+ #
131
+ # @api private
132
+ def default_help
133
+ str = []
134
+ str << "(Press "
135
+ str << "#{arrows_help} arrow"
136
+ str << " or 1-#{choices.size} number" if enumerate?
137
+ str << " to move, Space"
138
+ str << "/Ctrl+A|R" if @max.nil?
139
+ str << " to select"
140
+ str << " (all|rev)" if @max.nil?
141
+ str << (filterable? ? "," : " and")
142
+ str << " Enter to finish"
143
+ str << " and letters to filter" if filterable?
144
+ str << ")"
145
+ str.join
63
146
  end
64
147
 
65
148
  # Render initial help text and then currently selected choices
66
149
  #
67
150
  # @api private
68
151
  def render_header
69
- instructions = @prompt.decorate(help, :bright_black)
152
+ instructions = @prompt.decorate(help, @help_color)
153
+ minmax_suffix = @min || @max ? minmax_help : ""
154
+ print_selected = @selected.size.nonzero? && @echo
155
+
70
156
  if @done && @echo
71
157
  @prompt.decorate(selected_names, @active_color)
72
- elsif @selected.size.nonzero? && @echo
73
- help_suffix = filterable? && @filter.any? ? " #{filter_help}" : ""
74
- selected_names + (@first_render ? " #{instructions}" : help_suffix)
75
- elsif @first_render
76
- instructions
158
+ elsif (@first_render && (help_start? || help_always?)) ||
159
+ (help_always? && !@filter.any? && !@done)
160
+ minmax_suffix +
161
+ (print_selected ? "#{selected_names} " : "") +
162
+ instructions
77
163
  elsif filterable? && @filter.any?
78
- filter_help
164
+ minmax_suffix +
165
+ (print_selected ? "#{selected_names} " : "") +
166
+ @prompt.decorate(filter_help, @help_color)
167
+ else
168
+ minmax_suffix + (print_selected ? selected_names : "")
79
169
  end
80
170
  end
81
171
 
@@ -96,21 +186,22 @@ module TTY
96
186
  def render_menu
97
187
  output = []
98
188
 
99
- @paginator.paginate(choices, @active, @per_page) do |choice, index|
100
- num = enumerate? ? (index + 1).to_s + @enum + ' ' : ''
101
- indicator = (index + 1 == @active) ? @marker : ' '
102
- indicator += ' '
189
+ sync_paginators if @paging_changed
190
+ paginator.paginate(choices, @active, @per_page) do |choice, index|
191
+ num = enumerate? ? (index + 1).to_s + @enum + " " : ""
192
+ indicator = (index + 1 == @active) ? @symbols[:marker] : " "
193
+ indicator += " "
103
194
  message = if @selected.include?(choice) && !choice.disabled?
104
- selected = @prompt.decorate(symbols[:radio_on], @active_color)
105
- selected + ' ' + num + choice.name
195
+ selected = @prompt.decorate(@symbols[:radio_on], @active_color)
196
+ "#{selected} #{num}#{choice.name}"
106
197
  elsif choice.disabled?
107
- @prompt.decorate(symbols[:cross], :red) +
108
- ' ' + num + choice.name + ' ' + choice.disabled.to_s
198
+ @prompt.decorate(@symbols[:cross], :red) +
199
+ " #{num}#{choice.name} #{choice.disabled}"
109
200
  else
110
- symbols[:radio_off] + ' ' + num + choice.name
201
+ "#{@symbols[:radio_off]} #{num}#{choice.name}"
111
202
  end
112
- max_index = paginated? ? @paginator.max_index : choices.size - 1
113
- newline = (index == max_index) ? '' : "\n"
203
+ end_index = paginated? ? paginator.end_index : choices.size - 1
204
+ newline = (index == end_index) ? "" : "\n"
114
205
  output << indicator + message + newline
115
206
  end
116
207
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'question'
4
- require_relative 'symbols'
3
+ require_relative "question"
4
+ require_relative "symbols"
5
5
 
6
6
  module TTY
7
7
  class Prompt
@@ -9,9 +9,9 @@ module TTY
9
9
  #
10
10
  # @api private
11
11
  class Multiline < Question
12
- HELP = '(Press CTRL-D or CTRL-Z to finish)'.freeze
12
+ HELP = "(Press Ctrl+D or Ctrl+Z to finish)".freeze
13
13
 
14
- def initialize(prompt, options = {})
14
+ def initialize(prompt, **options)
15
15
  super
16
16
  @help = options[:help] || self.class::HELP
17
17
  @first_render = true
@@ -55,8 +55,9 @@ module TTY
55
55
  @prompt.print(question)
56
56
  @lines = read_input
57
57
  @input = "#{@lines.first.strip} ..." unless @lines.first.to_s.empty?
58
- if Utils.blank?(@input)
59
- @input = default? ? default : nil
58
+ if Utils.blank?(@input) && default?
59
+ @input = default
60
+ @lines = default
60
61
  end
61
62
  @evaluator.(@lines)
62
63
  end
@@ -5,23 +5,33 @@ module TTY
5
5
  class Paginator
6
6
  DEFAULT_PAGE_SIZE = 6
7
7
 
8
+ # The 0-based index of the first item on this page
9
+ attr_accessor :start_index
10
+
11
+ # The 0-based index of the last item on this page
12
+ attr_reader :end_index
13
+
14
+ # The 0-based index of the active item on this page
15
+ attr_reader :current_index
16
+
17
+ # The 0-based index of the previously active item on this page
18
+ attr_reader :last_index
19
+
8
20
  # Create a Paginator
9
21
  #
10
22
  # @api private
11
- def initialize(options = {})
23
+ def initialize(**options)
12
24
  @last_index = Array(options[:default]).flatten.first || 0
13
25
  @per_page = options[:per_page]
14
- @lower_index = Array(options[:default]).flatten.first
26
+ @start_index = Array(options[:default]).flatten.first
15
27
  end
16
28
 
17
- # Maximum index for current pagination
29
+ # Reset current page indexes
18
30
  #
19
- # @return [Integer]
20
- #
21
- # @api public
22
- def max_index
23
- raise ArgumentError, 'no max index' unless @per_page
24
- @lower_index + @per_page - 1
31
+ # @api private
32
+ def reset!
33
+ @start_index = nil
34
+ @end_index = nil
25
35
  end
26
36
 
27
37
  # Check if page size is valid
@@ -30,7 +40,7 @@ module TTY
30
40
  #
31
41
  # @api private
32
42
  def check_page_size!
33
- raise InvalidArgument, 'per_page must be > 0' if @per_page < 1
43
+ raise InvalidArgument, "per_page must be > 0" if @per_page < 1
34
44
  end
35
45
 
36
46
  # Paginate collection given an active index
@@ -43,21 +53,21 @@ module TTY
43
53
  # number of choice items per page
44
54
  #
45
55
  # @return [Enumerable]
56
+ # the list between start and end index
46
57
  #
47
58
  # @api public
48
59
  def paginate(list, active, per_page = nil, &block)
49
60
  current_index = active - 1
50
61
  default_size = (list.size <= DEFAULT_PAGE_SIZE ? list.size : DEFAULT_PAGE_SIZE)
51
62
  @per_page = @per_page || per_page || default_size
52
- @lower_index ||= current_index
53
- @upper_index ||= max_index
54
-
55
63
  check_page_size!
64
+ @start_index ||= (current_index / @per_page) * @per_page
65
+ @end_index ||= @start_index + @per_page - 1
56
66
 
57
67
  # Don't paginate short lists
58
68
  if list.size <= @per_page
59
- @lower_index = 0
60
- @upper_index = list.size - 1
69
+ @start_index = 0
70
+ @end_index = list.size - 1
61
71
  if block
62
72
  return list.each_with_index(&block)
63
73
  else
@@ -65,33 +75,35 @@ module TTY
65
75
  end
66
76
  end
67
77
 
78
+ step = (current_index - @last_index).abs
68
79
  if current_index > @last_index # going up
69
- if current_index > @upper_index && current_index < list.size - 1
70
- @lower_index += 1
80
+ if current_index >= @end_index && current_index < list.size - 1
81
+ last_page = list.size - @per_page
82
+ @start_index = [@start_index + step, last_page].min
71
83
  end
72
84
  elsif current_index < @last_index # going down
73
- if current_index < @lower_index && current_index > 0
74
- @lower_index -= 1
85
+ if current_index <= @start_index && current_index > 0
86
+ @start_index = [@start_index - step, 0].max
75
87
  end
76
88
  end
77
89
 
78
90
  # Cycle list
79
91
  if current_index.zero?
80
- @lower_index = 0
92
+ @start_index = 0
81
93
  elsif current_index == list.size - 1
82
- @lower_index = list.size - 1 - (@per_page - 1)
94
+ @start_index = list.size - 1 - (@per_page - 1)
83
95
  end
84
96
 
85
- @upper_index = @lower_index + (@per_page - 1)
97
+ @end_index = @start_index + (@per_page - 1)
86
98
  @last_index = current_index
87
99
 
88
- sliced_list = list[@lower_index..@upper_index]
89
- indices = (@lower_index..@upper_index)
100
+ sliced_list = list[@start_index..@end_index]
101
+ page_range = (@start_index..@end_index)
90
102
 
91
- return sliced_list.zip(indices).to_enum unless block_given?
103
+ return sliced_list.zip(page_range).to_enum unless block_given?
92
104
 
93
105
  sliced_list.each_with_index do |item, index|
94
- block[item, @lower_index + index]
106
+ block[item, @start_index + index]
95
107
  end
96
108
  end
97
109
  end # Paginator
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'converters'
4
- require_relative 'evaluator'
5
- require_relative 'question/modifier'
6
- require_relative 'question/validation'
7
- require_relative 'question/checks'
8
- require_relative 'utils'
3
+ require_relative "converters"
4
+ require_relative "evaluator"
5
+ require_relative "question/modifier"
6
+ require_relative "question/validation"
7
+ require_relative "question/checks"
8
+ require_relative "utils"
9
9
 
10
10
  module TTY
11
11
  # A class responsible for shell prompt interactions.
@@ -34,22 +34,31 @@ module TTY
34
34
  # Initialize a Question
35
35
  #
36
36
  # @api public
37
- def initialize(prompt, options = {})
38
- @prompt = prompt
39
- @prefix = options.fetch(:prefix) { @prompt.prefix }
40
- @default = options.fetch(:default) { UndefinedSetting }
41
- @required = options.fetch(:required) { false }
42
- @echo = options.fetch(:echo) { true }
43
- @in = options.fetch(:in) { UndefinedSetting }
44
- @modifier = options.fetch(:modifier) { [] }
45
- @validation = options.fetch(:validation) { UndefinedSetting }
46
- @convert = options.fetch(:convert) { UndefinedSetting }
37
+ def initialize(prompt, **options)
38
+ # Option deprecation
39
+ if options[:validation]
40
+ warn "[DEPRECATION] The `:validation` option is deprecated. Use `:validate` instead."
41
+ options[:validate] = options[:validation]
42
+ end
43
+
44
+ @prompt = prompt
45
+ @prefix = options.fetch(:prefix) { @prompt.prefix }
46
+ @default = options.fetch(:default) { UndefinedSetting }
47
+ @required = options.fetch(:required) { false }
48
+ @echo = options.fetch(:echo) { true }
49
+ @in = options.fetch(:in) { UndefinedSetting }
50
+ @modifier = options.fetch(:modifier) { [] }
51
+ @validation = options.fetch(:validate) { UndefinedSetting }
52
+ @convert = options.fetch(:convert) { UndefinedSetting }
47
53
  @active_color = options.fetch(:active_color) { @prompt.active_color }
48
- @help_color = options.fetch(:help_color) { @prompt.help_color }
49
- @error_color = options.fetch(:error_color) { :red }
50
- @messages = Utils.deep_copy(options.fetch(:messages) { { } })
51
- @done = false
52
- @input = nil
54
+ @help_color = options.fetch(:help_color) { @prompt.help_color }
55
+ @error_color = options.fetch(:error_color) { :red }
56
+ @value = options.fetch(:value) { UndefinedSetting }
57
+ @quiet = options.fetch(:quiet) { @prompt.quiet }
58
+ @messages = Utils.deep_copy(options.fetch(:messages) { { } })
59
+ @done = false
60
+ @first_render = true
61
+ @input = nil
53
62
 
54
63
  @evaluator = Evaluator.new(self)
55
64
 
@@ -58,6 +67,7 @@ module TTY
58
67
  @evaluator << CheckRange
59
68
  @evaluator << CheckValidation
60
69
  @evaluator << CheckModifier
70
+ @evaluator << CheckConversion
61
71
  end
62
72
 
63
73
  # Stores all the error messages displayed to user
@@ -83,7 +93,7 @@ module TTY
83
93
  if template && !template.match(/\%\{/).nil?
84
94
  [template % tokens]
85
95
  else
86
- [template || '']
96
+ [template || ""]
87
97
  end
88
98
  end
89
99
 
@@ -94,8 +104,7 @@ module TTY
94
104
  # @return [self]
95
105
  #
96
106
  # @api public
97
- def call(message, &block)
98
- return if Utils.blank?(message)
107
+ def call(message = "", &block)
99
108
  @message = message
100
109
  block.call(self) if block
101
110
  @prompt.subscribe(self) do
@@ -121,8 +130,8 @@ module TTY
121
130
  total_lines = @prompt.count_screen_lines(input_line)
122
131
  @prompt.print(refresh(question.lines.count, total_lines))
123
132
  end
124
- @prompt.print(render_question)
125
- convert_result(result.value)
133
+ @prompt.print(render_question) unless @quiet
134
+ result.value
126
135
  end
127
136
 
128
137
  # Render question
@@ -131,13 +140,16 @@ module TTY
131
140
  #
132
141
  # @api private
133
142
  def render_question
134
- header = ["#{@prefix}#{message} "]
143
+ header = []
144
+ if !Utils.blank?(@prefix) || !Utils.blank?(message)
145
+ header << "#{@prefix}#{message} "
146
+ end
135
147
  if !echo?
136
148
  header
137
149
  elsif @done
138
150
  header << @prompt.decorate(@input.to_s, @active_color)
139
151
  elsif default? && !Utils.blank?(@default)
140
- header << @prompt.decorate("(#{default})", @help_color) + ' '
152
+ header << @prompt.decorate("(#{default})", @help_color) + " "
141
153
  end
142
154
  header << "\n" if @done
143
155
  header.join
@@ -158,7 +170,12 @@ module TTY
158
170
  #
159
171
  # @api private
160
172
  def read_input(question)
161
- @prompt.read_line(question, echo: echo).chomp
173
+ options = {echo: echo}
174
+ if value? && @first_render
175
+ options[:value] = @value
176
+ @first_render = false
177
+ end
178
+ @prompt.read_line(question, **options).chomp
162
179
  end
163
180
 
164
181
  # Handle error condition
@@ -168,7 +185,7 @@ module TTY
168
185
  # @api private
169
186
  def render_error(errors)
170
187
  errors.reduce([]) do |acc, err|
171
- acc << @prompt.decorate('>>', :red) + ' ' + err
188
+ acc << @prompt.decorate(">>", :red) + " " + err
172
189
  acc
173
190
  end.join("\n")
174
191
  end
@@ -202,8 +219,13 @@ module TTY
202
219
  #
203
220
  # @api private
204
221
  def convert_result(value)
205
- if convert? & !Utils.blank?(value)
206
- Converters.convert(@convert, value)
222
+ if convert? && !Utils.blank?(value)
223
+ case @convert
224
+ when Proc
225
+ @convert.call(value)
226
+ else
227
+ Converters.convert(@convert, value)
228
+ end
207
229
  else
208
230
  value
209
231
  end
@@ -212,8 +234,13 @@ module TTY
212
234
  # Specify answer conversion
213
235
  #
214
236
  # @api public
215
- def convert(value)
216
- @convert = value
237
+ def convert(value = (not_set = true), message = nil)
238
+ messages[:convert?] = message if message
239
+ if not_set
240
+ @convert
241
+ else
242
+ @convert = value
243
+ end
217
244
  end
218
245
 
219
246
  # Check if conversion is set
@@ -266,6 +293,21 @@ module TTY
266
293
  @validation = (value || block)
267
294
  end
268
295
 
296
+ # Prepopulate input with custom content
297
+ #
298
+ # @api public
299
+ def value(val)
300
+ return @value if val.nil?
301
+ @value = val
302
+ end
303
+
304
+ # Check if custom value is present
305
+ #
306
+ # @api private
307
+ def value?
308
+ @value != UndefinedSetting
309
+ end
310
+
269
311
  def validation?
270
312
  @validation != UndefinedSetting
271
313
  end
@@ -321,6 +363,13 @@ module TTY
321
363
  @in != UndefinedSetting
322
364
  end
323
365
 
366
+ # Set quiet mode.
367
+ #
368
+ # @api public
369
+ def quiet(value)
370
+ @quiet = value
371
+ end
372
+
324
373
  # @api public
325
374
  def to_s
326
375
  message.to_s