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/menu.rb CHANGED
@@ -1,3 +1,6 @@
1
+ # coding: utf-8
2
+
3
+ #--
1
4
  # menu.rb
2
5
  #
3
6
  # Created by Gregory Thomas Brown on 2005-05-10.
@@ -6,30 +9,55 @@
6
9
  # This is Free Software. See LICENSE and COPYING for details.
7
10
 
8
11
  require "highline/question"
12
+ require "highline/menu/item"
9
13
 
10
14
  class HighLine
11
15
  #
12
- # Menu objects encapsulate all the details of a call to HighLine.choose().
13
- # Using the accessors and Menu.choice() and Menu.choices(), the block passed
14
- # to HighLine.choose() can detail all aspects of menu display and control.
16
+ # Menu objects encapsulate all the details of a call to
17
+ # {HighLine#choose HighLine#choose}.
18
+ # Using the accessors and {Menu#choice} and {Menu#choices}, the block passed
19
+ # to {HighLine#choose} can detail all aspects of menu display and control.
15
20
  #
16
21
  class Menu < Question
22
+ # Pass +false+ to _color_ to turn off HighLine::Menu's
23
+ # index coloring.
24
+ # Pass a color and the Menu's indices will be colored.
25
+ class << self
26
+ attr_writer :index_color
27
+ end
28
+
29
+ # Initialize it
30
+ self.index_color = false
31
+
32
+ # Returns color used for coloring Menu's indices
33
+ class << self
34
+ attr_reader :index_color
35
+ end
36
+
17
37
  #
18
38
  # Create an instance of HighLine::Menu. All customization is done
19
- # through the passed block, which should call accessors and choice() and
20
- # choices() as needed to define the Menu. Note that Menus are also
21
- # Questions, so all that functionality is available to the block as
22
- # well.
23
- #
24
- def initialize( )
39
+ # through the passed block, which should call accessors, {#choice} and
40
+ # {#choices} as needed to define the Menu. Note that Menus are also
41
+ # {HighLine::Question Questions}, so all that functionality is available
42
+ # to the block as well.
43
+ #
44
+ # @example Implicit menu creation through HighLine#choose
45
+ # cli = HighLine.new
46
+ # answer = cli.choose do |menu|
47
+ # menu.prompt = "Please choose your favorite programming language? "
48
+ # menu.choice(:ruby) { say("Good choice!") }
49
+ # menu.choices(:python, :perl) { say("Not from around here, are you?") }
50
+ # end
51
+
52
+ def initialize
25
53
  #
26
54
  # Initialize Question objects with ignored values, we'll
27
55
  # adjust ours as needed.
28
56
  #
29
- super("Ignored", [ ], &nil) # avoiding passing the block along
57
+ super("Ignored", [], &nil) # avoiding passing the block along
30
58
 
31
- @items = [ ]
32
- @hidden_items = [ ]
59
+ @items = []
60
+ @hidden_items = []
33
61
  @help = Hash.new("There's no help for that topic.")
34
62
 
35
63
  @index = :number
@@ -43,14 +71,18 @@ class HighLine
43
71
  @shell = false
44
72
  @nil_on_handled = false
45
73
 
74
+ # Used for coloring Menu indices.
75
+ # Set it to default. But you may override it.
76
+ @index_color = self.class.index_color
77
+
46
78
  # Override Questions responses, we'll set our own.
47
- @responses = { }
79
+ @responses = {}
48
80
  # Context for action code.
49
81
  @highline = nil
50
82
 
51
83
  yield self if block_given?
52
84
 
53
- init_help if @shell and not @help.empty?
85
+ init_help if @shell && !@help.empty?
54
86
  end
55
87
 
56
88
  #
@@ -119,6 +151,11 @@ class HighLine
119
151
  # Defaults to +false+.
120
152
  #
121
153
  attr_accessor :nil_on_handled
154
+ #
155
+ # The color of the index when displaying the menu. See Style class for
156
+ # available colors.
157
+ #
158
+ attr_accessor :index_color
122
159
 
