highline 1.7.10 → 2.0.0.pre.develop.2
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 +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,109 @@
|
|
1
|
+
#coding: utf-8
|
2
|
+
|
3
|
+
class HighLine
|
4
|
+
module BuiltinStyles
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
STYLE_LIST = {
|
10
|
+
erase_line: "\e[K",
|
11
|
+
erase_char: "\e[P",
|
12
|
+
clear: "\e[0m",
|
13
|
+
reset: "\e[0m",
|
14
|
+
bold: "\e[1m",
|
15
|
+
dark: "\e[2m",
|
16
|
+
underline: "\e[4m",
|
17
|
+
underscore: "\e[4m",
|
18
|
+
blink: "\e[5m",
|
19
|
+
reverse: "\e[7m",
|
20
|
+
concealed: "\e[8m"
|
21
|
+
}
|
22
|
+
|
23
|
+
STYLE_LIST.each do |style_name, code|
|
24
|
+
style = String(style_name).upcase
|
25
|
+
|
26
|
+
const_set style, code
|
27
|
+
const_set style + "_STYLE", Style.new(name: style_name, code: code, builtin: true)
|
28
|
+
end
|
29
|
+
|
30
|
+
STYLES = %w{CLEAR RESET BOLD DARK UNDERLINE UNDERSCORE BLINK REVERSE CONCEALED}
|
31
|
+
|
32
|
+
COLOR_LIST = {
|
33
|
+
black: { code: "\e[30m", rgb: [0, 0, 0] },
|
34
|
+
red: { code: "\e[31m", rgb: [128, 0, 0] },
|
35
|
+
green: { code: "\e[32m", rgb: [0, 128, 0] },
|
36
|
+
blue: { code: "\e[34m", rgb: [0, 0, 128] },
|
37
|
+
yellow: { code: "\e[33m", rgb: [128, 128, 0] },
|
38
|
+
magenta: { code: "\e[35m", rgb: [128, 0, 128] },
|
39
|
+
cyan: { code: "\e[36m", rgb: [0, 128, 128] },
|
40
|
+
white: { code: "\e[37m", rgb: [192, 192, 192] },
|
41
|
+
gray: { code: "\e[37m", rgb: [192, 192, 192] },
|
42
|
+
grey: { code: "\e[37m", rgb: [192, 192, 192] },
|
43
|
+
none: { code: "\e[38m", rgb: [0, 0, 0] }
|
44
|
+
}
|
45
|
+
|
46
|
+
COLOR_LIST.each do |color_name, attributes|
|
47
|
+
color = String(color_name).upcase
|
48
|
+
|
49
|
+
style = Style.new(
|
50
|
+
name: color_name,
|
51
|
+
code: attributes[:code],
|
52
|
+
rgb: attributes[:rgb],
|
53
|
+
builtin: true
|
54
|
+
)
|
55
|
+
|
56
|
+
const_set color + "_STYLE", style
|
57
|
+
end
|
58
|
+
|
59
|
+
BASIC_COLORS = %w{BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE GRAY GREY NONE}
|
60
|
+
|
61
|
+
colors = BASIC_COLORS.dup
|
62
|
+
BASIC_COLORS.each do |color|
|
63
|
+
bright_color = "BRIGHT_#{color}"
|
64
|
+
colors << bright_color
|
65
|
+
const_set bright_color + '_STYLE', const_get(color + '_STYLE').bright
|
66
|
+
|
67
|
+
light_color = "LIGHT_#{color}"
|
68
|
+
colors << light_color
|
69
|
+
const_set light_color + '_STYLE', const_get(color + '_STYLE').light
|
70
|
+
end
|
71
|
+
COLORS = colors
|
72
|
+
|
73
|
+
colors.each do |color|
|
74
|
+
const_set color, const_get("#{color}_STYLE").code
|
75
|
+
const_set "ON_#{color}_STYLE", const_get("#{color}_STYLE").on
|
76
|
+
const_set "ON_#{color}", const_get("ON_#{color}_STYLE").code
|
77
|
+
end
|
78
|
+
|
79
|
+
ON_NONE_STYLE.rgb = [255,255,255] # Override; white background
|
80
|
+
|
81
|
+
module ClassMethods
|
82
|
+
RGB_COLOR = /^(ON_)?(RGB_)([A-F0-9]{6})(_STYLE)?$/
|
83
|
+
|
84
|
+
def const_missing(name)
|
85
|
+
if name.to_s =~ RGB_COLOR
|
86
|
+
on = $1
|
87
|
+
suffix = $4
|
88
|
+
|
89
|
+
if suffix
|
90
|
+
code_name = $1.to_s + $2 + $3
|
91
|
+
else
|
92
|
+
code_name = name.to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
style_name = code_name + '_STYLE'
|
96
|
+
style = Style.rgb($3)
|
97
|
+
style = style.on if on
|
98
|
+
|
99
|
+
const_set(style_name, style)
|
100
|
+
const_set(code_name, style.code)
|
101
|
+
|
102
|
+
suffix ? style : style.code
|
103
|
+
else
|
104
|
+
raise NameError, "Bad color or uninitialized constant #{name}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#--
|
1
4
|
# color_scheme.rb
|
2
5
|
#
|
3
6
|
# Created by Jeremy Hinegardner on 2007-01-24
|
@@ -48,7 +51,7 @@ class HighLine
|
|
48
51
|
#
|
49
52
|
def initialize( h = nil )
|
50
53
|
@scheme = Hash.new
|
51
|
-
load_from_hash(h)
|
54
|
+
load_from_hash(h) if h
|
52
55
|
yield self if block_given?
|
53
56
|
end
|
54
57
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class HighLine
|
2
|
+
module CustomErrors
|
3
|
+
# Internal HighLine errors.
|
4
|
+
class QuestionError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class NotValidQuestionError < QuestionError
|
8
|
+
end
|
9
|
+
|
10
|
+
class NotInRangeQuestionError < QuestionError
|
11
|
+
end
|
12
|
+
|
13
|
+
class NoConfirmationQuestionError < QuestionError
|
14
|
+
end
|
15
|
+
|
16
|
+
class NoAutoCompleteMatch < StandardError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/highline/import.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
1
3
|
# import.rb
|
2
4
|
#
|
3
5
|
# Created by James Edward Gray II on 2005-04-26.
|
@@ -33,9 +35,9 @@ class Object
|
|
33
35
|
#
|
34
36
|
def or_ask( *args, &details )
|
35
37
|
ask(*args) do |question|
|
36
|
-
question.first_answer = String(self)
|
38
|
+
question.first_answer = String(self)
|
37
39
|
|
38
|
-
details.call(question)
|
40
|
+
details.call(question) if details
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
class HighLine
|
4
|
+
class List
|
5
|
+
attr_reader :items, :cols
|
6
|
+
attr_reader :transpose_mode, :col_down_mode
|
7
|
+
|
8
|
+
def initialize(items, options = {})
|
9
|
+
@items = items
|
10
|
+
@transpose_mode = options.fetch(:transpose) { false }
|
11
|
+
@col_down_mode = options.fetch(:col_down) { false }
|
12
|
+
@cols = options.fetch(:cols) { 1 }
|
13
|
+
build
|
14
|
+
end
|
15
|
+
|
16
|
+
def transpose
|
17
|
+
first_row = @list[0]
|
18
|
+
other_rows = @list[1..-1]
|
19
|
+
@list = first_row.zip(*other_rows)
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def col_down
|
24
|
+
slice_by_rows
|
25
|
+
transpose
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def slice_by_rows
|
30
|
+
@list = items_sliced_by_rows
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def slice_by_cols
|
35
|
+
@list = items_sliced_by_cols
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def cols=(cols)
|
40
|
+
@cols = cols
|
41
|
+
build
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_a
|
45
|
+
list
|
46
|
+
end
|
47
|
+
|
48
|
+
def list
|
49
|
+
@list.dup
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
list.map { |row| stringfy(row) }.join
|
54
|
+
end
|
55
|
+
|
56
|
+
def row_join_string
|
57
|
+
@row_join_string ||= " "
|
58
|
+
end
|
59
|
+
|
60
|
+
def row_join_string=(string)
|
61
|
+
@row_join_string = string
|
62
|
+
end
|
63
|
+
|
64
|
+
def row_join_str_size
|
65
|
+
row_join_string.size
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def build
|
71
|
+
slice_by_cols
|
72
|
+
transpose if transpose_mode
|
73
|
+
col_down if col_down_mode
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def items_sliced_by_cols
|
78
|
+
items.each_slice(cols).to_a
|
79
|
+
end
|
80
|
+
|
81
|
+
def items_sliced_by_rows
|
82
|
+
items.each_slice(row_count).to_a
|
83
|
+
end
|
84
|
+
|
85
|
+
def row_count
|
86
|
+
(items.count / cols.to_f).ceil
|
87
|
+
end
|
88
|
+
|
89
|
+
def stringfy(row)
|
90
|
+
row.compact.join(row_join_string) + "\n"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'highline/template_renderer'
|
4
|
+
require 'highline/wrapper'
|
5
|
+
require 'highline/list'
|
6
|
+
|
7
|
+
class HighLine::ListRenderer
|
8
|
+
attr_reader :items, :mode, :option, :highline
|
9
|
+
|
10
|
+
def initialize(items, mode = :rows, option = nil, highline)
|
11
|
+
@highline = highline
|
12
|
+
@mode = mode
|
13
|
+
@option = option
|
14
|
+
@items = render_list_items(items)
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# This method is a utility for quickly and easily laying out lists. It can
|
19
|
+
# be accessed within ERb replacements of any text that will be sent to the
|
20
|
+
# user.
|
21
|
+
#
|
22
|
+
# The only required parameter is _items_, which should be the Array of items
|
23
|
+
# to list. A specified _mode_ controls how that list is formed and _option_
|
24
|
+
# has different effects, depending on the _mode_. Recognized modes are:
|
25
|
+
#
|
26
|
+
# <tt>:columns_across</tt>:: _items_ will be placed in columns,
|
27
|
+
# flowing from left to right. If given,
|
28
|
+
# _option_ is the number of columns to be
|
29
|
+
# used. When absent, columns will be
|
30
|
+
# determined based on _wrap_at_ or a
|
31
|
+
# default of 80 characters.
|
32
|
+
# <tt>:columns_down</tt>:: Identical to <tt>:columns_across</tt>,
|
33
|
+
# save flow goes down.
|
34
|
+
# <tt>:uneven_columns_across</tt>:: Like <tt>:columns_across</tt> but each
|
35
|
+
# column is sized independently.
|
36
|
+
# <tt>:uneven_columns_down</tt>:: Like <tt>:columns_down</tt> but each
|
37
|
+
# column is sized independently.
|
38
|
+
# <tt>:inline</tt>:: All _items_ are placed on a single line.
|
39
|
+
# The last two _items_ are separated by
|
40
|
+
# _option_ or a default of " or ". All
|
41
|
+
# other _items_ are separated by ", ".
|
42
|
+
# <tt>:rows</tt>:: The default mode. Each of the _items_ is
|
43
|
+
# placed on its own line. The _option_
|
44
|
+
# parameter is ignored in this mode.
|
45
|
+
#
|
46
|
+
# Each member of the _items_ Array is passed through ERb and thus can contain
|
47
|
+
# their own expansions. Color escape expansions do not contribute to the
|
48
|
+
# final field width.
|
49
|
+
#
|
50
|
+
def render
|
51
|
+
return "" if items.empty?
|
52
|
+
|
53
|
+
case mode
|
54
|
+
when :inline
|
55
|
+
list_inline_mode
|
56
|
+
when :columns_across
|
57
|
+
list_columns_across_mode
|
58
|
+
when :columns_down
|
59
|
+
list_columns_down_mode
|
60
|
+
when :uneven_columns_across
|
61
|
+
list_uneven_columns_mode
|
62
|
+
when :uneven_columns_down
|
63
|
+
list_uneven_columns_down_mode
|
64
|
+
else
|
65
|
+
list_default_mode
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def render_list_items(items)
|
72
|
+
items.to_ary.map do |item|
|
73
|
+
item = String(item)
|
74
|
+
template = ERB.new(item, nil, "%")
|
75
|
+
template_renderer = HighLine::TemplateRenderer.new(template, self, highline)
|
76
|
+
template_renderer.render
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def list_default_mode
|
81
|
+
items.map { |i| "#{i}\n" }.join
|
82
|
+
end
|
83
|
+
|
84
|
+
def list_inline_mode
|
85
|
+
end_separator = option || " or "
|
86
|
+
|
87
|
+
if items.size == 1
|
88
|
+
items.first
|
89
|
+
else
|
90
|
+
items[0..-2].join(", ") + "#{end_separator}#{items.last}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def list_columns_across_mode
|
95
|
+
HighLine::List.new(right_padded_items, cols: col_count).to_s
|
96
|
+
end
|
97
|
+
|
98
|
+
def list_columns_down_mode
|
99
|
+
HighLine::List.new(right_padded_items, cols: col_count, col_down: true).to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
def list_uneven_columns_mode(list=nil)
|
103
|
+
list ||= HighLine::List.new(items)
|
104
|
+
|
105
|
+
col_max = option || items.size
|
106
|
+
col_max.downto(1) do |column_count|
|
107
|
+
list.cols = column_count
|
108
|
+
widths = get_col_widths(list)
|
109
|
+
|
110
|
+
if column_count == 1 or # last guess
|
111
|
+
inside_line_size_limit?(widths) or # good guess
|
112
|
+
option # defined by user
|
113
|
+
return pad_uneven_rows(list, widths)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def list_uneven_columns_down_mode
|
119
|
+
list = HighLine::List.new(items, col_down: true)
|
120
|
+
list_uneven_columns_mode(list)
|
121
|
+
end
|
122
|
+
|
123
|
+
def pad_uneven_rows(list, widths)
|
124
|
+
right_padded_list = Array(list).map do |row|
|
125
|
+
right_pad_row(row.compact, widths)
|
126
|
+
end
|
127
|
+
stringfy_list(right_padded_list)
|
128
|
+
end
|
129
|
+
|
130
|
+
def stringfy_list(list)
|
131
|
+
list.map { |row| row_to_s(row) }.join
|
132
|
+
end
|
133
|
+
|
134
|
+
def row_to_s(row)
|
135
|
+
row.compact.join(row_join_string) + "\n"
|
136
|
+
end
|
137
|
+
|
138
|
+
def right_pad_row(row, widths)
|
139
|
+
row.zip(widths).map do |field, width|
|
140
|
+
right_pad_field(field, width)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def right_pad_field(field, width)
|
145
|
+
field = String(field) # nil protection
|
146
|
+
pad_size = width - actual_length(field)
|
147
|
+
field + (pad_char * pad_size)
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_col_widths(lines)
|
151
|
+
lines = transpose(lines)
|
152
|
+
get_segment_widths(lines)
|
153
|
+
end
|
154
|
+
|
155
|
+
def get_row_widths(lines)
|
156
|
+
get_segment_widths(lines)
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_segment_widths(lines)
|
160
|
+
lines.map do |col|
|
161
|
+
actual_lengths_for(col).max
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def actual_lengths_for(line)
|
166
|
+
line.map do |item|
|
167
|
+
actual_length(item)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def transpose(lines)
|
172
|
+
lines = Array(lines)
|
173
|
+
first_line = lines.shift
|
174
|
+
first_line.zip(*lines)
|
175
|
+
end
|
176
|
+
|
177
|
+
def inside_line_size_limit?(widths)
|
178
|
+
line_size = widths.inject(0) { |sum, n| sum + n + row_join_str_size }
|
179
|
+
line_size <= line_size_limit + row_join_str_size
|
180
|
+
end
|
181
|
+
|
182
|
+
def actual_length(text)
|
183
|
+
HighLine::Wrapper.actual_length text
|
184
|
+
end
|
185
|
+
|
186
|
+
def items_max_length
|
187
|
+
@items_max_length ||= max_length(items)
|
188
|
+
end
|
189
|
+
|
190
|
+
def max_length(items)
|
191
|
+
items.map { |item| actual_length(item) }.max
|
192
|
+
end
|
193
|
+
|
194
|
+
def line_size_limit
|
195
|
+
@line_size_limit ||= ( highline.wrap_at || 80 )
|
196
|
+
end
|
197
|
+
|
198
|
+
def row_join_string
|
199
|
+
@row_join_string ||= " "
|
200
|
+
end
|
201
|
+
|
202
|
+
def row_join_string=(string)
|
203
|
+
@row_join_string = string
|
204
|
+
end
|
205
|
+
|
206
|
+
def row_join_str_size
|
207
|
+
row_join_string.size
|
208
|
+
end
|
209
|
+
|
210
|
+
def get_col_count
|
211
|
+
(line_size_limit + row_join_str_size) /
|
212
|
+
(items_max_length + row_join_str_size)
|
213
|
+
end
|
214
|
+
|
215
|
+
def col_count
|
216
|
+
option || get_col_count
|
217
|
+
end
|
218
|
+
|
219
|
+
def right_padded_items
|
220
|
+
items.map do |item|
|
221
|
+
right_pad_field(item, items_max_length)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def pad_char
|
226
|
+
" "
|
227
|
+
end
|
228
|
+
|
229
|
+
def row_count
|
230
|
+
(items.count / col_count.to_f).ceil
|
231
|
+
end
|
232
|
+
end
|