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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.simplecov +5 -0
- data/.travis.yml +11 -6
- data/Changelog.md +112 -20
- data/Gemfile +8 -7
- data/README.rdoc +3 -0
- data/Rakefile +7 -2
- data/appveyor.yml +19 -0
- data/examples/asking_for_arrays.rb +3 -0
- data/examples/basic_usage.rb +3 -0
- data/examples/get_character.rb +3 -0
- data/examples/limit.rb +3 -0
- data/examples/menus.rb +3 -0
- data/examples/overwrite.rb +3 -0
- data/examples/password.rb +3 -0
- data/examples/repeat_entry.rb +4 -1
- data/lib/highline.rb +182 -704
- data/lib/highline/builtin_styles.rb +109 -0
- data/lib/highline/color_scheme.rb +4 -1
- data/lib/highline/compatibility.rb +2 -0
- data/lib/highline/custom_errors.rb +19 -0
- data/lib/highline/import.rb +4 -2
- data/lib/highline/list.rb +93 -0
- data/lib/highline/list_renderer.rb +232 -0
- data/lib/highline/menu.rb +20 -20
- data/lib/highline/paginator.rb +43 -0
- data/lib/highline/question.rb +157 -97
- data/lib/highline/question/answer_converter.rb +84 -0
- data/lib/highline/question_asker.rb +147 -0
- data/lib/highline/simulate.rb +5 -1
- data/lib/highline/statement.rb +58 -0
- data/lib/highline/string.rb +34 -0
- data/lib/highline/string_extensions.rb +3 -28
- data/lib/highline/style.rb +18 -8
- data/lib/highline/template_renderer.rb +38 -0
- data/lib/highline/terminal.rb +78 -0
- data/lib/highline/terminal/io_console.rb +98 -0
- data/lib/highline/terminal/ncurses.rb +38 -0
- data/lib/highline/terminal/unix_stty.rb +94 -0
- data/lib/highline/version.rb +3 -1
- data/lib/highline/wrapper.rb +43 -0
- data/test/acceptance/acceptance.rb +62 -0
- data/test/acceptance/acceptance_test.rb +69 -0
- data/test/acceptance/at_color_output_using_erb_templates.rb +17 -0
- data/test/acceptance/at_echo_false.rb +23 -0
- data/test/acceptance/at_readline.rb +37 -0
- data/test/io_console_compatible.rb +37 -0
- data/test/string_methods.rb +3 -0
- data/test/test_answer_converter.rb +26 -0
- data/test/{tc_color_scheme.rb → test_color_scheme.rb} +7 -9
- data/test/test_helper.rb +26 -0
- data/test/{tc_highline.rb → test_highline.rb} +193 -136
- data/test/{tc_import.rb → test_import.rb} +5 -2
- data/test/test_list.rb +60 -0
- data/test/{tc_menu.rb → test_menu.rb} +6 -3
- data/test/test_paginator.rb +73 -0
- data/test/test_question_asker.rb +20 -0
- data/test/test_simulator.rb +24 -0
- data/test/test_string_extension.rb +72 -0
- data/test/{tc_string_highline.rb → test_string_highline.rb} +7 -3
- data/test/{tc_style.rb → test_style.rb} +70 -35
- data/test/test_wrapper.rb +188 -0
- metadata +57 -22
- data/lib/highline/system_extensions.rb +0 -254
- data/test/tc_simulator.rb +0 -33
- data/test/tc_string_extension.rb +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f330e1d4093d9a373e0471827e2acdf89076eac
|
4
|
+
data.tar.gz: 1bba362856956f240a80312fe26e74966790b783
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36049605dccd8943c87037214ca65c02feab5ead5f1ca4579ba5491b0b8b966c9a49377c40f9fb99d53bd5a63ea86ef664c55cae40158cc53bef31b4413249cf
|
7
|
+
data.tar.gz: fdd91732e30725625a9197226d70d2a8bf99002c3a029094617bbed2048a3dadc93a3f95012eca5a87a0ca3f0f69ac5fb9007a3afc48cc6d253afaa81b4236d9
|
data/.gitignore
CHANGED
data/.simplecov
ADDED
data/.travis.yml
CHANGED
@@ -3,15 +3,20 @@ language: ruby
|
|
3
3
|
sudo: false
|
4
4
|
script: "bundle exec rake test"
|
5
5
|
rvm:
|
6
|
-
- 1.9
|
7
|
-
- 2.0
|
8
|
-
- 2.1
|
9
|
-
- 2.2
|
10
|
-
-
|
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
|
data/Changelog.md
CHANGED
@@ -2,26 +2,118 @@
|
|
2
2
|
|
3
3
|
Below is a complete listing of changes for each revision of HighLine.
|
4
4
|
|
5
|
-
###
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
*
|
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
|
-
|
3
|
+
gem "rake", require: false
|
4
|
+
gem "rdoc", require: false
|
4
5
|
|
5
|
-
|
6
|
-
gem "
|
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
|
data/README.rdoc
CHANGED
@@ -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
|
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
|
data/appveyor.yml
ADDED
@@ -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 = ""
|
data/examples/basic_usage.rb
CHANGED
data/examples/get_character.rb
CHANGED
data/examples/limit.rb
CHANGED
data/examples/menus.rb
CHANGED
data/examples/overwrite.rb
CHANGED
data/examples/password.rb
CHANGED
data/examples/repeat_entry.rb
CHANGED
@@ -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("<%=
|
17
|
+
pass = ask("<%= key %>: ") do |q|
|
15
18
|
q.echo = '*'
|
16
19
|
q.verify_match = true
|
17
20
|
q.gather = {"Enter a password" => '',
|
data/lib/highline.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
3
|
+
#--
|
2
4
|
# highline.rb
|
3
5
|
#
|
4
6
|
# Created by James Edward Gray II on 2005-04-26.
|
@@ -12,12 +14,17 @@ require "erb"
|
|
12
14
|
require "optparse"
|
13
15
|
require "stringio"
|
14
16
|
require "abbrev"
|
15
|
-
require "highline/
|
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
|
-
|
33
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
84
|
+
@color_scheme = setting
|
76
85
|
end
|
77
86
|
|
78
87
|
# Returns the current color scheme.
|
79
88
|
def self.color_scheme
|
80
|
-
|
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
|
-
|
94
|
+
!!@color_scheme
|
86
95
|
end
|
87
96
|
|
88
|
-
#
|
89
|
-
#
|
90
|
-
|
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
|
-
|
148
|
-
|
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
|
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
|
-
|
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(
|
252
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
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
|
-
|
334
|
-
|
207
|
+
menu = Menu.new(&details)
|
208
|
+
menu.choices(*items) unless items.empty?
|
335
209
|
|
336
210
|
# Set auto-completion
|
337
|
-
|
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
|
-
|
344
|
-
|
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
|
-
|
348
|
-
|
349
|
-
|
216
|
+
options = menu.options
|
217
|
+
options.extend(OptionParser::Completion)
|
218
|
+
answer = options.complete(first_word)
|
350
219
|
|
351
|
-
|
352
|
-
|
353
|
-
|
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
|
-
#
|
358
|
-
|
359
|
-
|
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
|
362
|
-
|
363
|
-
@menu.select(self, *selected)
|
231
|
+
if menu.shell
|
232
|
+
menu.select(self, *selected)
|
364
233
|
else
|
365
|
-
|
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
|
-
|
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_
|
616
|
-
# embedded Ruby code. The template is evaluated
|
617
|
-
#
|
618
|
-
# and the HighLine
|
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(
|
621
|
-
statement =
|
622
|
-
return
|
293
|
+
def say(statement)
|
294
|
+
statement = render_statement(statement)
|
295
|
+
return if statement.empty?
|
623
296
|
|
624
|
-
|
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
|
-
|
630
|
-
|
302
|
+
output.print(statement)
|
303
|
+
output.flush
|
631
304
|
else
|
632
|
-
|
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
|
-
|
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
|
346
|
+
@multi_indent ||= multiline
|
670
347
|
begin
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
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
|
-
|
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
|
-
#
|
737
|
-
# of the question.
|
393
|
+
# Creates a new HighLine instance with the same options
|
738
394
|
#
|
739
|
-
def
|
740
|
-
|
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
|
-
#
|
750
|
-
#
|
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
|
755
|
-
|
756
|
-
|
757
|
-
|
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
|
-
|
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
|
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
|
-
|
830
|
-
|
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
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
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
|
-
|
860
|
-
|
861
|
-
|
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
|
-
|
869
|
-
@input.eof?
|
870
|
-
raw_answer = @input.gets
|
472
|
+
say("\n")
|
871
473
|
end
|
872
474
|
|
873
|
-
|
475
|
+
question.format_answer(line)
|
874
476
|
end
|
875
477
|
end
|
876
478
|
|
877
|
-
|
878
|
-
|
879
|
-
|
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
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
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
|
-
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
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
|
-
|
1004
|
-
|
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
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
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
|
-
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
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/
|
526
|
+
require "highline/string"
|