123
160
  #
124
161
  # Adds _name_ to the list of available menu items. Menu items will be
@@ -133,28 +170,85 @@ class HighLine
133
170
  # the current HighLine context before the action code is called and can
134
171
  # thus be used for adding output and the like.
135
172
  #
136
- def choice( name, help = nil, &action )
137
- @items << [name, action]
173
+ # @param name [#to_s] menu item title/header/name to be displayed.
174
+ # @param action [Proc] callback action to be run when the item is selected.
175
+ # @param help [String] help/hint string to be displayed.
176
+ # @return [void]
177
+ # @example (see HighLine::Menu#initialize)
178
+ # @example Use of help string on menu items
179
+ # cli = HighLine.new
180
+ # cli.choose do |menu|
181
+ # menu.shell = true
182
+ #
183
+ # menu.choice(:load, text: 'Load a file',
184
+ # help: "Load a file using your favourite editor.")
185
+ # menu.choice(:save, help: "Save data in file.")
186
+ # menu.choice(:quit, help: "Exit program.")
187
+ #
188
+ # menu.help("rules", "The rules of this system are as follows...")
189
+ # end
190
+
191
+ def choice(name, help = nil, text = nil, &action)
192
+ item = Menu::Item.new(name, text: text, help: help, action: action)
193
+ @items << item
194
+ @help.merge!(item.item_help)
195
+ update_responses # rebuild responses based on our settings
196
+ end
197
+
198
+ #
199
+ # This method helps reduce the namespaces in the original call,
200
+ # which would look like this: HighLine::Menu::Item.new(...)
201
+ # With #build_item, it looks like this: menu.build_item(...)
202
+ # @param *args splat args, the same args you would pass to an
203
+ # initialization of HighLine::Menu::Item
204
+ # @return [HighLine::Menu::Item] the menu item
205
+
206
+ def build_item(*args)
207
+ Menu::Item.new(*args)
208
+ end
209
+
210
+ #
211
+ # Adds an item directly to the menu. If you want more configuration
212
+ # or options, use this method
213
+ #
214
+ # @param item [Menu::Item] item containing choice fields and more
215
+ # @return [void]
138
216
 
139
- @help[name.to_s.downcase] = help unless help.nil?
140
- update_responses # rebuild responses based on our settings
217
+ def add_item(item)
218
+ @items << item
219
+ @help.merge!(item.item_help)
220
+ update_responses
141
221
  end
142
222
 
143
223
  #
144
- # A shortcut for multiple calls to the sister method choice(). <b>Be
224
+ # A shortcut for multiple calls to the sister method {#choice}. <b>Be
145
225
  # warned:</b> An _action_ set here will apply to *all* provided
146
226
  # _names_. This is considered to be a feature, so you can easily
147
227
  # hand-off interface processing to a different chunk of code.
228
+ # @param names [Array<#to_s>] menu item titles/headers/names to be
229
+ # displayed.
230
+ # @param action (see #choice)
231
+ # @return [void]
232
+ # @example (see HighLine::Menu#initialize)
233
+ #
234
+ # choice has more options available to you, like longer text or help (and
235
+ # of course, individual actions)
148
236
  #
149
- def choices( *names, &action )
237
+ def choices(*names, &action)
150
238
  names.each { |n| choice(n, &action) }
151
239
  end
152
240
 
153
- # Identical to choice(), but the item will not be listed for the user.
154
- def hidden( name, help = nil, &action )
155
- @hidden_items << [name, action]
241
+ # Identical to {#choice}, but the item will not be listed for the user.
242
+ # @see #choice
243
+ # @param name (see #choice)
244
+ # @param help (see #choice)
245
+ # @param action (see #choice)
246
+ # @return (see #choice)
156
247
 
157
- @help[name.to_s.downcase] = help unless help.nil?
248
+ def hidden(name, help = nil, &action)
249
+ item = Menu::Item.new(name, text: name, help: help, action: action)
250
+ @hidden_items << item
251
+ @help.merge!(item.item_help)
158
252
  end
159
253
 
