highline 1.7.10 → 2.0.3

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 (65) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +5 -0
  3. data/.rubocop.yml +84 -0
  4. data/.simplecov +5 -0
  5. data/.travis.yml +35 -9
  6. data/Changelog.md +214 -15
  7. data/Gemfile +16 -5
  8. data/README.md +202 -0
  9. data/Rakefile +8 -20
  10. data/appveyor.yml +37 -0
  11. data/examples/ansi_colors.rb +6 -11
  12. data/examples/asking_for_arrays.rb +6 -2
  13. data/examples/basic_usage.rb +31 -21
  14. data/examples/color_scheme.rb +11 -10
  15. data/examples/get_character.rb +8 -4
  16. data/examples/limit.rb +4 -0
  17. data/examples/menus.rb +16 -10
  18. data/examples/overwrite.rb +9 -5
  19. data/examples/page_and_wrap.rb +5 -4
  20. data/examples/password.rb +4 -0
  21. data/examples/repeat_entry.rb +10 -5
  22. data/examples/trapping_eof.rb +2 -1
  23. data/examples/using_readline.rb +2 -1
  24. data/highline.gemspec +25 -27
  25. data/lib/highline/builtin_styles.rb +129 -0
  26. data/lib/highline/color_scheme.rb +49 -32
  27. data/lib/highline/compatibility.rb +11 -4
  28. data/lib/highline/custom_errors.rb +57 -0
  29. data/lib/highline/import.rb +19 -12
  30. data/lib/highline/io_console_compatible.rb +37 -0
  31. data/lib/highline/list.rb +177 -0
  32. data/lib/highline/list_renderer.rb +261 -0
  33. data/lib/highline/menu/item.rb +32 -0
  34. data/lib/highline/menu.rb +306 -111
  35. data/lib/highline/paginator.rb +52 -0
  36. data/lib/highline/question/answer_converter.rb +103 -0
  37. data/lib/highline/question.rb +281 -131
  38. data/lib/highline/question_asker.rb +150 -0
  39. data/lib/highline/simulate.rb +24 -13
  40. data/lib/highline/statement.rb +88 -0
  41. data/lib/highline/string.rb +36 -0
  42. data/lib/highline/string_extensions.rb +83 -64
  43. data/lib/highline/style.rb +196 -63
  44. data/lib/highline/template_renderer.rb +62 -0
  45. data/lib/highline/terminal/io_console.rb +36 -0
  46. data/lib/highline/terminal/ncurses.rb +38 -0
  47. data/lib/highline/terminal/unix_stty.rb +51 -0
  48. data/lib/highline/terminal.rb +190 -0
  49. data/lib/highline/version.rb +3 -1
  50. data/lib/highline/wrapper.rb +53 -0
  51. data/lib/highline.rb +390 -788
  52. metadata +56 -35
  53. data/INSTALL +0 -59
  54. data/README.rdoc +0 -74
  55. data/lib/highline/system_extensions.rb +0 -254
  56. data/setup.rb +0 -1360
  57. data/test/string_methods.rb +0 -32
  58. data/test/tc_color_scheme.rb +0 -96
  59. data/test/tc_highline.rb +0 -1402
  60. data/test/tc_import.rb +0 -52
  61. data/test/tc_menu.rb +0 -439
  62. data/test/tc_simulator.rb +0 -33
  63. data/test/tc_string_extension.rb +0 -33
  64. data/test/tc_string_highline.rb +0 -38
  65. data/test/tc_style.rb +0 -578
data/lib/highline.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  # coding: utf-8
2
+
3
+ #--
2
4
  # highline.rb
3
5
  #
4
6
  # Created by James Edward Gray II on 2005-04-26.
@@ -8,212 +10,172 @@
8
10
  #
9
11
  # This is Free Software. See LICENSE and COPYING for details.
10
12
 
13
+ require "English"
11
14
  require "erb"
12
15
  require "optparse"
13
16
  require "stringio"
14
17
  require "abbrev"
15
- require "highline/system_extensions"
18
+ require "highline/terminal"
19
+ require "highline/custom_errors"
16
20
  require "highline/question"
21
+ require "highline/question_asker"
17
22
  require "highline/menu"
18
23
  require "highline/color_scheme"
19
24
  require "highline/style"
20
25
  require "highline/version"
26
+ require "highline/statement"
27
+ require "highline/list_renderer"
28
+ require "highline/builtin_styles"
21
29
 
22
30
  #
23
31
  # A HighLine object is a "high-level line oriented" shell over an input and an
24
32
  # output stream. HighLine simplifies common console interaction, effectively
25
- # replacing puts() and gets(). User code can simply specify the question to ask
26
- # and any details about user interaction, then leave the rest of the work to
27
- # HighLine. When HighLine.ask() returns, you'll have the answer you requested,
28
- # even if HighLine had to ask many times, validate results, perform range
29
- # checking, convert types, etc.
33
+ # replacing {Kernel#puts} and {Kernel#gets}. User code can simply specify the
34
+ # question to ask and any details about user interaction, then leave the rest
35
+ # of the work to HighLine. When {HighLine#ask} returns, you'll have the answer
36
+ # you requested, even if HighLine had to ask many times, validate results,
37
+ # perform range checking, convert types, etc.
38
+ #
39
+ # @example Basic usage
40
+ # cli = HighLine.new
41
+ # answer = cli.ask "What do you think?"
42
+ # puts "You have answered: #{answer}"
30
43
  #
31
44
  class HighLine
32
- # An internal HighLine error. User code does not need to trap this.
33
- class QuestionError < StandardError
34
- # do nothing, just creating a unique error type
35
- end
36
-
37
- # The setting used to disable color output.
38
- @@use_color = true
39
-
40
- # Pass +false+ to _setting_ to turn off HighLine's color escapes.
41
- def self.use_color=( setting )
42
- @@use_color = setting
43
- end
44
-
45
- # Returns true if HighLine is currently using color escapes.
46
- def self.use_color?
47
- @@use_color
48
- end
45
+ include BuiltinStyles
46
+ include CustomErrors
49
47
 
50
- # For checking if the current version of HighLine supports RGB colors
51
- # Usage: HighLine.supports_rgb_color? rescue false # rescue for compatibility with older versions
52
- # Note: color usage also depends on HighLine.use_color being set
53
- def self.supports_rgb_color?
54
- true
55
- end
48
+ extend SingleForwardable
49
+ def_single_delegators :@default_instance, :agree, :ask, :choose, :say,
50
+ :use_color=, :use_color?, :reset_use_color,
51
+ :track_eof=, :track_eof?,
52
+ :color, :uncolor, :color_code
56
53
 
57
- # The setting used to disable EOF tracking.
58
- @@track_eof = true
54
+ class << self
55
+ attr_accessor :default_instance
59
56
 
60
- # Pass +false+ to _setting_ to turn off HighLine's EOF tracking.
61
- def self.track_eof=( setting )
62
- @@track_eof = setting
63
- end
57
+ # Pass ColorScheme to set a HighLine color scheme.
58
+ attr_accessor :color_scheme
64
59
 
