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,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
|