highline 1.7.10 → 2.0.0.pre.develop.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.simplecov +5 -0
  4. data/.travis.yml +11 -6
  5. data/Changelog.md +112 -20
  6. data/Gemfile +8 -7
  7. data/README.rdoc +3 -0
  8. data/Rakefile +7 -2
  9. data/appveyor.yml +19 -0
  10. data/examples/asking_for_arrays.rb +3 -0
  11. data/examples/basic_usage.rb +3 -0
  12. data/examples/get_character.rb +3 -0
  13. data/examples/limit.rb +3 -0
  14. data/examples/menus.rb +3 -0
  15. data/examples/overwrite.rb +3 -0
  16. data/examples/password.rb +3 -0
  17. data/examples/repeat_entry.rb +4 -1
  18. data/lib/highline.rb +182 -704
  19. data/lib/highline/builtin_styles.rb +109 -0
  20. data/lib/highline/color_scheme.rb +4 -1
  21. data/lib/highline/compatibility.rb +2 -0
  22. data/lib/highline/custom_errors.rb +19 -0
  23. data/lib/highline/import.rb +4 -2
  24. data/lib/highline/list.rb +93 -0
  25. data/lib/highline/list_renderer.rb +232 -0
  26. data/lib/highline/menu.rb +20 -20
  27. data/lib/highline/paginator.rb +43 -0
  28. data/lib/highline/question.rb +157 -97
  29. data/lib/highline/question/answer_converter.rb +84 -0
  30. data/lib/highline/question_asker.rb +147 -0
  31. data/lib/highline/simulate.rb +5 -1
  32. data/lib/highline/statement.rb +58 -0
  33. data/lib/highline/string.rb +34 -0
  34. data/lib/highline/string_extensions.rb +3 -28
  35. data/lib/highline/style.rb +18 -8
  36. data/lib/highline/template_renderer.rb +38 -0
  37. data/lib/highline/terminal.rb +78 -0
  38. data/lib/highline/terminal/io_console.rb +98 -0
  39. data/lib/highline/terminal/ncurses.rb +38 -0
  40. data/lib/highline/terminal/unix_stty.rb +94 -0
  41. data/lib/highline/version.rb +3 -1
  42. data/lib/highline/wrapper.rb +43 -0
  43. data/test/acceptance/acceptance.rb +62 -0
  44. data/test/acceptance/acceptance_test.rb +69 -0
  45. data/test/acceptance/at_color_output_using_erb_templates.rb +17 -0
  46. data/test/acceptance/at_echo_false.rb +23 -0
  47. data/test/acceptance/at_readline.rb +37 -0
  48. data/test/io_console_compatible.rb +37 -0
  49. data/test/string_methods.rb +3 -0
  50. data/test/test_answer_converter.rb +26 -0
  51. data/test/{tc_color_scheme.rb → test_color_scheme.rb} +7 -9
  52. data/test/test_helper.rb +26 -0
  53. data/test/{tc_highline.rb → test_highline.rb} +193 -136
  54. data/test/{tc_import.rb → test_import.rb} +5 -2
  55. data/test/test_list.rb +60 -0
  56. data/test/{tc_menu.rb → test_menu.rb} +6 -3
  57. data/test/test_paginator.rb +73 -0
  58. data/test/test_question_asker.rb +20 -0
  59. data/test/test_simulator.rb +24 -0
  60. data/test/test_string_extension.rb +72 -0
  61. data/test/{tc_string_highline.rb → test_string_highline.rb} +7 -3
  62. data/test/{tc_style.rb → test_style.rb} +70 -35
  63. data/test/test_wrapper.rb +188 -0
  64. metadata +57 -22
  65. data/lib/highline/system_extensions.rb +0 -254
  66. data/test/tc_simulator.rb +0 -33
  67. data/test/tc_string_extension.rb +0 -33
@@ -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.
@@ -136,7 +139,7 @@ class HighLine
136
139
  def choice( name, help = nil, &action )
