tty-prompt 0.18.0 → 0.22.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 (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