highline 1.7.10 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +5 -0
- data/.rubocop.yml +84 -0
- data/.simplecov +5 -0
- data/.travis.yml +35 -9
- data/Changelog.md +214 -15
- data/Gemfile +16 -5
- data/README.md +202 -0
- data/Rakefile +8 -20
- data/appveyor.yml +37 -0
- data/examples/ansi_colors.rb +6 -11
- data/examples/asking_for_arrays.rb +6 -2
- data/examples/basic_usage.rb +31 -21
- data/examples/color_scheme.rb +11 -10
- data/examples/get_character.rb +8 -4
- data/examples/limit.rb +4 -0
- data/examples/menus.rb +16 -10
- data/examples/overwrite.rb +9 -5
- data/examples/page_and_wrap.rb +5 -4
- data/examples/password.rb +4 -0
- data/examples/repeat_entry.rb +10 -5
- data/examples/trapping_eof.rb +2 -1
- data/examples/using_readline.rb +2 -1
- data/highline.gemspec +25 -27
- data/lib/highline/builtin_styles.rb +129 -0
- data/lib/highline/color_scheme.rb +49 -32
- data/lib/highline/compatibility.rb +11 -4
- data/lib/highline/custom_errors.rb +57 -0
- data/lib/highline/import.rb +19 -12
- data/lib/highline/io_console_compatible.rb +37 -0
- data/lib/highline/list.rb +177 -0
- data/lib/highline/list_renderer.rb +261 -0
- data/lib/highline/menu/item.rb +32 -0
- data/lib/highline/menu.rb +306 -111
- data/lib/highline/paginator.rb +52 -0
- data/lib/highline/question/answer_converter.rb +103 -0
- data/lib/highline/question.rb +281 -131
- data/lib/highline/question_asker.rb +150 -0
- data/lib/highline/simulate.rb +24 -13
- data/lib/highline/statement.rb +88 -0
- data/lib/highline/string.rb +36 -0
- data/lib/highline/string_extensions.rb +83 -64
- data/lib/highline/style.rb +196 -63
- data/lib/highline/template_renderer.rb +62 -0
- data/lib/highline/terminal/io_console.rb +36 -0
- data/lib/highline/terminal/ncurses.rb +38 -0
- data/lib/highline/terminal/unix_stty.rb +51 -0
- data/lib/highline/terminal.rb +190 -0
- data/lib/highline/version.rb +3 -1
- data/lib/highline/wrapper.rb +53 -0
- data/lib/highline.rb +390 -788
- metadata +56 -35
- data/INSTALL +0 -59
- data/README.rdoc +0 -74
- data/lib/highline/system_extensions.rb +0 -254
- data/setup.rb +0 -1360
- data/test/string_methods.rb +0 -32
- data/test/tc_color_scheme.rb +0 -96
- data/test/tc_highline.rb +0 -1402
- data/test/tc_import.rb +0 -52
- data/test/tc_menu.rb +0 -439
- data/test/tc_simulator.rb +0 -33
- data/test/tc_string_extension.rb +0 -33
- data/test/tc_string_highline.rb +0 -38
- data/test/tc_style.rb +0 -578
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.
|
@@ -8,212 +10,172 @@
|
|
8
10
|
#
|
9
11
|
# This is Free Software. See LICENSE and COPYING for details.
|
10
12
|
|
13
|
+
require "English"
|
11
14
|
require "erb"
|
12
15
|
require "optparse"
|
13
16
|
require "stringio"
|
14
17
|
require "abbrev"
|
15
|
-
require "highline/
|
18
|
+
require "highline/terminal"
|
19
|
+
require "highline/custom_errors"
|
16
20
|
require "highline/question"
|
21
|
+
require "highline/question_asker"
|
17
22
|
require "highline/menu"
|
18
23
|
require "highline/color_scheme"
|
19
24
|
require "highline/style"
|
20
25
|
require "highline/version"
|
26
|
+
require "highline/statement"
|
27
|
+
require "highline/list_renderer"
|
28
|
+
require "highline/builtin_styles"
|
21
29
|
|
22
30
|
#
|
23
31
|
# A HighLine object is a "high-level line oriented" shell over an input and an
|
24
32
|
# output stream. HighLine simplifies common console interaction, effectively
|
25
|
-
# replacing puts
|
26
|
-
# and any details about user interaction, then leave the rest
|
27
|
-
# HighLine. When HighLine
|
28
|
-
# even if HighLine had to ask many times, validate results,
|
29
|
-
# checking, convert types, etc.
|
33
|
+
# replacing {Kernel#puts} and {Kernel#gets}. User code can simply specify the
|
34
|
+
# question to ask and any details about user interaction, then leave the rest
|
35
|
+
# of the work to HighLine. When {HighLine#ask} returns, you'll have the answer
|
36
|
+
# you requested, even if HighLine had to ask many times, validate results,
|
37
|
+
# perform range checking, convert types, etc.
|
38
|
+
#
|
39
|
+
# @example Basic usage
|
40
|
+
# cli = HighLine.new
|
41
|
+
# answer = cli.ask "What do you think?"
|
42
|
+
# puts "You have answered: #{answer}"
|
30
43
|
#
|
31
44
|
class HighLine
|
32
|
-
|
33
|
-
|
34
|
-
# do nothing, just creating a unique error type
|
35
|
-
end
|
36
|
-
|
37
|
-
# The setting used to disable color output.
|
38
|
-
@@use_color = true
|
39
|
-
|
40
|
-
# Pass +false+ to _setting_ to turn off HighLine's color escapes.
|
41
|
-
def self.use_color=( setting )
|
42
|
-
@@use_color = setting
|
43
|
-
end
|
44
|
-
|
45
|
-
# Returns true if HighLine is currently using color escapes.
|
46
|
-
def self.use_color?
|
47
|
-
@@use_color
|
48
|
-
end
|
45
|
+
include BuiltinStyles
|
46
|
+
include CustomErrors
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
48
|
+
extend SingleForwardable
|
49
|
+
def_single_delegators :@default_instance, :agree, :ask, :choose, :say,
|
50
|
+
:use_color=, :use_color?, :reset_use_color,
|
51
|
+
:track_eof=, :track_eof?,
|
52
|
+
:color, :uncolor, :color_code
|
56
53
|
|
57
|
-
|
58
|
-
|
54
|
+
class << self
|
55
|
+
attr_accessor :default_instance
|
59
56
|
|
60
|
-
|
61
|
-
|
62
|
-
@@track_eof = setting
|
63
|
-
end
|
57
|
+
# Pass ColorScheme to set a HighLine color scheme.
|
58
|
+
attr_accessor :color_scheme
|
64
59
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# The setting used to control color schemes.
|
71
|
-
@@color_scheme = nil
|
72
|
-
|
73
|
-
# Pass ColorScheme to _setting_ to set a HighLine color scheme.
|
74
|
-
def self.color_scheme=( setting )
|
75
|
-
@@color_scheme = setting
|
76
|
-
end
|
77
|
-
|
78
|
-
# Returns the current color scheme.
|
79
|
-
def self.color_scheme
|
80
|
-
@@color_scheme
|
81
|
-
end
|
82
|
-
|
83
|
-
# Returns +true+ if HighLine is currently using a color scheme.
|
84
|
-
def self.using_color_scheme?
|
85
|
-
not @@color_scheme.nil?
|
86
|
-
end
|
87
|
-
|
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
|
60
|
+
# Returns +true+ if HighLine is currently using a color scheme.
|
61
|
+
def using_color_scheme?
|
62
|
+
true if @color_scheme
|
63
|
+
end
|
139
64
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
ON_NONE_STYLE.rgb = [255,255,255] # Override; white background
|
65
|
+
# Reset color scheme to default (+nil+)
|
66
|
+
def reset_color_scheme
|
67
|
+
self.color_scheme = nil
|
68
|
+
end
|
146
69
|
|
147
|
-
|
148
|
-
|
149
|
-
|
70
|
+
# Reset HighLine to default.
|
71
|
+
# Clears Style index and resets color_scheme and use_color settings.
|
72
|
+
def reset
|
73
|
+
Style.clear_index
|
74
|
+
reset_color_scheme
|
75
|
+
reset_use_color
|
76
|
+
end
|
150
77
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
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}"
|
78
|
+
# For checking if the current version of HighLine supports RGB colors
|
79
|
+
# Usage: HighLine.supports_rgb_color? rescue false
|
80
|
+
# using rescue for compatibility with older versions
|
81
|
+
# Note: color usage also depends on HighLine.use_color being set
|
82
|
+
# TODO: Discuss removing this method
|
83
|
+
def supports_rgb_color?
|
84
|
+
true
|
173
85
|
end
|
174
86
|
end
|
175
87
|
|
176
|
-
#
|
177
|
-
|
178
|
-
|
179
|
-
#
|
180
|
-
|
181
|
-
|
88
|
+
# The setting used to control color schemes.
|
89
|
+
@color_scheme = nil
|
90
|
+
|
91
|
+
#
|
92
|
+
# Create an instance of HighLine connected to the given _input_
|
93
|
+
# and _output_ streams.
|
94
|
+
#
|
95
|
+
# @param input [IO] the default input stream for HighLine.
|
96
|
+
# @param output [IO] the default output stream for HighLine.
|
97
|
+
# @param wrap_at [Integer] all statements outputed through
|
98
|
+
# HighLine will be wrapped to this column size if set.
|
99
|
+
# @param page_at [Integer] page size and paginating.
|
100
|
+
# @param indent_size [Integer] indentation size in spaces.
|
101
|
+
# @param indent_level [Integer] how deep is indentated.
|
102
|
+
def initialize(input = $stdin, output = $stdout,
|
103
|
+
wrap_at = nil, page_at = nil,
|
104
|
+
indent_size = 3, indent_level = 0)
|
182
105
|
@input = input
|
183
106
|
@output = output
|
184
107
|
|
185
108
|
@multi_indent = true
|
186
|
-
@indent_size
|
109
|
+
@indent_size = indent_size
|
187
110
|
@indent_level = indent_level
|
188
111
|
|
189
112
|
self.wrap_at = wrap_at
|
190
113
|
self.page_at = page_at
|
191
114
|
|
192
|
-
@question = nil
|
193
|
-
@answer = nil
|
194
|
-
@menu = nil
|
195
115
|
@header = nil
|
196
116
|
@prompt = nil
|
197
|
-
@gather = nil
|
198
|
-
@answers = nil
|
199
117
|
@key = nil
|
200
118
|
|
201
|
-
|
119
|
+
@use_color = default_use_color
|
120
|
+
@track_eof = true # The setting used to disable EOF tracking.
|
121
|
+
@terminal = HighLine::Terminal.get_terminal(input, output)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Set it to false to disable ANSI coloring
|
125
|
+
attr_accessor :use_color
|
126
|
+
|
127
|
+
# Returns truethy if HighLine instance is currently using color escapes.
|
128
|
+
def use_color?
|
129
|
+
use_color
|
130
|
+
end
|
131
|
+
|
132
|
+
# Resets the use of color.
|
133
|
+
def reset_use_color
|
134
|
+
@use_color = true
|
202
135
|
end
|
203
136
|
|
204
|
-
|
137
|
+
# Pass +false+ to turn off HighLine's EOF tracking.
|
138
|
+
attr_accessor :track_eof
|
205
139
|
|
206
|
-
#
|
140
|
+
# Returns true if HighLine is currently tracking EOF for input.
|
141
|
+
def track_eof?
|
142
|
+
true if track_eof
|
143
|
+
end
|
144
|
+
|
145
|
+
# @return [Integer] The current column setting for wrapping output.
|
207
146
|
attr_reader :wrap_at
|
208
|
-
|
147
|
+
|
148
|
+
# @return [Integer] The current row setting for paging output.
|
209
149
|
attr_reader :page_at
|
210
|
-
|
150
|
+
|
151
|
+
# @return [Boolean] Indentation over multiple lines
|
211
152
|
attr_accessor :multi_indent
|
212
|
-
|
153
|
+
|
154
|
+
# @return [Integer] The indentation size in characters
|
213
155
|
attr_accessor :indent_size
|
214
|
-
|
156
|
+
|
157
|
+
# @return [Integer] The indentation level
|
215
158
|
attr_accessor :indent_level
|
216
159
|
|
160
|
+
# @return [IO] the default input stream for a HighLine instance
|
161
|
+
attr_reader :input
|
162
|
+
|
163
|
+
# @return [IO] the default output stream for a HighLine instance
|
164
|
+
attr_reader :output
|
165
|
+
|
166
|
+
# When gathering a Hash with {QuestionAsker#gather_hash},
|
167
|
+
# it tracks the current key being asked.
|
168
|
+
#
|
169
|
+
# @todo We should probably move this into the HighLine::Question
|
170
|
+
# object.
|
171
|
+
attr_accessor :key
|
172
|
+
|
173
|
+
# System specific that responds to #initialize_system_extensions,
|
174
|
+
# #terminal_size, #raw_no_echo_mode, #restore_mode, #get_character.
|
175
|
+
# It polymorphically handles specific cases for different platforms.
|
176
|
+
# @return [HighLine::Terminal]
|
177
|
+
attr_reader :terminal
|
178
|
+
|
217
179
|
#
|
218
180
|
# A shortcut to HighLine.ask() a question that only accepts "yes" or "no"
|
219
181
|
# answers ("y" and "n" are allowed) and returns +true+ or +false+
|
@@ -223,12 +185,18 @@ class HighLine
|
|
223
185
|
#
|
224
186
|
# Raises EOFError if input is exhausted.
|
225
187
|
#
|
226
|
-
|
227
|
-
|
228
|
-
|
188
|
+
# @param yes_or_no_question [String] a question that accepts yes and no as
|
189
|
+
# answers
|
190
|
+
# @param character [Boolean, :getc] character mode to be passed to
|
191
|
+
# Question#character
|
192
|
+
# @see Question#character
|
193
|
+
def agree(yes_or_no_question, character = nil)
|
194
|
+
ask(yes_or_no_question, ->(yn) { yn.downcase[0] == "y" }) do |q|
|
195
|
+
q.validate = /\A(?:y(?:es)?|no?)\Z/i
|
229
196
|
q.responses[:not_valid] = 'Please enter "yes" or "no".'
|
230
197
|
q.responses[:ask_on_error] = :question
|
231
198
|
q.character = character
|
199
|
+
q.completion = %w[yes no]
|
232
200
|
|
233
201
|
yield q if block_given?
|
234
202
|
end
|
@@ -238,79 +206,21 @@ class HighLine
|
|
238
206
|
# This method is the primary interface for user input. Just provide a
|
239
207
|
# _question_ to ask the user, the _answer_type_ you want returned, and
|
240
208
|
# optionally a code block setting up details of how you want the question
|
241
|
-
# handled. See
|
242
|
-
#
|
209
|
+
# handled. See {#say} for details on the format of _question_, and
|
210
|
+
# {Question} for more information about _answer_type_ and what's
|
243
211
|
# valid in the code block.
|
244
212
|
#
|
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
213
|
# Raises EOFError if input is exhausted.
|
250
214
|
#
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
215
|
+
# @param (see Question.build)
|
216
|
+
# @return answer converted to the class in answer_type
|
217
|
+
def ask(template_or_question, answer_type = nil, &details)
|
218
|
+
question = Question.build(template_or_question, answer_type, &details)
|
255
219
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
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
|
269
|
-
|
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.
|
220
|
+
if question.gather
|
221
|
+
QuestionAsker.new(question, self).gather_answers
|
222
|
+
else
|
223
|
+
QuestionAsker.new(question, self).ask_once
|
314
224
|
end
|
315
225
|
end
|
316
226
|
|
@@ -329,41 +239,60 @@ class HighLine
|
|
329
239
|
#
|
330
240
|
# Raises EOFError if input is exhausted.
|
331
241
|
#
|
332
|
-
|
333
|
-
|
334
|
-
|
242
|
+
# @param items [Array<String>]
|
243
|
+
# @param details [Proc] to be passed to Menu.new
|
244
|
+
# @return [String] answer
|
245
|
+
def choose(*items, &details)
|
246
|
+
menu = Menu.new(&details)
|
247
|
+
menu.choices(*items) unless items.empty?
|
335
248
|
|
336
249
|
# 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 || ""
|
250
|
+
menu.completion = menu.options
|
342
251
|
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
252
|
+
# Set _answer_type_ so we can double as the Question for ask().
|
253
|
+
# menu.option = normal menu selection, by index or name
|
254
|
+
menu.answer_type = menu.shell ? shell_style_lambda(menu) : menu.options
|
255
|
+
|
256
|
+
selected = ask(menu)
|
257
|
+
return unless selected
|
258
|
+
|
259
|
+
if menu.shell
|
260
|
+
if menu.gather
|
261
|
+
selection = []
|
262
|
+
details = []
|
263
|
+
selected.each do |value|
|
264
|
+
selection << value[0]
|
265
|
+
details << value[1]
|
349
266
|
end
|
350
|
-
|
351
|
-
|
267
|
+
else
|
268
|
+
selection, details = selected
|
352
269
|
end
|
353
270
|
else
|
354
|
-
|
271
|
+
selection = selected
|
355
272
|
end
|
356
273
|
|
357
|
-
|
358
|
-
|
359
|
-
@prompt = @menu.prompt
|
360
|
-
|
361
|
-
if @menu.shell
|
362
|
-
selected = ask("Ignored", @menu.answer_type)
|
363
|
-
@menu.select(self, *selected)
|
274
|
+
if menu.gather
|
275
|
+
menu.gather_selected(self, selection, details)
|
364
276
|
else
|
365
|
-
|
366
|
-
|
277
|
+
menu.select(self, selection, details)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Convenience method to craft a lambda suitable for
|
282
|
+
# beind used in autocompletion operations by {#choose}
|
283
|
+
# @return [lambda] lambda to be used in autocompletion operations
|
284
|
+
|
285
|
+
def shell_style_lambda(menu)
|
286
|
+
lambda do |command| # shell-style selection
|
287
|
+
first_word = command.to_s.split.first || ""
|
288
|
+
|
289
|
+
options = menu.options
|
290
|
+
options.extend(OptionParser::Completion)
|
291
|
+
answer = options.complete(first_word)
|
292
|
+
|
293
|
+
raise Question::NoAutoCompleteMatch unless answer
|
294
|
+
|
295
|
+
[answer.last, command.sub(/^\s*#{first_word}\s*/, "")]
|
367
296
|
end
|
368
297
|
end
|
369
298
|
|
@@ -375,236 +304,59 @@ class HighLine
|
|
375
304
|
# (:blue for BLUE, for example). A CLEAR will automatically be embedded to
|
376
305
|
# the end of the returned String.
|
377
306
|
#
|
378
|
-
# This method returns the original _string_ unchanged if
|
307
|
+
# This method returns the original _string_ unchanged if use_color?
|
379
308
|
# is +false+.
|
380
309
|
#
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
#
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
#
|
392
|
-
def
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
# Works as an instance method, same as the class method
|
397
|
-
def color(*args)
|
398
|
-
self.class.color(*args)
|
399
|
-
end
|
400
|
-
|
401
|
-
# Remove color codes from a string
|
402
|
-
def self.uncolor(string)
|
403
|
-
Style.uncolor(string)
|
404
|
-
end
|
405
|
-
|
406
|
-
# Works as an instance method, same as the class method
|
407
|
-
def uncolor(string)
|
408
|
-
self.class.uncolor(string)
|
310
|
+
# @param string [String] string to be colored
|
311
|
+
# @param colors [Array<Symbol>] array of colors like [:red, :blue]
|
312
|
+
# @return [String] (ANSI escaped) colored string
|
313
|
+
# @example
|
314
|
+
# cli = HighLine.new
|
315
|
+
# cli.color("Sustainable", :green, :bold)
|
316
|
+
# # => "\e[32m\e[1mSustainable\e[0m"
|
317
|
+
#
|
318
|
+
# # As class method (delegating to HighLine.default_instance)
|
319
|
+
# HighLine.color("Sustainable", :green, :bold)
|
320
|
+
#
|
321
|
+
def color(string, *colors)
|
322
|
+
return string unless use_color?
|
323
|
+
HighLine.Style(*colors).color(string)
|
409
324
|
end
|
410
325
|
|
326
|
+
# In case you just want the color code, without the embedding and
|
327
|
+
# the CLEAR sequence.
|
411
328
|
#
|
412
|
-
#
|
413
|
-
#
|
414
|
-
# user.
|
329
|
+
# @param colors [Array<Symbol>]
|
330
|
+
# @return [String] ANSI escape code for the given colors.
|
415
331
|
#
|
416
|
-
#
|
417
|
-
#
|
418
|
-
#
|
332
|
+
# @example
|
333
|
+
# s = HighLine.Style(:red, :blue)
|
334
|
+
# s.code # => "\e[31m\e[34m"
|
419
335
|
#
|
420
|
-
#
|
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.
|
336
|
+
# HighLine.color_code(:red, :blue) # => "\e[31m\e[34m"
|
439
337
|
#
|
440
|
-
#
|
441
|
-
#
|
442
|
-
# final field width.
|
338
|
+
# cli = HighLine.new
|
339
|
+
# cli.color_code(:red, :blue) # => "\e[31m\e[34m"
|
443
340
|
#
|
444
|
-
def
|
445
|
-
|
446
|
-
|
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
|
341
|
+
def color_code(*colors)
|
342
|
+
HighLine.Style(*colors).code
|
343
|
+
end
|
480
344
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
345
|
+
# Remove color codes from a string.
|
346
|
+
# @param string [String] to be decolorized
|
347
|
+
# @return [String] without the ANSI escape sequence (colors)
|
348
|
+
def uncolor(string)
|
349
|
+
Style.uncolor(string)
|
350
|
+
end
|
486
351
|
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
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
|
352
|
+
# Renders a list of itens using a {ListRenderer}
|
353
|
+
# @param items [Array]
|
354
|
+
# @param mode [Symbol]
|
355
|
+
# @param option
|
356
|
+
# @return [String]
|
357
|
+
# @see ListRenderer#initialize ListRenderer#initialize for parameter details
|
358
|
+
def list(items, mode = :rows, option = nil)
|
359
|
+
ListRenderer.new(items, mode, option, self).render
|
608
360
|
end
|
609
361
|
|
610
362
|
#
|
@@ -612,34 +364,42 @@ class HighLine
|
|
612
364
|
# ends with a space or tab character, a newline will not be appended (output
|
613
365
|
# will be flush()ed). All other cases are passed straight to Kernel.puts().
|
614
366
|
#
|
615
|
-
# The _statement_
|
616
|
-
# embedded Ruby code. The template is evaluated
|
617
|
-
#
|
618
|
-
# and the HighLine
|
367
|
+
# The _statement_ argument is processed as an ERb template, supporting
|
368
|
+
# embedded Ruby code. The template is evaluated within a HighLine
|
369
|
+
# instance's binding for providing easy access to the ANSI color constants
|
370
|
+
# and the HighLine#color() method.
|
619
371
|
#
|
620
|
-
|
621
|
-
|
622
|
-
|
372
|
+
# @param statement [Statement, String] what to be said
|
373
|
+
def say(statement)
|
374
|
+
statement = render_statement(statement)
|
375
|
+
return if statement.empty?
|
623
376
|
|
624
|
-
|
377
|
+
statement = (indentation + statement)
|
625
378
|
|
626
379
|
# Don't add a newline if statement ends with whitespace, OR
|
627
380
|
# if statement ends with whitespace before a color escape code.
|
628
381
|
if /[ \t](\e\[\d+(;\d+)*m)?\Z/ =~ statement
|
629
|
-
|
630
|
-
|
382
|
+
output.print(statement)
|
383
|
+
output.flush
|
631
384
|
else
|
632
|
-
|
385
|
+
output.puts(statement)
|
633
386
|
end
|
634
387
|
end
|
635
388
|
|
389
|
+
# Renders a statement using {HighLine::Statement}
|
390
|
+
# @param statement [String] any string
|
391
|
+
# @return [Statement] rendered statement
|
392
|
+
def render_statement(statement)
|
393
|
+
Statement.new(statement, self).to_s
|
394
|
+
end
|
395
|
+
|
636
396
|
#
|
637
397
|
# Set to an integer value to cause HighLine to wrap output lines at the
|
638
398
|
# indicated character limit. When +nil+, the default, no wrapping occurs. If
|
639
399
|
# set to <tt>:auto</tt>, HighLine will attempt to determine the columns
|
640
400
|
# available for the <tt>@output</tt> or use a sensible default.
|
641
401
|
#
|
642
|
-
def wrap_at=(
|
402
|
+
def wrap_at=(setting)
|
643
403
|
@wrap_at = setting == :auto ? output_cols : setting
|
644
404
|
end
|
645
405
|
|
@@ -649,7 +409,7 @@ class HighLine
|
|
649
409
|
# set to <tt>:auto</tt>, HighLine will attempt to determine the rows available
|
650
410
|
# for the <tt>@output</tt> or use a sensible default.
|
651
411
|
#
|
652
|
-
def page_at=(
|
412
|
+
def page_at=(setting)
|
653
413
|
@page_at = setting == :auto ? output_rows - 2 : setting
|
654
414
|
end
|
655
415
|
|
@@ -657,29 +417,31 @@ class HighLine
|
|
657
417
|
# Outputs indentation with current settings
|
658
418
|
#
|
659
419
|
def indentation
|
660
|
-
|
420
|
+
" " * @indent_size * @indent_level
|
661
421
|
end
|
662
422
|
|
663
423
|
#
|
664
424
|
# Executes block or outputs statement with indentation
|
665
425
|
#
|
666
|
-
|
426
|
+
# @param increase [Integer] how much to increase indentation
|
427
|
+
# @param statement [Statement, String] to be said
|
428
|
+
# @param multiline [Boolean]
|
429
|
+
# @return [void]
|
430
|
+
# @see #multi_indent
|
431
|
+
def indent(increase = 1, statement = nil, multiline = nil)
|
667
432
|
@indent_level += increase
|
668
433
|
multi = @multi_indent
|
669
|
-
@multi_indent
|
434
|
+
@multi_indent ||= multiline
|
670
435
|
begin
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
raise
|
436
|
+
if block_given?
|
437
|
+
yield self
|
438
|
+
else
|
439
|
+
say(statement)
|
440
|
+
end
|
441
|
+
ensure
|
442
|
+
@multi_indent = multi
|
443
|
+
@indent_level -= increase
|
680
444
|
end
|
681
|
-
@multi_indent = multi
|
682
|
-
@indent_level -= increase
|
683
445
|
end
|
684
446
|
|
685
447
|
#
|
@@ -695,8 +457,8 @@ class HighLine
|
|
695
457
|
#
|
696
458
|
def output_cols
|
697
459
|
return 80 unless @output.tty?
|
698
|
-
terminal_size.first
|
699
|
-
rescue
|
460
|
+
terminal.terminal_size.first
|
461
|
+
rescue NoMethodError
|
700
462
|
return 80
|
701
463
|
end
|
702
464
|
|
@@ -706,105 +468,31 @@ class HighLine
|
|
706
468
|
#
|
707
469
|
def output_rows
|
708
470
|
return 24 unless @output.tty?
|
709
|
-
terminal_size.last
|
710
|
-
rescue
|
471
|
+
terminal.terminal_size.last
|
472
|
+
rescue NoMethodError
|
711
473
|
return 24
|
712
474
|
end
|
713
475
|
|
714
|
-
|
715
|
-
|
716
|
-
def
|
717
|
-
|
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
|
476
|
+
# Call #puts on the HighLine's output stream
|
477
|
+
# @param args [String] same args for Kernel#puts
|
478
|
+
def puts(*args)
|
479
|
+
@output.puts(*args)
|
733
480
|
end
|
734
481
|
|
735
482
|
#
|
736
|
-
#
|
737
|
-
# of the question.
|
483
|
+
# Creates a new HighLine instance with the same options
|
738
484
|
#
|
739
|
-
def
|
740
|
-
|
741
|
-
|
742
|
-
say(@question)
|
743
|
-
elsif @question.responses[:ask_on_error]
|
744
|
-
say(@question.responses[:ask_on_error])
|
745
|
-
end
|
485
|
+
def new_scope
|
486
|
+
self.class.new(@input, @output, @wrap_at,
|
487
|
+
@page_at, @indent_size, @indent_level)
|
746
488
|
end
|
747
489
|
|
748
|
-
|
749
|
-
# Collects an Array/Hash full of answers as described in
|
750
|
-
# HighLine::Question.gather().
|
751
|
-
#
|
752
|
-
# Raises EOFError if input is exhausted.
|
753
|
-
#
|
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
|
490
|
+
private
|
806
491
|
|
807
|
-
|
492
|
+
# Adds a layer of scope (new_scope) to ask a question inside a
|
493
|
+
# question, without destroying instance data
|
494
|
+
def confirm(question)
|
495
|
+
new_scope.agree(question.confirm_question(self))
|
808
496
|
end
|
809
497
|
|
810
498
|
#
|
@@ -812,10 +500,25 @@ class HighLine
|
|
812
500
|
# for finding whether a list of answers match or differ
|
813
501
|
# from each other.
|
814
502
|
#
|
815
|
-
def unique_answers(list
|
503
|
+
def unique_answers(list)
|
816
504
|
(list.respond_to?(:values) ? list.values : list).uniq
|
817
505
|
end
|
818
506
|
|
507
|
+
def last_answer(answers)
|
508
|
+
answers.respond_to?(:values) ? answers.values.last : answers.last
|
509
|
+
end
|
510
|
+
|
511
|
+
# Get response one line at time
|
512
|
+
# @param question [Question]
|
513
|
+
# @return [String] response
|
514
|
+
def get_response_line_mode(question)
|
515
|
+
if question.echo == true && !question.limit
|
516
|
+
get_line(question)
|
517
|
+
else
|
518
|
+
get_line_raw_no_echo_mode(question)
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
819
522
|
#
|
820
523
|
# Read a line of input from the input stream and process whitespace as
|
821
524
|
# requested by the Question object.
|
@@ -825,224 +528,123 @@ class HighLine
|
|
825
528
|
#
|
826
529
|
# Raises EOFError if input is exhausted.
|
827
530
|
#
|
828
|
-
def get_line(
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
Readline.completion_proc = lambda do |string|
|
841
|
-
@question.selection.grep(/\A#{Regexp.escape(string)}/)
|
842
|
-
end
|
531
|
+
def get_line(question)
|
532
|
+
terminal.get_line(question, self)
|
533
|
+
end
|
534
|
+
|
535
|
+
def get_line_raw_no_echo_mode(question)
|
536
|
+
line = ""
|
537
|
+
|
538
|
+
terminal.raw_no_echo_mode_exec do
|
539
|
+
loop do
|
540
|
+
character = terminal.get_character
|
541
|
+
break unless character
|
542
|
+
break if ["\n", "\r"].include? character
|
843
543
|
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
raise EOFError, "The input stream is exhausted."
|
544
|
+
# honor backspace and delete
|
545
|
+
if character == "\b" || character == "\u007F"
|
546
|
+
chopped = line.chop!
|
547
|
+
output_erase_char if chopped && question.echo
|
548
|
+
elsif character == "\e"
|
549
|
+
ignore_arrow_key
|
851
550
|
else
|
852
|
-
|
551
|
+
line << character
|
552
|
+
say_last_char_or_echo_char(line, question)
|
853
553
|
end
|
854
|
-
end
|
855
|
-
answer = @question.change_case(
|
856
|
-
@question.remove_whitespace(raw_answer))
|
857
|
-
$VERBOSE = old_verbose
|
858
554
|
|
859
|
-
|
860
|
-
else
|
861
|
-
if JRUBY
|
862
|
-
statement = format_statement(@question)
|
863
|
-
raw_answer = @java_console.readLine(statement, nil)
|
555
|
+
@output.flush
|
864
556
|
|
865
|
-
|
866
|
-
@@track_eof
|
867
|
-
else
|
868
|
-
raise EOFError, "The input stream is exhausted." if @@track_eof and
|
869
|
-
@input.eof?
|
870
|
-
raw_answer = @input.gets
|
557
|
+
break if line_overflow_for_question?(line, question)
|
871
558
|
end
|
872
|
-
|
873
|
-
@question.change_case(@question.remove_whitespace(raw_answer))
|
874
559
|
end
|
875
|
-
end
|
876
560
|
|
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?
|
561
|
+
say_new_line_or_overwrite(question)
|
888
562
|
|
889
|
-
|
890
|
-
|
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
|
563
|
+
question.format_answer(line)
|
564
|
+
end
|
948
565
|
|
949
|
-
|
950
|
-
|
566
|
+
def say_new_line_or_overwrite(question)
|
567
|
+
if question.overwrite
|
568
|
+
@output.print("\r#{HighLine.Style(:erase_line).code}")
|
569
|
+
@output.flush
|
951
570
|
else
|
952
|
-
|
953
|
-
say @question
|
954
|
-
end
|
955
|
-
|
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)
|
571
|
+
say("\n")
|
980
572
|
end
|
981
573
|
end
|
982
574
|
|
983
|
-
|
984
|
-
|
985
|
-
|
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?
|
575
|
+
def ignore_arrow_key
|
576
|
+
2.times do
|
577
|
+
terminal.get_character
|
998
578
|
end
|
999
|
-
return lines.join
|
1000
579
|
end
|
1001
580
|
|
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.
|
581
|
+
def say_last_char_or_echo_char(line, question)
|
582
|
+
@output.print(line[-1]) if question.echo == true
|
583
|
+
@output.print(question.echo) if question.echo && question.echo != true
|
1011
584
|
end
|
1012
585
|
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
586
|
+
def line_overflow_for_question?(line, question)
|
587
|
+
question.limit && line.size == question.limit
|
588
|
+
end
|
589
|
+
|
590
|
+
def output_erase_char
|
591
|
+
@output.print("\b#{HighLine.Style(:erase_char).code}")
|
592
|
+
end
|
593
|
+
|
594
|
+
# Get response using #getc
|
595
|
+
# @param question [Question]
|
596
|
+
# @return [String] response
|
597
|
+
def get_response_getc_mode(question)
|
598
|
+
terminal.raw_no_echo_mode_exec do
|
599
|
+
response = @input.getc
|
600
|
+
question.format_answer(response)
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
# Get response each character per turn
|
605
|
+
# @param question [Question]
|
606
|
+
# @return [String] response
|
607
|
+
def get_response_character_mode(question)
|
608
|
+
terminal.raw_no_echo_mode_exec do
|
609
|
+
response = terminal.get_character
|
610
|
+
if question.overwrite
|
611
|
+
erase_current_line
|
612
|
+
else
|
613
|
+
echo = question.get_echo_for_response(response)
|
614
|
+
say("#{echo}\n")
|
1033
615
|
end
|
1034
|
-
|
616
|
+
question.format_answer(response)
|
1035
617
|
end
|
1036
|
-
return wrapped.join
|
1037
618
|
end
|
1038
619
|
|
620
|
+
def erase_current_line
|
621
|
+
@output.print("\r#{HighLine.Style(:erase_line).code}")
|
622
|
+
@output.flush
|
623
|
+
end
|
624
|
+
|
625
|
+
public :get_response_character_mode, :get_response_line_mode
|
626
|
+
public :get_response_getc_mode
|
627
|
+
|
628
|
+
def actual_length(text)
|
629
|
+
Wrapper.actual_length text
|
630
|
+
end
|
631
|
+
|
632
|
+
# Check to see if there's already a HighLine.default_instance or if
|
633
|
+
# this is the first time the method is called (eg: at
|
634
|
+
# HighLine.default_instance initialization).
|
635
|
+
# If there's already one, copy use_color settings.
|
636
|
+
# This is here most to help migrate code from HighLine 1.7.x to 2.0.x
|
1039
637
|
#
|
1040
|
-
#
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1044
|
-
|
638
|
+
# @return [Boolean]
|
639
|
+
def default_use_color
|
640
|
+
if HighLine.default_instance
|
641
|
+
HighLine.default_instance.use_color
|
642
|
+
else
|
643
|
+
true
|
644
|
+
end
|
1045
645
|
end
|
1046
646
|
end
|
1047
647
|
|
1048
|
-
|
648
|
+
HighLine.default_instance = HighLine.new
|
649
|
+
|
650
|
+
require "highline/string"
|