137
140
  @items << [name, action]
138
141
 
139
- @help[name.to_s.downcase] = help unless help.nil?
142
+ @help[name.to_s.downcase] = help if help
140
143
  update_responses # rebuild responses based on our settings
141
144
  end
142
145
 
@@ -154,7 +157,7 @@ class HighLine
154
157
  def hidden( name, help = nil, &action )
155
158
  @hidden_items << [name, action]
156
159
 
157
- @help[name.to_s.downcase] = help unless help.nil?
160
+ @help[name.to_s.downcase] = help if help
158
161
  end
159
162
 
160
163
  #
@@ -235,11 +238,10 @@ class HighLine
235
238
  # <tt>:menu_only</tt>:: Just the menu items, followed up by a likely
236
239
  # short _prompt_.
237
240
  # <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.
241
+ # String can access <tt>header</tt>,
242
+ # <tt>menu</tt> and <tt>prompt</tt>, but is
243
+ # otherwise evaluated in the TemplateRenderer
244
+ # context so each method is properly delegated.
243
245
  #
244
246
  # If set to either <tt>:one_line</tt>, or <tt>:menu_only</tt>, _index_
245
247
  # will default to <tt>:none</tt> and _flow_ will default to
@@ -304,7 +306,7 @@ class HighLine
304
306
  end
305
307
 
306
308
  # Run or return it.
307
- if not action.nil?
309
+ if action
308
310
  @highline = highline_context
309
311
  if @shell
310
312
  result = action.call(name, details)
@@ -312,10 +314,8 @@ class HighLine
312
314
  result = action.call(name)
313
315
  end
314
316
  @nil_on_handled ? nil : result
315
- elsif action.nil?
316
- name
317
317
  else
318
- nil
318
+ name
319
319
  end
320
320
  ensure
321
321
  # make sure the hidden items are removed, before we return
@@ -348,19 +348,19 @@ class HighLine
348
348
  def to_s( )
349
349
  case @layout
350
350
  when :list
351
- '<%= if @header.nil? then '' else "#{@header}:\n" end %>' +
352
- "<%= list( @menu, #{@flow.inspect},
351
+ %(<%= header ? "#{header}:\n" : '' %>) +
352
+ "<%= list( menu, #{@flow.inspect},
353
353
  #{@list_option.inspect} ) %>" +
354
- "<%= @prompt %>"
354
+ "<%= prompt %>"
355
355
  when :one_line
356
- '<%= if @header.nil? then '' else "#{@header}: " end %>' +
357
- "<%= @prompt %>" +
358
- "(<%= list( @menu, #{@flow.inspect},
356
+ %(<%= header ? "#{header}: " : '' %>) +
357
+ "<%= prompt %>" +
358
+ "(<%= list( menu, #{@flow.inspect},
359
359
  #{@list_option.inspect} ) %>)" +
360
- "<%= @prompt[/\s*$/] %>"
360
+ "<%= prompt[/\s*$/] %>"
361
361
  when :menu_only
362
- "<%= list( @menu, #{@flow.inspect},
363
- #{@list_option.inspect} ) %><%= @prompt %>"
362
+ "<%= list( menu, #{@flow.inspect},
363
+ #{@list_option.inspect} ) %><%= prompt %>"
364
364
  else
365
365
  @layout
366
366
  end