160
254
  #
@@ -172,31 +266,36 @@ class HighLine
172
266
  # _index_suffix_ to a single space and _select_by_ to <tt>:name</tt>.
173
267
  # Because of this, you should make a habit of setting the _index_ first.
174
268
  #
175
- def index=( style )
269
+ def index=(style)
176
270
  @index = style
177
271
 
272
+ return unless @index == :none || @index.is_a?(::String)
273
+
178
274
  # Default settings.
179
- if @index == :none or @index.is_a?(::String)
180
- @index_suffix = " "
181
- @select_by = :name
182
- end
275
+ @index_suffix = " "
276
+ @select_by = :name
183
277
  end
184
278
 
185
279
  #
186
280
  # Initializes the help system by adding a <tt>:help</tt> choice, some
187
281
  # action code, and the default help listing.
188
282
  #
189
- def init_help( )
283
+ def init_help
190
284
  return if @items.include?(:help)
191
285
 
192
286
  topics = @help.keys.sort
193
- help_help = @help.include?("help") ? @help["help"] :
194
- "This command will display helpful messages about " +
195
- "functionality, like this one. To see the help for " +
196
- "a specific topic enter:\n\thelp [TOPIC]\nTry asking " +
197
- "for help on any of the following:\n\n" +
198
- "<%= list(#{topics.inspect}, :columns_across) %>"
199
- choice(:help, help_help) do |command, topic|
287
+ help_help =
288
+ if @help.include?("help")
289
+ @help["help"]
290
+ else
291
+ "This command will display helpful messages about " \
292
+ "functionality, like this one. To see the help for " \
293
+ "a specific topic enter:\n\thelp [TOPIC]\nTry asking " \
294
+ "for help on any of the following:\n\n" \
295
+ "<%= list(#{topics.inspect}, :columns_across) %>"
296
+ end
297
+
298
+ choice(:help, help_help) do |_command, topic|
200
299
  topic.strip!
201
300
  topic.downcase!
202
301
  if topic.empty?
@@ -209,9 +308,13 @@ class HighLine
209
308
 
210
309
  #
211
310
  # Used to set help for arbitrary topics. Use the topic <tt>"help"</tt>
212
- # to override the default message.
311
+ # to override the default message. Mainly for internal use.
213
312
  #
214
- def help( topic, help )
313
+ # @param topic [String] the menu item header/title/name to be associated
314
+ # with a help message.
315
+ # @param help [String] the help message to be associated with the menu
316
+ # item/title/name.
317
+ def help(topic, help)
215
318
  @help[topic] = help
216
319
  end
217
320
 
@@ -235,24 +338,23 @@ class HighLine
235
338
  # <tt>:menu_only</tt>:: Just the menu items, followed up by a likely
236
339
  # short _prompt_.
237
340
  # <i>any ERb String</i>:: Will be taken as the literal _layout_. This
238
- # String can access <tt>@header</tt>,
239
- # <tt>@menu</tt> and <tt>@prompt</tt>, but is
240
- # otherwise evaluated in the typical HighLine
241
- # context, to provide access to utilities like
242
- # HighLine.list() primarily.
341
+ # String can access <tt>header</tt>,
342
+ # <tt>menu</tt> and <tt>prompt</tt>, but is
343
+ # otherwise evaluated in the TemplateRenderer
344
+ # context so each method is properly delegated.
243
345
  #
244
346
  # If set to either <tt>:one_line</tt>, or <tt>:menu_only</tt>, _index_
245
347
  # will default to <tt>:none</tt> and _flow_ will default to
246
348
  # <tt>:inline</tt>.
247
349
  #
248
- def layout=( new_layout )
350
+ def layout=(new_layout)
249
351
  @layout = new_layout
250
352
 
251
353
  # Default settings.
252
354
  case @layout
253
355
  when :one_line, :menu_only
254
356
  self.index = :none
255
- @flow = :inline
357
+ @flow = :inline
256
358
  end
257
359
  end
258
360
 
