console-glitter 0.2.1 → 0.3.0
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/lib/console-glitter.rb +10 -254
- data/lib/console-glitter/ansi.rb +20 -16
- data/lib/console-glitter/cursor.rb +112 -0
- data/lib/console-glitter/screen.rb +106 -0
- data/lib/console-glitter/ui.rb +264 -0
- metadata +8 -6
- data/lib/console-glitter/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bca0b708e7d721160c7371babc90ae03901a0715
|
4
|
+
data.tar.gz: 37d30153c76f6f2118aa9419beeeb46b605b10eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 16e22ce68dfda6b492be65e4b26b7147a3bbf241c29454870d09c8f6b8b2cc86dd1f162716385eddfad9cd506adb0ae670a09ff3ccf89036d103ae9bdb2d819a
|
7
|
+
data.tar.gz: 5ae03963d116bfe502a8f54584c6dadba29e8f8a557afb2fdfd8a9417319dc0b616586c5ea4b7c266df6e3344fc5059860d3a1e86c749542b8ada5ae832752cb
|
data/lib/console-glitter.rb
CHANGED
@@ -1,264 +1,20 @@
|
|
1
|
-
require 'console-glitter/version'
|
2
|
-
require 'console-glitter/ansi'
|
3
|
-
require 'readline'
|
4
|
-
require 'io/console'
|
5
|
-
|
6
1
|
module ConsoleGlitter
|
7
|
-
|
8
|
-
extend ANSI
|
9
|
-
# Public: Prompt user for input, allowing for a default answer and a list
|
10
|
-
# of valid responses to be provided.
|
11
|
-
#
|
12
|
-
# question - Query to be presented to the user.
|
13
|
-
# options - Hash containing arguments defining acceptable responses.
|
14
|
-
# (default: {}):
|
15
|
-
# :default_answer - String containing the default answer. If
|
16
|
-
# this is nil, a non-empty answer MUST be
|
17
|
-
# given.
|
18
|
-
# :allow_empty - Whether or not to allow empty responses.
|
19
|
-
# Unless explicitly allowed, empty answers
|
20
|
-
# will be rejected.
|
21
|
-
# :valid_answers - An Array containing all valid responses. If
|
22
|
-
# this is empty, any answer will be accepted
|
23
|
-
# (unless empty answers are disallowed as
|
24
|
-
# specified above). Valid responses may be
|
25
|
-
# any class with a match method such as
|
26
|
-
# Strings or Regexps.
|
27
|
-
# wordlist - Array of words to be used for input auto-completion.
|
28
|
-
# (default: [])
|
29
|
-
# block - Lambda which will override the default autocompletion lambda
|
30
|
-
# as defined in autocomplete_lambda if present. (default: nil)
|
31
|
-
#
|
32
|
-
# Returns a String containing the answer provided by the user.
|
33
|
-
def prompt(question, options = {}, wordlist = [], block = nil)
|
34
|
-
default = options[:default_answer].to_s
|
35
|
-
allow_empty = options[:allow_empty]
|
36
|
-
valid = regexify_answers(options[:valid_answers])
|
37
|
-
|
38
|
-
default_display = " [#{default.strip}]" unless default.empty?
|
39
|
-
question = "#{question.strip}#{default_display}> "
|
40
|
-
|
41
|
-
answer = nil
|
42
|
-
while answer.nil?
|
43
|
-
answer = user_prompt(question, wordlist, block)
|
44
|
-
answer = default if answer.empty?
|
45
|
-
|
46
|
-
if answer.empty?
|
47
|
-
answer = nil unless allow_empty
|
48
|
-
elsif valid.any?
|
49
|
-
answer = nil unless valid.map { |valid| answer.match(valid) }.any?
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
answer
|
54
|
-
end
|
55
|
-
|
56
|
-
# Public: Wrap Console#prompt but accept only a Y/N response.
|
57
|
-
#
|
58
|
-
# question - String containing the question to present to the user.
|
59
|
-
# args - Hash containing arguments to control acceptable responses.
|
60
|
-
# (default: {}):
|
61
|
-
# :default_answer - String containing the default answer.
|
62
|
-
#
|
63
|
-
# Returns true or false corresponding to Y or N answer respectively.
|
64
|
-
def prompt_yn(question, args = {})
|
65
|
-
args[:valid_answers] = [/^[yn]/i]
|
66
|
-
/^n/i.match(prompt(question, args)).nil?
|
67
|
-
end
|
2
|
+
VERSION = '0.3.0'
|
68
3
|
|
69
|
-
|
70
|
-
#
|
71
|
-
# question - Query to be presented to the user.
|
72
|
-
# options - Hash containing arguments defining acceptable responses.
|
73
|
-
# (default: {}):
|
74
|
-
# :default_answer - String containing the default answer. If
|
75
|
-
# this is nil, a non-empty answer MUST be
|
76
|
-
# given.
|
77
|
-
# :allow_empty - Whether or not to allow empty responses.
|
78
|
-
# Unless explicitly allowed, empty answers
|
79
|
-
# will be rejected.
|
80
|
-
# :valid_answers - An Array containing all valid responses. If
|
81
|
-
# this is empty, any answer will be accepted
|
82
|
-
# (unless empty answers are disallowed as
|
83
|
-
# specified above). Valid responses may be
|
84
|
-
# any class with a match method such as
|
85
|
-
# Strings or Regexps.
|
86
|
-
#
|
87
|
-
# Returns a String containing the answer provided by the user.
|
88
|
-
def secure_prompt(question, args = {})
|
89
|
-
IO.console.echo = false
|
90
|
-
prompt(question, args)
|
91
|
-
ensure
|
92
|
-
IO.console.echo = true
|
93
|
-
puts
|
94
|
-
end
|
4
|
+
extend self
|
95
5
|
|
96
|
-
# Public:
|
6
|
+
# Public: Return an appropriate escape sequence depending upon the current
|
7
|
+
# platform.
|
97
8
|
#
|
98
|
-
#
|
99
|
-
# options - Hash containing arguments defining acceptable responses.
|
100
|
-
# (default: {}):
|
101
|
-
# :default_answer - String containing the default answer. If
|
102
|
-
# this is nil, a non-empty answer MUST be
|
103
|
-
# given.
|
104
|
-
# :allow_empty - Whether or not to allow empty responses.
|
105
|
-
# Unless explicitly allowed, empty answers
|
106
|
-
# will be rejected.
|
107
|
-
# :valid_answers - An Array containing all valid responses. If
|
108
|
-
# this is empty, any answer will be accepted
|
109
|
-
# (unless empty answers are disallowed as
|
110
|
-
# specified above). Valid responses may be
|
111
|
-
# any class with a match method such as
|
112
|
-
# Strings or Regexps.
|
9
|
+
# sequence - String containing control code(s) to be escaped.
|
113
10
|
#
|
114
|
-
#
|
115
|
-
def prompt_filesystem(question, args = {})
|
116
|
-
fs_lambda = lambda { |d| Dir[d + '*'].grep(/^#{Regexp.escape(d)}/) }
|
117
|
-
old_append = Readline.completion_append_character
|
118
|
-
|
119
|
-
Readline.completion_append_character = ''
|
120
|
-
response = prompt(question, args, [], fs_lambda)
|
121
|
-
Readline.completion_append_character = old_append
|
122
|
-
|
123
|
-
response
|
124
|
-
end
|
125
|
-
|
126
|
-
# Public: Render a "spinner" on the command line and yield to a block,
|
127
|
-
# reporting success if nothing is raised, or else reporting failure.
|
11
|
+
# Examples
|
128
12
|
#
|
129
|
-
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
# Returns the result of the yielded block if successful.
|
133
|
-
# Raises whatever is raised inside the yielded block.
|
134
|
-
def spinner(message, &block)
|
135
|
-
success = nil
|
136
|
-
result = nil
|
137
|
-
|
138
|
-
pre = "\r#{bold}#{white} [#{reset}"
|
139
|
-
post = "#{bold}#{white}] #{reset}#{message}"
|
140
|
-
pre_ok = "\r#{bold}#{white} [#{green} ok "
|
141
|
-
pre_fail = "\r#{bold}#{white} [#{red}fail"
|
142
|
-
|
143
|
-
thread = Thread.new do
|
144
|
-
step = 0
|
145
|
-
spin = [" ", ". ", ".. ", "... ", "....", " ...", " ..", " ."]
|
146
|
-
while success.nil?
|
147
|
-
print "#{pre}#{spin[step % 8]}#{post}"
|
148
|
-
step += 1
|
149
|
-
sleep 0.5
|
150
|
-
end
|
151
|
-
|
152
|
-
if success
|
153
|
-
print "#{pre_ok}#{post}\n"
|
154
|
-
else
|
155
|
-
print "#{pre_fail}#{post}\n"
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
begin
|
160
|
-
result = yield
|
161
|
-
success = true
|
162
|
-
thread.join
|
163
|
-
return result
|
164
|
-
rescue
|
165
|
-
success = false
|
166
|
-
thread.join
|
167
|
-
raise
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
# Public: Generate a formatted, printable table.
|
172
|
-
#
|
173
|
-
# rows - An Array containing Hashes which contain desired options to
|
174
|
-
# display. (e.g. [{"col1" => "a", "col2" => "b"}])
|
175
|
-
# labels - Hash containing key-value pairs to label each key in options.
|
176
|
-
# (default: nil)
|
177
|
-
#
|
178
|
-
# Returns a String containing the grid.
|
179
|
-
# Raises ArgumentError if anything but an Array is passed as rows.
|
180
|
-
def build_grid(rows, labels = nil)
|
181
|
-
if labels.nil?
|
182
|
-
labels = rows[0].keys.reduce({}) { |c,e| c.merge({e => e}) }
|
183
|
-
end
|
184
|
-
|
185
|
-
keys = labels.keys
|
186
|
-
|
187
|
-
max_width = labels.reduce({}) do |c,e|
|
188
|
-
c.merge({e[0]=> ([labels] + rows).map { |r| r[e[0]].length }.max})
|
189
|
-
end
|
190
|
-
|
191
|
-
grid_rule = max_width.reduce('+') do |c,e|
|
192
|
-
c + ('-' * (e[1] + 2)) + '+'
|
193
|
-
end
|
194
|
-
grid_rule << "\n"
|
195
|
-
|
196
|
-
grid = grid_rule.dup
|
197
|
-
grid << keys.reduce('|') do |c,e|
|
198
|
-
c + " #{bold}% #{max_width[e]}s#{reset} |" % labels[e]
|
199
|
-
end
|
200
|
-
grid << "\n"
|
201
|
-
|
202
|
-
grid << rows.reduce(grid_rule) do |c,e|
|
203
|
-
content = keys.reduce('') do |s,k|
|
204
|
-
s + " % #{max_width[k]}s |" % e[k]
|
205
|
-
end
|
206
|
-
c + "|#{content}\n"
|
207
|
-
end
|
208
|
-
grid << grid_rule
|
209
|
-
end
|
210
|
-
|
211
|
-
private
|
212
|
-
|
213
|
-
# Internal: Promp user for input via Readline, handling automatic setting
|
214
|
-
# and restoring of Readline's autocompletion proc, avoiding trampling over
|
215
|
-
# other uses.
|
216
|
-
#
|
217
|
-
# question - String containing a question to be displayed to the user.
|
218
|
-
# wordlist - Array containing Strings which are considered valid answers
|
219
|
-
# for autocompletion.
|
220
|
-
# block - Lambda which will override the default autocompletion lambda
|
221
|
-
# as defined in autocomplete_lambda if present. (default: nil)
|
13
|
+
# ConsoleGlitter.escape 'A'
|
14
|
+
# # => "\033[A"
|
222
15
|
#
|
223
16
|
# Returns a String.
|
224
|
-
def
|
225
|
-
|
226
|
-
old_completion_proc = Readline.completion_proc
|
227
|
-
|
228
|
-
Readline.completion_proc = block
|
229
|
-
response = Readline.readline(question)
|
230
|
-
Readline.completion_proc = old_completion_proc
|
231
|
-
|
232
|
-
response.to_s
|
233
|
-
end
|
234
|
-
|
235
|
-
# Internal: Generate a lambda which retuns an array of autocompletion
|
236
|
-
# candidates.
|
237
|
-
#
|
238
|
-
# wordlist - Array containing Strings which are considered valid answers
|
239
|
-
# for autocompletion.
|
240
|
-
#
|
241
|
-
# Returns a lambda which returns an Array.
|
242
|
-
def autocomplete_lambda(wordlist)
|
243
|
-
lambda { |s| wordlist.grep(/^#{Regexp.escape(s)}/) }
|
244
|
-
end
|
245
|
-
|
246
|
-
# Internal: Generate Regexps for any String in an array to be used in order
|
247
|
-
# to match against user input. Strings are expected to indicate exact
|
248
|
-
# matches desired.
|
249
|
-
#
|
250
|
-
# valid_answers - Array containing Objects against which user input may be
|
251
|
-
# matched.
|
252
|
-
#
|
253
|
-
# Returns an Array of Regexps.
|
254
|
-
def regexify_answers(valid_answers)
|
255
|
-
valid_answers.to_a.map do |answer|
|
256
|
-
if answer.is_a?(String)
|
257
|
-
Regexp.new("^#{Regexp.escape(answer)}$")
|
258
|
-
else
|
259
|
-
answer
|
260
|
-
end
|
261
|
-
end
|
17
|
+
def escape(sequence)
|
18
|
+
RUBY_PLATFORM =~ /(^win)|mingw/i ? '' : "\033[#{sequence}"
|
262
19
|
end
|
263
|
-
end
|
264
20
|
end
|
data/lib/console-glitter/ansi.rb
CHANGED
@@ -1,21 +1,7 @@
|
|
1
|
+
require 'console-glitter'
|
2
|
+
|
1
3
|
module ConsoleGlitter
|
2
4
|
module ANSI extend self
|
3
|
-
# Public: Return an appropriate escape sequence depending upon the current
|
4
|
-
# platform.
|
5
|
-
#
|
6
|
-
# sequence - Control code(s) to be escaped. Multiple codes may be chained,
|
7
|
-
# separated by ';'.
|
8
|
-
#
|
9
|
-
# Examples
|
10
|
-
#
|
11
|
-
# ConsoleGlitter::ANSI.escape 0
|
12
|
-
# # => "\033[0m"
|
13
|
-
#
|
14
|
-
# Returns a String.
|
15
|
-
def escape(sequence)
|
16
|
-
RUBY_PLATFORM =~ /win|mingw/i ? '' : "\033[#{sequence}m"
|
17
|
-
end
|
18
|
-
|
19
5
|
# Public: Generate an escape sequence to set the foreground color to an
|
20
6
|
# approximation of a 4 or 8 bit per channel hex RGB color a la CSS.
|
21
7
|
#
|
@@ -56,6 +42,24 @@ module ConsoleGlitter
|
|
56
42
|
|
57
43
|
private
|
58
44
|
|
45
|
+
# Internal: Wrap ConsoleGlitter#escape, appending the character 'm' to the
|
46
|
+
# control code (per SGR spec).
|
47
|
+
#
|
48
|
+
# sequence - String containing the control code(s) to be escaped. Multiple
|
49
|
+
# codes may be chained, separated by a ';'.
|
50
|
+
#
|
51
|
+
# Examples
|
52
|
+
#
|
53
|
+
# escape('0')
|
54
|
+
# # => "\033[0m"
|
55
|
+
# escape('1;31')
|
56
|
+
# # => "\033[1;31m"
|
57
|
+
#
|
58
|
+
# Returns a String.
|
59
|
+
def escape(sequence)
|
60
|
+
ConsoleGlitter.escape(sequence.to_s + 'm')
|
61
|
+
end
|
62
|
+
|
59
63
|
# Internal: Allow on-the-fly definition of ANSI control sequences. Methods
|
60
64
|
# are created for convenience which will either wrap the result of a block
|
61
65
|
# in the tag specified and a reset tag, or else simply return the code in
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'console-glitter'
|
2
|
+
|
3
|
+
module ConsoleGlitter
|
4
|
+
module Cursor extend self
|
5
|
+
# Public: Move the cursor up for a given distance.
|
6
|
+
#
|
7
|
+
# distance - Distance to move the cursor.
|
8
|
+
#
|
9
|
+
# Returns a String containing the VT control code.
|
10
|
+
def up(distance = 1)
|
11
|
+
ConsoleGlitter.escape("#{distance}A")
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Move the cursor down for a given distance.
|
15
|
+
#
|
16
|
+
# distance - Distance to move the cursor.
|
17
|
+
#
|
18
|
+
# Returns a String containing the VT control code.
|
19
|
+
def down(distance = 1)
|
20
|
+
ConsoleGlitter.escape("#{distance}B")
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Move the cursor forward for a given distance.
|
24
|
+
#
|
25
|
+
# distance - Distance to move the cursor.
|
26
|
+
#
|
27
|
+
# Returns a String containing the VT control code.
|
28
|
+
def forward(distance = 1)
|
29
|
+
ConsoleGlitter.escape("#{distance}C")
|
30
|
+
end
|
31
|
+
alias :right :forward
|
32
|
+
|
33
|
+
# Public: Move the cursor back for a given distance.
|
34
|
+
#
|
35
|
+
# distance - Distance to move the cursor.
|
36
|
+
#
|
37
|
+
# Returns a String containing the VT control code.
|
38
|
+
def back(distance = 1)
|
39
|
+
ConsoleGlitter.escape("#{distance}D")
|
40
|
+
end
|
41
|
+
alias :left :back
|
42
|
+
|
43
|
+
# Public: Move the cursor to the next line, returning the cursor to the
|
44
|
+
# beginning of the line.
|
45
|
+
#
|
46
|
+
# Returns a String containing the VT control code.
|
47
|
+
def nextline(distance = 1)
|
48
|
+
ConsoleGlitter.escape("#{distance}E")
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public: Move the cursor to the previous line, returning the cursor to the
|
52
|
+
# beginning of the line.
|
53
|
+
#
|
54
|
+
# Returns a String containing the VT control code.
|
55
|
+
def prevline(distance = 1)
|
56
|
+
ConsoleGlitter.escape("#{distance}F")
|
57
|
+
end
|
58
|
+
alias :previousline :prevline
|
59
|
+
|
60
|
+
# Public: Move the absolute horizontal position specified.
|
61
|
+
#
|
62
|
+
# position - Number of the column to which the cursor should be moved.
|
63
|
+
#
|
64
|
+
# Returns a String containing the VT control code.
|
65
|
+
def column(position)
|
66
|
+
ConsoleGlitter.escape("#{position}G")
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Move the cursor to the position specified. The top left position
|
70
|
+
# is 1,1.
|
71
|
+
#
|
72
|
+
# x - Column (x-position) to which the cursor should be moved.
|
73
|
+
# x - Row (y-position) to which the cursor should be moved.
|
74
|
+
#
|
75
|
+
# Returns a String containing the VT control code.
|
76
|
+
def move(x, y)
|
77
|
+
ConsoleGlitter.escape("#{y};#{x}H")
|
78
|
+
end
|
79
|
+
|
80
|
+
# Public: Save the cursor's current position, replacing any previously
|
81
|
+
# saved position.
|
82
|
+
#
|
83
|
+
# Returns a String containing the VT control code.
|
84
|
+
def save
|
85
|
+
ConsoleGlitter.escape("s")
|
86
|
+
end
|
87
|
+
|
88
|
+
# Public: Move the cursor to a previously saved position.
|
89
|
+
#
|
90
|
+
# Note: If the cursor's position was never previously saved, it will
|
91
|
+
# default to 1,1.
|
92
|
+
#
|
93
|
+
# Returns a String containing the VT control code.
|
94
|
+
def restore
|
95
|
+
ConsoleGlitter.escape("u")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Public: Hide the cursor.
|
99
|
+
#
|
100
|
+
# Returns a String containing the VT control code.
|
101
|
+
def hide
|
102
|
+
ConsoleGlitter.escape("?25l")
|
103
|
+
end
|
104
|
+
|
105
|
+
# Public: Show the cursor.
|
106
|
+
#
|
107
|
+
# Returns a String containing the VT control code.
|
108
|
+
def show
|
109
|
+
ConsoleGlitter.escape("?25h")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'console-glitter'
|
2
|
+
|
3
|
+
module ConsoleGlitter
|
4
|
+
module Screen extend self
|
5
|
+
# Public: From the cursor's position clear either the whole screen, or from
|
6
|
+
# the cursor's position to the beginning or end of the screen.
|
7
|
+
#
|
8
|
+
# direction - Symbol denoting the direction the screen should be cleared,
|
9
|
+
# between :both :end or :beginning. (default: :both)
|
10
|
+
#
|
11
|
+
# Returns a String containing the VT control code.
|
12
|
+
def clear(direction = :both)
|
13
|
+
options = [:both, :beginning, :end]
|
14
|
+
target = "clear_to_#{direction}"
|
15
|
+
return(self.send(target)) if options.grep(direction).any?
|
16
|
+
|
17
|
+
raise(ArgumentError,
|
18
|
+
"Expected :both, :end, or :beginning, got #{direction}.")
|
19
|
+
end
|
20
|
+
alias :clear_to :clear
|
21
|
+
|
22
|
+
# Public: Return the VT control code to clear from the cursor to the end of
|
23
|
+
# the screen.
|
24
|
+
#
|
25
|
+
# Returns a String containing the VT control code.
|
26
|
+
def clear_to_end
|
27
|
+
ConsoleGlitter.escape('0J')
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: Return the VT control code to clear from the cursor to the
|
31
|
+
# beginning of the screen.
|
32
|
+
#
|
33
|
+
# Returns a String containing the VT control code.
|
34
|
+
def clear_to_beginning
|
35
|
+
ConsoleGlitter.escape('1J')
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Return the VT control code to clear the screen.
|
39
|
+
#
|
40
|
+
# Returns a String containing the VT control code.
|
41
|
+
def clear_to_both
|
42
|
+
ConsoleGlitter.escape('2J')
|
43
|
+
end
|
44
|
+
alias :clear_screen :clear_to_both
|
45
|
+
|
46
|
+
# Public: From the cursor's position clear either the whole line, or from
|
47
|
+
# the cursor's position to the beginning or end of the line.
|
48
|
+
#
|
49
|
+
# direction - Symbol denoting the direction the line should be cleared,
|
50
|
+
# between :both :end or :beginning. (default: :both)
|
51
|
+
#
|
52
|
+
# Returns a String containing the VT control code.
|
53
|
+
def erase_line(direction = :both)
|
54
|
+
options = [:both, :beginning, :end]
|
55
|
+
target = "erase_line_to_#{direction}"
|
56
|
+
return(self.send(target)) if options.grep(direction).any?
|
57
|
+
|
58
|
+
raise(ArgumentError,
|
59
|
+
"Expected :both, :end, or :beginning, got #{direction}.")
|
60
|
+
end
|
61
|
+
alias :erase_line_to :erase_line
|
62
|
+
|
63
|
+
# Public: Return the VT control code to clear from the cursor to the end of
|
64
|
+
# the line.
|
65
|
+
#
|
66
|
+
# Returns a String containing the VT control code.
|
67
|
+
def erase_line_to_end
|
68
|
+
ConsoleGlitter.escape('0K')
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: Return the VT control code to clear from the cursor to the
|
72
|
+
# beginning of the line.
|
73
|
+
#
|
74
|
+
# Returns a String containing the VT control code.
|
75
|
+
def erase_line_to_beginning
|
76
|
+
ConsoleGlitter.escape('1K')
|
77
|
+
end
|
78
|
+
|
79
|
+
# Public: Return the VT control code to clear the line where the cursor is.
|
80
|
+
#
|
81
|
+
# Returns a String containing the VT control code.
|
82
|
+
def erase_line_to_both
|
83
|
+
ConsoleGlitter.escape('2K')
|
84
|
+
end
|
85
|
+
|
86
|
+
# Public: Return the VT control code to scroll the screen up by a given
|
87
|
+
# amount.
|
88
|
+
#
|
89
|
+
# distance - Number of lines to scroll the screen. (default: 1)
|
90
|
+
#
|
91
|
+
# Returns a String containing the VT control code.
|
92
|
+
def scroll_up(distance = 1)
|
93
|
+
ConsoleGlitter.escape("#{distance}S")
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Return the VT control code to scroll the screen down by a given
|
97
|
+
# amount.
|
98
|
+
#
|
99
|
+
# distance - Number of lines to scroll the screen. (default: 1)
|
100
|
+
#
|
101
|
+
# Returns a String containing the VT control code.
|
102
|
+
def scroll_down(distance = 1)
|
103
|
+
ConsoleGlitter.escape("#{distance}T")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
require 'console-glitter'
|
2
|
+
require 'console-glitter/ansi'
|
3
|
+
require 'readline'
|
4
|
+
require 'io/console'
|
5
|
+
|
6
|
+
module ConsoleGlitter
|
7
|
+
module UI extend self
|
8
|
+
extend ANSI
|
9
|
+
# Public: Prompt user for input, allowing for a default answer and a list
|
10
|
+
# of valid responses to be provided.
|
11
|
+
#
|
12
|
+
# question - Query to be presented to the user.
|
13
|
+
# options - Hash containing arguments defining acceptable responses.
|
14
|
+
# (default: {}):
|
15
|
+
# :default_answer - String containing the default answer. If
|
16
|
+
# this is nil, a non-empty answer MUST be
|
17
|
+
# given.
|
18
|
+
# :allow_empty - Whether or not to allow empty responses.
|
19
|
+
# Unless explicitly allowed, empty answers
|
20
|
+
# will be rejected.
|
21
|
+
# :valid_answers - An Array containing all valid responses. If
|
22
|
+
# this is empty, any answer will be accepted
|
23
|
+
# (unless empty answers are disallowed as
|
24
|
+
# specified above). Valid responses may be
|
25
|
+
# any class with a match method such as
|
26
|
+
# Strings or Regexps.
|
27
|
+
# wordlist - Array of words to be used for input auto-completion.
|
28
|
+
# (default: [])
|
29
|
+
# block - Lambda which will override the default autocompletion lambda
|
30
|
+
# as defined in autocomplete_lambda if present. (default: nil)
|
31
|
+
#
|
32
|
+
# Returns a String containing the answer provided by the user.
|
33
|
+
def prompt(question, options = {}, wordlist = [], block = nil)
|
34
|
+
default = options[:default_answer].to_s
|
35
|
+
allow_empty = options[:allow_empty]
|
36
|
+
valid = regexify_answers(options[:valid_answers])
|
37
|
+
|
38
|
+
default_display = " [#{default.strip}]" unless default.empty?
|
39
|
+
question = "#{question.strip}#{default_display}> "
|
40
|
+
|
41
|
+
answer = nil
|
42
|
+
while answer.nil?
|
43
|
+
answer = user_prompt(question, wordlist, block)
|
44
|
+
answer = default if answer.empty?
|
45
|
+
|
46
|
+
if answer.empty?
|
47
|
+
answer = nil unless allow_empty
|
48
|
+
elsif valid.any?
|
49
|
+
answer = nil unless valid.map { |valid| answer.match(valid) }.any?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
answer
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Wrap Console#prompt but accept only a Y/N response.
|
57
|
+
#
|
58
|
+
# question - String containing the question to present to the user.
|
59
|
+
# args - Hash containing arguments to control acceptable responses.
|
60
|
+
# (default: {}):
|
61
|
+
# :default_answer - String containing the default answer.
|
62
|
+
#
|
63
|
+
# Returns true or false corresponding to Y or N answer respectively.
|
64
|
+
def prompt_yn(question, args = {})
|
65
|
+
args[:valid_answers] = [/^[yn]/i]
|
66
|
+
/^n/i.match(prompt(question, args)).nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Public: Wrap Console#prompt, disabling local echo of user input.
|
70
|
+
#
|
71
|
+
# question - Query to be presented to the user.
|
72
|
+
# options - Hash containing arguments defining acceptable responses.
|
73
|
+
# (default: {}):
|
74
|
+
# :default_answer - String containing the default answer. If
|
75
|
+
# this is nil, a non-empty answer MUST be
|
76
|
+
# given.
|
77
|
+
# :allow_empty - Whether or not to allow empty responses.
|
78
|
+
# Unless explicitly allowed, empty answers
|
79
|
+
# will be rejected.
|
80
|
+
# :valid_answers - An Array containing all valid responses. If
|
81
|
+
# this is empty, any answer will be accepted
|
82
|
+
# (unless empty answers are disallowed as
|
83
|
+
# specified above). Valid responses may be
|
84
|
+
# any class with a match method such as
|
85
|
+
# Strings or Regexps.
|
86
|
+
#
|
87
|
+
# Returns a String containing the answer provided by the user.
|
88
|
+
def secure_prompt(question, args = {})
|
89
|
+
IO.console.echo = false
|
90
|
+
prompt(question, args)
|
91
|
+
ensure
|
92
|
+
IO.console.echo = true
|
93
|
+
puts
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: Wrap Console#prompt, specifically targeting filesystem paths.
|
97
|
+
#
|
98
|
+
# question - Query to be presented to the user.
|
99
|
+
# options - Hash containing arguments defining acceptable responses.
|
100
|
+
# (default: {}):
|
101
|
+
# :default_answer - String containing the default answer. If
|
102
|
+
# this is nil, a non-empty answer MUST be
|
103
|
+
# given.
|
104
|
+
# :allow_empty - Whether or not to allow empty responses.
|
105
|
+
# Unless explicitly allowed, empty answers
|
106
|
+
# will be rejected.
|
107
|
+
# :valid_answers - An Array containing all valid responses. If
|
108
|
+
# this is empty, any answer will be accepted
|
109
|
+
# (unless empty answers are disallowed as
|
110
|
+
# specified above). Valid responses may be
|
111
|
+
# any class with a match method such as
|
112
|
+
# Strings or Regexps.
|
113
|
+
#
|
114
|
+
# Returns a String containing the answer provided by the user.
|
115
|
+
def prompt_filesystem(question, args = {})
|
116
|
+
fs_lambda = lambda { |d| Dir[d + '*'].grep(/^#{Regexp.escape(d)}/) }
|
117
|
+
old_append = Readline.completion_append_character
|
118
|
+
|
119
|
+
Readline.completion_append_character = ''
|
120
|
+
response = prompt(question, args, [], fs_lambda)
|
121
|
+
Readline.completion_append_character = old_append
|
122
|
+
|
123
|
+
response
|
124
|
+
end
|
125
|
+
|
126
|
+
# Public: Render a "spinner" on the command line and yield to a block,
|
127
|
+
# reporting success if nothing is raised, or else reporting failure.
|
128
|
+
#
|
129
|
+
# message - Message to be displayed describing the task being evaluated.
|
130
|
+
# block - Block to be yielded to determine pass or fail.
|
131
|
+
#
|
132
|
+
# Returns the result of the yielded block if successful.
|
133
|
+
# Raises whatever is raised inside the yielded block.
|
134
|
+
def spinner(message, &block)
|
135
|
+
success = nil
|
136
|
+
result = nil
|
137
|
+
|
138
|
+
pre = "\r#{bold}#{white} [#{reset}"
|
139
|
+
post = "#{bold}#{white}] #{reset}#{message}"
|
140
|
+
pre_ok = "\r#{bold}#{white} [#{green} ok "
|
141
|
+
pre_fail = "\r#{bold}#{white} [#{red}fail"
|
142
|
+
|
143
|
+
thread = Thread.new do
|
144
|
+
step = 0
|
145
|
+
spin = [" ", ". ", ".. ", "... ", "....", " ...", " ..", " ."]
|
146
|
+
while success.nil?
|
147
|
+
print "#{pre}#{spin[step % 8]}#{post}"
|
148
|
+
step += 1
|
149
|
+
sleep 0.5
|
150
|
+
end
|
151
|
+
|
152
|
+
if success
|
153
|
+
print "#{pre_ok}#{post}\n"
|
154
|
+
else
|
155
|
+
print "#{pre_fail}#{post}\n"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
begin
|
160
|
+
result = yield
|
161
|
+
success = true
|
162
|
+
thread.join
|
163
|
+
return result
|
164
|
+
rescue
|
165
|
+
success = false
|
166
|
+
thread.join
|
167
|
+
raise
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Public: Generate a formatted, printable table.
|
172
|
+
#
|
173
|
+
# rows - An Array containing Hashes which contain desired options to
|
174
|
+
# display. (e.g. [{"col1" => "a", "col2" => "b"}])
|
175
|
+
# labels - Hash containing key-value pairs to label each key in options.
|
176
|
+
# (default: nil)
|
177
|
+
#
|
178
|
+
# Returns a String containing the grid.
|
179
|
+
# Raises ArgumentError if anything but an Array is passed as rows.
|
180
|
+
def build_grid(rows, labels = nil)
|
181
|
+
if labels.nil?
|
182
|
+
labels = rows[0].keys.reduce({}) { |c,e| c.merge({e => e}) }
|
183
|
+
end
|
184
|
+
|
185
|
+
keys = labels.keys
|
186
|
+
|
187
|
+
max_width = labels.reduce({}) do |c,e|
|
188
|
+
c.merge({e[0]=> ([labels] + rows).map { |r| r[e[0]].length }.max})
|
189
|
+
end
|
190
|
+
|
191
|
+
grid_rule = max_width.reduce('+') do |c,e|
|
192
|
+
c + ('-' * (e[1] + 2)) + '+'
|
193
|
+
end
|
194
|
+
grid_rule << "\n"
|
195
|
+
|
196
|
+
grid = grid_rule.dup
|
197
|
+
grid << keys.reduce('|') do |c,e|
|
198
|
+
c + " #{bold}% #{max_width[e]}s#{reset} |" % labels[e]
|
199
|
+
end
|
200
|
+
grid << "\n"
|
201
|
+
|
202
|
+
grid << rows.reduce(grid_rule) do |c,e|
|
203
|
+
content = keys.reduce('') do |s,k|
|
204
|
+
s + " % #{max_width[k]}s |" % e[k]
|
205
|
+
end
|
206
|
+
c + "|#{content}\n"
|
207
|
+
end
|
208
|
+
grid << grid_rule
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Internal: Promp user for input via Readline, handling automatic setting
|
214
|
+
# and restoring of Readline's autocompletion proc, avoiding trampling over
|
215
|
+
# other uses.
|
216
|
+
#
|
217
|
+
# question - String containing a question to be displayed to the user.
|
218
|
+
# wordlist - Array containing Strings which are considered valid answers
|
219
|
+
# for autocompletion.
|
220
|
+
# block - Lambda which will override the default autocompletion lambda
|
221
|
+
# as defined in autocomplete_lambda if present. (default: nil)
|
222
|
+
#
|
223
|
+
# Returns a String.
|
224
|
+
def user_prompt(question, wordlist, block = nil)
|
225
|
+
block = autocomplete_lambda(wordlist) if block.nil?
|
226
|
+
old_completion_proc = Readline.completion_proc
|
227
|
+
|
228
|
+
Readline.completion_proc = block
|
229
|
+
response = Readline.readline(question)
|
230
|
+
Readline.completion_proc = old_completion_proc
|
231
|
+
|
232
|
+
response.to_s
|
233
|
+
end
|
234
|
+
|
235
|
+
# Internal: Generate a lambda which retuns an array of autocompletion
|
236
|
+
# candidates.
|
237
|
+
#
|
238
|
+
# wordlist - Array containing Strings which are considered valid answers
|
239
|
+
# for autocompletion.
|
240
|
+
#
|
241
|
+
# Returns a lambda which returns an Array.
|
242
|
+
def autocomplete_lambda(wordlist)
|
243
|
+
lambda { |s| wordlist.grep(/^#{Regexp.escape(s)}/) }
|
244
|
+
end
|
245
|
+
|
246
|
+
# Internal: Generate Regexps for any String in an array to be used in order
|
247
|
+
# to match against user input. Strings are expected to indicate exact
|
248
|
+
# matches desired.
|
249
|
+
#
|
250
|
+
# valid_answers - Array containing Objects against which user input may be
|
251
|
+
# matched.
|
252
|
+
#
|
253
|
+
# Returns an Array of Regexps.
|
254
|
+
def regexify_answers(valid_answers)
|
255
|
+
valid_answers.to_a.map do |answer|
|
256
|
+
if answer.is_a?(String)
|
257
|
+
Regexp.new("^#{Regexp.escape(answer)}$")
|
258
|
+
else
|
259
|
+
answer
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
metadata
CHANGED
@@ -1,24 +1,26 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: console-glitter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Tina Wuest
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Tools for building nice looking CLI applications
|
14
|
-
email:
|
14
|
+
email: tina@wuest.me
|
15
15
|
executables: []
|
16
16
|
extensions: []
|
17
17
|
extra_rdoc_files: []
|
18
18
|
files:
|
19
19
|
- lib/console-glitter.rb
|
20
20
|
- lib/console-glitter/ansi.rb
|
21
|
-
- lib/console-glitter/
|
21
|
+
- lib/console-glitter/cursor.rb
|
22
|
+
- lib/console-glitter/screen.rb
|
23
|
+
- lib/console-glitter/ui.rb
|
22
24
|
homepage: https://gitlab.com/wuest/console-glitter
|
23
25
|
licenses:
|
24
26
|
- MIT
|
@@ -39,7 +41,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
41
|
version: '0'
|
40
42
|
requirements: []
|
41
43
|
rubyforge_project:
|
42
|
-
rubygems_version: 2.
|
44
|
+
rubygems_version: 2.4.5
|
43
45
|
signing_key:
|
44
46
|
specification_version: 4
|
45
47
|
summary: Tools for prettier CLI apps
|