@@ -0,0 +1,43 @@
1
+ # coding: utf-8
2
+
3
+ class HighLine
4
+ class Paginator
5
+ attr_reader :highline
6
+
7
+ def initialize(highline)
8
+ @highline = highline
9
+ end
10
+
11
+ #
12
+ # Page print a series of at most _page_at_ lines for _output_. After each
13
+ # page is printed, HighLine will pause until the user presses enter/return
14
+ # then display the next page of data.
15
+ #
16
+ # Note that the final page of _output_ is *not* printed, but returned
17
+ # instead. This is to support any special handling for the final sequence.
18
+ #
19
+ def page_print(text)
20
+ return text unless highline.page_at
21
+
22
+ lines = text.lines.to_a
23
+ while lines.size > highline.page_at
24
+ highline.puts lines.slice!(0...highline.page_at).join
25
+ highline.puts
26
+ # Return last line if user wants to abort paging
27
+ return "...\n#{lines.last}" unless continue_paging?
28
+ end
29
+ return lines.join
30
+ end
31
+
32
+ #
33
+ # Ask user if they wish to continue paging output. Allows them to type "q" to
34
+ # cancel the paging process.
35
+ #
36
+ def continue_paging?
37
+ command = highline.new_scope.ask(
38
+ "-- press enter/return to continue or q to stop -- "
39
+ ) { |q| q.character = true }
40
+ command !~ /\A[qQ]\Z/ # Only continue paging if Q was not hit.
41
+ end
42
+ end
43
+ end
@@ -1,3 +1,6 @@
1
+ # coding: utf-8
2
+
3
+ #--
1
4
  # question.rb
2
5
  #
3
6
  # Created by James Edward Gray II on 2005-04-26.
@@ -8,6 +11,7 @@
8
11
  require "optparse"
9
12
  require "date"
10
13
  require "pathname"
14
+ require "highline/question/answer_converter"
11
15
 
12
16
  class HighLine
13
17
  #
@@ -17,23 +21,32 @@ class HighLine
17
21
  # process is handled according to the users wishes.
18
22
  #
19
23
  class Question
20
- # An internal HighLine error. User code does not need to trap this.
21
- class NoAutoCompleteMatch < StandardError
22
- # do nothing, just creating a unique error type
24
+ include CustomErrors
25
+
26
+ #
27
+ # If _template_or_question_ is already a Question object just return it.
28
+ # If not, build it.
29
+ #
30
+ def self.build(template_or_question, answer_type = nil, &details)
31
+ if template_or_question.is_a? Question
32
+ template_or_question
33
+ else
34
+ Question.new(template_or_question, answer_type, &details)
35
+ end
23
36
  end
24
37
 
25
38
  #
26
- # Create an instance of HighLine::Question. Expects a _question_ to ask
39
+ # Create an instance of HighLine::Question. Expects a _template_ to ask
27
40
  # (can be <tt>""</tt>) and an _answer_type_ to convert the answer to.
28
41
  # The _answer_type_ parameter must be a type recognized by
29
42
  # Question.convert(). If given, a block is yielded the new Question
30
43
  # object to allow custom initialization.
31
44
  #
32
- def initialize( question, answer_type )
45
+ def initialize(template, answer_type)
33
46
  # initialize instance data
34
- @question = String(question).dup
47
+ @template = template.dup
35
48
  @answer_type = answer_type
36
- @completion = @answer_type
49
+ @completion = @answer_type
37
50
 
38
51
  @character = nil
39
52
  @limit = nil
@@ -63,7 +76,11 @@ class HighLine
63
76
  end
64
77
 
65
78
  # The ERb template of the question to be asked.
66
- attr_accessor :question
79
+ attr_accessor :template
80
+
81
+ # The answer, set by HighLine#ask
82
+ attr_accessor :answer
83
+
67
84
  # The type that will be used to convert this answer.
68
85
  attr_accessor :answer_type
69
86
  # For Auto-completion
@@ -214,12 +231,9 @@ class HighLine
214
231
  # Returns the provided _answer_string_ or the default answer for this
215
232
  # Question if a default was set and the answer is empty.
216
233
  #
217
- def answer_or_default( answer_string )
218
- if answer_string.length == 0 and not @default.nil?
219
- @default
220
- else
221
- answer_string
222
- end
234
+ def answer_or_default(answer_string)
235
+ return @default if answer_string.empty? && @default
236
+ answer_string
223
237
  end
224
238
 
