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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6de473a3d81859ef33fbaae779ea5d201215bfcb
4
- data.tar.gz: 18d772cb47ed0d65627f782044a1f01e9c6baa9e
3
+ metadata.gz: 7f330e1d4093d9a373e0471827e2acdf89076eac
4
+ data.tar.gz: 1bba362856956f240a80312fe26e74966790b783
5
5
  SHA512:
6
- metadata.gz: 10d9975025b395161bb73cae4f35e2047d4b935d1906ac91f306907100b6f13fdda740f6fc822de076e1fc08143cd64cd2f08fdcbe7ebece11d0b218945dba0a
7
- data.tar.gz: 17600d300c546a50083e664ba0f76fda1b0e872b3e5409c3a53929dfdd13231a9eaa923270b83a3e98c8b2ce250c8d6cb1cca6c85b56169640f324debf62d755
6
+ metadata.gz: 36049605dccd8943c87037214ca65c02feab5ead5f1ca4579ba5491b0b8b966c9a49377c40f9fb99d53bd5a63ea86ef664c55cae40158cc53bef31b4413249cf
7
+ data.tar.gz: fdd91732e30725625a9197226d70d2a8bf99002c3a029094617bbed2048a3dadc93a3f95012eca5a87a0ca3f0f69ac5fb9007a3afc48cc6d253afaa81b4236d9
data/.gitignore CHANGED
@@ -1,2 +1,4 @@
1
1
  doc
2
2
  pkg
3
+ .DS_Store
4
+ coverage
@@ -0,0 +1,5 @@
1
+ unless SimpleCov.running
2
+ SimpleCov.start do
3
+ add_filter "test_"
4
+ end
5
+ end
@@ -3,15 +3,20 @@ language: ruby
3
3
  sudo: false
4
4
  script: "bundle exec rake test"
5
5
  rvm:
6
- - 1.9.3
7
- - 2.0.0
8
- - 2.1.5
9
- - 2.2.0
10
- - 2.2.1
11
- - 2.2.2
6
+ - 1.9
7
+ - 2.0
8
+ - 2.1
9
+ - 2.2
10
+ - ruby-head
12
11
  - rbx-2
12
+ - jruby-19mode # JRuby in 1.9 mode
13
+ - jruby-head
14
+
13
15
  notifications:
14
16
  email: false
15
17
  matrix:
16
18
  allow_failures:
19
+ - rvm: ruby-head
17
20
  - rvm: rbx-2
21
+ - rvm: jruby-19mode # JRuby in 1.9 mode
22
+ - rvm: jruby-head
@@ -2,26 +2,118 @@
2
2
 
3
3
  Below is a complete listing of changes for each revision of HighLine.
4
4
 