@@ -260,66 +362,142 @@ class HighLine
260
362
  # This method returns all possible options for auto-completion, based
261
363
  # on the settings of _index_ and _select_by_.
262
364
  #
263
- def options( )
264
- # add in any hidden menu commands
265
- @items.concat(@hidden_items)
266
-
267
- by_index = if @index == :letter
268
- l_index = "`"
269
- @items.map { "#{l_index.succ!}" }
365
+ def options
366
+ case @select_by
367
+ when :index
368
+ map_items_by_index
369
+ when :name
370
+ map_items_by_name
270
371
  else
271
- (1 .. @items.size).collect { |s| String(s) }
372
+ map_items_by_index + map_items_by_name
272
373
  end
273
- by_name = @items.collect { |c| c.first }
374
+ end
274
375
 
275
- case @select_by
276
- when :index then
277
- by_index
278
- when :name
279
- by_name
376
+ def map_items_by_index
377
+ if [:letter, :capital_letter].include?(@index)
378
+ # @ and ` are the previous ASCII characters to A and a respectively
379
+ prev_char = (@index == :capital_letter ? '@' : '`')
380
+ all_items.map { prev_char.succ!.dup }
280
381
  else
281
- by_index + by_name
382
+ (1..all_items.size).map(&:to_s)
282
383
  end
283
- ensure
284
- # make sure the hidden items are removed, before we return
285
- @items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
384
+ end
385
+
386
+ def map_items_by_name
387
+ all_items.map(&:name)
388
+ end
389
+
390
+ def all_items
391
+ @items + @hidden_items
286
392
  end
287
393
 
288
394
  #
289
395
  # This method processes the auto-completed user selection, based on the
290
396
  # rules for this Menu object. If an action was provided for the
291
- # selection, it will be executed as described in Menu.choice().
292
- #
293
- def select( highline_context, selection, details = nil )
397
+ # selection, it will be executed as described in {#choice}.
398
+ #
399
+ # @param highline_context [HighLine] a HighLine instance to be used
400
+ # as context.
401
+ # @param selection [String, Integer] index or title of the selected
402
+ # menu item.
403
+ # @param details additional parameter to be passed when in shell mode.
404
+ # @return [nil, Object] if @nil_on_handled is set it returns +nil+,
405
+ # else it returns the action return value.
406
+ def select(highline_context, selection, details = nil)
294
407
  # add in any hidden menu commands
295
- @items.concat(@hidden_items)
408
+ items = all_items
296
409
 
297
410
  # Find the selected action.
298
- name, action = if selection =~ /^\d+$/
299
- @items[selection.to_i - 1]
411
+ selected_item = find_item_from_selection(items, selection)
412
+
413
+ # Run or return it.
414
+ @highline = highline_context
415
+ value_for_selected_item(selected_item, details)
416
+ end
417
+
418
+ def find_item_from_selection(items, selection)
419
+ if selection =~ /^\d+$/ # is a number?
420
+ get_item_by_number(items, selection)
300
421
  else
301
- l_index = "`"
302
- index = @items.map { "#{l_index.succ!}" }.index(selection)
303
- @items.find { |c| c.first == selection } or @items[index]
422
+ get_item_by_letter(items, selection)
304
423
  end
424
+ end
305
425
 
306
- # Run or return it.
307
- if not action.nil?
308
- @highline = highline_context
309
- if @shell
310
- result = action.call(name, details)
311
- else
312
- result = action.call(name)
313
- end
426
+ # Returns the menu item referenced by its index
427
+ # @param selection [Integer] menu item's index.
428
+ def get_item_by_number(items, selection)
429
+ items[selection.to_i - 1]
430
+ end
431
+
432
+ # Returns the menu item referenced by its title/header/name.
433
+ # @param selection [String] menu's title/header/name
434
+ def get_item_by_letter(items, selection)
435
+ item = items.find { |i| i.name == selection }
436
+ return item if item
437
+
438
+ # 97 is the "a" letter at ascii table
439
+ # Ex: For "a" it will return 0, and for "c" it will return 2
440
+ index = selection.downcase.ord - 97
441
+ items[index]
442
+ end
443
+
444
+ def value_for_selected_item(item, details)
445
+ if item.action
446
+ result = if @shell
447
+ item.action.call(item.name, details)
448
+ else
449
+ item.action.call(item.name)
450
+ end
314
451
  @nil_on_handled ? nil : result