225
239
  #
@@ -230,35 +244,28 @@ class HighLine
230
244
  def build_responses(message_source = answer_type, new_hash_wins = false)
231
245
  append_default if [::String, Symbol].include? default.class
232
246
 
233
- choice_error_str_func = lambda do
234
- message_source.is_a?(Array) \
235
- ? '[' + message_source.map { |s| "#{s}" }.join(', ') + ']' \
236
- : message_source.inspect
237
- end
238
-
239
247
  old_hash = @responses
240
248
 
241
- new_hash = { :ambiguous_completion =>
242
- "Ambiguous choice. Please choose one of " +
243
- choice_error_str_func.call + '.',
244
- :ask_on_error =>
245
- "? ",
246
- :invalid_type =>
247
- "You must enter a valid #{message_source}.",
248
- :no_completion =>
249
- "You must choose one of " + choice_error_str_func.call + '.',
250
- :not_in_range =>
251
- "Your answer isn't within the expected range " +
252
- "(#{expected_range}).",
253
- :mismatch =>
254
- "Your entries didn't match.",
255
- :not_valid =>
256
- "Your answer isn't valid (must match " +
257
- "#{@validate.inspect})." }
249
+ new_hash = build_responses_new_hash(message_source)
258
250
 
259
251
  @responses = new_hash_wins ? old_hash.merge(new_hash) : new_hash.merge(old_hash)
260
252
  end
261
253
 
254
+ def build_responses_new_hash(message_source)
255
+ { :ambiguous_completion => "Ambiguous choice. Please choose one of " +
256
+ choice_error_str(message_source) + '.',
257
+ :ask_on_error => "? ",
258
+ :invalid_type => "You must enter a valid #{message_source}.",
259
+ :no_completion => "You must choose one of " +
260
+ choice_error_str(message_source) + '.',
261
+ :not_in_range => "Your answer isn't within the expected range " +
262
+ "(#{expected_range}).",
263
+ :mismatch => "Your entries didn't match.",
264
+ :not_valid => "Your answer isn't valid (must match " +
265
+ "#{@validate.inspect})." }
266
+ end
267
+
268
+
262
269
  #
263
270
  # Returns the provided _answer_string_ after changing character case by
264
271
  # the rules of this Question. Valid settings for whitespace are:
@@ -273,7 +280,7 @@ class HighLine
273
280
  #
274
281
  # An unrecognized choice (like <tt>:none</tt>) is treated as +nil+.
275
282
  #
276
- def change_case( answer_string )
283
+ def change_case(answer_string)
277
284
  if [:up, :upcase].include?(@case)
278
285
  answer_string.upcase
279
286
  elsif [:down, :downcase].include?(@case)
@@ -314,46 +321,30 @@ class HighLine
314
321
  # This method throws ArgumentError, if the conversion cannot be
315
322
  # completed for any reason.
316
323
  #
317
- def convert( answer_string )
318
- if @answer_type.nil?
319
- answer_string
320
- elsif [::String, HighLine::String].include?(@answer_type)
321
- HighLine::String(answer_string)
322
- elsif [Float, Integer, String].include?(@answer_type)
323
- Kernel.send(@answer_type.to_s.to_sym, answer_string)
324
- elsif @answer_type == Symbol
325
- answer_string.to_sym
326
- elsif @answer_type == Regexp
327
- Regexp.new(answer_string)
328
- elsif @answer_type.is_a?(Array) or [File, Pathname].include?(@answer_type)
329
- # cheating, using OptionParser's Completion module
330
- choices = selection
331
- choices.extend(OptionParser::Completion)
332
- answer = choices.complete(answer_string)
333
- if answer.nil?
334
- raise NoAutoCompleteMatch
335
- end
336
- if @answer_type.is_a?(Array)
337
- answer.last
338
- elsif @answer_type == File
339
- File.open(File.join(@directory.to_s, answer.last))
340
- else
341
- Pathname.new(File.join(@directory.to_s, answer.last))
342
- end
343
- elsif [Date, DateTime].include?(@answer_type) or @answer_type.is_a?(Class)
344
- @answer_type.parse(answer_string)
345
- elsif @answer_type.is_a?(Proc)
346
- @answer_type[answer_string]
347
- end
324
+ def convert
325
+ AnswerConverter.new(self).convert
326
+ end
327
+
328
+ def check_range
329
+ raise NotInRangeQuestionError unless in_range?
330
+ end
331
+
332
+ def choices_complete(answer_string)
333
+ # cheating, using OptionParser's Completion module
334
+ choices = selection
335
+ choices.extend(OptionParser::Completion)
336
+ answer = choices.complete(answer_string)
337
+ raise NoAutoCompleteMatch unless answer
338
+ answer
348
339
  end