65
- # Returns true if HighLine is currently tracking EOF for input.
66
- def self.track_eof?
67
- @@track_eof
68
- end
69
-
70
- # The setting used to control color schemes.
71
- @@color_scheme = nil
72
-
73
- # Pass ColorScheme to _setting_ to set a HighLine color scheme.
74
- def self.color_scheme=( setting )
75
- @@color_scheme = setting
76
- end
77
-
78
- # Returns the current color scheme.
79
- def self.color_scheme
80
- @@color_scheme
81
- end
82
-
83
- # Returns +true+ if HighLine is currently using a color scheme.
84
- def self.using_color_scheme?
85
- not @@color_scheme.nil?
86
- end
87
-
88
- #
89
- # Embed in a String to clear all previous ANSI sequences. This *MUST* be
90
- # done before the program exits!
91
- #
92
-
93
- ERASE_LINE_STYLE = Style.new(:name=>:erase_line, :builtin=>true, :code=>"\e[K") # Erase the current line of terminal output
94
- ERASE_CHAR_STYLE = Style.new(:name=>:erase_char, :builtin=>true, :code=>"\e[P") # Erase the character under the cursor.
95
- CLEAR_STYLE = Style.new(:name=>:clear, :builtin=>true, :code=>"\e[0m") # Clear color settings
96
- RESET_STYLE = Style.new(:name=>:reset, :builtin=>true, :code=>"\e[0m") # Alias for CLEAR.
97
- BOLD_STYLE = Style.new(:name=>:bold, :builtin=>true, :code=>"\e[1m") # Bold; Note: bold + a color works as you'd expect,
98
- # for example bold black. Bold without a color displays
99
- # the system-defined bold color (e.g. red on Mac iTerm)
100
- DARK_STYLE = Style.new(:name=>:dark, :builtin=>true, :code=>"\e[2m") # Dark; support uncommon
101
- UNDERLINE_STYLE = Style.new(:name=>:underline, :builtin=>true, :code=>"\e[4m") # Underline
102
- UNDERSCORE_STYLE = Style.new(:name=>:underscore, :builtin=>true, :code=>"\e[4m") # Alias for UNDERLINE
103
- BLINK_STYLE = Style.new(:name=>:blink, :builtin=>true, :code=>"\e[5m") # Blink; support uncommon
104
- REVERSE_STYLE = Style.new(:name=>:reverse, :builtin=>true, :code=>"\e[7m") # Reverse foreground and background
105
- CONCEALED_STYLE = Style.new(:name=>:concealed, :builtin=>true, :code=>"\e[8m") # Concealed; support uncommon
106
-
107
- STYLES = %w{CLEAR RESET BOLD DARK UNDERLINE UNDERSCORE BLINK REVERSE CONCEALED}
108
-
109
- # These RGB colors are approximate; see http://en.wikipedia.org/wiki/ANSI_escape_code
110
- BLACK_STYLE = Style.new(:name=>:black, :builtin=>true, :code=>"\e[30m", :rgb=>[ 0, 0, 0])
111
- RED_STYLE = Style.new(:name=>:red, :builtin=>true, :code=>"\e[31m", :rgb=>[128, 0, 0])
112
- GREEN_STYLE = Style.new(:name=>:green, :builtin=>true, :code=>"\e[32m", :rgb=>[ 0,128, 0])
113
- BLUE_STYLE = Style.new(:name=>:blue, :builtin=>true, :code=>"\e[34m", :rgb=>[ 0, 0,128])
114
- YELLOW_STYLE = Style.new(:name=>:yellow, :builtin=>true, :code=>"\e[33m", :rgb=>[128,128, 0])
115
- MAGENTA_STYLE = Style.new(:name=>:magenta, :builtin=>true, :code=>"\e[35m", :rgb=>[128, 0,128])
116
- CYAN_STYLE = Style.new(:name=>:cyan, :builtin=>true, :code=>"\e[36m", :rgb=>[ 0,128,128])
117
- # On Mac OSX Terminal, white is actually gray
118
- WHITE_STYLE = Style.new(:name=>:white, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
119
- # Alias for WHITE, since WHITE is actually a light gray on Macs
120
- GRAY_STYLE = Style.new(:name=>:gray, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
121
- GREY_STYLE = Style.new(:name=>:grey, :builtin=>true, :code=>"\e[37m", :rgb=>[192,192,192])
122
- # On Mac OSX Terminal, this is black foreground, or bright white background.
123
- # Also used as base for RGB colors, if available
124
- NONE_STYLE = Style.new(:name=>:none, :builtin=>true, :code=>"\e[38m", :rgb=>[ 0, 0, 0])
125
-
126
- BASIC_COLORS = %w{BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE GRAY GREY NONE}
127
-
128
- colors = BASIC_COLORS.dup
129
- BASIC_COLORS.each do |color|
130
- bright_color = "BRIGHT_#{color}"
131
- colors << bright_color
132
- const_set bright_color+'_STYLE', const_get(color + '_STYLE').bright
133
-
134
- light_color = "LIGHT_#{color}"
135
- colors << light_color
136
- const_set light_color+'_STYLE', const_get(color + '_STYLE').light
137
- end
138
- COLORS = colors
60
+ # Returns +true+ if HighLine is currently using a color scheme.
61
+ def using_color_scheme?
62
+ true if @color_scheme
63
+ end
139
64
 
140
- colors.each do |color|
141
- const_set color, const_get("#{color}_STYLE").code
142
- const_set "ON_#{color}_STYLE", const_get("#{color}_STYLE").on
143
- const_set "ON_#{color}", const_get("ON_#{color}_STYLE").code
144
- end
145
- ON_NONE_STYLE.rgb = [255,255,255] # Override; white background
65
+ # Reset color scheme to default (+nil+)
66
+ def reset_color_scheme
67
+ self.color_scheme = nil
68
+ end
146
69
 
147
- STYLES.each do |style|
148
- const_set style, const_get("#{style}_STYLE").code
149
- end
70
+ # Reset HighLine to default.
71
+ # Clears Style index and resets color_scheme and use_color settings.
72
+ def reset
73
+ Style.clear_index
74
+ reset_color_scheme
75
+ reset_use_color
76
+ end
150
77
 
151
- # For RGB colors:
152
- def self.const_missing(name)
153
- if name.to_s =~ /^(ON_)?(RGB_)([A-F0-9]{6})(_STYLE)?$/ # RGB color
154
- on = $1
155
- suffix = $4
156
- if suffix
157
- code_name = $1.to_s + $2 + $3
158
- else
159
- code_name = name.to_s
160
- end
161
- style_name = code_name + '_STYLE'
162
- style = Style.rgb($3)
163
- style = style.on if on
164
- const_set(style_name, style)
165
- const_set(code_name, style.code)
166
- if suffix
167
- style
168
- else
169
- style.code
170
- end
171
- else
172
- raise NameError, "Bad color or uninitialized constant #{name}"
78
+ # For checking if the current version of HighLine supports RGB colors
79
+ # Usage: HighLine.supports_rgb_color? rescue false
80
+ # using rescue for compatibility with older versions
81
+ # Note: color usage also depends on HighLine.use_color being set
82
+ # TODO: Discuss removing this method
83
+ def supports_rgb_color?
84
+ true
173
85
  end
174
86
  end
175
87
 
176
- #
177
- # Create an instance of HighLine, connected to the streams _input_
178
- # and _output_.
179
- #
180
- def initialize( input = $stdin, output = $stdout,
181
- wrap_at = nil, page_at = nil, indent_size=3, indent_level=0 )
88
+ # The setting used to control color schemes.
89
+ @color_scheme = nil
90
+
91
+ #
92
+ # Create an instance of HighLine connected to the given _input_
93
+ # and _output_ streams.
94
+ #
95
+ # @param input [IO] the default input stream for HighLine.
96
+ # @param output [IO] the default output stream for HighLine.
97
+ # @param wrap_at [Integer] all statements outputed through
98
+ # HighLine will be wrapped to this column size if set.
99
+ # @param page_at [Integer] page size and paginating.
100
+ # @param indent_size [Integer] indentation size in spaces.
101
+ # @param indent_level [Integer] how deep is indentated.
102
+ def initialize(input = $stdin, output = $stdout,
103
+ wrap_at = nil, page_at = nil,
104
+ indent_size = 3, indent_level = 0)
182
105
  @input = input
183
106
  @output = output
184
107
 
185
108
  @multi_indent = true
186
- @indent_size = indent_size
109
+ @indent_size = indent_size
187
110
  @indent_level = indent_level
188
111
 
189
112
  self.wrap_at = wrap_at
190
113
  self.page_at = page_at
191
114
 
192
- @question = nil
193
- @answer = nil
194
- @menu = nil
195
115
  @header = nil
196
116
  @prompt = nil
197
- @gather = nil
198
- @answers = nil
199
117
  @key = nil
200
118
 
201
- initialize_system_extensions if respond_to?(:initialize_system_extensions)
119
+ @use_color = default_use_color
120
+ @track_eof = true # The setting used to disable EOF tracking.
121
+ @terminal = HighLine::Terminal.get_terminal(input, output)
122
+ end
123
+
124
+ # Set it to false to disable ANSI coloring
125
+ attr_accessor :use_color
126
+
127
+ # Returns truethy if HighLine instance is currently using color escapes.
128
+ def use_color?
129
+ use_color
130
+ end
131
+
132
+ # Resets the use of color.
133
+ def reset_use_color
134
+ @use_color = true
202
135
  end
203
136
 
204
- include HighLine::SystemExtensions
137
+ # Pass +false+ to turn off HighLine's EOF tracking.
138
+ attr_accessor :track_eof
205
139
 
206
- # The current column setting for wrapping output.
140
+ # Returns true if HighLine is currently tracking EOF for input.
141
+ def track_eof?
142
+ true if track_eof
143
+ end
144
+
145
+ # @return [Integer] The current column setting for wrapping output.
207
146
  attr_reader :wrap_at
208
- # The current row setting for paging output.
147
+
148
+ # @return [Integer] The current row setting for paging output.
209
149
  attr_reader :page_at
210
- # Indentation over multiple lines
150
+
151
+ # @return [Boolean] Indentation over multiple lines
211
152
  attr_accessor :multi_indent
212
- # The indentation size
153
+
154
+ # @return [Integer] The indentation size in characters
213
155
  attr_accessor :indent_size
214
- # The indentation level
156
+
157
+ # @return [Integer] The indentation level
215
158
  attr_accessor :indent_level
216
159
 
160
+ # @return [IO] the default input stream for a HighLine instance
161
+ attr_reader :input
162
+
163
+ # @return [IO] the default output stream for a HighLine instance
164
+ attr_reader :output
165
+
166
+ # When gathering a Hash with {QuestionAsker#gather_hash},
167
+ # it tracks the current key being asked.
168
+ #
169
+ # @todo We should probably move this into the HighLine::Question
170
+ # object.
171
+ attr_accessor :key
172
+
173
+ # System specific that responds to #initialize_system_extensions,
174
+ # #terminal_size, #raw_no_echo_mode, #restore_mode, #get_character.
175
+ # It polymorphically handles specific cases for different platforms.
176
+ # @return [HighLine::Terminal]
177
+ attr_reader :terminal
178
+
217
179
  #
218
180
  # A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
219
181
  # answers ("y" and "n" are allowed) and returns +true+ or +false+
@@ -223,12 +185,18 @@ class HighLine
223
185
  #
224
186
  # Raises EOFError if input is exhausted.
225
187
  #
226
- def agree( yes_or_no_question, character = nil )
227
- ask(yes_or_no_question, lambda { |yn| yn.downcase[0] == ?y}) do |q|
228
- q.validate = /\Ay(?:es)?|no?\Z/i
188
+ # @param yes_or_no_question [String] a question that accepts yes and no as
189
+ # answers
190
+ # @param character [Boolean, :getc] character mode to be passed to
191
+ # Question#character
192
+ # @see Question#character
193
+ def agree(yes_or_no_question, character = nil)
194
+ ask(yes_or_no_question, ->(yn) { yn.downcase[0] == "y" }) do |q|
195
+ q.validate = /\A(?:y(?:es)?|no?)\Z/i
229
196
  q.responses[:not_valid] = 'Please enter "yes" or "no".'
230
197
  q.responses[:ask_on_error] = :question
231
198
  q.character = character
199
+ q.completion = %w[yes no]
232
200
 
233
201
  yield q if block_given?
234
202
  end
@@ -238,79 +206,21 @@ class HighLine
238
206
  # This method is the primary interface for user input. Just provide a
239
207
  # _question_ to ask the user, the _answer_type_ you want returned, and
240
208
  # optionally a code block setting up details of how you want the question
241
- # handled. See HighLine.say() for details on the format of _question_, and
242
- # HighLine::Question for more information about _answer_type_ and what's
209
+ # handled. See {#say} for details on the format of _question_, and
210
+ # {Question} for more information about _answer_type_ and what's
243
211
  # valid in the code block.
244
212
  #
245
- # If <tt>@question</tt> is set before ask() is called, parameters are
246
- # ignored and that object (must be a HighLine::Question) is used to drive
247
- # the process instead.
248
- #
249
213
  # Raises EOFError if input is exhausted.
250
214
  #
251
- def ask( question, answer_type = nil, &details ) # :yields: question
252
- @question ||= Question.new(question, answer_type, &details)
253
-
254
- return gather if @question.gather
215
+ # @param (see Question.build)
216
+ # @return answer converted to the class in answer_type
217
+ def ask(template_or_question, answer_type = nil, &details)
218
+ question = Question.build(template_or_question, answer_type, &details)
255
219
 
256
- # readline() needs to handle its own output, but readline only supports
257
- # full line reading. Therefore if @question.echo is anything but true,
258
- # the prompt will not be issued. And we have to account for that now.
259
- # Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt
260
- # to handle line editing properly.
261
- say(@question) unless ((JRUBY or @question.readline) and (@question.echo == true and @question.limit.nil?))
262
-
263
- begin
264
- @answer = @question.answer_or_default(get_response)
265
- unless @question.valid_answer?(@answer)
266
- explain_error(:not_valid)
267
- raise QuestionError
268
- end
269
-
270
- @answer = @question.convert(@answer)
271
-
272
- if @question.in_range?(@answer)
273
- if @question.confirm
274
- # need to add a layer of scope to ask a question inside a
275
- # question, without destroying instance data
276
- context_change = self.class.new(@input, @output, @wrap_at, @page_at, @indent_size, @indent_level)
277
- if @question.confirm == true
278
- confirm_question = "Are you sure? "
279
- else
280
- # evaluate ERb under initial scope, so it will have
281
- # access to @question and @answer
282
- template = ERB.new(@question.confirm, nil, "%")
283
- confirm_question = template.result(binding)
284
- end
285
- unless context_change.agree(confirm_question)
286
- explain_error(nil)
287
- raise QuestionError
288
- end
289
- end
290
-
291
- @answer
292
- else
293
- explain_error(:not_in_range)
294
- raise QuestionError
295
- end
296
- rescue QuestionError
297
- retry
298
- rescue ArgumentError, NameError => error
299
- raise if error.is_a?(NoMethodError)
300
- if error.message =~ /ambiguous/
301
- # the assumption here is that OptionParser::Completion#complete
302
- # (used for ambiguity resolution) throws exceptions containing
303
- # the word 'ambiguous' whenever resolution fails
304
- explain_error(:ambiguous_completion)
305
- else
306
- explain_error(:invalid_type)
307
- end
308
- retry
309
- rescue Question::NoAutoCompleteMatch
310
- explain_error(:no_completion)
311
- retry
312
- ensure
313
- @question = nil # Reset Question object.
220
+ if question.gather
221
+ QuestionAsker.new(question, self).gather_answers
222
+ else
223
+ QuestionAsker.new(question, self).ask_once
314
224
  end
315
225
  end
316
226
 
@@ -329,41 +239,60 @@ class HighLine
329
239
  #
330
240
  # Raises EOFError if input is exhausted.
331
241
  #
332
- def choose( *items, &details )
333
- @menu = @question = Menu.new(&details)
334
- @menu.choices(*items) unless items.empty?
242
+ # @param items [Array<String>]
243
+ # @param details [Proc] to be passed to Menu.new
244
+ # @return [String] answer
245
+ def choose(*items, &details)
246
+ menu = Menu.new(&details)
247
+ menu.choices(*items) unless items.empty?
335
248
 
336
249
  # Set auto-completion
337
- @menu.completion = @menu.options
338
- # Set _answer_type_ so we can double as the Question for ask().
339
- @menu.answer_type = if @menu.shell
340
- lambda do |command| # shell-style selection
341
- first_word = command.to_s.split.first || ""
250
+ menu.completion = menu.options
342
251
 
343
- options = @menu.options
344
- options.extend(OptionParser::Completion)
345
- answer = options.complete(first_word)
346
-
347
- if answer.nil?
348
- raise Question::NoAutoCompleteMatch
252
+ # Set _answer_type_ so we can double as the Question for ask().
253
+ # menu.option = normal menu selection, by index or name
254
+ menu.answer_type = menu.shell ? shell_style_lambda(menu) : menu.options
255
+
256
+ selected = ask(menu)
257
+ return unless selected
258
+
259
+ if menu.shell
260
+ if menu.gather
261
+ selection = []
262
+ details = []
263
+ selected.each do |value|
264
+ selection << value[0]
265
+ details << value[1]
349
266
  end
350
-
351
- [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
267
+ else
268
+ selection, details = selected
352
269
  end
353
270
  else
354
- @menu.options # normal menu selection, by index or name
271
+ selection = selected
355
272
  end
356
273
 
357
- # Provide hooks for ERb layouts.
358
- @header = @menu.header
359
- @prompt = @menu.prompt
360
-
361
- if @menu.shell
362
- selected = ask("Ignored", @menu.answer_type)
363
- @menu.select(self, *selected)
274
+ if menu.gather
275
+ menu.gather_selected(self, selection, details)
364
276
  else
365
- selected = ask("Ignored", @menu.answer_type)
366
- @menu.select(self, selected)
277
+ menu.select(self, selection, details)
278
+ end
279
+ end
280
+
281
+ # Convenience method to craft a lambda suitable for
282
+ # beind used in autocompletion operations by {#choose}
283
+ # @return [lambda] lambda to be used in autocompletion operations
284
+
285
+ def shell_style_lambda(menu)
286
+ lambda do |command| # shell-style selection
287
+ first_word = command.to_s.split.first || ""
288
+
289
+ options = menu.options
290
+ options.extend(OptionParser::Completion)
291
+ answer = options.complete(first_word)
292
+
293
+ raise Question::NoAutoCompleteMatch unless answer
294
+
295
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
367
296
  end
368
297
  end
369
298
 
@@ -375,236 +304,59 @@ class HighLine
375
304
  # (:blue for BLUE, for example). A CLEAR will automatically be embedded to
376
305
  # the end of the returned String.
377
306
  #
378
- # This method returns the original _string_ unchanged if HighLine::use_color?
307
+ # This method returns the original _string_ unchanged if use_color?
379
308
  # is +false+.
380
309
  #
381
- def self.color( string, *colors )
382
- return string unless self.use_color?
383
- Style(*colors).color(string)
384
- end
385
-
386
- # In case you just want the color code, without the embedding and the CLEAR
387
- def self.color_code(*colors)
388
- Style(*colors).code
389
- end
390
-
391
- # Works as an instance method, same as the class method
392
- def color_code(*colors)
393
- self.class.color_code(*colors)
394
- end
395
-
396
- # Works as an instance method, same as the class method
397
- def color(*args)
398
- self.class.color(*args)
399
- end
400
-
401
- # Remove color codes from a string
402
- def self.uncolor(string)
403
- Style.uncolor(string)
404
- end
405
-
406
- # Works as an instance method, same as the class method
407
- def uncolor(string)
408
- self.class.uncolor(string)
310
+ # @param string [String] string to be colored
311
+ # @param colors [Array<Symbol>] array of colors like [:red, :blue]
312
+ # @return [String] (ANSI escaped) colored string
313
+ # @example
314
+ # cli = HighLine.new
315
+ # cli.color("Sustainable", :green, :bold)
316
+ # # => "\e[32m\e[1mSustainable\e[0m"
317
+ #
318
+ # # As class method (delegating to HighLine.default_instance)
319
+ # HighLine.color("Sustainable", :green, :bold)
320
+ #
321
+ def color(string, *colors)
322
+ return string unless use_color?
323
+ HighLine.Style(*colors).color(string)
409
324
  end
410
325
 
326
+ # In case you just want the color code, without the embedding and
327
+ # the CLEAR sequence.
411
328
  #
412
- # This method is a utility for quickly and easily laying out lists. It can
413
- # be accessed within ERb replacements of any text that will be sent to the
414
- # user.
329
+ # @param colors [Array<Symbol>]
330
+ # @return [String] ANSI escape code for the given colors.
415
331
  #
416
- # The only required parameter is _items_, which should be the Array of items
417
- # to list. A specified _mode_ controls how that list is formed and _option_
418
- # has different effects, depending on the _mode_. Recognized modes are:
332
+ # @example
333
+ # s = HighLine.Style(:red, :blue)
334
+ # s.code # => "\e[31m\e[34m"
419
335
  #
420
- # <tt>:columns_across</tt>:: _items_ will be placed in columns,
421
- # flowing from left to right. If given,
422
- # _option_ is the number of columns to be
423
- # used. When absent, columns will be
424
- # determined based on _wrap_at_ or a
425
- # default of 80 characters.
426
- # <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>,
427
- # save flow goes down.
428
- # <tt>:uneven_columns_across</tt>:: Like <tt>:columns_across</tt> but each
429
- # column is sized independently.
430
- # <tt>:uneven_columns_down</tt>:: Like <tt>:columns_down</tt> but each
431
- # column is sized independently.
432
- # <tt>:inline</tt>:: All _items_ are placed on a single line.
433
- # The last two _items_ are separated by
434
- # _option_ or a default of " or ". All
435
- # other _items_ are separated by ", ".
436
- # <tt>:rows</tt>:: The default mode. Each of the _items_ is
437
- # placed on its own line. The _option_
438
- # parameter is ignored in this mode.
336
+ # HighLine.color_code(:red, :blue) # => "\e[31m\e[34m"
439
337
  #
440
- # Each member of the _items_ Array is passed through ERb and thus can contain
441
- # their own expansions. Color escape expansions do not contribute to the
442
- # final field width.
338
+ # cli = HighLine.new
339
+ # cli.color_code(:red, :blue) # => "\e[31m\e[34m"
443
340
  #
444
- def list( items, mode = :rows, option = nil )
445
- items = items.to_ary.map do |item|
446
- if item.nil?
447
- ""
448
- else
449
- ERB.new(item, nil, "%").result(binding)
450
- end
451
- end
452
-
453
- if items.empty?
454
- ""
455
- else
456
- case mode
457
- when :inline
458
- option = " or " if option.nil?
459
-
460
- if items.size == 1
461
- items.first
462
- else
463
- items[0..-2].join(", ") + "#{option}#{items.last}"
464
- end
465
- when :columns_across, :columns_down
466
- max_length = actual_length(
467
- items.max { |a, b| actual_length(a) <=> actual_length(b) }
468
- )
469
-
470
- if option.nil?
471
- limit = @wrap_at || 80
472
- option = (limit + 2) / (max_length + 2)
473
- end
474
-
475
- items = items.map do |item|
476
- pad = max_length + (item.to_s.length - actual_length(item))
477
- "%-#{pad}s" % item
478
- end
479
- row_count = (items.size / option.to_f).ceil
341
+ def color_code(*colors)
342
+ HighLine.Style(*colors).code
343
+ end
480
344
 
481
- if mode == :columns_across
482
- rows = Array.new(row_count) { Array.new }
483
- items.each_with_index do |item, index|
484
- rows[index / option] << item
485
- end
345
+ # Remove color codes from a string.
346
+ # @param string [String] to be decolorized
347
+ # @return [String] without the ANSI escape sequence (colors)
348
+ def uncolor(string)
349
+ Style.uncolor(string)
350
+ end
486
351
 
487
- rows.map { |row| row.join(" ") + "\n" }.join
488
- else
489
- columns = Array.new(option) { Array.new }
490
- items.each_with_index do |item, index|
491
- columns[index / row_count] << item
492
- end
493
-
494
- list = ""
495
- columns.first.size.times do |index|
496
- list << columns.map { |column| column[index] }.
497
- compact.join(" ") + "\n"
498
- end
499
- list
500
- end
501
- when :uneven_columns_across
502
- if option.nil?
503
- limit = @wrap_at || 80
504
- items.size.downto(1) do |column_count|
505
- row_count = (items.size / column_count.to_f).ceil
506
- rows = Array.new(row_count) { Array.new }
507
- items.each_with_index do |item, index|
508
- rows[index / column_count] << item
509
- end
510
-
511
- widths = Array.new(column_count, 0)
512
- rows.each do |row|
513
- row.each_with_index do |field, column|
514
- size = actual_length(field)
515
- widths[column] = size if size > widths[column]
516
- end
517
- end
518
-
519
- if column_count == 1 or
520
- widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
521
- return rows.map { |row|
522
- row.zip(widths).map { |field, i|
523
- "%-#{i + (field.to_s.length - actual_length(field))}s" % field
524
- }.join(" ") + "\n"
525
- }.join
526
- end
527
- end
528
- else
529
- row_count = (items.size / option.to_f).ceil
530
- rows = Array.new(row_count) { Array.new }
531
- items.each_with_index do |item, index|
532
- rows[index / option] << item
533
- end
534
-
535
- widths = Array.new(option, 0)
536
- rows.each do |row|
537
- row.each_with_index do |field, column|
538
- size = actual_length(field)
539
- widths[column] = size if size > widths[column]
540
- end
541
- end
542
-
543
- return rows.map { |row|
544
- row.zip(widths).map { |field, i|
545
- "%-#{i + (field.to_s.length - actual_length(field))}s" % field
546
- }.join(" ") + "\n"
547
- }.join
548
- end
549
- when :uneven_columns_down
550
- if option.nil?
551
- limit = @wrap_at || 80
552
- items.size.downto(1) do |column_count|
553
- row_count = (items.size / column_count.to_f).ceil
554
- columns = Array.new(column_count) { Array.new }
555
- items.each_with_index do |item, index|
556
- columns[index / row_count] << item
557
- end
558
-
559
- widths = Array.new(column_count, 0)
560
- columns.each_with_index do |column, i|
561
- column.each do |field|
562
- size = actual_length(field)
563
- widths[i] = size if size > widths[i]
564
- end
565
- end
566
-
567
- if column_count == 1 or
568
- widths.inject(0) { |sum, n| sum + n + 2 } <= limit + 2
569
- list = ""
570
- columns.first.size.times do |index|
571
- list << columns.zip(widths).map { |column, width|
572
- field = column[index]
573
- "%-#{width + (field.to_s.length - actual_length(field))}s" %
574
- field
575
- }.compact.join(" ").strip + "\n"
576
- end
577
- return list
578
- end
579
- end
580
- else
581
- row_count = (items.size / option.to_f).ceil
582
- columns = Array.new(option) { Array.new }
583
- items.each_with_index do |item, index|
584
- columns[index / row_count] << item
585
- end
586
-
587
- widths = Array.new(option, 0)
588
- columns.each_with_index do |column, i|
589
- column.each do |field|
590
- size = actual_length(field)
591
- widths[i] = size if size > widths[i]
592
- end
593
- end
594
-
595
- list = ""
596
- columns.first.size.times do |index|
597
- list << columns.zip(widths).map { |column, width|
598
- field = column[index]
599
- "%-#{width + (field.to_s.length - actual_length(field))}s" % field
600
- }.compact.join(" ").strip + "\n"
601
- end
602
- return list
603
- end
604
- else
605
- items.map { |i| "#{i}\n" }.join
606
- end
607
- end
352
+ # Renders a list of itens using a {ListRenderer}
353
+ # @param items [Array]
354
+ # @param mode [Symbol]
355
+ # @param option
356
+ # @return [String]
357
+ # @see ListRenderer#initialize ListRenderer#initialize for parameter details
358
+ def list(items, mode = :rows, option = nil)
359
+ ListRenderer.new(items, mode, option, self).render
608
360
  end
609
361
 
610
362
  #
@@ -612,34 +364,42 @@ class HighLine
612
364
  # ends with a space or tab character, a newline will not be appended (output
613
365
  # will be flush()ed). All other cases are passed straight to Kernel.puts().
614
366
  #
615
- # The _statement_ parameter is processed as an ERb template, supporting
616
- # embedded Ruby code. The template is evaluated with a binding inside
617
- # the HighLine instance, providing easy access to the ANSI color constants
618
- # and the HighLine.color() method.
367
+ # The _statement_ argument is processed as an ERb template, supporting
368
+ # embedded Ruby code. The template is evaluated within a HighLine
369
+ # instance's binding for providing easy access to the ANSI color constants
370
+ # and the HighLine#color() method.
619
371
  #
620
- def say( statement )
621
- statement = format_statement(statement)
622
- return unless statement.length > 0
372
+ # @param statement [Statement, String] what to be said
373
+ def say(statement)
374
+ statement = render_statement(statement)
375
+ return if statement.empty?
623
376
 
624
- out = (indentation+statement).encode(Encoding.default_external, { :undef => :replace } )
377
+ statement = (indentation + statement)
625
378
 
626
379
  # Don't add a newline if statement ends with whitespace, OR
627
380
  # if statement ends with whitespace before a color escape code.
628
381
  if /[ \t](\e\[\d+(;\d+)*m)?\Z/ =~ statement
629
- @output.print(out)
630
- @output.flush
382
+ output.print(statement)
383
+ output.flush
631
384
  else
632
- @output.puts(out)
385
+ output.puts(statement)
633
386
  end
634
387
  end
635
388
 
389
+ # Renders a statement using {HighLine::Statement}
390
+ # @param statement [String] any string
391
+ # @return [Statement] rendered statement
392
+ def render_statement(statement)
393
+ Statement.new(statement, self).to_s
394
+ end
395
+
636
396
  #
637
397
  # Set to an integer value to cause HighLine to wrap output lines at the
638
398
  # indicated character limit. When +nil+, the default, no wrapping occurs. If
639
399
  # set to <tt>:auto</tt>, HighLine will attempt to determine the columns
640
400
  # available for the <tt>@output</tt> or use a sensible default.
641
401
  #
642
- def wrap_at=( setting )
402
+ def wrap_at=(setting)
643
403
  @wrap_at = setting == :auto ? output_cols : setting
644
404
  end
645
405
 
@@ -649,7 +409,7 @@ class HighLine
649
409
  # set to <tt>:auto</tt>, HighLine will attempt to determine the rows available
650
410
  # for the <tt>@output</tt> or use a sensible default.
651
411
  #
652
- def page_at=( setting )
412
+ def page_at=(setting)
653
413
  @page_at = setting == :auto ? output_rows - 2 : setting
654
414
  end
655
415
 
@@ -657,29 +417,31 @@ class HighLine
657
417
  # Outputs indentation with current settings
658
418
  #
659
419
  def indentation
660
- return ' '*@indent_size*@indent_level
420
+ " " * @indent_size * @indent_level
661
421
  end
662
422
 
663
423
  #
664
424
  # Executes block or outputs statement with indentation
665
425
  #
666
- def indent(increase=1, statement=nil, multiline=nil)
426
+ # @param increase [Integer] how much to increase indentation
427
+ # @param statement [Statement, String] to be said
428
+ # @param multiline [Boolean]
429
+ # @return [void]
430
+ # @see #multi_indent
431
+ def indent(increase = 1, statement = nil, multiline = nil)
667
432
  @indent_level += increase
668
433
  multi = @multi_indent
669
- @multi_indent = multiline unless multiline.nil?
434
+ @multi_indent ||= multiline
670
435
  begin
671
- if block_given?
672
- yield self
673
- else
674
- say(statement)
675
- end
676
- rescue
677
- @multi_indent = multi
678
- @indent_level -= increase
679
- raise
436
+ if block_given?
437
+ yield self
438
+ else
439
+ say(statement)
440
+ end
441
+ ensure
442
+ @multi_indent = multi
443
+ @indent_level -= increase
680
444
  end
681
- @multi_indent = multi
682
- @indent_level -= increase
683
445
  end
684
446
 
685
447
  #
@@ -695,8 +457,8 @@ class HighLine
695
457
  #
696
458
  def output_cols
697
459
  return 80 unless @output.tty?
698
- terminal_size.first
699
- rescue
460
+ terminal.terminal_size.first
461
+ rescue NoMethodError
700
462
  return 80
701
463
  end
702
464
 
@@ -706,105 +468,31 @@ class HighLine
706
468
  #
707
469
  def output_rows
708
470
  return 24 unless @output.tty?
709
- terminal_size.last
710
- rescue
471
+ terminal.terminal_size.last
472
+ rescue NoMethodError
711
473
  return 24
712
474
  end
713
475
 
714
- private
715
-
716
- def format_statement statement
717
- statement = String(statement || "").dup
718
- return statement unless statement.length > 0
719
-
720
- template = ERB.new(statement, nil, "%")
721
- statement = template.result(binding)
722
-
723
- statement = wrap(statement) unless @wrap_at.nil?
724
- statement = page_print(statement) unless @page_at.nil?
725
-
726
- # 'statement' is encoded in US-ASCII when using ruby 1.9.3(-p551)
727
- # 'indentation' is correctly encoded (same as default_external encoding)
728
- statement = statement.force_encoding(Encoding.default_external)
729
-
730
- statement = statement.gsub(/\n(?!$)/,"\n#{indentation}") if @multi_indent
731
-
732
- statement
476
+ # Call #puts on the HighLine's output stream
477
+ # @param args [String] same args for Kernel#puts
478
+ def puts(*args)
479
+ @output.puts(*args)
733
480
  end
734
481
 
735
482
  #
736
- # A helper method for sending the output stream and error and repeat
737
- # of the question.
483
+ # Creates a new HighLine instance with the same options
738
484
  #
739
- def explain_error( error )
740
- say(@question.responses[error]) unless error.nil?
741
- if @question.responses[:ask_on_error] == :question
742
- say(@question)
743
- elsif @question.responses[:ask_on_error]
744
- say(@question.responses[:ask_on_error])
745
- end
485
+ def new_scope
486
+ self.class.new(@input, @output, @wrap_at,
487
+ @page_at, @indent_size, @indent_level)
746
488
  end
747
489
 
748
- #
749
- # Collects an Array/Hash full of answers as described in
750
- # HighLine::Question.gather().
751
- #
752
- # Raises EOFError if input is exhausted.
753
- #
754
- def gather( )
755
- original_question = @question
756
- original_question_string = @question.question
757
- original_gather = @question.gather
758
-
759
- verify_match = @question.verify_match
760
- @question.gather = false
761
-
762
- begin # when verify_match is set this loop will repeat until unique_answers == 1
763
- @answers = [ ]
764
- @gather = original_gather
765
- original_question.question = original_question_string
766
-
767
- case @gather
768
- when Integer
769
- @answers << ask(@question)
770
- @gather -= 1
771
-
772
- original_question.question = ""
773
- until @gather.zero?
774
- @question = original_question
775
- @answers << ask(@question)
776
- @gather -= 1
777
- end
778
- when ::String, Regexp
779
- @answers << ask(@question)
780
-
781
- original_question.question = ""
782
- until (@gather.is_a?(::String) and @answers.last.to_s == @gather) or
783
- (@gather.is_a?(Regexp) and @answers.last.to_s =~ @gather)
784
- @question = original_question
785
- @answers << ask(@question)
786
- end
787
-
788
- @answers.pop
789
- when Hash
790
- @answers = { }
791
- @gather.keys.sort.each do |key|
792
- @question = original_question
793
- @key = key
794
- @answers[key] = ask(@question)
795
- end
796
- end
797
-
798
- if verify_match && (unique_answers(@answers).size > 1)
799
- @question = original_question
800
- explain_error(:mismatch)
801
- else
802
- verify_match = false
803
- end
804
-
805
- end while verify_match
490
+ private
806
491
 
807
- original_question.verify_match ? @answer : @answers
492
+ # Adds a layer of scope (new_scope) to ask a question inside a
493
+ # question, without destroying instance data
494
+ def confirm(question)
495
+ new_scope.agree(question.confirm_question(self))
808
496
  end
809
497
 
810
498
  #
@@ -812,10 +500,25 @@ class HighLine
812
500
  # for finding whether a list of answers match or differ
813
501
  # from each other.
814
502
  #
815
- def unique_answers(list = @answers)
503
+ def unique_answers(list)
816
504
  (list.respond_to?(:values) ? list.values : list).uniq
817
505
  end
818
506
 
507
+ def last_answer(answers)
508
+ answers.respond_to?(:values) ? answers.values.last : answers.last
509
+ end
510
+
511
+ # Get response one line at time
512
+ # @param question [Question]
513
+ # @return [String] response
514
+ def get_response_line_mode(question)
515
+ if question.echo == true && !question.limit
516
+ get_line(question)
517
+ else
518
+ get_line_raw_no_echo_mode(question)
519
+ end
520
+ end
521
+
819
522
  #
820
523
  # Read a line of input from the input stream and process whitespace as
821
524
  # requested by the Question object.
@@ -825,224 +528,123 @@ class HighLine
825
528
  #
826
529
  # Raises EOFError if input is exhausted.
827
530
  #
828
- def get_line( )
829
- if @question.readline
830
- require "readline" # load only if needed
831
-
832
- # capture say()'s work in a String to feed to readline()
833
- old_output = @output
834
- @output = StringIO.new
835
- say(@question)
836
- question = @output.string
837
- @output = old_output
838
-
839
- # prep auto-completion
840
- Readline.completion_proc = lambda do |string|
841
- @question.selection.grep(/\A#{Regexp.escape(string)}/)
842
- end
531
+ def get_line(question)
532
+ terminal.get_line(question, self)
533
+ end
534
+
535
+ def get_line_raw_no_echo_mode(question)
536
+ line = ""
537
+
538
+ terminal.raw_no_echo_mode_exec do
539
+ loop do
540
+ character = terminal.get_character
541
+ break unless character
542
+ break if ["\n", "\r"].include? character
843
543
 
844
- # work-around ugly readline() warnings
845
- old_verbose = $VERBOSE
846
- $VERBOSE = nil
847
- raw_answer = Readline.readline(question, true)
848
- if raw_answer.nil?
849
- if @@track_eof
850
- raise EOFError, "The input stream is exhausted."
544
+ # honor backspace and delete
545
+ if character == "\b" || character == "\u007F"
546
+ chopped = line.chop!
547
+ output_erase_char if chopped && question.echo
548
+ elsif character == "\e"
549
+ ignore_arrow_key
851
550
  else
852
- raw_answer = String.new # Never return nil
551
+ line << character
552
+ say_last_char_or_echo_char(line, question)
853
553
  end
854
- end
855
- answer = @question.change_case(
856
- @question.remove_whitespace(raw_answer))
857
- $VERBOSE = old_verbose
858
554
 
859
- answer
860
- else
861
- if JRUBY
862
- statement = format_statement(@question)
863
- raw_answer = @java_console.readLine(statement, nil)
555
+ @output.flush
864
556
 
865
- raise EOFError, "The input stream is exhausted." if raw_answer.nil? and
866
- @@track_eof
867
- else
868
- raise EOFError, "The input stream is exhausted." if @@track_eof and
869
- @input.eof?
870
- raw_answer = @input.gets
557
+ break if line_overflow_for_question?(line, question)
871
558
  end
872
-
873
- @question.change_case(@question.remove_whitespace(raw_answer))
874
559
  end
875
- end
876
560
 
877
- #
878
- # Return a line or character of input, as requested for this question.
879
- # Character input will be returned as a single character String,
880
- # not an Integer.
881
- #
882
- # This question's _first_answer_ will be returned instead of input, if set.
883
- #
884
- # Raises EOFError if input is exhausted.
885
- #
886
- def get_response( )
887
- return @question.first_answer if @question.first_answer?
561
+ say_new_line_or_overwrite(question)
888
562
 
889
- if @question.character.nil?
890
- if @question.echo == true and @question.limit.nil?
891
- get_line
892
- else
893
- raw_no_echo_mode
894
-
895
- line = "".encode(Encoding::BINARY)
896
- backspace_limit = 0
897
- begin
898
-
899
- while character = get_character(@input)
900
- # honor backspace and delete
901
- if character == 127 or character == 8
902
- line = line.force_encoding(Encoding.default_external)
903
- line.slice!(-1, 1)
904
- backspace_limit -= 1
905
- line = line.force_encoding(Encoding::BINARY)
906
- else
907
- line << character.chr
908
- backspace_limit = line.dup.force_encoding(Encoding.default_external).size
909
- end
910
- # looking for carriage return (decimal 13) or
911
- # newline (decimal 10) in raw input
912
- break if character == 13 or character == 10
913
- if @question.echo != false
914
- if character == 127 or character == 8
915
- # only backspace if we have characters on the line to
916
- # eliminate, otherwise we'll tromp over the prompt
917
- if backspace_limit >= 0 then
918
- @output.print("\b#{HighLine.Style(:erase_char).code}")
919
- else
920
- # do nothing
921
- end
922
- else
923
- line_with_next_char_encoded = line.dup.force_encoding(Encoding.default_external)
924
- # For multi-byte character, does this
925
- # last character completes the character?
926
- # Then print it.
927
- if line_with_next_char_encoded.valid_encoding?
928
- if @question.echo == true
929
- @output.print(line_with_next_char_encoded[-1])
930
- else
931
- @output.print(@question.echo)
932
- end
933
- end
934
- end
935
- @output.flush
936
- end
937
- break if @question.limit and line.size == @question.limit
938
- end
939
- ensure
940
- restore_mode
941
- end
942
- if @question.overwrite
943
- @output.print("\r#{HighLine.Style(:erase_line).code}")
944
- @output.flush
945
- else
946
- say("\n")
947
- end
563
+ question.format_answer(line)
564
+ end
948
565
 
949
- @question.change_case(@question.remove_whitespace(line.force_encoding(Encoding.default_external)))
950
- end
566
+ def say_new_line_or_overwrite(question)
567
+ if question.overwrite
568
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
569
+ @output.flush
951
570
  else
952
- if JRUBY #prompt has not been shown
953
- say @question
954
- end
955
-
956
- raw_no_echo_mode
957
- begin
958
- if @question.character == :getc
959
- response = @input.getbyte.chr
960
- else
961
- response = get_character(@input).chr
962
- if @question.overwrite
963
- @output.print("\r#{HighLine.Style(:erase_line).code}")
964
- @output.flush
965
- else
966
- echo = if @question.echo == true
967
- response
968
- elsif @question.echo != false
969
- @question.echo
970
- else
971
- ""
972
- end
973
- say("#{echo}\n")
974
- end
975
- end
976
- ensure
977
- restore_mode
978
- end
979
- @question.change_case(response)
571
+ say("\n")
980
572
  end
981
573
  end
982
574
 
983
- #
984
- # Page print a series of at most _page_at_ lines for _output_. After each
985
- # page is printed, HighLine will pause until the user presses enter/return
986
- # then display the next page of data.
987
- #
988
- # Note that the final page of _output_ is *not* printed, but returned
989
- # instead. This is to support any special handling for the final sequence.
990
- #
991
- def page_print( output )
992
- lines = output.lines.to_a
993
- while lines.size > @page_at
994
- @output.puts lines.slice!(0...@page_at).join
995
- @output.puts
996
- # Return last line if user wants to abort paging
997
- return "...\n#{lines.last}" unless continue_paging?
575
+ def ignore_arrow_key
576
+ 2.times do
577
+ terminal.get_character
998
578
  end
999
- return lines.join
1000
579
  end
1001
580
 
1002
- #
1003
- # Ask user if they wish to continue paging output. Allows them to type "q" to
1004
- # cancel the paging process.
1005
- #
1006
- def continue_paging?
1007
- command = HighLine.new(@input, @output).ask(
1008
- "-- press enter/return to continue or q to stop -- "
1009
- ) { |q| q.character = true }
1010
- command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
581
+ def say_last_char_or_echo_char(line, question)
582
+ @output.print(line[-1]) if question.echo == true
583
+ @output.print(question.echo) if question.echo && question.echo != true
1011
584
  end
1012
585
 
1013
- #
1014
- # Wrap a sequence of _lines_ at _wrap_at_ characters per line. Existing
1015
- # newlines will not be affected by this process, but additional newlines
1016
- # may be added.
1017
- #
1018
- def wrap( text )
1019
- wrapped = [ ]
1020
- text.each_line do |line|
1021
- # take into account color escape sequences when wrapping
1022
- wrap_at = @wrap_at + (line.length - actual_length(line))
1023
- while line =~ /([^\n]{#{wrap_at + 1},})/
1024
- search = $1.dup
1025
- replace = $1.dup
1026
- if index = replace.rindex(" ", wrap_at)
1027
- replace[index, 1] = "\n"
1028
- replace.sub!(/\n[ \t]+/, "\n")
1029
- line.sub!(search, replace)
1030
- else
1031
- line[$~.begin(1) + wrap_at, 0] = "\n"
1032
- end
586
+ def line_overflow_for_question?(line, question)
587
+ question.limit && line.size == question.limit
588
+ end
589
+
590
+ def output_erase_char
591
+ @output.print("\b#{HighLine.Style(:erase_char).code}")
592
+ end
593
+
594
+ # Get response using #getc
595
+ # @param question [Question]
596
+ # @return [String] response
597
+ def get_response_getc_mode(question)
598
+ terminal.raw_no_echo_mode_exec do
599
+ response = @input.getc
600
+ question.format_answer(response)
601
+ end
602
+ end
603
+
604
+ # Get response each character per turn
605
+ # @param question [Question]
606
+ # @return [String] response
607
+ def get_response_character_mode(question)
608
+ terminal.raw_no_echo_mode_exec do
609
+ response = terminal.get_character
610
+ if question.overwrite
611
+ erase_current_line
612
+ else
613
+ echo = question.get_echo_for_response(response)
614
+ say("#{echo}\n")
1033
615
  end
1034
- wrapped << line
616
+ question.format_answer(response)
1035
617
  end
1036
- return wrapped.join
1037
618
  end
1038
619
 
620
+ def erase_current_line
621
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
622
+ @output.flush
623
+ end
624
+
625
+ public :get_response_character_mode, :get_response_line_mode
626
+ public :get_response_getc_mode
627
+
628
+ def actual_length(text)
629
+ Wrapper.actual_length text
630
+ end
631
+
632
+ # Check to see if there's already a HighLine.default_instance or if
633
+ # this is the first time the method is called (eg: at
634
+ # HighLine.default_instance initialization).
635
+ # If there's already one, copy use_color settings.
636
+ # This is here most to help migrate code from HighLine 1.7.x to 2.0.x
1039
637
  #
1040
- # Returns the length of the passed +string_with_escapes+, minus and color
1041
- # sequence escapes.
1042
- #
1043
- def actual_length( string_with_escapes )
1044
- string_with_escapes.to_s.gsub(/\e\[\d{1,2}m/, "").length
638
+ # @return [Boolean]
639
+ def default_use_color
640
+ if HighLine.default_instance
641
+ HighLine.default_instance.use_color
642
+ else
643
+ true
644
+ end
1045
645
  end
1046
646
  end
1047
647
 
1048
- require "highline/string_extensions"
648
+ HighLine.default_instance = HighLine.new
649
+
650
+ require "highline/string"