315
- elsif action.nil?
316
- name
317
452
  else
318
- nil
453
+ item.name
454
+ end
455
+ end
456
+
457
+ def gather_selected(highline_context, selections, details = nil)
458
+ @highline = highline_context
459
+ # add in any hidden menu commands
460
+ items = all_items
461
+
462
+ if selections.is_a?(Array)
463
+ value_for_array_selections(items, selections, details)
464
+ elsif selections.is_a?(Hash)
465
+ value_for_hash_selections(items, selections, details)
466
+ else
467
+ raise ArgumentError, "selections must be either Array or Hash"
468
+ end
469
+ end
470
+
471
+ def value_for_array_selections(items, selections, details)
472
+ # Find the selected items and return values
473
+ selected_items = selections.map do |selection|
474
+ find_item_from_selection(items, selection)
475
+ end
476
+ index = 0
477
+ selected_items.map do |selected_item|
478
+ value = value_for_selected_item(selected_item, self.shell ? details[index] : nil)
479
+ index += 1
480
+ value
481
+ end
482
+ end
483
+
484
+ def value_for_hash_selections(items, selections, details)
485
+ # Find the selected items and return in hash form
486
+ index = 0
487
+ selections.each_with_object({}) do |(key, selection), memo|
488
+ selected_item = find_item_from_selection(items, selection)
489
+ value = value_for_selected_item(selected_item, self.shell ? details[index] : nil)
490
+ index += 1
491
+ memo[key] = value
492
+ end
493
+ end
494
+
495
+ def decorate_index(index)
496
+ if index_color
497
+ HighLine.color(index, index_color)
498
+ else
499
+ index
319
500
  end
320
- ensure
321
- # make sure the hidden items are removed, before we return
322
- @items.slice!(@items.size - @hidden_items.size, @hidden_items.size)
323
501
  end
324
502
 
325
503
  #
@@ -327,17 +505,26 @@ class HighLine
327
505
  # This method returns all menu items to be displayed, complete with
328
506
  # indexes.
329
507
  #
330
- def to_ary( )
508
+ def to_ary
509
+ @items.map.with_index { |item, ix| decorate_item(item.text.to_s, ix) }
510
+ end
511
+
512
+ def decorate_item(text, ix)
513
+ decorated, non_decorated = mark_for_decoration(text, ix)
514
+ decorate_index(decorated) + non_decorated
515
+ end
516
+
517
+ def mark_for_decoration(text, ix)
331
518
  case @index
332
519
  when :number
333
- @items.map { |c| "#{@items.index(c) + 1}#{@index_suffix}#{c.first}" }
334
- when :letter
335
- l_index = "`"
336
- @items.map { |c| "#{l_index.succ!}#{@index_suffix}#{c.first}" }
520
+ ["#{ix + 1}#{@index_suffix}", text]
521
+ when :letter, :capital_letter
522
+ first_letter = (@index == :capital_letter ? 'A' : 'a')
523
+ ["#{(first_letter.ord + ix).chr}#{@index_suffix}", text]
337
524
  when :none
338
- @items.map { |c| "#{c.first}" }
525
+ [text, ""]
339
526
  else
340
- @items.map { |c| "#{index}#{@index_suffix}#{c.first}" }
527
+ ["#{index}#{@index_suffix}", text]
341
528
  end
342
529
  end
343
530
 
@@ -345,37 +532,45 @@ class HighLine
345
532
  # Allows Menu to behave as a String, just like Question. Returns the
346
533
  # _layout_ to be rendered, which is used by HighLine.say().
347
534
  #
348
- def to_s( )
535
+ def to_s
349
536
  case @layout
350
537
  when :list