349
340
 
350
341
  # Returns an English explanation of the current range settings.
351
- def expected_range( )
342
+ def expected_range
352
343
  expected = [ ]
353
344
 
354
- expected << "above #{@above}" unless @above.nil?
355
- expected << "below #{@below}" unless @below.nil?
356
- expected << "included in #{@in.inspect}" unless @in.nil?
345
+ expected << "above #{@above}" if @above
346
+ expected << "below #{@below}" if @below
347
+ expected << "included in #{@in.inspect}" if @in
357
348
 
358
349
  case expected.size
359
350
  when 0 then ""
@@ -364,15 +355,15 @@ class HighLine
364
355
  end
365
356
 
366
357
  # Returns _first_answer_, which will be unset following this call.
367
- def first_answer( )
358
+ def first_answer
368
359
  @first_answer
369
360
  ensure
370
361
  @first_answer = nil
371
362
  end
372
363
 
373
364
  # Returns true if _first_answer_ is set.
374
- def first_answer?( )
375
- not @first_answer.nil?
365
+ def first_answer?
366
+ !!@first_answer
376
367
  end
377
368
 
378
369
  #
@@ -381,10 +372,10 @@ class HighLine
381
372
  # _in_ attribute. Otherwise, +false+ is returned. Any +nil+ attributes
382
373
  # are not checked.
383
374
  #
384
- def in_range?( answer_object )
385
- (@above.nil? or answer_object > @above) and
386
- (@below.nil? or answer_object < @below) and
387
- (@in.nil? or @in.include?(answer_object))
375
+ def in_range?
376
+ (!@above or answer > @above) and
377
+ (!@below or answer < @below) and
378
+ (!@in or @in.include?(answer))
388
379
  end
389
380
 
390
381
  #
@@ -406,8 +397,8 @@ class HighLine
406
397
  #
407
398
  # This process is skipped for single character input.
408
399
  #
409
- def remove_whitespace( answer_string )
410
- if @whitespace.nil?
400
+ def remove_whitespace(answer_string)
401
+ if !@whitespace
411
402
  answer_string
412
403
  elsif [:strip, :chomp].include?(@whitespace)
413
404
  answer_string.send(@whitespace)
@@ -423,12 +414,18 @@ class HighLine
423
414
  end
424
415
  end
425
416
 
417
+ def format_answer(answer_string)
418
+ answer_string = String(answer_string)
419
+ answer_string = remove_whitespace(answer_string)
420
+ change_case(answer_string)
421
+ end
422
+
426
423
  #
427
424
  # Returns an Array of valid answers to this question. These answers are
428
425
  # only known when _answer_type_ is set to an Array of choices, File, or
429
426
  # Pathname. Any other time, this method will return an empty Array.
430
427
  #
431
- def selection( )
428
+ def selection
432
429
  if @completion.is_a?(Array)
433
430
  @completion
434
431
  elsif [File, Pathname].include?(@completion)
@@ -440,9 +437,9 @@ class HighLine
440
437
  end
441
438
  end
442
439
 
