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
@@ -0,0 +1,84 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
class HighLine
|
6
|
+
class Question
|
7
|
+
class AnswerConverter
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@question,
|
11
|
+
:answer, :answer=, :check_range,
|
12
|
+
:directory, :answer_type, :choices_complete
|
13
|
+
|
14
|
+
def initialize(question)
|
15
|
+
@question = question
|
16
|
+
end
|
17
|
+
|
18
|
+
def convert
|
19
|
+
return unless answer_type
|
20
|
+
|
21
|
+
self.answer = convert_by_answer_type
|
22
|
+
check_range
|
23
|
+
answer
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_string
|
27
|
+
HighLine::String(answer)
|
28
|
+
end
|
29
|
+
|
30
|
+
# That's a weird name for a method!
|
31
|
+
# But it's working ;-)
|
32
|
+
define_method "to_highline::string" do
|
33
|
+
HighLine::String(answer)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_integer
|
37
|
+
Kernel.send(:Integer, answer)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_float
|
41
|
+
Kernel.send(:Float, answer)
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_symbol
|
45
|
+
answer.to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_regexp
|
49
|
+
Regexp.new(answer)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_file
|
53
|
+
self.answer = choices_complete(answer)
|
54
|
+
File.open(File.join(directory.to_s, answer.last))
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_pathname
|
58
|
+
self.answer = choices_complete(answer)
|
59
|
+
Pathname.new(File.join(directory.to_s, answer.last))
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_array
|
63
|
+
self.answer = choices_complete(answer)
|
64
|
+
answer.last
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_proc
|
68
|
+
answer_type.call(answer)
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def convert_by_answer_type
|
74
|
+
if answer_type.respond_to? :parse
|
75
|
+
answer_type.parse(answer)
|
76
|
+
elsif answer_type.is_a? Class
|
77
|
+
send("to_#{answer_type.name.downcase}")
|
78
|
+
else
|
79
|
+
send("to_#{answer_type.class.name.downcase}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
class HighLine
|
2
|
+
class QuestionAsker
|
3
|
+
attr_reader :question
|
4
|
+
|
5
|
+
include CustomErrors
|
6
|
+
|
7
|
+
def initialize(question, highline)
|
8
|
+
@question = question
|
9
|
+
@highline = highline
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Gets just one answer, as opposed to #gather_answers
|
14
|
+
#
|
15
|
+
def ask_once
|
16
|
+
question.show_question(@highline)
|
17
|
+
|
18
|
+
begin
|
19
|
+
question.get_response_or_default(@highline)
|
20
|
+
raise NotValidQuestionError unless question.valid_answer?
|
21
|
+
|
22
|
+
question.convert
|
23
|
+
|
24
|
+
if question.confirm
|
25
|
+
raise NoConfirmationQuestionError unless @highline.send(:confirm, question)
|
26
|
+
end
|
27
|
+
|
28
|
+
rescue NoConfirmationQuestionError
|
29
|
+
explain_error(nil)
|
30
|
+
retry
|
31
|
+
|
32
|
+
rescue NotInRangeQuestionError
|
33
|
+
explain_error(:not_in_range)
|
34
|
+
retry
|
35
|
+
|
36
|
+
rescue NotValidQuestionError
|
37
|
+
explain_error(:not_valid)
|
38
|
+
retry
|
39
|
+
|
40
|
+
rescue QuestionError
|
41
|
+
retry
|
42
|
+
|
43
|
+
rescue ArgumentError => error
|
44
|
+
case error.message
|
45
|
+
when /ambiguous/
|
46
|
+
# the assumption here is that OptionParser::Completion#complete
|
47
|
+
# (used for ambiguity resolution) throws exceptions containing
|
48
|
+
# the word 'ambiguous' whenever resolution fails
|
49
|
+
explain_error(:ambiguous_completion)
|
50
|
+
retry
|
51
|
+
when /invalid value for/
|
52
|
+
explain_error(:invalid_type)
|
53
|
+
retry
|
54
|
+
else
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
|
58
|
+
rescue NoAutoCompleteMatch
|
59
|
+
explain_error(:no_completion)
|
60
|
+
retry
|
61
|
+
end
|
62
|
+
question.answer
|
63
|
+
end
|
64
|
+
|
65
|
+
## Multiple questions
|
66
|
+
|
67
|
+
#
|
68
|
+
# Collects an Array/Hash full of answers as described in
|
69
|
+
# HighLine::Question.gather().
|
70
|
+
#
|
71
|
+
def gather_answers
|
72
|
+
original_question_template = question.template
|
73
|
+
verify_match = question.verify_match
|
74
|
+
|
75
|
+
begin # when verify_match is set this loop will repeat until unique_answers == 1
|
76
|
+
question.template = original_question_template
|
77
|
+
|
78
|
+
answers = gather_answers_based_on_type
|
79
|
+
|
80
|
+
if verify_match && (@highline.send(:unique_answers, answers).size > 1)
|
81
|
+
explain_error(:mismatch)
|
82
|
+
else
|
83
|
+
verify_match = false
|
84
|
+
end
|
85
|
+
end while verify_match
|
86
|
+
|
87
|
+
question.verify_match ? @highline.send(:last_answer, answers) : answers
|
88
|
+
end
|
89
|
+
|
90
|
+
def gather_integer
|
91
|
+
gather_with_array do |answers|
|
92
|
+
(question.gather-1).times { answers << ask_once }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def gather_regexp
|
97
|
+
gather_with_array do |answers|
|
98
|
+
answers << ask_once until answer_matches_regex(answers.last)
|
99
|
+
answers.pop
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def gather_hash
|
104
|
+
answers = {}
|
105
|
+
|
106
|
+
question.gather.keys.sort.each do |key|
|
107
|
+
@highline.key = key
|
108
|
+
answers[key] = ask_once
|
109
|
+
end
|
110
|
+
answers
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
## Delegate to Highline
|
117
|
+
def explain_error(error)
|
118
|
+
@highline.say(question.responses[error]) if error
|
119
|
+
@highline.say(question.ask_on_error_msg)
|
120
|
+
end
|
121
|
+
|
122
|
+
def gather_with_array
|
123
|
+
[].tap do |answers|
|
124
|
+
answers << ask_once
|
125
|
+
question.template = ""
|
126
|
+
|
127
|
+
yield answers
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def answer_matches_regex(answer)
|
132
|
+
(question.gather.is_a?(::String) && answer.to_s == question.gather) ||
|
133
|
+
(question.gather.is_a?(Regexp) && answer.to_s =~ question.gather)
|
134
|
+
end
|
135
|
+
|
136
|
+
def gather_answers_based_on_type
|
137
|
+
case question.gather
|
138
|
+
when Integer
|
139
|
+
gather_integer
|
140
|
+
when ::String, Regexp
|
141
|
+
gather_regexp
|
142
|
+
when Hash
|
143
|
+
gather_hash
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
data/lib/highline/simulate.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#--
|
1
4
|
# simulate.rb
|
2
5
|
#
|
3
6
|
# Created by Andy Rossmeissl on 2012-04-29.
|
@@ -6,6 +9,7 @@
|
|
6
9
|
# This is Free Software. See LICENSE and COPYING for details.
|
7
10
|
#
|
8
11
|
# adapted from https://gist.github.com/194554
|
12
|
+
|
9
13
|
class HighLine
|
10
14
|
|
11
15
|
# Simulates Highline input for use in tests.
|
@@ -23,7 +27,7 @@ class HighLine
|
|
23
27
|
|
24
28
|
# Simulate StringIO#getbyte by shifting a single character off of the next line of the script
|
25
29
|
def getbyte
|
26
|
-
line = gets
|
30
|
+
line = gets
|
27
31
|
if line.length > 0
|
28
32
|
char = line.slice! 0
|
29
33
|
@strings.unshift line
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'highline/wrapper'
|
4
|
+
require 'highline/paginator'
|
5
|
+
require 'highline/template_renderer'
|
6
|
+
|
7
|
+
class HighLine::Statement
|
8
|
+
attr_reader :source, :highline
|
9
|
+
attr_reader :template_string
|
10
|
+
|
11
|
+
def initialize(source, highline)
|
12
|
+
@highline = highline
|
13
|
+
@source = source
|
14
|
+
@template_string = stringfy(source)
|
15
|
+
end
|
16
|
+
|
17
|
+
def statement
|
18
|
+
@statement ||= format_statement
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
statement
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def stringfy(template_string)
|
28
|
+
String(template_string || "").dup
|
29
|
+
end
|
30
|
+
|
31
|
+
def format_statement
|
32
|
+
return template_string unless template_string.length > 0
|
33
|
+
|
34
|
+
statement = render_template
|
35
|
+
|
36
|
+
statement = HighLine::Wrapper.wrap(statement, highline.wrap_at)
|
37
|
+
statement = HighLine::Paginator.new(highline).page_print(statement)
|
38
|
+
|
39
|
+
statement = statement.gsub(/\n(?!$)/,"\n#{highline.indentation}") if highline.multi_indent
|
40
|
+
statement
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_template
|
44
|
+
# Assigning to a local var so it may be
|
45
|
+
# used inside instance eval block
|
46
|
+
|
47
|
+
template_renderer = TemplateRenderer.new(template, source, highline)
|
48
|
+
template_renderer.render
|
49
|
+
end
|
50
|
+
|
51
|
+
def template
|
52
|
+
@template ||= ERB.new(template_string, nil, "%")
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.const_missing(constant)
|
56
|
+
HighLine.const_get(constant)
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require "highline/string_extensions"
|
4
|
+
|
5
|
+
#
|
6
|
+
# HighLine::String is a subclass of String with convenience methods added for colorization.
|
7
|
+
#
|
8
|
+
# Available convenience methods include:
|
9
|
+
# * 'color' method e.g. highline_string.color(:bright_blue, :underline)
|
10
|
+
# * colors e.g. highline_string.magenta
|
11
|
+
# * RGB colors e.g. highline_string.rgb_ff6000
|
12
|
+
# or highline_string.rgb(255,96,0)
|
13
|
+
# * background colors e.g. highline_string.on_magenta
|
14
|
+
# * RGB background colors e.g. highline_string.on_rgb_ff6000
|
15
|
+
# or highline_string.on_rgb(255,96,0)
|
16
|
+
# * styles e.g. highline_string.underline
|
17
|
+
#
|
18
|
+
# Additionally, convenience methods can be chained, for instance the following are equivalent:
|
19
|
+
# highline_string.bright_blue.blink.underline
|
20
|
+
# highline_string.color(:bright_blue, :blink, :underline)
|
21
|
+
# HighLine.color(highline_string, :bright_blue, :blink, :underline)
|
22
|
+
#
|
23
|
+
# For those less squeamish about possible conflicts, the same convenience methods can be
|
24
|
+
# added to the built-in String class, as follows:
|
25
|
+
#
|
26
|
+
# require 'highline'
|
27
|
+
# Highline.colorize_strings
|
28
|
+
#
|
29
|
+
|
30
|
+
class HighLine
|
31
|
+
class String < ::String
|
32
|
+
include StringExtensions
|
33
|
+
end
|
34
|
+
end
|
@@ -1,33 +1,12 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# HighLine::String is a subclass of String with convenience methods added for colorization.
|
4
|
-
#
|
5
|
-
# Available convenience methods include:
|
6
|
-
# * 'color' method e.g. highline_string.color(:bright_blue, :underline)
|
7
|
-
# * colors e.g. highline_string.magenta
|
8
|
-
# * RGB colors e.g. highline_string.rgb_ff6000
|
9
|
-
# or highline_string.rgb(255,96,0)
|
10
|
-
# * background colors e.g. highline_string.on_magenta
|
11
|
-
# * RGB background colors e.g. highline_string.on_rgb_ff6000
|
12
|
-
# or highline_string.on_rgb(255,96,0)
|
13
|
-
# * styles e.g. highline_string.underline
|
14
|
-
#
|
15
|
-
# Additionally, convenience methods can be chained, for instance the following are equivalent:
|
16
|
-
# highline_string.bright_blue.blink.underline
|
17
|
-
# highline_string.color(:bright_blue, :blink, :underline)
|
18
|
-
# HighLine.color(highline_string, :bright_blue, :blink, :underline)
|
19
|
-
#
|
20
|
-
# For those less squeamish about possible conflicts, the same convenience methods can be
|
21
|
-
# added to the built-in String class, as follows:
|
22
|
-
#
|
23
|
-
# require 'highline'
|
24
|
-
# Highline.colorize_strings
|
1
|
+
# coding: utf-8
|
25
2
|
|
26
3
|
class HighLine
|
27
4
|
def self.String(s)
|
28
5
|
HighLine::String.new(s)
|
29
6
|
end
|
30
7
|
|
8
|
+
# HighLine extensions for String class
|
9
|
+
# Included by HighLine::String
|
31
10
|
module StringExtensions
|
32
11
|
def self.included(base)
|
33
12
|
HighLine::COLORS.each do |color|
|
@@ -101,10 +80,6 @@ class HighLine
|
|
101
80
|
end
|
102
81
|
end
|
103
82
|
|
104
|
-
class HighLine::String < ::String
|
105
|
-
include StringExtensions
|
106
|
-
end
|
107
|
-
|
108
83
|
def self.colorize_strings
|
109
84
|
::String.send(:include, StringExtensions)
|
110
85
|
end
|
data/lib/highline/style.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#--
|
1
4
|
# color_scheme.rb
|
2
5
|
#
|
3
6
|
# Created by Richard LeBer on 2011-06-27.
|
@@ -42,18 +45,25 @@ class HighLine
|
|
42
45
|
|
43
46
|
def self.index(style)
|
44
47
|
if style.name
|
45
|
-
|
46
|
-
|
48
|
+
@styles ||= {}
|
49
|
+
@styles[style.name] = style
|
47
50
|
end
|
48
51
|
if !style.list
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
52
|
+
@code_index ||= {}
|
53
|
+
@code_index[style.code] ||= []
|
54
|
+
@code_index[style.code].reject!{|indexed_style| indexed_style.name == style.name}
|
55
|
+
@code_index[style.code] << style
|
53
56
|
end
|
54
57
|
style
|
55
58
|
end
|
56
59
|
|
60
|
+
def self.clear_index
|
61
|
+
# reset to builtin only styles
|
62
|
+
@styles = list.select { |name, style| style.builtin }
|
63
|
+
@code_index = {}
|
64
|
+
@styles.each { |name, style| index(style) }
|
65
|
+
end
|
66
|
+
|
57
67
|
def self.rgb_hex(*colors)
|
58
68
|
colors.map do |color|
|
59
69
|
color.is_a?(Numeric) ? '%02x'%color : color.to_s
|
@@ -87,11 +97,11 @@ class HighLine
|
|
87
97
|
end
|
88
98
|
|
89
99
|
def self.list
|
90
|
-
|
100
|
+
@styles ||= {}
|
91
101
|
end
|
92
102
|
|
93
103
|
def self.code_index
|
94
|
-
|
104
|
+
@code_index ||= {}
|
95
105
|
end
|
96
106
|
|
97
107
|
def self.uncolor(string)
|