351
- '<%= if @header.nil? then '' else "#{@header}:\n" end %>' +
352
- "<%= list( @menu, #{@flow.inspect},
353
- #{@list_option.inspect} ) %>" +
354
- "<%= @prompt %>"
538
+ %(<%= header ? "#{header}:\n" : '' %>) +
539
+ parse_list +
540
+ show_default_if_any +
541
+ "<%= prompt %>"
355
542
  when :one_line
356
- '<%= if @header.nil? then '' else "#{@header}: " end %>' +
357
- "<%= @prompt %>" +
358
- "(<%= list( @menu, #{@flow.inspect},
359
- #{@list_option.inspect} ) %>)" +
360
- "<%= @prompt[/\s*$/] %>"
543
+ %(<%= header ? "#{header}: " : '' %>) +
544
+ "<%= prompt %>" \
545
+ "(" + parse_list + ")" +
546
+ show_default_if_any +
547
+ "<%= prompt[/\s*$/] %>"
361
548
  when :menu_only
362
- "<%= list( @menu, #{@flow.inspect},
363
- #{@list_option.inspect} ) %><%= @prompt %>"
549
+ parse_list +
550
+ show_default_if_any +
551
+ "<%= prompt %>"
364
552
  else
365
553
  @layout
366
554
  end
367
555
  end
368
556
 
557
+ def parse_list
558
+ "<%= list( menu, #{@flow.inspect},
559
+ #{@list_option.inspect} ) %>"
560
+ end
561
+
562
+ def show_default_if_any
563
+ default.to_s.empty? ? "" : "(#{default}) "
564
+ end
565
+
369
566
  #
370
567
  # This method will update the intelligent responses to account for
371
568
  # Menu specific differences. Calls the superclass' (Question's)
372
569
  # build_responses method, overriding its default arguments to specify
373
- # 'options' will be used to populate choice lists, and that
374
- # the newly built hash will predominate over the preexisting hash
375
- # for any keys that are the same.
570
+ # 'options' will be used to populate choice lists.
376
571
  #
377
- def update_responses( )
378
- build_responses(options, true)
572
+ def update_responses
573
+ build_responses(options)
379
574
  end
380
575
  end
381
576
  end
@@ -0,0 +1,52 @@
1
+ # coding: utf-8
2
+
3
+ class HighLine
4
+ # Take the task of paginating some piece of text given a HighLine context
5
+ class Paginator
6
+ # @return [HighLine] HighLine context
7
+ attr_reader :highline
8
+
9
+ # Returns a HighLine::Paginator instance where you can
10
+ # call {#page_print} on it.
11
+ # @param highline [HighLine] context
12
+ # @example
13
+ # HighLine::Paginator.new(highline).page_print(statement)
14
+ def initialize(highline)
15
+ @highline = highline
16
+ end
17
+
18
+ #
19
+ # Page print a series of at most _page_at_ lines for _output_. After each
20
+ # page is printed, HighLine will pause until the user presses enter/return
21
+ # then display the next page of data.
22
+ #
23
+ # Note that the final page of _output_ is *not* printed, but returned
24
+ # instead. This is to support any special handling for the final sequence.
25
+ #
26
+ # @param text [String] text to be paginated
27
+ # @return [String] last line if paging is aborted
28
+ def page_print(text)
29
+ return text unless highline.page_at
30
+
31
+ lines = text.lines.to_a
32
+ while lines.size > highline.page_at
33
+ highline.puts lines.slice!(0...highline.page_at).join
34
+ highline.puts
35
+ # Return last line if user wants to abort paging
36
+ return "...\n#{lines.last}" unless continue_paging?
37
+ end
38
+ lines.join
39
+ end
40
+
41
+ #
42
+ # Ask user if they wish to continue paging output. Allows them to
43
+ # type "q" to cancel the paging process.
44
+ #
45
+ def continue_paging?
46
+ command = highline.new_scope.ask(
47
+ "-- press enter/return to continue or q to stop -- "
48
+ ) { |q| q.character = true }
49
+ command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
50
+ end
51
+ end
52
+ end