443
- # Stringifies the question to be asked.
440
+ # Stringifies the template to be asked.
444
441
  def to_s
445
- @question
442
+ @template
446
443
  end
447
444
 
448
445
  #
@@ -452,10 +449,65 @@ class HighLine
452
449
  # It's important to realize that an answer is validated after whitespace
453
450
  # and case handling.
454
451
  #
455
- def valid_answer?( answer_string )
456
- @validate.nil? or
457
- (@validate.is_a?(Regexp) and answer_string =~ @validate) or
458
- (@validate.is_a?(Proc) and @validate[answer_string])
452
+ def valid_answer?
453
+ !@validate or
454
+ (@validate.is_a?(Regexp) and answer =~ @validate) or
455
+ (@validate.is_a?(Proc) and @validate[answer])
456
+ end
457
+
458
+ #
459
+ # Return a line or character of input, as requested for this question.
460
+ # Character input will be returned as a single character String,
461
+ # not an Integer.
462
+ #
463
+ # This question's _first_answer_ will be returned instead of input, if set.
464
+ #
465
+ # Raises EOFError if input is exhausted.
466
+ #
467
+ def get_response(highline)
468
+ return first_answer if first_answer?
469
+
470
+ case character
471
+ when :getc
472
+ highline.get_response_getc_mode(self)
473
+ when true
474
+ highline.get_response_character_mode(self)
475
+ else
476
+ highline.get_response_line_mode(self)
477
+ end
478
+ end
479
+
480
+ def get_response_or_default(highline)
481
+ self.answer = answer_or_default(get_response(highline))
482
+ end
483
+
484
+ def confirm_question(highline)
485
+ if confirm == true
486
+ "Are you sure? "
487
+ else
488
+ # evaluate ERb under initial scope, so it will have
489
+ # access to question and answer
490
+ template = ERB.new(confirm, nil, "%")
491
+ template_renderer = TemplateRenderer.new(template, self, highline)
492
+ template_renderer.render
493
+ end
494
+ end
495
+
496
+ def ask_on_error_msg
497
+ if responses[:ask_on_error] == :question
498
+ self
499
+ elsif responses[:ask_on_error]
500
+ responses[:ask_on_error]
501
+ end
502
+ end
503
+
504
+ # readline() needs to handle its own output, but readline only supports
505
+ # full line reading. Therefore if question.echo is anything but true,
506
+ # the prompt will not be issued. And we have to account for that now.
507
+ # Also, JRuby-1.7's ConsoleReader.readLine() needs to be passed the prompt
508
+ # to handle line editing properly.
509
+ def show_question(highline)
510
+ highline.say(self) unless (@readline && (@echo == true && !@limit))
459
511
  end
460
512
 
461
513
  private
@@ -465,15 +517,23 @@ class HighLine
465
517
  # Trailing whitespace is preserved so the function of HighLine.say() is
466
518
  # not affected.
467
519
  #
468
- def append_default( )
469
- if @question =~ /([\t ]+)\Z/
470
- @question << "|#{@default}|#{$1}"
471
- elsif @question == ""
472
- @question << "|#{@default}| "
473
- elsif @question[-1, 1] == "\n"
474
- @question[-2, 0] = " |#{@default}|"
520
+ def append_default
521
+ if @template =~ /([\t ]+)\Z/
522
+ @template << "|#{@default}|#{$1}"
523
+ elsif @template == ""
524
+ @template << "|#{@default}| "
525
+ elsif @template[-1, 1] == "\n"
526
+ @template[-2, 0] = " |#{@default}|"
527
+ else
528
+ @template << " |#{@default}|"
529
+ end
530
+ end
531
+
532
+ def choice_error_str(message_source)
533
+ if message_source.is_a? Array
534
+ '[' + message_source.join(', ') + ']'
475
535
  else
476
- @question << " |#{@default}|"
536
+ message_source.inspect
477
537
  end
478
538
  end
479
539
  end