highline 2.0.0.pre.develop.2 → 2.0.0.pre.develop.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changelog.md +28 -0
- data/README.md +188 -0
- data/Rakefile +0 -11
- data/highline.gemspec +2 -4
- data/lib/highline.rb +170 -73
- data/lib/highline/builtin_styles.rb +18 -2
- data/lib/highline/color_scheme.rb +15 -5
- data/lib/highline/compatibility.rb +6 -1
- data/lib/highline/custom_errors.rb +43 -6
- data/lib/highline/import.rb +10 -3
- data/lib/highline/list.rb +95 -7
- data/lib/highline/list_renderer.rb +36 -17
- data/lib/highline/menu.rb +73 -18
- data/lib/highline/paginator.rb +10 -0
- data/lib/highline/question.rb +98 -41
- data/lib/highline/question/answer_converter.rb +19 -0
- data/lib/highline/question_asker.rb +21 -17
- data/lib/highline/simulate.rb +10 -1
- data/lib/highline/statement.rb +62 -38
- data/lib/highline/string.rb +26 -25
- data/lib/highline/string_extensions.rb +60 -32
- data/lib/highline/style.rb +125 -25
- data/lib/highline/template_renderer.rb +25 -1
- data/lib/highline/terminal.rb +124 -1
- data/lib/highline/terminal/io_console.rb +6 -75
- data/lib/highline/terminal/ncurses.rb +7 -8
- data/lib/highline/terminal/unix_stty.rb +35 -81
- data/lib/highline/version.rb +1 -1
- data/lib/highline/wrapper.rb +9 -0
- data/test/test_highline.rb +1 -1
- metadata +6 -13
- data/INSTALL +0 -59
- data/README.rdoc +0 -77
- data/setup.rb +0 -1360
data/lib/highline/style.rb
CHANGED
@@ -1,48 +1,77 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
#--
|
4
|
-
# color_scheme.rb
|
4
|
+
# originally color_scheme.rb
|
5
5
|
#
|
6
6
|
# Created by Richard LeBer on 2011-06-27.
|
7
7
|
# Copyright 2011. All rights reserved
|
8
8
|
#
|
9
9
|
# This is Free Software. See LICENSE and COPYING for details
|
10
10
|
|
11
|
+
|
11
12
|
class HighLine
|
12
13
|
|
14
|
+
# Creates a style using {.find_or_create_style} or
|
15
|
+
# {.find_or_create_style_list}
|
16
|
+
# @param args [Array<Style, Hash, String>] style properties
|
17
|
+
# @return [Style]
|
13
18
|
def self.Style(*args)
|
14
19
|
args = args.compact.flatten
|
15
20
|
if args.size==1
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
elsif arg.to_s.downcase =~ /^on_rgb_([a-f0-9]{6})$/
|
34
|
-
Style.rgb($1).on
|
21
|
+
find_or_create_style(args.first)
|
22
|
+
else
|
23
|
+
find_or_create_style_list(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Search for a Style with the given properties and return it.
|
28
|
+
# If there's no matched Style, it creates one.
|
29
|
+
# You can pass a Style, String or a Hash.
|
30
|
+
# @param arg [Style, String, Hash] style properties
|
31
|
+
# @return [Style] found or creted Style
|
32
|
+
def self.find_or_create_style(arg)
|
33
|
+
if arg.is_a?(Style)
|
34
|
+
Style.list[arg.name] || Style.index(arg)
|
35
|
+
elsif arg.is_a?(::String) && arg =~ /^\e\[/ # arg is a code
|
36
|
+
if styles = Style.code_index[arg]
|
37
|
+
styles.first
|
35
38
|
else
|
36
|
-
|
39
|
+
Style.new(:code=>arg)
|
37
40
|
end
|
41
|
+
elsif style = Style.list[arg]
|
42
|
+
style
|
43
|
+
elsif HighLine.color_scheme && HighLine.color_scheme[arg]
|
44
|
+
HighLine.color_scheme[arg]
|
45
|
+
elsif arg.is_a?(Hash)
|
46
|
+
Style.new(arg)
|
47
|
+
elsif arg.to_s.downcase =~ /^rgb_([a-f0-9]{6})$/
|
48
|
+
Style.rgb($1)
|
49
|
+
elsif arg.to_s.downcase =~ /^on_rgb_([a-f0-9]{6})$/
|
50
|
+
Style.rgb($1).on
|
38
51
|
else
|
39
|
-
|
40
|
-
Style.list[name] || Style.new(:list=>args)
|
52
|
+
raise NameError, "#{arg.inspect} is not a defined Style"
|
41
53
|
end
|
42
54
|
end
|
43
55
|
|
56
|
+
# Find a Style list or create a new one.
|
57
|
+
# @param args [Array<Symbol>] an Array of Symbols of each style
|
58
|
+
# that will be on the style list.
|
59
|
+
# @return [Style] Style list
|
60
|
+
# @example Creating a Style list of the combined RED and BOLD styles.
|
61
|
+
# style_list = HighLine.find_or_create_style_list(:red, :bold)
|
62
|
+
|
63
|
+
def self.find_or_create_style_list(*args)
|
64
|
+
name = args
|
65
|
+
Style.list[name] || Style.new(:list=>args)
|
66
|
+
end
|
67
|
+
|
68
|
+
# ANSI styles to be used by HighLine.
|
44
69
|
class Style
|
45
70
|
|
71
|
+
# Index the given style.
|
72
|
+
# Uses @code_index (Hash) as repository.
|
73
|
+
# @param style [Style]
|
74
|
+
# @return [Style] the given style
|
46
75
|
def self.index(style)
|
47
76
|
if style.name
|
48
77
|
@styles ||= {}
|
@@ -57,6 +86,9 @@ class HighLine
|
|
57
86
|
style
|
58
87
|
end
|
59
88
|
|
89
|
+
# Clear all custom Styles, restoring the Style index to
|
90
|
+
# builtin styles only.
|
91
|
+
# @return [void]
|
60
92
|
def self.clear_index
|
61
93
|
# reset to builtin only styles
|
62
94
|
@styles = list.select { |name, style| style.builtin }
|
@@ -64,16 +96,40 @@ class HighLine
|
|
64
96
|
@styles.each { |name, style| index(style) }
|
65
97
|
end
|
66
98
|
|
99
|
+
# Converts all given color codes to hexadecimal and
|
100
|
+
# join them in a single string. If any given color code
|
101
|
+
# is already a String, doesn't perform any convertion.
|
102
|
+
#
|
103
|
+
# @param colors [Array<Numeric, String>] color codes
|
104
|
+
# @return [String] all color codes joined
|
105
|
+
# @example
|
106
|
+
# HighLine::Style.rgb_hex(9, 10, "11") # => "090a11"
|
67
107
|
def self.rgb_hex(*colors)
|
68
108
|
colors.map do |color|
|
69
109
|
color.is_a?(Numeric) ? '%02x'%color : color.to_s
|
70
110
|
end.join
|
71
111
|
end
|
72
112
|
|
113
|
+
# Split an rgb code string into its 3 numerical compounds.
|
114
|
+
# @param hex [String] rgb code string like "010F0F"
|
115
|
+
# @return [Array<Numeric>] numerical compounds like [1, 15, 15]
|
116
|
+
# @example
|
117
|
+
# HighLine::Style.rgb_parts("090A0B") # => [9, 10, 11]
|
118
|
+
|
73
119
|
def self.rgb_parts(hex)
|
74
120
|
hex.scan(/../).map{|part| part.to_i(16)}
|
75
121
|
end
|
76
122
|
|
123
|
+
# Search for or create a new Style from the colors provided.
|
124
|
+
# @param colors (see .rgb_hex)
|
125
|
+
# @return [Style] a Style with the rgb colors provided.
|
126
|
+
# @example Creating a new Style based on rgb code
|
127
|
+
# rgb_style = HighLine::Style.rgb(9, 10, 11)
|
128
|
+
#
|
129
|
+
# rgb_style.name # => :rgb_090a0b
|
130
|
+
# rgb_style.code # => "\e[38;5;16m"
|
131
|
+
# rgb_style.rgb # => [9, 10, 11]
|
132
|
+
#
|
77
133
|
def self.rgb(*colors)
|
78
134
|
hex = rgb_hex(*colors)
|
79
135
|
name = ('rgb_' + hex).to_sym
|
@@ -85,34 +141,58 @@ class HighLine
|
|
85
141
|
end
|
86
142
|
end
|
87
143
|
|
144
|
+
# Returns the rgb number to be used as escape code on ANSI terminals.
|
145
|
+
# @param parts [Array<Numeric>] three numerical codes for red, green and blue
|
146
|
+
# @return [Numeric] to be used as escape code on ANSI terminals
|
88
147
|
def self.rgb_number(*parts)
|
89
148
|
parts = parts.flatten
|
90
149
|
16 + parts.inject(0) {|kode, part| kode*6 + (part/256.0*6.0).floor}
|
91
150
|
end
|
92
151
|
|
152
|
+
# From an ANSI number (color escape code), craft an 'rgb_hex' code of it
|
153
|
+
# @param ansi_number [Integer] ANSI escape code
|
154
|
+
# @return [String] all color codes joined as {.rgb_hex}
|
93
155
|
def self.ansi_rgb_to_hex(ansi_number)
|
94
156
|
raise "Invalid ANSI rgb code #{ansi_number}" unless (16..231).include?(ansi_number)
|
95
157
|
parts = (ansi_number-16).to_s(6).rjust(3,'0').scan(/./).map{|d| (d.to_i*255.0/6.0).ceil}
|
96
158
|
rgb_hex(*parts)
|
97
159
|
end
|
98
160
|
|
161
|
+
# @return [Hash] list of all cached Styles
|
99
162
|
def self.list
|
100
163
|
@styles ||= {}
|
101
164
|
end
|
102
165
|
|
166
|
+
# @return [Hash] list of all cached Style codes
|
103
167
|
def self.code_index
|
104
168
|
@code_index ||= {}
|
105
169
|
end
|
106
170
|
|
171
|
+
# Remove any ANSI color escape sequence of the given String.
|
172
|
+
# @param string [String]
|
173
|
+
# @return [String]
|
107
174
|
def self.uncolor(string)
|
108
175
|
string.gsub(/\e\[\d+(;\d+)*m/, '')
|
109
176
|
end
|
110
177
|
|
111
|
-
|
112
|
-
|
178
|
+
# Style name
|
179
|
+
# @return [Symbol] the name of the Style
|
180
|
+
attr_reader :name
|
181
|
+
|
182
|
+
# When a compound Style, returns a list of its components.
|
183
|
+
# @return [Array<Symbol>] components of a Style list
|
184
|
+
attr_reader :list
|
185
|
+
|
186
|
+
# @return [Array] the three numerical rgb codes. Ex: [10, 12, 127]
|
187
|
+
attr_accessor :rgb
|
188
|
+
|
189
|
+
# @return [Boolean] true if the Style is builtin or not.
|
190
|
+
attr_accessor :builtin
|
113
191
|
|
114
192
|
# Single color/styles have :name, :code, :rgb (possibly), :builtin
|
115
193
|
# Compound styles have :name, :list, :builtin
|
194
|
+
#
|
195
|
+
# @param defn [Hash] options for the Style to be created.
|
116
196
|
def initialize(defn = {})
|
117
197
|
@definition = defn
|
118
198
|
@name = defn[:name]
|
@@ -129,18 +209,27 @@ class HighLine
|
|
129
209
|
self.class.index self unless defn[:no_index]
|
130
210
|
end
|
131
211
|
|
212
|
+
# Duplicate current Style using the same definition used to create it.
|
213
|
+
# @return [Style] duplicated Style
|
132
214
|
def dup
|
133
215
|
self.class.new(@definition)
|
134
216
|
end
|
135
217
|
|
218
|
+
# @return [Hash] the definition used to create this Style
|
136
219
|
def to_hash
|
137
220
|
@definition
|
138
221
|
end
|
139
222
|
|
223
|
+
# Uses the Style definition to add ANSI color escape codes
|
224
|
+
# to a a given String
|
225
|
+
# @param string [String] to be colored
|
226
|
+
# @return [String] an ANSI colored string
|
140
227
|
def color(string)
|
141
228
|
code + string + HighLine::CLEAR
|
142
229
|
end
|
143
230
|
|
231
|
+
# @return [String] all codes of the Style list joined together (if a Style list)
|
232
|
+
# @return [String] the Style code
|
144
233
|
def code
|
145
234
|
if @list
|
146
235
|
@list.map{|element| HighLine.Style(element).code}.join
|
@@ -149,18 +238,25 @@ class HighLine
|
|
149
238
|
end
|
150
239
|
end
|
151
240
|
|
241
|
+
# @return [Integer] the RED component of the rgb code
|
152
242
|
def red
|
153
243
|
@rgb && @rgb[0]
|
154
244
|
end
|
155
245
|
|
246
|
+
# @return [Integer] the GREEN component of the rgb code
|
156
247
|
def green
|
157
248
|
@rgb && @rgb[1]
|
158
249
|
end
|
159
250
|
|
251
|
+
# @return [Integer] the BLUE component of the rgb code
|
160
252
|
def blue
|
161
253
|
@rgb && @rgb[2]
|
162
254
|
end
|
163
255
|
|
256
|
+
# Duplicate Style with some minor changes
|
257
|
+
# @param new_name [Symbol]
|
258
|
+
# @param options [Hash] Style attributes to be changed
|
259
|
+
# @return [Style] new Style with changed attributes
|
164
260
|
def variant(new_name, options={})
|
165
261
|
raise "Cannot create a variant of a style list (#{inspect})" if @list
|
166
262
|
new_code = options[:code] || code
|
@@ -172,15 +268,19 @@ class HighLine
|
|
172
268
|
self.class.new(self.to_hash.merge(:name=>new_name, :code=>new_code, :rgb=>new_rgb))
|
173
269
|
end
|
174
270
|
|
271
|
+
# Uses the color as background and return a new style.
|
272
|
+
# @return [Style]
|
175
273
|
def on
|
176
274
|
new_name = ('on_'+@name.to_s).to_sym
|
177
275
|
self.class.list[new_name] ||= variant(new_name, :increment=>10)
|
178
276
|
end
|
179
277
|
|
278
|
+
# @return [Style] a brighter version of this Style
|
180
279
|
def bright
|
181
280
|
create_bright_variant(:bright)
|
182
281
|
end
|
183
282
|
|
283
|
+
# @return [Style] a lighter version of this Style
|
184
284
|
def light
|
185
285
|
create_bright_variant(:light)
|
186
286
|
end
|
@@ -3,13 +3,29 @@
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
class HighLine
|
6
|
+
# Renders an erb template taking a {Question} and a {HighLine} instance
|
7
|
+
# as context.
|
6
8
|
class TemplateRenderer
|
7
9
|
extend Forwardable
|
8
10
|
|
9
11
|
def_delegators :@highline, :color, :list, :key
|
10
12
|
def_delegators :@source, :answer_type, :prompt, :header, :answer
|
11
13
|
|
12
|
-
|
14
|
+
# @return [ERB] ERB template being rendered
|
15
|
+
attr_reader :template
|
16
|
+
|
17
|
+
# @return [Question, Menu] Question instance used as context
|
18
|
+
attr_reader :source
|
19
|
+
|
20
|
+
# @return [HighLine] HighLine instance used as context
|
21
|
+
attr_reader :highline
|
22
|
+
|
23
|
+
# Initializes the TemplateRenderer object with its template and
|
24
|
+
# HighLine and Question contexts.
|
25
|
+
#
|
26
|
+
# @param template [ERB] ERB template.
|
27
|
+
# @param source [Question] question object.
|
28
|
+
# @param highline [HighLine] HighLine instance.
|
13
29
|
|
14
30
|
def initialize(template, source, highline)
|
15
31
|
@template = template
|
@@ -17,20 +33,28 @@ class HighLine
|
|
17
33
|
@highline = highline
|
18
34
|
end
|
19
35
|
|
36
|
+
# @return [String] rendered template
|
20
37
|
def render
|
21
38
|
template.result(binding)
|
22
39
|
end
|
23
40
|
|
41
|
+
# Returns an error message when the called method
|
42
|
+
# is not available.
|
43
|
+
# @return [String] error message.
|
24
44
|
def method_missing(method, *args)
|
25
45
|
"Method #{method} with args #{args.inspect} " +
|
26
46
|
"is not available on #{self.inspect}. " +
|
27
47
|
"Try #{methods(false).sort.inspect}"
|
28
48
|
end
|
29
49
|
|
50
|
+
# @return [Question, Menu] {#source} attribute.
|
30
51
|
def menu
|
31
52
|
source
|
32
53
|
end
|
33
54
|
|
55
|
+
# If some constant is missing at this TemplateRenderer instance,
|
56
|
+
# get it from HighLine. Useful to get color and style contants.
|
57
|
+
# @param name [Symbol] automatically passed constant's name as Symbol
|
34
58
|
def self.const_missing(name)
|
35
59
|
HighLine.const_get(name)
|
36
60
|
end
|
data/lib/highline/terminal.rb
CHANGED
@@ -12,7 +12,14 @@
|
|
12
12
|
require "highline/compatibility"
|
13
13
|
|
14
14
|
class HighLine
|
15
|
+
# Basic Terminal class which HighLine will direct
|
16
|
+
# input and output to.
|
17
|
+
# The specialized Terminals all decend from this HighLine::Terminal class
|
15
18
|
class Terminal
|
19
|
+
|
20
|
+
# Probe for and return a suitable Terminal instance
|
21
|
+
# @param input [IO] input stream
|
22
|
+
# @param output [IO] output stream
|
16
23
|
def self.get_terminal(input, output)
|
17
24
|
terminal = nil
|
18
25
|
|
@@ -34,22 +41,37 @@ class HighLine
|
|
34
41
|
terminal
|
35
42
|
end
|
36
43
|
|
37
|
-
|
44
|
+
# @return [IO] input stream
|
45
|
+
attr_reader :input
|
46
|
+
|
47
|
+
# @return [IO] output stream
|
48
|
+
attr_reader :output
|
38
49
|
|
50
|
+
# Creates a terminal instance based on given input and output streams.
|
51
|
+
# @param input [IO] input stream
|
52
|
+
# @param output [IO] output stream
|
39
53
|
def initialize(input, output)
|
40
54
|
@input = input
|
41
55
|
@output = output
|
42
56
|
end
|
43
57
|
|
58
|
+
# An initialization callback.
|
59
|
+
# It is called by {.get_terminal}.
|
44
60
|
def initialize_system_extensions
|
45
61
|
end
|
46
62
|
|
63
|
+
# @return [Array<Integer, Integer>] two value terminal
|
64
|
+
# size like [columns, lines]
|
47
65
|
def terminal_size
|
66
|
+
[80, 24]
|
48
67
|
end
|
49
68
|
|
69
|
+
# Enter Raw No Echo mode.
|
50
70
|
def raw_no_echo_mode
|
51
71
|
end
|
52
72
|
|
73
|
+
# Yieds a block to be executed in Raw No Echo mode and
|
74
|
+
# then restore the terminal state.
|
53
75
|
def raw_no_echo_mode_exec
|
54
76
|
raw_no_echo_mode
|
55
77
|
yield
|
@@ -57,22 +79,123 @@ class HighLine
|
|
57
79
|
restore_mode
|
58
80
|
end
|
59
81
|
|
82
|
+
# Restore terminal to its default mode
|
60
83
|
def restore_mode
|
61
84
|
end
|
62
85
|
|
86
|
+
# Get one character from the terminal
|
87
|
+
# @return [String] one character
|
63
88
|
def get_character
|
64
89
|
end
|
65
90
|
|
91
|
+
# Get one line from the terminal and format accordling.
|
92
|
+
# Use readline if question has readline mode set.
|
93
|
+
# @param question [HighLine::Question]
|
94
|
+
# @param highline [HighLine]
|
95
|
+
# @param options [Hash]
|
96
|
+
def get_line(question, highline, options={})
|
97
|
+
raw_answer =
|
98
|
+
if question.readline
|
99
|
+
get_line_with_readline(question, highline, options={})
|
100
|
+
else
|
101
|
+
get_line_default(highline)
|
102
|
+
end
|
103
|
+
|
104
|
+
question.format_answer(raw_answer)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Get one line using #readline_read
|
108
|
+
# @param (see #get_line)
|
109
|
+
def get_line_with_readline(question, highline, options={})
|
110
|
+
require "readline" # load only if needed
|
111
|
+
|
112
|
+
question_string = highline.render_statement(question)
|
113
|
+
|
114
|
+
raw_answer = readline_read(question_string, question)
|
115
|
+
|
116
|
+
if !raw_answer and highline.track_eof?
|
117
|
+
raise EOFError, "The input stream is exhausted."
|
118
|
+
end
|
119
|
+
|
120
|
+
raw_answer || ""
|
121
|
+
end
|
122
|
+
|
123
|
+
# Use readline to read one line
|
124
|
+
# @param prompt [String] Readline prompt
|
125
|
+
# @param question [HighLine::Question] question from where to get
|
126
|
+
# autocomplete candidate strings
|
127
|
+
def readline_read(prompt, question)
|
128
|
+
# prep auto-completion
|
129
|
+
unless question.selection.empty?
|
130
|
+
Readline.completion_proc = lambda do |str|
|
131
|
+
question.selection.grep(/\A#{Regexp.escape(str)}/)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# work-around ugly readline() warnings
|
136
|
+
old_verbose = $VERBOSE
|
137
|
+
$VERBOSE = nil
|
138
|
+
|
139
|
+
raw_answer = run_preserving_stty do
|
140
|
+
Readline.readline(prompt, true)
|
141
|
+
end
|
142
|
+
|
143
|
+
$VERBOSE = old_verbose
|
144
|
+
|
145
|
+
raw_answer
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get one line from terminal using default #gets method.
|
149
|
+
# @param highline (see #get_line)
|
150
|
+
def get_line_default(highline)
|
151
|
+
raise EOFError, "The input stream is exhausted." if highline.track_eof? and
|
152
|
+
highline.input.eof?
|
153
|
+
highline.input.gets
|
154
|
+
end
|
155
|
+
|
156
|
+
# @!group Enviroment queries
|
157
|
+
|
158
|
+
# Running on JRuby?
|
66
159
|
def jruby?
|
67
160
|
defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
68
161
|
end
|
69
162
|
|
163
|
+
# Running on Rubinius?
|
70
164
|
def rubinius?
|
71
165
|
defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
|
72
166
|
end
|
73
167
|
|
168
|
+
# Running on Windows?
|
74
169
|
def windows?
|
75
170
|
defined?(RUBY_PLATFORM) && (RUBY_PLATFORM =~ /mswin|mingw|cygwin/)
|
76
171
|
end
|
172
|
+
|
173
|
+
# @!endgroup
|
174
|
+
|
175
|
+
# Returns the class name as String. Useful for debuggin.
|
176
|
+
# @return [String] class name. Ex: "HighLine::Terminal::IOConsole"
|
177
|
+
def character_mode
|
178
|
+
self.class.name
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
# Yield a block using stty shell commands to preserve the terminal state.
|
184
|
+
def run_preserving_stty
|
185
|
+
save_stty
|
186
|
+
yield
|
187
|
+
ensure
|
188
|
+
restore_stty
|
189
|
+
end
|
190
|
+
|
191
|
+
# Saves terminal state using shell stty command.
|
192
|
+
def save_stty
|
193
|
+
@stty_save = `stty -g`.chomp rescue nil
|
194
|
+
end
|
195
|
+
|
196
|
+
# Restores terminal state using shell stty command.
|
197
|
+
def restore_stty
|
198
|
+
system("stty", @stty_save) if @stty_save
|
199
|
+
end
|
77
200
|
end
|
78
201
|
end
|