5
- ### 1.7.10 / 2017-11-23
6
- * Add gemspec to Gemfile. Address #223. (@abinoam)
7
-
8
- ### 1.7.9 / 2017-05-08
9
- * Fix frozen string issue on HighLine::Simulate. (Ivan Giuliani (@ivgiuliani), PR #210)
10
-
11
- ### 1.7.8 / 2015-10-09
12
- * Fix some issues when paginating. (Nick Carboni (@carbonin) and Abinoam P. Marques Jr. (@abinoam), #168, PRs #169 #170)
13
-
14
- ### 1.7.7 / 2015-09-22
15
- * Make HighLine::Question coerce its question argument into a String. (@97-109-107 and Abinoam P. Marques Jr. (@abinoam), #159, PR #160)
16
-
17
- ### 1.7.6 / 2015-09-17
18
- * Fix a typo in a var name affecting solaris. (Danek Duvall (@dhduvall) and Abinoam P. Marques Jr. (@abinoam), #155, PR #156)
19
-
20
- ### 1.7.5 / 2015-09-14
21
- * Support jruby9k for system extensions (Michael (@mmmries), PR #153)
22
-
23
- ### 1.7.4 / 2015-06-16
24
- * Workaround on #55 for stty
5
+ ### 2.0.0-develop.2 / 2015-09-09
6
+
7
+ (by Abinoam P. Marques Jr. - @abinoam)
8
+
9
+ #### NOTES
10
+
11
+ This version brings greater compatibility with JRuby and Windows.
12
+ But we still have a lot of small issues in both platforms.
13
+ We were able to unify/converge all approaches into using io/console,
14
+ so we could delete old code that relied solely on stty, termios, java api and
15
+ windows apis (DL and Fiddle).
16
+
17
+ Another improvement is the beginning of what I called "acceptance tests".
18
+ If you type ```rake acceptance``` you'll be guided through some tests
19
+ where you have to input some thing and see if everything work as expected.
20
+ This makes easier to catch bugs that otherwise would be over-sighted.
21
+
22
+ #### CHANGES SUMMARY
23
+
24
+ * Fix Simplecov - it was reporting erroneous code coverage
25
+ * Add new tests. Improves code coverage
26
+ * Extract HighLine::BuiltinStyles
27
+ * Try to avoid nil checking
28
+ * Try to avoid class variables (mis)use
29
+ * Fix RDoc include path and some small fixes to the docs
30
+ * Move HighLine::String to its own file
31
+ * Add HighLine::Terminal::IOConsole
32
+ - Add an IOConsoleCompatibility module with some stubbed
33
+ methods for using at StringIO, File and Tempfile to help
34
+ on tests.
35
+ - Any enviroment that can require 'io/console' will
36
+ use HighLine::Terminal::IOConsole by default. This kind
37
+ of unifies most environments where HighLine runs. For
38
+ example, we can use Terminal::IOConsole on JRuby!!!
39
+ * Add ruby-head and JRuby (19mode and head) to Travis CI matrix. Yes, this
40
+ our first step to a more peaceful JRuby compatibility.
41
+ * Add AppVeyor Continuous Integration for Windows
42
+ * Add _acceptance_ tests for HighLine
43
+ - Use ```rake acceptance``` to run them
44
+ - Basically it interactively asks the user to confirm if
45
+ some expected HighLine behavior is actually happening.
46
+ After that it gather some environment debug information,
47
+ so the use could send to the HighLine contributors in case
48
+ of failure.
49
+ * Remove old and unused files (as a result of relying on io/console)
50
+ - JRuby
51
+ - Windows (DL and Fiddle)
52
+ - Termios
53
+ * Fix some small (old and new) bugs
54
+ * Make some more tuning for Windows compatibility
55
+ * Make some more tuning for JRuby compatibility
56
+
57
+ ### 2.0.0-develop.1 / 2015-06-11
58
+
59
+ This is the first development version of the 2.0.0 series. It's the begining of a refactoring phase on HighLine development cycle.
60
+
61
+ #### SOME HISTORY
62
+
63
+ In 2014 I emailed James Edward Gray II (@JEG2) about HighLine. One of his ideas was to completely refactor the library so that it could be easier to reuse and improve it. I've began my contributions to HighLine trying to fix some of the open issues at that time so that we could "freeze" a stable version of HighLine that people could rely on. Then I've began to study HighLine source code with James' help and started to refactor some parts of the code. Abinoam P. Marques Jr. (@abinoam)
64
+
65
+ #### NOTES
66
+
67
+ * This release differs from current master branch by more than 180 commits.
68
+ * The main changes will be only summarized bellow (as there are many, and a detailed description of each is not productive).
69
+ * You could try `git log -p` to see all of them.
70
+ * During the last commits, all possible efforts were taken to preserve the tests passing status.
71
+ * 100% test passing gives you no guarantee that this new version will work for you. This happens for many reasons. One of them is that we don't currently have 100% test coverage.
72
+ * So, this version is not suitable for use in production.
73
+ * [Metric_fu](https://github.com/metricfu/metric_fu) and [Code Climate](https://codeclimate.com/github/abinoam/highline) were used here not to strictly "guide" what should be changed, but to have some way to objectively measure the progresses made so far.
74
+
75
+ #### CHANGES SUMMARY
76
+ * Extracted a lot of smaller methods from bigger ones
77
+ * Extracted smaller classes/modules from bigger ones, so they could be self contained with less external dependencies as possible, for example:
78
+ * HighLine::Statement
79
+ * HighLine::List
80
+ * HighLine::ListRenderer
81
+ * HighLine::TemplateRenderer
82
+ * HighLine::Question::AnswerConverter
83
+ * HighLine::Terminal
84
+ * HighLine::Terminal::UnixStty
85
+ * HighLine::Paginator
86
+ * HighLine::Wrapper
87
+ * After extracting each class/module some refactoring were applied to them lowering code complexity
88
+
89
+ #### METRICS SUMMARY
90
+ Some of the metrics used to track progress are summarized bellow. Some of them have got a lot better as Flay, Flog and Reek, others like Cane haven't (probably because we didn't commented out the new code yet)
91
+
92
+ __CODECLIMATE__
93
+
94
+ * GPA: 3.60 -> 3.67 (higher is better)
95
+
96
+ __CANE__ - reports code quality threshold violations (lower is better)
97
+
98
+ * Total 92 -> 105
99
+ * Methods exceeding allowed Abc complexity: 14 -> 10
100
+ * Lines violating style requirements: 69 -> 72
101
+ * Class definitions requiring comments: 9 -> 23
102
+
103
+ __FLAY__ - analyzes ruby code for structural similarities (code duplication - lower is better)
104
+
105
+ * Total: 490 -> 94
106
+
107
+ __FLOG__ - measures code complexity (lower is better)
108
+
109
+ * Top 5% average: 127.9458 -> 40.99812
110
+ * Average: 17.37982 -> 7.663875
111
+ * Total: 2158.5 -> 1969.6
112
+
113
+ __REEK__ - detects common code smells in ruby code (lower is better)
114
+
115
+ * DuplicateMethodCall: 144 -> 54
116
+ * TooManyStatements: 26 -> 30
25
117
 
26
118
  ### 1.7.3 / 2015-06-29
27
119
  * Add HighLine::Simulator tests (Bala Paranj (@bparanj) and Abinoam Marques Jr. (@abinoam), #142, PR #143)
data/Gemfile CHANGED
@@ -1,11 +1,12 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec
3
+ gem "rake", require: false
4
+ gem "rdoc", require: false
4
5
 
5
- gem "rake", :require => false
6
- gem "rdoc", :require => false
7
-
8
- group(:development, :tests) do
9
- gem "code_statistics", :require => false
10
- gem "test-unit", :require => false
6
+ group :development, :test do
7
+ gem "code_statistics", require: false
8
+ gem "minitest", require: false
11
9
  end
10
+
11
+ gem "codeclimate-test-reporter", group: :test, require: false
12
+ gem "simplecov", group: :test, require: false
@@ -3,7 +3,10 @@
3
3
  by James Edward Gray II
4
4
 
5
5
  {<img src="https://travis-ci.org/JEG2/highline.svg" alt="Build Status" />}[https://travis-ci.org/JEG2/highline]
6
+ {<img src="https://ci.appveyor.com/api/projects/status/4p05fijpah77d28x?svg=true" alt="AppVeyor Build Status" />}[https://ci.appveyor.com/project/abinoam/highline]
6
7
  {<img src="https://img.shields.io/gem/v/highline.svg?style=flat" />}[http://rubygems.org/gems/highline]
8
+ {<img src="https://codeclimate.com/github/JEG2/highline/badges/gpa.svg" />}[https://codeclimate.com/github/JEG2/highline]
9
+ {<img src="https://codeclimate.com/github/JEG2/highline/badges/coverage.svg" />}[https://codeclimate.com/github/JEG2/highline/coverage]
7
10
 
8
11
  == Description
9
12
 
data/Rakefile CHANGED
@@ -10,16 +10,16 @@ task :default => [:test]
10
10
 
11
11
  Rake::TestTask.new do |test|
12
12
  test.libs = ["lib", "test"]
13
- test.test_files = FileList[ "test/tc_*.rb"]
14
13
  test.verbose = true
15
14
  test.warning = true
15
+ test.test_files = FileList['test/test*.rb']
16
16
  end
17
17
 
18
18
  RDoc::Task.new do |rdoc|
19
19
  rdoc.rdoc_files.include( "README.rdoc", "INSTALL",
20
20
  "TODO", "Changelog.md",
21
21
  "AUTHORS", "COPYING",
22
- "LICENSE", "lib /*.rb" )
22
+ "LICENSE", "lib/**/*.rb")
23
23
  rdoc.main = "README.rdoc"
24
24
  rdoc.rdoc_dir = "doc/html"
25
25
  rdoc.title = "HighLine Documentation"
@@ -28,3 +28,8 @@ end
28
28
  Gem::PackageTask.new(SPEC) do |package|
29
29
  # do nothing: I just need a gem but this block is required
30
30
  end
31
+
32
+ desc "Run some interactive acceptance tests"
33
+ task :acceptance do
34
+ load "test/acceptance/acceptance.rb"
35
+ end
@@ -0,0 +1,19 @@
1
+ version: '{build}'
2
+
3
+ skip_tags: true
4
+
5
+ environment:
6
+ matrix:
7
+ - ruby_version: "21"
8
+ - ruby_version: "21-x64"
9
+
10
+ install:
11
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
12
+ - gem install bundler --no-document -v 1.10.5
13
+ - bundle install --retry=3
14
+
15
+ test_script:
16
+ - bundle exec rake
17
+
18
+ build: off
19
+
@@ -9,6 +9,9 @@ require "rubygems"
9
9
  require "highline/import"
10
10
  require "pp"
11
11
 
12
+ puts "Using: #{$terminal.terminal.class}"
13
+ puts
14
+
12
15
  grades = ask( "Enter test scores (or a blank line to quit):",
13
16
  lambda { |ans| ans =~ /^-?\d+$/ ? Integer(ans) : ans} ) do |q|
14
17
  q.gather = ""
@@ -9,6 +9,9 @@ require "rubygems"
9
9
  require "highline/import"
10
10
  require "yaml"
11
11
 
12
+ puts "Using: #{$terminal.terminal.class}"
13
+ puts
14
+
12
15
  contacts = [ ]
13
16
 
14
17
  class NameClass
@@ -3,6 +3,9 @@
3
3
  require "rubygems"
4
4
  require "highline/import"
5
5
 
6
+ puts "Using: #{$terminal.terminal.class}"
7
+ puts
8
+
6
9
  choices = "ynaq"
7
10
  answer = ask("Your choice [#{choices}]? ") do |q|
8
11
  q.echo = false
@@ -8,5 +8,8 @@
8
8
  require "rubygems"
9
9
  require "highline/import"
10
10
 
11
+ puts "Using: #{$terminal.terminal.class}"
12
+ puts
13
+
11
14
  text = ask("Enter text (max 10 chars): ") { |q| q.limit = 10 }
12
15
  puts "You entered: #{text}!"
@@ -3,6 +3,9 @@
3
3
  require "rubygems"
4
4
  require "highline/import"
5
5
 
6
+ puts "Using: #{$terminal.terminal.class}"
7
+ puts
8
+
6
9
  # The old way, using ask() and say()...
7
10
  choices = %w{ruby python perl}
8
11
  say("This is the old way using ask() and say()...")
@@ -8,6 +8,9 @@
8
8
  require 'rubygems'
9
9
  require 'highline/import'
10
10
 
11
+ puts "Using: #{$terminal.terminal.class}"
12
+ puts
13
+
11
14
  prompt = "here is your password:"
12
15
  ask(
13
16
  "#{prompt} <%= color('mypassword', RED, BOLD) %> (Press Any Key to blank) "
@@ -3,5 +3,8 @@
3
3
  require "rubygems"
4
4
  require "highline/import"
5
5
 
6
+ puts "Using: #{$terminal.terminal.class}"
7
+ puts
8
+
6
9
  pass = ask("Enter your password: ") { |q| q.echo = false }
7
10
  puts "Your password is #{pass}!"
@@ -3,6 +3,9 @@
3
3
  require "rubygems"
4
4
  require "highline/import"
5
5
 
6
+ puts "Using: #{$terminal.terminal.class}"
7
+ puts
8
+
6
9
  tounge_twister = ask("... try saying that three times fast") do |q|
7
10
  q.gather = 3
8
11
  q.verify_match = true
@@ -11,7 +14,7 @@ end
11
14
 
12
15
  puts "Ok, you did it."
13
16
 
14
- pass = ask("<%= @key %>: ") do |q|
17
+ pass = ask("<%= key %>: ") do |q|
15
18
  q.echo = '*'
16
19
  q.verify_match = true
17
20
  q.gather = {"Enter a password" => '',
@@ -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.
@@ -12,12 +14,17 @@ require "erb"
12
14
  require "optparse"
13
15
  require "stringio"
14
16
  require "abbrev"
15
- require "highline/system_extensions"
17
+ require "highline/terminal"
18
+ require "highline/custom_errors"
16
19
  require "highline/question"
20
+ require "highline/question_asker"
17
21
  require "highline/menu"
18
22
  require "highline/color_scheme"
19
23
  require "highline/style"
20
24
  require "highline/version"
25
+ require "highline/statement"
26
+ require "highline/list_renderer"
27
+ require "highline/builtin_styles"
21
28
 
22
29
  #
23
30
  # A HighLine object is a "high-level line oriented" shell over an input and an
@@ -29,22 +36,20 @@ require "highline/version"
29
36
  # checking, convert types, etc.
30
37
  #
31
38
  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
39
+ include BuiltinStyles
40
+ include CustomErrors
36
41
 
37
42
  # The setting used to disable color output.
38
- @@use_color = true
43
+ @use_color = true
39
44
 
40
45
  # Pass +false+ to _setting_ to turn off HighLine's color escapes.
41
46
  def self.use_color=( setting )
42
- @@use_color = setting
47
+ @use_color = setting
43
48
  end
44
49
 
45
50
  # Returns true if HighLine is currently using color escapes.
46
51
  def self.use_color?
47
- @@use_color
52
+ @use_color
48
53
  end
49
54
 
50
55
  # For checking if the current version of HighLine supports RGB colors
@@ -55,122 +60,49 @@ class HighLine
55
60
  end
56
61
 
57
62
  # The setting used to disable EOF tracking.
58
- @@track_eof = true
63
+ @track_eof = true
59
64
 
60
65
  # Pass +false+ to _setting_ to turn off HighLine's EOF tracking.
61
66
  def self.track_eof=( setting )
62
- @@track_eof = setting
67
+ @track_eof = setting
63
68
  end
64
69
 
65
70
  # Returns true if HighLine is currently tracking EOF for input.
66
71
  def self.track_eof?
67
- @@track_eof
72
+ @track_eof
73
+ end
74
+
75
+ def track_eof?
76
+ self.class.track_eof?
68
77
  end
69
78
 
70
79
  # The setting used to control color schemes.
71
- @@color_scheme = nil
80
+ @color_scheme = nil
72
81
 
73
82
  # Pass ColorScheme to _setting_ to set a HighLine color scheme.
74
83
  def self.color_scheme=( setting )
75
- @@color_scheme = setting
84
+ @color_scheme = setting
76
85
  end
77
86
 
78
87
  # Returns the current color scheme.
79
88
  def self.color_scheme
80
- @@color_scheme
89
+ @color_scheme
81
90
  end
82
91
 
83
92
  # Returns +true+ if HighLine is currently using a color scheme.
84
93
  def self.using_color_scheme?
85
- not @@color_scheme.nil?
94
+ !!@color_scheme
86
95
  end
87
96
 
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
139
-
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
97
+ # Reset HighLine to default.
98
+ # Clears Style index and reset color scheme.
99
+ def self.reset
100
+ Style.clear_index
101
+ reset_color_scheme
144
102
  end
145
- ON_NONE_STYLE.rgb = [255,255,255] # Override; white background
146
103
 
147
- STYLES.each do |style|
148
- const_set style, const_get("#{style}_STYLE").code
149
- end
150
-
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}"
173
- end
104
+ def self.reset_color_scheme
105
+ self.color_scheme = nil
174
106
  end
175
107
 
176
108
  #
@@ -183,26 +115,19 @@ class HighLine
183
115
  @output = output
184
116
 
185
117
  @multi_indent = true
186
- @indent_size = indent_size
118
+ @indent_size = indent_size
187
119
  @indent_level = indent_level
188
120
 
189
121
  self.wrap_at = wrap_at
190
122
  self.page_at = page_at
191
123
 
192
- @question = nil
193
- @answer = nil
194
- @menu = nil
195
124
  @header = nil
196
125
  @prompt = nil
197
- @gather = nil
198
- @answers = nil
199
126
  @key = nil
200
127
 
201
- initialize_system_extensions if respond_to?(:initialize_system_extensions)
128
+ @terminal = HighLine::Terminal.get_terminal(input, output)
202
129
  end
203
130
 
204
- include HighLine::SystemExtensions
205
-
206
131
  # The current column setting for wrapping output.
207
132
  attr_reader :wrap_at
208
133
  # The current row setting for paging output.
@@ -214,6 +139,15 @@ class HighLine
214
139
  # The indentation level
215
140
  attr_accessor :indent_level
216
141
 
142
+ attr_reader :input, :output
143
+
144
+ attr_accessor :key
145
+
146
+ # System specific that responds to #initialize_system_extensions,
147
+ # #terminal_size, #raw_no_echo_mode, #restore_mode, #get_character.
148
+ # It polymorphically handles specific cases for different platforms.
149
+ attr_reader :terminal
150
+
217
151
  #
218
152
  # A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
219
153
  # answers ("y" and "n" are allowed) and returns +true+ or +false+
@@ -242,75 +176,15 @@ class HighLine
242
176
  # HighLine::Question for more information about _answer_type_ and what's
243
177
  # valid in the code block.
244
178
  #
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
179
  # Raises EOFError if input is exhausted.
250
180
  #
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
255
-
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
181
+ def ask(template_or_question, answer_type = nil, &details)
182
+ question = Question.build(template_or_question, answer_type, &details)
269
183
 
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.
184
+ if question.gather
185
+ QuestionAsker.new(question, self).gather_answers
186
+ else
187
+ QuestionAsker.new(question, self).ask_once
314
188
  end
315
189
  end
316
190
 
@@ -330,40 +204,34 @@ class HighLine
330
204
  # Raises EOFError if input is exhausted.
331
205
  #
332
206
  def choose( *items, &details )
333
- @menu = @question = Menu.new(&details)
334
- @menu.choices(*items) unless items.empty?
207
+ menu = Menu.new(&details)
208
+ menu.choices(*items) unless items.empty?
335
209
 
336
210
  # 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 || ""
211
+ menu.completion = menu.options
342
212
 
343
- options = @menu.options
344
- options.extend(OptionParser::Completion)
345
- answer = options.complete(first_word)
213
+ shell_style_lambda = lambda do |command| # shell-style selection
214
+ first_word = command.to_s.split.first || ""
346
215
 
347
- if answer.nil?
348
- raise Question::NoAutoCompleteMatch
349
- end
216
+ options = menu.options
217
+ options.extend(OptionParser::Completion)
218
+ answer = options.complete(first_word)
350
219
 
351
- [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
352
- end
353
- else
354
- @menu.options # normal menu selection, by index or name
220
+ raise Question::NoAutoCompleteMatch unless answer
221
+
222
+ [answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
355
223
  end
356
224
 
357
- # Provide hooks for ERb layouts.
358
- @header = @menu.header
359
- @prompt = @menu.prompt
225
+ # Set _answer_type_ so we can double as the Question for ask().
226
+ # menu.option = normal menu selection, by index or name
227
+ menu.answer_type = menu.shell ? shell_style_lambda : menu.options
228
+
229
+ selected = ask(menu)
360
230
 
361
- if @menu.shell
362
- selected = ask("Ignored", @menu.answer_type)
363
- @menu.select(self, *selected)
231
+ if menu.shell
232
+ menu.select(self, *selected)
364
233
  else
365
- selected = ask("Ignored", @menu.answer_type)
366
- @menu.select(self, selected)
234
+ menu.select(self, selected)
367
235
  end
368
236
  end
369
237
 
@@ -408,203 +276,8 @@ class HighLine
408
276
  self.class.uncolor(string)
409
277
  end
410
278
 
411
- #
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.
415
- #
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:
419
- #
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.
439
- #
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.
443
- #
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
480
-
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
486
-
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
279
+ def list(items, mode = :rows, option = nil)
280
+ ListRenderer.new(items, mode, option, self).render
608
281
  end
609
282
 
610
283
  #
@@ -612,27 +285,31 @@ class HighLine
612
285
  # ends with a space or tab character, a newline will not be appended (output
613
286
  # will be flush()ed). All other cases are passed straight to Kernel.puts().
614
287
  #
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.
288
+ # The _statement_ argument is processed as an ERb template, supporting
289
+ # embedded Ruby code. The template is evaluated within a HighLine
290
+ # instance's binding for providing easy access to the ANSI color constants
291
+ # and the HighLine#color() method.
619
292
  #
620
- def say( statement )
621
- statement = format_statement(statement)
622
- return unless statement.length > 0
293
+ def say(statement)
294
+ statement = render_statement(statement)
295
+ return if statement.empty?
623
296
 
624
- out = (indentation+statement).encode(Encoding.default_external, { :undef => :replace } )
297
+ statement = (indentation+statement)
625
298
 
626
299
  # Don't add a newline if statement ends with whitespace, OR
627
300
  # if statement ends with whitespace before a color escape code.
628
301
  if /[ \t](\e\[\d+(;\d+)*m)?\Z/ =~ statement
629
- @output.print(out)
630
- @output.flush
302
+ output.print(statement)
303
+ output.flush
631
304
  else
632
- @output.puts(out)
305
+ output.puts(statement)
633
306
  end
634
307
  end
635
308
 
309
+ def render_statement(statement)
310
+ Statement.new(statement, self).to_s
311
+ end
312
+
636
313
  #
637
314
  # Set to an integer value to cause HighLine to wrap output lines at the
638
315
  # indicated character limit. When +nil+, the default, no wrapping occurs. If
@@ -657,7 +334,7 @@ class HighLine
657
334
  # Outputs indentation with current settings
658
335
  #
659
336
  def indentation
660
- return ' '*@indent_size*@indent_level
337
+ ' '*@indent_size*@indent_level
661
338
  end
662
339
 
663
340
  #
@@ -666,20 +343,17 @@ class HighLine
666
343
  def indent(increase=1, statement=nil, multiline=nil)
667
344
  @indent_level += increase
668
345
  multi = @multi_indent
669
- @multi_indent = multiline unless multiline.nil?
346
+ @multi_indent ||= multiline
670
347
  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
348
+ if block_given?
349
+ yield self
350
+ else
351
+ say(statement)
352
+ end
353
+ ensure
354
+ @multi_indent = multi
355
+ @indent_level -= increase
680
356
  end
681
- @multi_indent = multi
682
- @indent_level -= increase
683
357
  end
684
358
 
685
359
  #
@@ -695,7 +369,7 @@ class HighLine
695
369
  #
696
370
  def output_cols
697
371
  return 80 unless @output.tty?
698
- terminal_size.first
372
+ terminal.terminal_size.first
699
373
  rescue
700
374
  return 80
701
375
  end
@@ -706,105 +380,37 @@ class HighLine
706
380
  #
707
381
  def output_rows
708
382
  return 24 unless @output.tty?
709
- terminal_size.last
383
+ terminal.terminal_size.last
710
384
  rescue
711
385
  return 24
712
386
  end
713
387
 
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
388
+ def puts(*args)
389
+ @output.puts(*args)
733
390
  end
734
391
 
735
392
  #
736
- # A helper method for sending the output stream and error and repeat
737
- # of the question.
393
+ # Creates a new HighLine instance with the same options
738
394
  #
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
395
+ def new_scope
396
+ self.class.new(@input, @output, @wrap_at, @page_at, @indent_size, @indent_level)
746
397
  end
747
398
 
399
+ private
400
+
748
401
  #
749
- # Collects an Array/Hash full of answers as described in
750
- # HighLine::Question.gather().
751
- #
752
- # Raises EOFError if input is exhausted.
402
+ # A helper method for sending the output stream and error and repeat
403
+ # of the question.
753
404
  #
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
405
+ def explain_error(error, question)
406
+ say(question.responses[error]) if error
407
+ say(question.ask_on_error_msg)
408
+ end
806
409
 
807
- original_question.verify_match ? @answer : @answers
410
+ # Adds a layer of scope (new_scope) to ask a question inside a
411
+ # question, without destroying instance data
412
+ def confirm(question)
413
+ new_scope.agree(question.confirm_question(self))
808
414
  end
809
415
 
810
416
  #
@@ -812,10 +418,14 @@ class HighLine
812
418
  # for finding whether a list of answers match or differ
813
419
  # from each other.
814
420
  #
815
- def unique_answers(list = @answers)
421
+ def unique_answers(list)
816
422
  (list.respond_to?(:values) ? list.values : list).uniq
817
423
  end
818
424
 
425
+ def last_answer(answers)
426
+ answers.respond_to?(:values) ? answers.values.last : answers.last
427
+ end
428
+
819
429
  #
820
430
  # Read a line of input from the input stream and process whitespace as
821
431
  # requested by the Question object.
@@ -825,224 +435,92 @@ class HighLine
825
435
  #
826
436
  # Raises EOFError if input is exhausted.
827
437
  #
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
438
+ def get_line(question)
439
+ terminal.get_line(question, self)
440
+ end
843
441
 
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."
851
- else
852
- raw_answer = String.new # Never return nil
442
+ def get_response_line_mode(question)
443
+ if question.echo == true and !question.limit
444
+ get_line(question)
445
+ else
446
+ line = ""
447
+
448
+ terminal.raw_no_echo_mode_exec do
449
+ while character = terminal.get_character
450
+ break if character == "\n" or character == "\r"
451
+
452
+ # honor backspace and delete
453
+ if character == "\b"
454
+ chopped = line.chop!
455
+ output_erase_char if chopped and question.echo
456
+ else
457
+ line << character
458
+ @output.print(line[-1]) if question.echo == true
459
+ @output.print(question.echo) if question.echo and question.echo != true
460
+ end
461
+
462
+ @output.flush
463
+
464
+ break if question.limit and line.size == question.limit
853
465
  end
854
466
  end
855
- answer = @question.change_case(
856
- @question.remove_whitespace(raw_answer))
857
- $VERBOSE = old_verbose
858
467
 
859
- answer
860
- else
861
- if JRUBY
862
- statement = format_statement(@question)
863
- raw_answer = @java_console.readLine(statement, nil)
864
-
865
- raise EOFError, "The input stream is exhausted." if raw_answer.nil? and
866
- @@track_eof
468
+ if question.overwrite
469
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
470
+ @output.flush
867
471
  else
868
- raise EOFError, "The input stream is exhausted." if @@track_eof and
869
- @input.eof?
870
- raw_answer = @input.gets
472
+ say("\n")
871
473
  end
872
474
 
873
- @question.change_case(@question.remove_whitespace(raw_answer))
475
+ question.format_answer(line)
874
476
  end
875
477
  end
876
478
 
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?
888
-
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
948
-
949
- @question.change_case(@question.remove_whitespace(line.force_encoding(Encoding.default_external)))
950
- end
951
- else
952
- if JRUBY #prompt has not been shown
953
- say @question
954
- end
479
+ def output_erase_char
480
+ @output.print("\b#{HighLine.Style(:erase_char).code}")
481
+ end
955
482
 
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)
483
+ def get_response_getc_mode(question)
484
+ terminal.raw_no_echo_mode_exec do
485
+ response = @input.getc
486
+ question.format_answer(response)
980
487
  end
981
488
  end
982
489
 
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?
490
+ def get_response_character_mode(question)
491
+ terminal.raw_no_echo_mode_exec do
492
+ response = terminal.get_character
493
+ if question.overwrite
494
+ erase_current_line
495
+ else
496
+ echo = get_echo(question, response)
497
+ say("#{echo}\n")
498
+ end
499
+ question.format_answer(response)
998
500
  end
999
- return lines.join
1000
501
  end
1001
502
 
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.
503
+ def erase_current_line
504
+ @output.print("\r#{HighLine.Style(:erase_line).code}")
505
+ @output.flush
1011
506
  end
1012
507
 
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
1033
- end
1034
- wrapped << line
508
+ def get_echo(question, response)
509
+ if question.echo == true
510
+ response
511
+ elsif question.echo != false
512
+ question.echo
513
+ else
514
+ ""
1035
515
  end
1036
- return wrapped.join
1037
516
  end
1038
517
 
1039
- #
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
518
+ public :get_response_character_mode, :get_response_line_mode
519
+ public :get_response_getc_mode
520
+
521
+ def actual_length(text)
522
+ Wrapper.actual_length text
1045
523
  end
1046
524
  end
1047
525
 
1048
- require "highline/string_extensions"
526
+ require "highline/string"