grmenu 2.0.0 → 4.0.1
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/README.md +707 -262
- data/data/borders.json +77 -57
- data/data/colors.json +356 -13
- data/data/help.txt +432 -0
- data/data/themes/cyberpunk.gr +60 -0
- data/data/themes/dracula.gr +60 -0
- data/data/themes/matrix.gr +60 -0
- data/data/themes/monokai.gr +60 -0
- data/data/themes/neon_red.gr +51 -0
- data/data/themes/nord.gr +60 -0
- data/data/themes/sunset.gr +60 -0
- data/grmenu.rb +4242 -0
- metadata +15 -4
- data/GRmenu.rb +0 -1184
data/GRmenu.rb
DELETED
|
@@ -1,1184 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'io/console'
|
|
4
|
-
require 'json'
|
|
5
|
-
|
|
6
|
-
class GRmenu
|
|
7
|
-
CLEAR_SCREEN_SEQUENCE = "\e[H\e[2J\e[3J"
|
|
8
|
-
HIDE_CURSOR = "\e[?25l"
|
|
9
|
-
SHOW_CURSOR = "\e[?25h"
|
|
10
|
-
CURSOR_HOME = "\e[H"
|
|
11
|
-
CLEAR_TO_EOL = "\e[K"
|
|
12
|
-
CLEAR_TO_EOS = "\e[J"
|
|
13
|
-
|
|
14
|
-
module Color
|
|
15
|
-
RESET = "\e[0m"
|
|
16
|
-
BOLD = "\e[1m"
|
|
17
|
-
|
|
18
|
-
CODES = {
|
|
19
|
-
black: { 1 => "\e[30m", 2 => "\e[90m" },
|
|
20
|
-
gray: { 1 => "\e[90m", 2 => "\e[38;5;245m" },
|
|
21
|
-
grey: { 1 => "\e[90m", 2 => "\e[38;5;245m" },
|
|
22
|
-
red: { 1 => "\e[31m", 2 => "\e[91m" },
|
|
23
|
-
green: { 1 => "\e[32m", 2 => "\e[92m" },
|
|
24
|
-
yellow: { 1 => "\e[33m", 2 => "\e[93m" },
|
|
25
|
-
blue: { 1 => "\e[34m", 2 => "\e[94m" },
|
|
26
|
-
magenta: { 1 => "\e[35m", 2 => "\e[95m" },
|
|
27
|
-
purple: { 1 => "\e[38;5;129m", 2 => "\e[38;5;141m" },
|
|
28
|
-
pink: { 1 => "\e[38;5;205m", 2 => "\e[38;5;218m" },
|
|
29
|
-
cyan: { 1 => "\e[36m", 2 => "\e[96m" },
|
|
30
|
-
aqua: { 1 => "\e[38;5;45m", 2 => "\e[38;5;51m" },
|
|
31
|
-
orange: { 1 => "\e[38;5;208m", 2 => "\e[38;5;214m" },
|
|
32
|
-
white: { 1 => "\e[37m", 2 => "\e[97m" }
|
|
33
|
-
}.freeze
|
|
34
|
-
|
|
35
|
-
module_function
|
|
36
|
-
|
|
37
|
-
def paint(text, color_name, level = 1)
|
|
38
|
-
code = CODES.dig(color_name.to_sym, level) || "\e[37m"
|
|
39
|
-
"#{code}#{text}#{RESET}"
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def red(s); paint(s, :red, 1); end
|
|
43
|
-
def bright_red(s); paint(s, :red, 2); end
|
|
44
|
-
def dark_red(s); paint(s, :red, 1); end
|
|
45
|
-
|
|
46
|
-
def green(s); paint(s, :green, 1); end
|
|
47
|
-
def bright_green(s); paint(s, :green, 2); end
|
|
48
|
-
def dark_green(s); paint(s, :green, 1); end
|
|
49
|
-
|
|
50
|
-
def yellow(s); paint(s, :yellow, 1); end
|
|
51
|
-
def bright_yellow(s); paint(s, :yellow, 2); end
|
|
52
|
-
|
|
53
|
-
def blue(s); paint(s, :blue, 1); end
|
|
54
|
-
def bright_blue(s); paint(s, :blue, 2); end
|
|
55
|
-
|
|
56
|
-
def magenta(s); paint(s, :magenta, 1); end
|
|
57
|
-
def bright_magenta(s); paint(s, :magenta, 2); end
|
|
58
|
-
|
|
59
|
-
def purple(s); paint(s, :purple, 1); end
|
|
60
|
-
def bright_purple(s); paint(s, :purple, 2); end
|
|
61
|
-
|
|
62
|
-
def pink(s); paint(s, :pink, 1); end
|
|
63
|
-
def bright_pink(s); paint(s, :pink, 2); end
|
|
64
|
-
|
|
65
|
-
def cyan(s); paint(s, :cyan, 1); end
|
|
66
|
-
def bright_cyan(s); paint(s, :cyan, 2); end
|
|
67
|
-
|
|
68
|
-
def aqua(s); paint(s, :aqua, 1); end
|
|
69
|
-
def bright_aqua(s); paint(s, :aqua, 2); end
|
|
70
|
-
|
|
71
|
-
def orange(s); paint(s, :orange, 1); end
|
|
72
|
-
def bright_orange(s); paint(s, :orange, 2); end
|
|
73
|
-
|
|
74
|
-
def white(s); paint(s, :white, 1); end
|
|
75
|
-
def bright_white(s); paint(s, :white, 2); end
|
|
76
|
-
|
|
77
|
-
def black(s); paint(s, :black, 1); end
|
|
78
|
-
def gray(s); paint(s, :gray, 1); end
|
|
79
|
-
def bright_gray(s); paint(s, :gray, 2); end
|
|
80
|
-
def grey(s); gray(s); end
|
|
81
|
-
|
|
82
|
-
def r(s); bright_red(s); end
|
|
83
|
-
def dr(s); dark_red(s); end
|
|
84
|
-
def g(s); bright_green(s); end
|
|
85
|
-
def y(s); bright_yellow(s); end
|
|
86
|
-
def w(s); bright_white(s); end
|
|
87
|
-
def gr(s); gray(s); end
|
|
88
|
-
def cy(s); bright_cyan(s); end
|
|
89
|
-
def mg(s); bright_magenta(s); end
|
|
90
|
-
def bl(s); bright_blue(s); end
|
|
91
|
-
end
|
|
92
|
-
C = Color
|
|
93
|
-
|
|
94
|
-
STYLES = {
|
|
95
|
-
1 => "#", 2 => "┌", 3 => "╔", 4 => "┏", 5 => "╒",
|
|
96
|
-
6 => "╓", 7 => "╭", 8 => "▛", 9 => "▓", 10 => "▒",
|
|
97
|
-
11 => "░", 12 => "█", 13 => "*", 14 => "+", 15 => "=",
|
|
98
|
-
16 => "~", 17 => "-", 18 => "◆", 19 => "●", 20 => "★"
|
|
99
|
-
}.freeze
|
|
100
|
-
|
|
101
|
-
COLORS = {
|
|
102
|
-
"black" => { 1 => "\e[30m", 2 => "\e[90m" },
|
|
103
|
-
"gray" => { 1 => "\e[90m", 2 => "\e[38;5;245m" },
|
|
104
|
-
"grey" => { 1 => "\e[90m", 2 => "\e[38;5;245m" },
|
|
105
|
-
"red" => { 1 => "\e[31m", 2 => "\e[91m" },
|
|
106
|
-
"green" => { 1 => "\e[32m", 2 => "\e[92m" },
|
|
107
|
-
"yellow" => { 1 => "\e[33m", 2 => "\e[93m" },
|
|
108
|
-
"blue" => { 1 => "\e[34m", 2 => "\e[94m" },
|
|
109
|
-
"magenta" => { 1 => "\e[35m", 2 => "\e[95m" },
|
|
110
|
-
"purple" => { 1 => "\e[38;5;129m", 2 => "\e[38;5;141m" },
|
|
111
|
-
"pink" => { 1 => "\e[38;5;205m", 2 => "\e[38;5;218m" },
|
|
112
|
-
"cyan" => { 1 => "\e[36m", 2 => "\e[96m" },
|
|
113
|
-
"aqua" => { 1 => "\e[38;5;45m", 2 => "\e[38;5;51m" },
|
|
114
|
-
"orange" => { 1 => "\e[38;5;208m", 2 => "\e[38;5;214m" },
|
|
115
|
-
"white" => { 1 => "\e[37m", 2 => "\e[97m" },
|
|
116
|
-
"reset" => "\e[0m"
|
|
117
|
-
}.freeze
|
|
118
|
-
|
|
119
|
-
BORDERS = {
|
|
120
|
-
1 => { h: "=-", v: "|", tl: "#", tr: "#", bl: "#", br: "#" },
|
|
121
|
-
2 => { h: "─", v: "│", tl: "┌", tr: "┐", bl: "└", br: "┘" },
|
|
122
|
-
3 => { h: "═", v: "║", tl: "╔", tr: "╗", bl: "╚", br: "╝" },
|
|
123
|
-
4 => { h: "━", v: "┃", tl: "┏", tr: "┓", bl: "┗", br: "┛" },
|
|
124
|
-
5 => { h: "═", v: "│", tl: "╒", tr: "╕", bl: "╘", br: "╛" },
|
|
125
|
-
6 => { h: "─", v: "║", tl: "╓", tr: "╖", bl: "╙", br: "╜" },
|
|
126
|
-
7 => { h: "─", v: "│", tl: "╭", tr: "╮", bl: "╰", br: "╯" },
|
|
127
|
-
8 => { h: "▀", hb: "▄", v: "▌", vl: "▌", vr: "▐", tl: "▛", tr: "▜", bl: "▙", br: "▟" },
|
|
128
|
-
19 => { h: "●○", v: "●", tl: "●", tr: "●", bl: "●", br: "●" },
|
|
129
|
-
20 => { h: "★☆", v: "★", tl: "★", tr: "★", bl: "★", br: "★" }
|
|
130
|
-
}.freeze
|
|
131
|
-
|
|
132
|
-
def self._normalize_font(f)
|
|
133
|
-
normalized = {}
|
|
134
|
-
f.each do |key, lines|
|
|
135
|
-
max_w = lines.map(&:length).max
|
|
136
|
-
normalized[key] = lines.map { |line| line.ljust(max_w) }.freeze
|
|
137
|
-
end
|
|
138
|
-
normalized.freeze
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def self._load_fonts
|
|
142
|
-
possible_paths = [
|
|
143
|
-
File.expand_path("data/fonts.json", __dir__),
|
|
144
|
-
File.expand_path("../data/fonts.json", __dir__),
|
|
145
|
-
File.expand_path("fonts.json", __dir__)
|
|
146
|
-
]
|
|
147
|
-
path = possible_paths.find { |p| File.file?(p) }
|
|
148
|
-
return {}.freeze unless path
|
|
149
|
-
|
|
150
|
-
raw_fonts = JSON.parse(File.read(path))
|
|
151
|
-
loaded = {}
|
|
152
|
-
raw_fonts.each do |font_key, chars|
|
|
153
|
-
font_id = font_key.to_i
|
|
154
|
-
loaded[font_id] = _normalize_font(chars)
|
|
155
|
-
end
|
|
156
|
-
loaded.freeze
|
|
157
|
-
rescue StandardError
|
|
158
|
-
{}.freeze
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
FONTS = _load_fonts
|
|
162
|
-
FONTS.each { |id, data| const_set("FONT_#{id}", data) }
|
|
163
|
-
FONT = FONTS[1] || {}.freeze
|
|
164
|
-
|
|
165
|
-
class ProgressBar
|
|
166
|
-
attr_reader :total, :current, :title, :status
|
|
167
|
-
|
|
168
|
-
def initialize(total = 100, title: nil, color: "cyan", level: 2, style: 3, width: nil)
|
|
169
|
-
@total = [total.to_i, 1].max
|
|
170
|
-
@current = 0
|
|
171
|
-
@title = title
|
|
172
|
-
@status = ""
|
|
173
|
-
@color = color.to_s.downcase
|
|
174
|
-
@level = level.to_i
|
|
175
|
-
@style = style.to_i
|
|
176
|
-
@width = width
|
|
177
|
-
@closed = false
|
|
178
|
-
@drawn_lines_count = 0
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def advance(step = 1, status: nil)
|
|
182
|
-
return if @closed
|
|
183
|
-
@current = [(@current + step), @total].min
|
|
184
|
-
@status = status.to_s if status
|
|
185
|
-
render
|
|
186
|
-
end
|
|
187
|
-
alias_method :increment, :advance
|
|
188
|
-
alias_method :step, :advance
|
|
189
|
-
|
|
190
|
-
def set(value, status: nil)
|
|
191
|
-
return if @closed
|
|
192
|
-
@current = [[value.to_i, 0].max, @total].min
|
|
193
|
-
@status = status.to_s if status
|
|
194
|
-
render
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def render
|
|
198
|
-
term_w = GRmenu.terminal_width
|
|
199
|
-
box_w = @width || [term_w - 4, 60].min
|
|
200
|
-
box_w = [box_w, 36].max
|
|
201
|
-
|
|
202
|
-
color_code = GRmenu::COLORS.dig(@color, @level) || "\e[1;96m"
|
|
203
|
-
reset_code = GRmenu::COLORS["reset"]
|
|
204
|
-
border_cfg = GRmenu::BORDERS[@style] || GRmenu::BORDERS[3]
|
|
205
|
-
|
|
206
|
-
v_l = border_cfg[:vl] || border_cfg[:v]
|
|
207
|
-
v_r = border_cfg[:vr] || border_cfg[:v]
|
|
208
|
-
h_t = border_cfg[:ht] || border_cfg[:h]
|
|
209
|
-
h_b = border_cfg[:hb] || border_cfg[:h]
|
|
210
|
-
|
|
211
|
-
top_fill = (h_t * ((box_w - 2).to_f / h_t.length).ceil)[0...(box_w - 2)]
|
|
212
|
-
bot_fill = (h_b * ((box_w - 2).to_f / h_b.length).ceil)[0...(box_w - 2)]
|
|
213
|
-
|
|
214
|
-
pct = ((@current.to_f / @total) * 100).round
|
|
215
|
-
pct_str = "#{pct}% (#{@current}/#{@total})"
|
|
216
|
-
|
|
217
|
-
inner_w = box_w - 4
|
|
218
|
-
bar_w = [inner_w - pct_str.length - 3, 10].max
|
|
219
|
-
filled_len = ((@current.to_f / @total) * bar_w).round
|
|
220
|
-
empty_len = bar_w - filled_len
|
|
221
|
-
|
|
222
|
-
bar_str = "[#{"█" * filled_len}#{"░" * empty_len}] #{pct_str}"
|
|
223
|
-
bar_line = bar_str.ljust(inner_w)[0...inner_w]
|
|
224
|
-
|
|
225
|
-
lines = []
|
|
226
|
-
lines << "#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}"
|
|
227
|
-
if @title && !@title.empty?
|
|
228
|
-
lines << "#{color_code}#{v_l}#{reset_code} #{@title.center(inner_w)} #{color_code}#{v_r}#{reset_code}"
|
|
229
|
-
lines << "#{color_code}#{v_l}#{top_fill}#{v_r}#{reset_code}"
|
|
230
|
-
end
|
|
231
|
-
lines << "#{color_code}#{v_l}#{reset_code} #{color_code}#{bar_line}#{reset_code} #{color_code}#{v_r}#{reset_code}"
|
|
232
|
-
if @status && !@status.empty?
|
|
233
|
-
stat_line = @status.ljust(inner_w)[0...inner_w]
|
|
234
|
-
lines << "#{color_code}#{v_l}#{reset_code} #{Color.gray(stat_line)} #{color_code}#{v_r}#{reset_code}"
|
|
235
|
-
end
|
|
236
|
-
lines << "#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}"
|
|
237
|
-
|
|
238
|
-
frame = lines.join("\r\n") + "\r\n"
|
|
239
|
-
|
|
240
|
-
if @drawn_lines_count && @drawn_lines_count > 0
|
|
241
|
-
Kernel.print("\e[#{@drawn_lines_count}A\e[J")
|
|
242
|
-
end
|
|
243
|
-
Kernel.print(frame)
|
|
244
|
-
$stdout.flush
|
|
245
|
-
@drawn_lines_count = lines.length
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
def finish(status: "¡Completado!")
|
|
249
|
-
return if @closed
|
|
250
|
-
set(@total, status: status)
|
|
251
|
-
@closed = true
|
|
252
|
-
Kernel.print(GRmenu::SHOW_CURSOR)
|
|
253
|
-
end
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
def self.spinner(message = "Cargando...", color: "cyan", level: 2, delay: 0.08, &block)
|
|
257
|
-
frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
258
|
-
color_name = color.to_s.downcase
|
|
259
|
-
color_code = COLORS.dig(color_name, level) || "\e[1;96m"
|
|
260
|
-
reset_code = COLORS["reset"]
|
|
261
|
-
|
|
262
|
-
stop_spinner = false
|
|
263
|
-
spinner_thread = Thread.new do
|
|
264
|
-
frame_idx = 0
|
|
265
|
-
while !stop_spinner
|
|
266
|
-
f = frames[frame_idx % frames.length]
|
|
267
|
-
Kernel.print("\r\e[K#{color_code}#{f}#{reset_code} #{message}")
|
|
268
|
-
$stdout.flush
|
|
269
|
-
frame_idx += 1
|
|
270
|
-
sleep(delay)
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
begin
|
|
275
|
-
Kernel.print(HIDE_CURSOR)
|
|
276
|
-
result = block ? block.call : nil
|
|
277
|
-
stop_spinner = true
|
|
278
|
-
spinner_thread.join
|
|
279
|
-
success_color = COLORS.dig("green", 2) || "\e[1;92m"
|
|
280
|
-
Kernel.print("\r\e[K#{success_color}✔#{reset_code} #{message} #{Color.gray("¡Listo!")}\r\n")
|
|
281
|
-
result
|
|
282
|
-
rescue Exception => e
|
|
283
|
-
stop_spinner = true
|
|
284
|
-
spinner_thread.join rescue nil
|
|
285
|
-
error_color = COLORS.dig("red", 2) || "\e[1;91m"
|
|
286
|
-
Kernel.print("\r\e[K#{error_color}✖#{reset_code} #{message} #{Color.bright_red("(Error: #{e.message})")}\r\n")
|
|
287
|
-
raise e
|
|
288
|
-
ensure
|
|
289
|
-
stop_spinner = true
|
|
290
|
-
Kernel.print(SHOW_CURSOR)
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
def self.progress(total = 100, title: nil, color: "cyan", level: 2, style: 3, width: nil, &block)
|
|
295
|
-
bar = ProgressBar.new(total, title: title, color: color, level: level, style: style, width: width)
|
|
296
|
-
Kernel.print(HIDE_CURSOR)
|
|
297
|
-
bar.render
|
|
298
|
-
begin
|
|
299
|
-
result = block ? block.call(bar) : bar
|
|
300
|
-
bar.finish
|
|
301
|
-
result
|
|
302
|
-
ensure
|
|
303
|
-
Kernel.print(SHOW_CURSOR)
|
|
304
|
-
end
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
class SetStyle
|
|
308
|
-
def initialize(
|
|
309
|
-
border: { color: "cyan", level: 1 },
|
|
310
|
-
options: { color: "white", level: 1 },
|
|
311
|
-
focus: { color: "green", level: 2 },
|
|
312
|
-
title: { color: "yellow", level: 2 },
|
|
313
|
-
banner: { color: "magenta", level: 2 },
|
|
314
|
-
subtitle: { color: "cyan", level: 2 },
|
|
315
|
-
divider: { color: "blue", level: 1 },
|
|
316
|
-
font: 1
|
|
317
|
-
)
|
|
318
|
-
@border = border.dup
|
|
319
|
-
@options = options.dup
|
|
320
|
-
@focus = focus.dup
|
|
321
|
-
@title = title.dup
|
|
322
|
-
@banner = banner.dup
|
|
323
|
-
@subtitle = subtitle.dup
|
|
324
|
-
@divider = divider.dup
|
|
325
|
-
@font = font.to_i
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
def border(color_name = nil, brightness_level = 1)
|
|
329
|
-
return @border if color_name.nil?
|
|
330
|
-
@border = parse_color(color_name, brightness_level)
|
|
331
|
-
end
|
|
332
|
-
alias_method :Border, :border
|
|
333
|
-
alias_method :set_border, :border
|
|
334
|
-
alias_method :border=, :border
|
|
335
|
-
|
|
336
|
-
def options(color_name = nil, brightness_level = 1)
|
|
337
|
-
return @options if color_name.nil?
|
|
338
|
-
@options = parse_color(color_name, brightness_level)
|
|
339
|
-
end
|
|
340
|
-
alias_method :Options, :options
|
|
341
|
-
alias_method :set_options, :options
|
|
342
|
-
alias_method :options=, :options
|
|
343
|
-
|
|
344
|
-
def focus(color_name = nil, brightness_level = 2)
|
|
345
|
-
return @focus if color_name.nil?
|
|
346
|
-
@focus = parse_color(color_name, brightness_level)
|
|
347
|
-
end
|
|
348
|
-
alias_method :Focus, :focus
|
|
349
|
-
alias_method :set_focus, :focus
|
|
350
|
-
alias_method :focus=, :focus
|
|
351
|
-
|
|
352
|
-
def title(color_name = nil, brightness_level = 2)
|
|
353
|
-
return @title if color_name.nil?
|
|
354
|
-
@title = parse_color(color_name, brightness_level)
|
|
355
|
-
end
|
|
356
|
-
alias_method :Title, :title
|
|
357
|
-
alias_method :set_title, :title
|
|
358
|
-
alias_method :title=, :title
|
|
359
|
-
|
|
360
|
-
def banner(color_name = nil, brightness_level = 2)
|
|
361
|
-
return @banner if color_name.nil?
|
|
362
|
-
@banner = parse_color(color_name, brightness_level)
|
|
363
|
-
end
|
|
364
|
-
alias_method :Banner, :banner
|
|
365
|
-
alias_method :set_banner, :banner
|
|
366
|
-
alias_method :banner=, :banner
|
|
367
|
-
|
|
368
|
-
def subtitle(color_name = nil, brightness_level = 2)
|
|
369
|
-
return @subtitle if color_name.nil?
|
|
370
|
-
@subtitle = parse_color(color_name, brightness_level)
|
|
371
|
-
end
|
|
372
|
-
alias_method :Subtitle, :subtitle
|
|
373
|
-
alias_method :set_subtitle, :subtitle
|
|
374
|
-
alias_method :subtitle=, :subtitle
|
|
375
|
-
|
|
376
|
-
def divider(color_name = nil, brightness_level = 1)
|
|
377
|
-
return @divider if color_name.nil?
|
|
378
|
-
@divider = parse_color(color_name, brightness_level)
|
|
379
|
-
end
|
|
380
|
-
alias_method :Divider, :divider
|
|
381
|
-
alias_method :set_divider, :divider
|
|
382
|
-
alias_method :divider=, :divider
|
|
383
|
-
|
|
384
|
-
def font(font_id = nil)
|
|
385
|
-
return @font if font_id.nil?
|
|
386
|
-
@font = font_id.to_i
|
|
387
|
-
end
|
|
388
|
-
alias_method :Font, :font
|
|
389
|
-
alias_method :set_font, :font
|
|
390
|
-
alias_method :font=, :font
|
|
391
|
-
|
|
392
|
-
private
|
|
393
|
-
|
|
394
|
-
def parse_color(color_val, default_level = 1)
|
|
395
|
-
if color_val.is_a?(Hash)
|
|
396
|
-
{ color: (color_val[:color] || color_val["color"]).to_s, level: (color_val[:level] || color_val["level"] || default_level).to_i }
|
|
397
|
-
else
|
|
398
|
-
{ color: color_val.to_s, level: default_level.to_i }
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
class << self
|
|
403
|
-
def border(color_name = nil, brightness_level = 1)
|
|
404
|
-
@default_border ||= { color: "cyan", level: 1 }
|
|
405
|
-
return @default_border if color_name.nil?
|
|
406
|
-
@default_border = { color: color_name.to_s, level: brightness_level.to_i }
|
|
407
|
-
end
|
|
408
|
-
alias_method :Border, :border
|
|
409
|
-
alias_method :border=, :border
|
|
410
|
-
|
|
411
|
-
def options(color_name = nil, brightness_level = 1)
|
|
412
|
-
@default_options ||= { color: "white", level: 1 }
|
|
413
|
-
return @default_options if color_name.nil?
|
|
414
|
-
@default_options = { color: color_name.to_s, level: brightness_level.to_i }
|
|
415
|
-
end
|
|
416
|
-
alias_method :Options, :options
|
|
417
|
-
alias_method :options=, :options
|
|
418
|
-
|
|
419
|
-
def focus(color_name = nil, brightness_level = 2)
|
|
420
|
-
@default_focus ||= { color: "green", level: 2 }
|
|
421
|
-
return @default_focus if color_name.nil?
|
|
422
|
-
@default_focus = { color: color_name.to_s, level: brightness_level.to_i }
|
|
423
|
-
end
|
|
424
|
-
alias_method :Focus, :focus
|
|
425
|
-
alias_method :focus=, :focus
|
|
426
|
-
|
|
427
|
-
def title(color_name = nil, brightness_level = 2)
|
|
428
|
-
@default_title ||= { color: "yellow", level: 2 }
|
|
429
|
-
return @default_title if color_name.nil?
|
|
430
|
-
@default_title = { color: color_name.to_s, level: brightness_level.to_i }
|
|
431
|
-
end
|
|
432
|
-
alias_method :Title, :title
|
|
433
|
-
alias_method :title=, :title
|
|
434
|
-
|
|
435
|
-
def banner(color_name = nil, brightness_level = 2)
|
|
436
|
-
@default_banner ||= { color: "magenta", level: 2 }
|
|
437
|
-
return @default_banner if color_name.nil?
|
|
438
|
-
@default_banner = { color: color_name.to_s, level: brightness_level.to_i }
|
|
439
|
-
end
|
|
440
|
-
alias_method :Banner, :banner
|
|
441
|
-
alias_method :banner=, :banner
|
|
442
|
-
|
|
443
|
-
def subtitle(color_name = nil, brightness_level = 2)
|
|
444
|
-
@default_subtitle ||= { color: "cyan", level: 2 }
|
|
445
|
-
return @default_subtitle if color_name.nil?
|
|
446
|
-
@default_subtitle = { color: color_name.to_s, level: brightness_level.to_i }
|
|
447
|
-
end
|
|
448
|
-
alias_method :Subtitle, :subtitle
|
|
449
|
-
alias_method :subtitle=, :subtitle
|
|
450
|
-
|
|
451
|
-
def divider(color_name = nil, brightness_level = 1)
|
|
452
|
-
@default_divider ||= { color: "blue", level: 1 }
|
|
453
|
-
return @default_divider if color_name.nil?
|
|
454
|
-
@default_divider = { color: color_name.to_s, level: brightness_level.to_i }
|
|
455
|
-
end
|
|
456
|
-
alias_method :Divider, :divider
|
|
457
|
-
alias_method :divider=, :divider
|
|
458
|
-
|
|
459
|
-
def font(font_id = nil)
|
|
460
|
-
@default_font ||= 1
|
|
461
|
-
return @default_font if font_id.nil?
|
|
462
|
-
@default_font = font_id.to_i
|
|
463
|
-
end
|
|
464
|
-
alias_method :Font, :font
|
|
465
|
-
alias_method :set_font, :font
|
|
466
|
-
alias_method :font=, :font
|
|
467
|
-
end
|
|
468
|
-
end
|
|
469
|
-
|
|
470
|
-
module GRprint
|
|
471
|
-
module_function
|
|
472
|
-
|
|
473
|
-
def p(text = "", ending = "\r\n")
|
|
474
|
-
Kernel.print("#{text}#{ending}")
|
|
475
|
-
end
|
|
476
|
-
end
|
|
477
|
-
|
|
478
|
-
attr_accessor :functions, :title, :subtitle, :banner, :banner_style, :divider, :style, :index, :style_config, :center, :page_size
|
|
479
|
-
|
|
480
|
-
alias_method :options, :functions
|
|
481
|
-
alias_method :options=, :functions=
|
|
482
|
-
alias_method :selected_index, :index
|
|
483
|
-
alias_method :selected_index=, :index=
|
|
484
|
-
alias_method :SetStyle, :style_config
|
|
485
|
-
alias_method :set_style, :style_config
|
|
486
|
-
alias_method :description, :subtitle
|
|
487
|
-
alias_method :description=, :subtitle=
|
|
488
|
-
|
|
489
|
-
def self.STYLES; STYLES; end
|
|
490
|
-
def self.COLORS; COLORS; end
|
|
491
|
-
def self.BORDERS; BORDERS; end
|
|
492
|
-
def self.FONTS; FONTS; end
|
|
493
|
-
def self.FONT; FONT_1; end
|
|
494
|
-
|
|
495
|
-
def self.terminal_width
|
|
496
|
-
cols = $stdout.winsize[1] rescue nil
|
|
497
|
-
cols = $stdin.winsize[1] rescue nil if cols.nil? || cols <= 0
|
|
498
|
-
(cols && cols > 0) ? cols : (ENV['COLUMNS'] ? ENV['COLUMNS'].to_i : 80)
|
|
499
|
-
rescue StandardError
|
|
500
|
-
80
|
|
501
|
-
end
|
|
502
|
-
|
|
503
|
-
def self.terminal_height
|
|
504
|
-
rows = $stdout.winsize[0] rescue nil
|
|
505
|
-
rows = $stdin.winsize[0] rescue nil if rows.nil? || rows <= 0
|
|
506
|
-
(rows && rows > 0) ? rows : (ENV['LINES'] ? ENV['LINES'].to_i : 24)
|
|
507
|
-
rescue StandardError
|
|
508
|
-
24
|
|
509
|
-
end
|
|
510
|
-
|
|
511
|
-
def self.clear_screen
|
|
512
|
-
Kernel.print(CLEAR_SCREEN_SEQUENCE)
|
|
513
|
-
end
|
|
514
|
-
class << self
|
|
515
|
-
alias_method :clr, :clear_screen
|
|
516
|
-
end
|
|
517
|
-
|
|
518
|
-
def self.div(long = nil, color = "blue", level = 1, char = "─")
|
|
519
|
-
width = long || [terminal_width - 2, 64].min
|
|
520
|
-
color_code = COLORS.dig(color.to_s.downcase, level) || "\e[34m"
|
|
521
|
-
Kernel.print("#{color_code}#{char * width}#{COLORS['reset']}\r\n")
|
|
522
|
-
end
|
|
523
|
-
|
|
524
|
-
def self.help(section = :all)
|
|
525
|
-
w = [terminal_width - 4, 70].min
|
|
526
|
-
w = [w, 46].max
|
|
527
|
-
inner_w = w - 2
|
|
528
|
-
h_line = "═" * inner_w
|
|
529
|
-
s_line = "─" * w
|
|
530
|
-
|
|
531
|
-
Kernel.print "\r\n"
|
|
532
|
-
Kernel.print "#{Color.bright_cyan("╔" + h_line + "╗")}\r\n"
|
|
533
|
-
Kernel.print "#{Color.bright_cyan("║")}#{Color.bright_yellow("GRmenu - Guia y Referencia Completa (v2.0)".center(inner_w))}#{Color.bright_cyan("║")}\r\n"
|
|
534
|
-
Kernel.print "#{Color.bright_cyan("║")}#{Color.gray("Menus interactivos, Banners 3D, Barras de Progreso y TTY".center(inner_w))}#{Color.bright_cyan("║")}\r\n"
|
|
535
|
-
Kernel.print "#{Color.bright_cyan("╚" + h_line + "╝")}\r\n\r\n"
|
|
536
|
-
|
|
537
|
-
Kernel.print "#{Color.bright_magenta("[1] HELPERS NATIVOS")}\r\n"
|
|
538
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
539
|
-
Kernel.print " #{Color.bright_green("GRmenu.clear_screen")} #{Color.gray("(o GRmenu.clr)")}\r\n"
|
|
540
|
-
Kernel.print " * Limpia la terminal al instante con secuencias ANSI.\r\n"
|
|
541
|
-
Kernel.print " #{Color.bright_green("GRmenu.continue(mensaje)")}\r\n"
|
|
542
|
-
Kernel.print " * Pausa interactiva: espera una sola tecla en modo TTY crudo.\r\n"
|
|
543
|
-
Kernel.print " #{Color.bright_green("GRmenu.banner(texto, delay, color:, level:, style:, font:)")}\r\n"
|
|
544
|
-
Kernel.print " * Renderiza banner ASCII 3D con marco y animacion opcional.\r\n"
|
|
545
|
-
Kernel.print " #{Color.bright_green("GRmenu.spinner(mensaje, color:, level:, delay:, &bloque)")}\r\n"
|
|
546
|
-
Kernel.print " * Animacion giratoria fluida para tareas de tiempo desconocido.\r\n"
|
|
547
|
-
Kernel.print " * Ejemplo: #{Color.bright_white("GRmenu.spinner(\"Conectando...\") { conectar_db }")}\r\n"
|
|
548
|
-
Kernel.print " #{Color.bright_green("GRmenu.progress(total, title:, color:, level:, style:, width:, &bloque)")}\r\n"
|
|
549
|
-
Kernel.print " * Barra de progreso porcentual dentro de un recuadro estilizado.\r\n"
|
|
550
|
-
Kernel.print " * El bloque recibe 'bar'. Metodos disponibles:\r\n"
|
|
551
|
-
Kernel.print " - #{Color.cyan("bar.advance(n, status: \"...\")")} -> Avanza n pasos (alias: increment, step).\r\n"
|
|
552
|
-
Kernel.print " - #{Color.cyan("bar.set(valor, status: \"...\")")} -> Fija el valor exacto actual.\r\n"
|
|
553
|
-
Kernel.print " - #{Color.cyan("bar.finish(status: \"...\")")} -> Finaliza la barra al 100%.\r\n"
|
|
554
|
-
Kernel.print " * Ejemplo: #{Color.bright_white("GRmenu.progress(10, title: \"Copia\") { |b| 10.times { b.advance(1) } }")}\r\n"
|
|
555
|
-
Kernel.print " #{Color.bright_green("GRmenu.div(longitud, color, level, char)")}\r\n"
|
|
556
|
-
Kernel.print " * Dibuja linea divisoria horizontal adaptable a la consola.\r\n\r\n"
|
|
557
|
-
|
|
558
|
-
Kernel.print "#{Color.bright_magenta("[2] FORMATOS DE OPCIONES Y TOOLTIPS DINAMICOS")}\r\n"
|
|
559
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
560
|
-
Kernel.print " #{Color.cyan("1. Metodo directo:")} #{Color.bright_white("method(:iniciar)")} #{Color.gray("(auto-capitaliza nombre)")}\r\n"
|
|
561
|
-
Kernel.print " #{Color.cyan("2. Simbolo:")} #{Color.bright_white(":iniciar")}\r\n"
|
|
562
|
-
Kernel.print " #{Color.cyan("3. Nombre propio:")} #{Color.bright_white("[\"Mi Accion\", method(:iniciar)]")}\r\n"
|
|
563
|
-
Kernel.print " #{Color.cyan("4. Con Tooltip/Info:")} #{Color.bright_white("[\"Mi Accion\", method(:iniciar), \"Descripcion que sale abajo\"]")}\r\n"
|
|
564
|
-
Kernel.print " #{Color.cyan("5. Lambda / Proc:")} #{Color.bright_white("[\"Test\", -> { puts \"Hola\" }, \"Tooltip opcional\"]")}\r\n"
|
|
565
|
-
Kernel.print " #{Color.cyan("6. Hash:")} #{Color.bright_white("{ name: \"Test\", action: method(:iniciar), desc: \"Info\" }")}\r\n\r\n"
|
|
566
|
-
|
|
567
|
-
Kernel.print "#{Color.bright_magenta("[3] PARAMETROS DE GRmenu.new(functions, ...)")}\r\n"
|
|
568
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
569
|
-
Kernel.print " #{Color.bright_green("functions:")} #{Color.white("Array")} -> Lista de opciones (metodos, simbolos, arreglos, lambdas).\r\n"
|
|
570
|
-
Kernel.print " #{Color.bright_green("banner:")} #{Color.white("String")} -> Texto grande a renderizar en arte ASCII 3D.\r\n"
|
|
571
|
-
Kernel.print " #{Color.bright_green("title:")} #{Color.white("String")} -> Titulo en el encabezado del recuadro.\r\n"
|
|
572
|
-
Kernel.print " #{Color.bright_green("subtitle:")} #{Color.white("String")} -> Subtitulo / descripcion (soporta \\n).\r\n"
|
|
573
|
-
Kernel.print " #{Color.bright_green("page_size:")} #{Color.white("Integer")} -> Limite visible para scroll y paginacion automatica.\r\n"
|
|
574
|
-
Kernel.print " #{Color.bright_green("font:")} #{Color.white("Integer")} -> Fuente ASCII del banner (1 al 10, default 1).\r\n"
|
|
575
|
-
Kernel.print " #{Color.bright_green("style:")} #{Color.white("Integer")} -> Estilo de marco de opciones (1 al 20, default 19).\r\n"
|
|
576
|
-
Kernel.print " #{Color.bright_green("banner_style:")} #{Color.white("Integer")} -> Estilo de marco del banner (1 al 20, default 3).\r\n"
|
|
577
|
-
Kernel.print " #{Color.bright_green("divider:")} #{Color.white("Boolean")} -> Divisores alineados al banner (true/false).\r\n"
|
|
578
|
-
Kernel.print " #{Color.bright_green("center:")} #{Color.white("Boolean")} -> Centrado simetrico de subtitulo y menu (default true).\r\n\r\n"
|
|
579
|
-
|
|
580
|
-
Kernel.print "#{Color.bright_magenta("[4] AUTO-PAGINACION Y SCROLL")}\r\n"
|
|
581
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
582
|
-
Kernel.print " * #{Color.white("100% Automatica:")} Si la lista tiene muchas opciones o la pantalla es pequena,\r\n"
|
|
583
|
-
Kernel.print " GRmenu calcula el espacio disponible y genera una ventana deslizante suave.\r\n"
|
|
584
|
-
Kernel.print " * Indicadores visuales: #{Color.bright_yellow("▲ (+N arriba)")} y #{Color.bright_yellow("▼ (+M abajo)")}.\r\n"
|
|
585
|
-
Kernel.print " * Opcional: fija el limite con #{Color.bright_white("page_size: 8")} al instanciar #{Color.bright_green("GRmenu.new")}.\r\n\r\n"
|
|
586
|
-
|
|
587
|
-
Kernel.print "#{Color.bright_magenta("[5] MODULO DE COLORES (Color / C)")}\r\n"
|
|
588
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
589
|
-
Kernel.print " #{Color.cyan("Uso directo: ")}#{Color.bright_white("puts Color.green(\"Texto\")")} | #{Color.bright_white("puts Color.bright_cyan(\"Texto\")")}\r\n"
|
|
590
|
-
Kernel.print " #{Color.cyan("Paleta: ")}#{Color.red("red")}, #{Color.green("green")}, #{Color.yellow("yellow")}, #{Color.blue("blue")}, #{Color.magenta("magenta")}, #{Color.purple("purple")}, #{Color.pink("pink")}, #{Color.cyan("cyan")}, #{Color.aqua("aqua")}, #{Color.orange("orange")}, #{Color.white("white")}, #{Color.gray("gray")}, #{Color.black("black")}.\r\n"
|
|
591
|
-
Kernel.print " #{Color.cyan("Brillo: ")}#{Color.white("1")} = Normal, #{Color.bright_white("2")} = Brillante / Bold.\r\n\r\n"
|
|
592
|
-
|
|
593
|
-
Kernel.print "#{Color.bright_magenta("[6] FUENTES ASCII 3D DEL BANNER (font: 1 al 10)")}\r\n"
|
|
594
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
595
|
-
Kernel.print " #{Color.yellow("1")} -> #{Color.bright_white("ANSI Shadow 3D (Default)")} #{Color.cyan("[██████╗ ██╗ ██╗]")}\r\n"
|
|
596
|
-
Kernel.print " #{Color.yellow("2")} -> #{Color.bright_white("Slant 3D (FIGlet)")} #{Color.cyan("[ ____ __ __]")}\r\n"
|
|
597
|
-
Kernel.print " #{Color.yellow("3")} -> #{Color.bright_white("Doom / Standard 3D")} #{Color.cyan("[ ____ _ _]")}\r\n"
|
|
598
|
-
Kernel.print " #{Color.yellow("4")} -> #{Color.bright_white("Graffiti Shadow 3D")} #{Color.cyan("[ ,---. ,--. ,--.]")}\r\n"
|
|
599
|
-
Kernel.print " #{Color.yellow("5")} -> #{Color.bright_white("Small Slant / Mini 3D")} #{Color.cyan("[ ___ _ _]")}\r\n"
|
|
600
|
-
Kernel.print " #{Color.yellow("6")} -> #{Color.bright_white("Modular Pipe 3D")} #{Color.cyan("[ _____ _____]")}\r\n"
|
|
601
|
-
Kernel.print " #{Color.yellow("7")} -> #{Color.bright_white("Bubble / Round Gothic")} #{Color.cyan("[ ____ _ _]")}\r\n"
|
|
602
|
-
Kernel.print " #{Color.yellow("8")} -> #{Color.bright_white("Double-Line Wire 3D")} #{Color.cyan("[ ╔═════╗ ║ ║]")}\r\n"
|
|
603
|
-
Kernel.print " #{Color.yellow("9")} -> #{Color.bright_white("Solid Fat 3D Block")} #{Color.cyan("[ ██████▄ ██ ██]")}\r\n"
|
|
604
|
-
Kernel.print " #{Color.yellow("10")}-> #{Color.bright_white("Arcade Stars Matrix")} #{Color.cyan("[ ★★★★ ★ ★]")}\r\n\r\n"
|
|
605
|
-
|
|
606
|
-
Kernel.print "#{Color.bright_magenta("[7] ESTILOS DE MARCO (style / banner_style: 1 al 20)")}\r\n"
|
|
607
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
608
|
-
Kernel.print " #{Color.yellow("3")} -> #{Color.bright_white("Doble linea")} #{Color.cyan("╔═══╗ ║ ║ ╚═══╝")} (Default en Banner)\r\n"
|
|
609
|
-
Kernel.print " #{Color.yellow("7")} -> #{Color.bright_white("Curvas redondeadas")} #{Color.cyan("╭───╮ │ │ ╰───╯")}\r\n"
|
|
610
|
-
Kernel.print " #{Color.yellow("4")} -> #{Color.bright_white("Linea gruesa")} #{Color.cyan("┏━━━┓ ┃ ┃ ┗━━━┛")}\r\n"
|
|
611
|
-
Kernel.print " #{Color.yellow("2")} -> #{Color.bright_white("Linea simple")} #{Color.cyan("┌───┐ │ │ └───┘")}\r\n"
|
|
612
|
-
Kernel.print " #{Color.yellow("8")} -> #{Color.bright_white("Bloques outline")} #{Color.cyan("▛▀▀▀▜ ▌ ▐ ▙▄▄▄▟")}\r\n"
|
|
613
|
-
Kernel.print " #{Color.yellow("19")} -> #{Color.bright_white("Circulos")} #{Color.cyan("●○○○● ● ● ●○○○●")} (Default en Opciones)\r\n"
|
|
614
|
-
Kernel.print " #{Color.yellow("20")} -> #{Color.bright_white("Estrellas")} #{Color.cyan("★☆☆☆★ ★ ★ ★☆☆☆★")}\r\n\r\n"
|
|
615
|
-
|
|
616
|
-
Kernel.print "#{Color.bright_magenta("[8] METODOS DE CONFIGURACION (menu.set_style)")}\r\n"
|
|
617
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
618
|
-
Kernel.print " #{Color.cyan("menu.set_style.font(id)")} -> Cambia fuente ASCII (1..10)\r\n"
|
|
619
|
-
Kernel.print " #{Color.cyan("menu.set_style.banner(color, level)")} -> Color y brillo del banner ASCII\r\n"
|
|
620
|
-
Kernel.print " #{Color.cyan("menu.set_style.title(color, level)")} -> Color y brillo del titulo\r\n"
|
|
621
|
-
Kernel.print " #{Color.cyan("menu.set_style.subtitle(color, level)")} -> Color y brillo del subtitulo\r\n"
|
|
622
|
-
Kernel.print " #{Color.cyan("menu.set_style.divider(color, level)")} -> Color y brillo de las lineas divisorias\r\n"
|
|
623
|
-
Kernel.print " #{Color.cyan("menu.set_style.border(color, level)")} -> Color y brillo del marco de opciones\r\n"
|
|
624
|
-
Kernel.print " #{Color.cyan("menu.set_style.options(color, level)")} -> Color y brillo de opciones no activas\r\n"
|
|
625
|
-
Kernel.print " #{Color.cyan("menu.set_style.focus(color, level)")} -> Color y brillo de la opcion resaltada\r\n\r\n"
|
|
626
|
-
|
|
627
|
-
Kernel.print "#{Color.bright_magenta("[9] EJECUCION (menu.draw)")}\r\n"
|
|
628
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n"
|
|
629
|
-
Kernel.print " #{Color.bright_white("menu.draw(size_max: 38)")} -> Inicia el menu interactivo con ancho minimo.\r\n"
|
|
630
|
-
Kernel.print "#{Color.bright_blue(s_line)}\r\n\r\n"
|
|
631
|
-
end
|
|
632
|
-
|
|
633
|
-
def help
|
|
634
|
-
self.class.help
|
|
635
|
-
end
|
|
636
|
-
|
|
637
|
-
def self.continue(text = "Presiona cualquier tecla para continuar...")
|
|
638
|
-
Kernel.print("#{Color.gray(text)} ")
|
|
639
|
-
if $stdin.respond_to?(:raw) && $stdin.respond_to?(:tty?) && $stdin.tty?
|
|
640
|
-
$stdin.raw(&:getch)
|
|
641
|
-
elsif $stdin.respond_to?(:getch)
|
|
642
|
-
$stdin.getch
|
|
643
|
-
else
|
|
644
|
-
$stdin.read(1)
|
|
645
|
-
end
|
|
646
|
-
Kernel.print("\r\n")
|
|
647
|
-
end
|
|
648
|
-
|
|
649
|
-
def self.build_ascii_lines(text, max_cols = terminal_width, font_id = 1)
|
|
650
|
-
target_font = FONTS[font_id.to_i] || FONTS[1]
|
|
651
|
-
clean_chars = text.to_s.upcase.chars.select { |c| target_font.key?(c) }
|
|
652
|
-
return [] if clean_chars.empty?
|
|
653
|
-
|
|
654
|
-
font_height = target_font.values.first.length
|
|
655
|
-
|
|
656
|
-
[2, 1, 0].each do |spacing|
|
|
657
|
-
lines = Array.new(font_height, "")
|
|
658
|
-
clean_chars.each_with_index do |c, idx|
|
|
659
|
-
fig = target_font[c]
|
|
660
|
-
pad = (idx == clean_chars.length - 1) ? "" : (" " * spacing)
|
|
661
|
-
font_height.times { |i| lines[i] += fig[i] + pad }
|
|
662
|
-
end
|
|
663
|
-
|
|
664
|
-
max_len = lines.map(&:length).max
|
|
665
|
-
return lines if (max_len + 6) <= max_cols
|
|
666
|
-
end
|
|
667
|
-
|
|
668
|
-
nil
|
|
669
|
-
end
|
|
670
|
-
|
|
671
|
-
def self.banner(text, delay = 0, color: "magenta", level: 2, style: 3, font: 1)
|
|
672
|
-
cols = terminal_width
|
|
673
|
-
color_code = COLORS.dig(color.to_s.downcase, level) || "\e[1;95m"
|
|
674
|
-
reset_code = COLORS["reset"]
|
|
675
|
-
|
|
676
|
-
ascii_rows = build_ascii_lines(text, cols, font)
|
|
677
|
-
border_cfg = BORDERS[style] || BORDERS[3]
|
|
678
|
-
h_top = border_cfg[:ht] || border_cfg[:h]
|
|
679
|
-
h_bot = border_cfg[:hb] || border_cfg[:h]
|
|
680
|
-
v_l = border_cfg[:vl] || border_cfg[:v]
|
|
681
|
-
v_r = border_cfg[:vr] || border_cfg[:v]
|
|
682
|
-
|
|
683
|
-
if ascii_rows
|
|
684
|
-
max_len = ascii_rows.map(&:length).max
|
|
685
|
-
top_fill = (h_top * ((max_len + 4).to_f / h_top.length).ceil)[0...(max_len + 4)]
|
|
686
|
-
bot_fill = (h_bot * ((max_len + 4).to_f / h_bot.length).ceil)[0...(max_len + 4)]
|
|
687
|
-
|
|
688
|
-
Kernel.print("#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}\r\n")
|
|
689
|
-
ascii_rows.each do |line|
|
|
690
|
-
pad = " " * (max_len - line.length)
|
|
691
|
-
Kernel.print("#{color_code}#{v_l} #{line}#{pad} #{v_r}#{reset_code}\r\n")
|
|
692
|
-
sleep(delay) if delay > 0
|
|
693
|
-
end
|
|
694
|
-
Kernel.print("#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}\r\n")
|
|
695
|
-
else
|
|
696
|
-
clean_t = text.to_s.strip
|
|
697
|
-
box_w = [clean_t.length + 6, cols - 2].min
|
|
698
|
-
top_fill = (h_top * ((box_w - 2).to_f / h_top.length).ceil)[0...(box_w - 2)]
|
|
699
|
-
bot_fill = (h_bot * ((box_w - 2).to_f / h_b.length).ceil)[0...(box_w - 2)]
|
|
700
|
-
|
|
701
|
-
Kernel.print("#{color_code}#{border_cfg[:tl]}#{top_fill}#{border_cfg[:tr]}#{reset_code}\r\n")
|
|
702
|
-
Kernel.print("#{color_code}#{v_l} #{clean_t.center(box_w - 4)} #{v_r}#{reset_code}\r\n")
|
|
703
|
-
Kernel.print("#{color_code}#{border_cfg[:bl]}#{bot_fill}#{border_cfg[:br]}#{reset_code}\r\n")
|
|
704
|
-
end
|
|
705
|
-
end
|
|
706
|
-
|
|
707
|
-
class << self
|
|
708
|
-
alias_method :message, :banner
|
|
709
|
-
alias_method :logo, :banner
|
|
710
|
-
end
|
|
711
|
-
|
|
712
|
-
def initialize(functions, *positional_arguments, title: nil, banner: nil, subtitle: nil, description: nil, divider: nil, style: nil, banner_style: nil, center: true, font: nil, page_size: nil, **keyword_arguments)
|
|
713
|
-
@functions = functions.is_a?(Array) ? functions : Array(functions)
|
|
714
|
-
|
|
715
|
-
pos_title = positional_arguments[0]
|
|
716
|
-
pos_style = positional_arguments[1]
|
|
717
|
-
|
|
718
|
-
@title = (title || pos_title || keyword_arguments[:title] || "").to_s
|
|
719
|
-
@banner = (banner || keyword_arguments[:banner] || "").to_s
|
|
720
|
-
@subtitle = (subtitle || description || keyword_arguments[:subtitle] || keyword_arguments[:description] || "").to_s
|
|
721
|
-
@divider = divider.nil? ? (!@banner.empty? || !@subtitle.empty?) : divider
|
|
722
|
-
@style = (style || pos_style || keyword_arguments[:style] || 19).to_i
|
|
723
|
-
@banner_style = (banner_style || keyword_arguments[:banner_style] || 3).to_i
|
|
724
|
-
@center = center.nil? ? true : center
|
|
725
|
-
@page_size = (page_size || keyword_arguments[:page_size])&.to_i
|
|
726
|
-
@index = 0
|
|
727
|
-
|
|
728
|
-
init_font = font || keyword_arguments[:font_style] || SetStyle.font || 1
|
|
729
|
-
|
|
730
|
-
@style_config = SetStyle.new(
|
|
731
|
-
border: SetStyle.border.dup,
|
|
732
|
-
options: SetStyle.options.dup,
|
|
733
|
-
focus: SetStyle.focus.dup,
|
|
734
|
-
title: SetStyle.title.dup,
|
|
735
|
-
banner: SetStyle.banner.dup,
|
|
736
|
-
subtitle: SetStyle.subtitle.dup,
|
|
737
|
-
divider: SetStyle.divider.dup,
|
|
738
|
-
font: init_font
|
|
739
|
-
)
|
|
740
|
-
end
|
|
741
|
-
|
|
742
|
-
def move_up
|
|
743
|
-
return @index if @functions.empty?
|
|
744
|
-
@index = (@index - 1) % @functions.length
|
|
745
|
-
end
|
|
746
|
-
alias_method :_up, :move_up
|
|
747
|
-
|
|
748
|
-
def move_down
|
|
749
|
-
return @index if @functions.empty?
|
|
750
|
-
@index = (@index + 1) % @functions.length
|
|
751
|
-
end
|
|
752
|
-
alias_method :_down, :move_down
|
|
753
|
-
|
|
754
|
-
def colorize(text, color_config)
|
|
755
|
-
return text.to_s if color_config.nil? || color_config.empty?
|
|
756
|
-
|
|
757
|
-
color_name = (color_config[:color] || color_config["color"]).to_s.downcase
|
|
758
|
-
brightness_level = (color_config[:level] || color_config["level"] || 1).to_i
|
|
759
|
-
|
|
760
|
-
color_code = COLORS.dig(color_name, brightness_level)
|
|
761
|
-
return text.to_s unless color_code
|
|
762
|
-
|
|
763
|
-
"#{color_code}#{text}#{COLORS['reset']}"
|
|
764
|
-
end
|
|
765
|
-
alias_method :_colorize, :colorize
|
|
766
|
-
|
|
767
|
-
def build_horizontal_line(pattern, target_width)
|
|
768
|
-
return "" if target_width <= 0 || pattern.nil? || pattern.empty?
|
|
769
|
-
|
|
770
|
-
pattern_length = pattern.length
|
|
771
|
-
repetitions_needed = (target_width.to_f / pattern_length).ceil + 1
|
|
772
|
-
(pattern * repetitions_needed)[0...target_width]
|
|
773
|
-
end
|
|
774
|
-
alias_method :_hline, :build_horizontal_line
|
|
775
|
-
|
|
776
|
-
def render_banner_lines(term_cols)
|
|
777
|
-
return [[], 0] if @banner.nil? || @banner.empty?
|
|
778
|
-
|
|
779
|
-
font_id = @style_config.font || 1
|
|
780
|
-
ascii_rows = self.class.build_ascii_lines(@banner, term_cols, font_id)
|
|
781
|
-
banner_border = BORDERS[@banner_style] || BORDERS[3]
|
|
782
|
-
banner_color_cfg = @style_config.banner
|
|
783
|
-
|
|
784
|
-
h_top = banner_border[:ht] || banner_border[:h]
|
|
785
|
-
h_bot = banner_border[:hb] || banner_border[:h]
|
|
786
|
-
v_l = banner_border[:vl] || banner_border[:v]
|
|
787
|
-
v_r = banner_border[:vr] || banner_border[:v]
|
|
788
|
-
|
|
789
|
-
lines = []
|
|
790
|
-
box_w = 0
|
|
791
|
-
if ascii_rows
|
|
792
|
-
content_w = ascii_rows.map(&:length).max
|
|
793
|
-
box_w = content_w + 6
|
|
794
|
-
top_fill = build_horizontal_line(h_top, content_w + 4)
|
|
795
|
-
bot_fill = build_horizontal_line(h_bot, content_w + 4)
|
|
796
|
-
|
|
797
|
-
lines << colorize("#{banner_border[:tl]}#{top_fill}#{banner_border[:tr]}", banner_color_cfg)
|
|
798
|
-
ascii_rows.each do |row|
|
|
799
|
-
pad = " " * (content_w - row.length)
|
|
800
|
-
lines << colorize("#{v_l} #{row}#{pad} #{v_r}", banner_color_cfg)
|
|
801
|
-
end
|
|
802
|
-
lines << colorize("#{banner_border[:bl]}#{bot_fill}#{banner_border[:br]}", banner_color_cfg)
|
|
803
|
-
else
|
|
804
|
-
clean_b = @banner.strip
|
|
805
|
-
box_w = [clean_b.length + 6, term_cols - 2].min
|
|
806
|
-
top_fill = build_horizontal_line(h_top, box_w - 2)
|
|
807
|
-
bot_fill = build_horizontal_line(h_bot, box_w - 2)
|
|
808
|
-
|
|
809
|
-
lines << colorize("#{banner_border[:tl]}#{top_fill}#{banner_border[:tr]}", banner_color_cfg)
|
|
810
|
-
lines << colorize("#{v_l} #{clean_b.center(box_w - 4)} #{v_r}", banner_color_cfg)
|
|
811
|
-
lines << colorize("#{banner_border[:bl]}#{bot_fill}#{banner_border[:br]}", banner_color_cfg)
|
|
812
|
-
end
|
|
813
|
-
[lines, box_w]
|
|
814
|
-
end
|
|
815
|
-
|
|
816
|
-
def render_lines(size_max = 20)
|
|
817
|
-
term_cols = self.class.terminal_width
|
|
818
|
-
term_rows = self.class.terminal_height
|
|
819
|
-
rendered_lines = []
|
|
820
|
-
|
|
821
|
-
banner_box_width = 0
|
|
822
|
-
banner_lines_count = 0
|
|
823
|
-
if @banner && !@banner.empty?
|
|
824
|
-
banner_lines, banner_box_width = render_banner_lines(term_cols)
|
|
825
|
-
rendered_lines.concat(banner_lines)
|
|
826
|
-
rendered_lines << ""
|
|
827
|
-
banner_lines_count = banner_lines.length + 1
|
|
828
|
-
end
|
|
829
|
-
|
|
830
|
-
all_names = @functions.map { |func| extract_name_from_action(func) }
|
|
831
|
-
all_descriptions = @functions.map { |func| extract_description_from_action(func) }
|
|
832
|
-
|
|
833
|
-
active_desc = all_descriptions[@index] || ""
|
|
834
|
-
|
|
835
|
-
calculated_width = [size_max, @title.length + 4].max
|
|
836
|
-
calculated_width = ([calculated_width] + all_names.map { |name| name.length + 6 }).max
|
|
837
|
-
calculated_width = ([calculated_width, active_desc.length + 8].max) unless active_desc.empty?
|
|
838
|
-
total_width = [calculated_width, term_cols - 2].min
|
|
839
|
-
|
|
840
|
-
reference_width = banner_box_width > 0 ? banner_box_width : total_width
|
|
841
|
-
margin_left = (@center && reference_width > total_width) ? " " * ((reference_width - total_width) / 2) : ""
|
|
842
|
-
|
|
843
|
-
subtitle_lines_count = 0
|
|
844
|
-
if @subtitle && !@subtitle.empty?
|
|
845
|
-
subtitle_lines = @subtitle.lines.map(&:chomp)
|
|
846
|
-
div_w = @divider.is_a?(Numeric) ? @divider.to_i : [reference_width, term_cols - 2].min
|
|
847
|
-
|
|
848
|
-
if @divider
|
|
849
|
-
rendered_lines << colorize("─" * div_w, @style_config.divider)
|
|
850
|
-
subtitle_lines_count += 1
|
|
851
|
-
end
|
|
852
|
-
|
|
853
|
-
subtitle_lines.each do |sub_line|
|
|
854
|
-
formatted_sub = @center ? sub_line.center(div_w) : sub_line
|
|
855
|
-
rendered_lines << colorize(formatted_sub, @style_config.subtitle)
|
|
856
|
-
subtitle_lines_count += 1
|
|
857
|
-
end
|
|
858
|
-
|
|
859
|
-
if @divider
|
|
860
|
-
rendered_lines << colorize("─" * div_w, @style_config.divider)
|
|
861
|
-
subtitle_lines_count += 1
|
|
862
|
-
end
|
|
863
|
-
rendered_lines << ""
|
|
864
|
-
subtitle_lines_count += 1
|
|
865
|
-
end
|
|
866
|
-
|
|
867
|
-
border_color_cfg = @style_config.border
|
|
868
|
-
options_color_cfg = @style_config.options
|
|
869
|
-
focus_color_cfg = @style_config.focus
|
|
870
|
-
title_color_cfg = @style_config.title
|
|
871
|
-
|
|
872
|
-
box_border = BORDERS[@style]
|
|
873
|
-
|
|
874
|
-
total_items = @functions.length
|
|
875
|
-
overhead = banner_lines_count + subtitle_lines_count + 6
|
|
876
|
-
overhead += 2 unless active_desc.empty?
|
|
877
|
-
available_rows = [term_rows - overhead - 2, 3].max
|
|
878
|
-
|
|
879
|
-
effective_page_size = if @page_size && @page_size > 0
|
|
880
|
-
[@page_size, total_items].min
|
|
881
|
-
elsif total_items > available_rows
|
|
882
|
-
available_rows
|
|
883
|
-
else
|
|
884
|
-
total_items
|
|
885
|
-
end
|
|
886
|
-
|
|
887
|
-
start_idx = 0
|
|
888
|
-
end_idx = total_items - 1
|
|
889
|
-
if total_items > effective_page_size
|
|
890
|
-
half = effective_page_size / 2
|
|
891
|
-
start_idx = [[@index - half, 0].max, total_items - effective_page_size].min
|
|
892
|
-
end_idx = start_idx + effective_page_size - 1
|
|
893
|
-
end
|
|
894
|
-
|
|
895
|
-
visible_indices = (start_idx..end_idx).to_a
|
|
896
|
-
has_more_above = start_idx > 0
|
|
897
|
-
has_more_below = end_idx < (total_items - 1)
|
|
898
|
-
|
|
899
|
-
avail_w = [total_width - 6, 1].max
|
|
900
|
-
|
|
901
|
-
if box_border
|
|
902
|
-
h_top = box_border[:ht] || box_border[:h]
|
|
903
|
-
h_bot = box_border[:hb] || box_border[:h]
|
|
904
|
-
v_l_raw = box_border[:vl] || box_border[:v]
|
|
905
|
-
v_r_raw = box_border[:vr] || box_border[:v]
|
|
906
|
-
|
|
907
|
-
top_fill = build_horizontal_line(h_top, total_width - 2)
|
|
908
|
-
bot_fill = build_horizontal_line(h_bot, total_width - 2)
|
|
909
|
-
mid_fill = build_horizontal_line(h_top, total_width - 2)
|
|
910
|
-
|
|
911
|
-
v_left = colorize(v_l_raw, border_color_cfg)
|
|
912
|
-
v_right = colorize(v_r_raw, border_color_cfg)
|
|
913
|
-
|
|
914
|
-
top_border_line = box_border[:tl] + top_fill + box_border[:tr]
|
|
915
|
-
rendered_lines << "#{margin_left}#{colorize(top_border_line, border_color_cfg)}"
|
|
916
|
-
|
|
917
|
-
unless @title.empty?
|
|
918
|
-
centered_title = colorize(@title.center(total_width - 4), title_color_cfg)
|
|
919
|
-
rendered_lines << "#{margin_left}#{v_left} #{centered_title} #{v_right}"
|
|
920
|
-
|
|
921
|
-
separator_line = v_l_raw + mid_fill + v_r_raw
|
|
922
|
-
rendered_lines << "#{margin_left}#{colorize(separator_line, border_color_cfg)}"
|
|
923
|
-
end
|
|
924
|
-
|
|
925
|
-
if has_more_above
|
|
926
|
-
up_indicator = colorize("▲ (+#{start_idx} arriba)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
927
|
-
rendered_lines << "#{margin_left}#{v_left} #{up_indicator} #{v_right}"
|
|
928
|
-
end
|
|
929
|
-
|
|
930
|
-
visible_indices.each do |current_index|
|
|
931
|
-
option_name = all_names[current_index]
|
|
932
|
-
if @index == current_index
|
|
933
|
-
highlighted_text = colorize("> #{option_name.ljust(avail_w)}", focus_color_cfg)
|
|
934
|
-
rendered_lines << "#{margin_left}#{v_left} #{highlighted_text} #{v_right}"
|
|
935
|
-
else
|
|
936
|
-
normal_text = colorize(" #{option_name.ljust(avail_w)}", options_color_cfg)
|
|
937
|
-
rendered_lines << "#{margin_left}#{v_left} #{normal_text} #{v_right}"
|
|
938
|
-
end
|
|
939
|
-
end
|
|
940
|
-
|
|
941
|
-
if has_more_below
|
|
942
|
-
remaining_below = total_items - 1 - end_idx
|
|
943
|
-
down_indicator = colorize("▼ (+#{remaining_below} abajo)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
944
|
-
rendered_lines << "#{margin_left}#{v_left} #{down_indicator} #{v_right}"
|
|
945
|
-
end
|
|
946
|
-
|
|
947
|
-
unless active_desc.empty?
|
|
948
|
-
separator_line = v_l_raw + mid_fill + v_r_raw
|
|
949
|
-
rendered_lines << "#{margin_left}#{colorize(separator_line, border_color_cfg)}"
|
|
950
|
-
desc_text = colorize("ℹ #{active_desc.ljust(avail_w)}", { color: "cyan", level: 1 })
|
|
951
|
-
rendered_lines << "#{margin_left}#{v_left} #{desc_text} #{v_right}"
|
|
952
|
-
end
|
|
953
|
-
|
|
954
|
-
bottom_border_line = box_border[:bl] + bot_fill + box_border[:br]
|
|
955
|
-
rendered_lines << "#{margin_left}#{colorize(bottom_border_line, border_color_cfg)}"
|
|
956
|
-
else
|
|
957
|
-
symbol_char = STYLES[@style] || "#"
|
|
958
|
-
solid_border = colorize(symbol_char, border_color_cfg)
|
|
959
|
-
solid_line = symbol_char * total_width
|
|
960
|
-
|
|
961
|
-
rendered_lines << "#{margin_left}#{colorize(solid_line, border_color_cfg)}"
|
|
962
|
-
|
|
963
|
-
unless @title.empty?
|
|
964
|
-
centered_title = colorize(@title.center(total_width - 4), title_color_cfg)
|
|
965
|
-
rendered_lines << "#{margin_left}#{solid_border} #{centered_title} #{solid_border}"
|
|
966
|
-
rendered_lines << "#{margin_left}#{colorize(solid_line, border_color_cfg)}"
|
|
967
|
-
end
|
|
968
|
-
|
|
969
|
-
if has_more_above
|
|
970
|
-
up_indicator = colorize("▲ (+#{start_idx} arriba)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
971
|
-
rendered_lines << "#{margin_left}#{solid_border} #{up_indicator} #{solid_border}"
|
|
972
|
-
end
|
|
973
|
-
|
|
974
|
-
visible_indices.each do |current_index|
|
|
975
|
-
option_name = all_names[current_index]
|
|
976
|
-
if @index == current_index
|
|
977
|
-
highlighted_text = colorize("> #{option_name.ljust(avail_w)}", focus_color_cfg)
|
|
978
|
-
rendered_lines << "#{margin_left}#{solid_border} #{highlighted_text} #{solid_border}"
|
|
979
|
-
else
|
|
980
|
-
normal_text = colorize(" #{option_name.ljust(avail_w)}", options_color_cfg)
|
|
981
|
-
rendered_lines << "#{margin_left}#{solid_border} #{normal_text} #{solid_border}"
|
|
982
|
-
end
|
|
983
|
-
end
|
|
984
|
-
|
|
985
|
-
if has_more_below
|
|
986
|
-
remaining_below = total_items - 1 - end_idx
|
|
987
|
-
down_indicator = colorize("▼ (+#{remaining_below} abajo)".center(avail_w + 2), { color: "gray", level: 2 })
|
|
988
|
-
rendered_lines << "#{margin_left}#{solid_border} #{down_indicator} #{solid_border}"
|
|
989
|
-
end
|
|
990
|
-
|
|
991
|
-
unless active_desc.empty?
|
|
992
|
-
rendered_lines << "#{margin_left}#{colorize(solid_line, border_color_cfg)}"
|
|
993
|
-
desc_text = colorize("ℹ #{active_desc.ljust(avail_w)}", { color: "cyan", level: 1 })
|
|
994
|
-
rendered_lines << "#{margin_left}#{solid_border} #{desc_text} #{solid_border}"
|
|
995
|
-
end
|
|
996
|
-
|
|
997
|
-
rendered_lines << "#{margin_left}#{colorize(solid_line, border_color_cfg)}"
|
|
998
|
-
end
|
|
999
|
-
|
|
1000
|
-
rendered_lines
|
|
1001
|
-
end
|
|
1002
|
-
|
|
1003
|
-
def draw(size_max: 20, min_width: nil)
|
|
1004
|
-
target_width = min_width || size_max || 20
|
|
1005
|
-
action_to_execute = nil
|
|
1006
|
-
|
|
1007
|
-
is_tty = $stdin.respond_to?(:tty?) && $stdin.tty?
|
|
1008
|
-
|
|
1009
|
-
begin
|
|
1010
|
-
Kernel.print("#{HIDE_CURSOR}#{CLEAR_SCREEN_SEQUENCE}")
|
|
1011
|
-
|
|
1012
|
-
if is_tty
|
|
1013
|
-
$stdin.raw do |raw_input_stream|
|
|
1014
|
-
action_to_execute = run_interactive_loop(raw_input_stream, target_width)
|
|
1015
|
-
end
|
|
1016
|
-
else
|
|
1017
|
-
action_to_execute = run_interactive_loop($stdin, target_width)
|
|
1018
|
-
end
|
|
1019
|
-
ensure
|
|
1020
|
-
Kernel.print(SHOW_CURSOR)
|
|
1021
|
-
end
|
|
1022
|
-
|
|
1023
|
-
if action_to_execute
|
|
1024
|
-
Kernel.print(CLEAR_SCREEN_SEQUENCE)
|
|
1025
|
-
execute_action(action_to_execute)
|
|
1026
|
-
else
|
|
1027
|
-
Kernel.print(CLEAR_SCREEN_SEQUENCE)
|
|
1028
|
-
end
|
|
1029
|
-
rescue Interrupt
|
|
1030
|
-
Kernel.print("#{SHOW_CURSOR}#{CLEAR_SCREEN_SEQUENCE}")
|
|
1031
|
-
nil
|
|
1032
|
-
end
|
|
1033
|
-
|
|
1034
|
-
private
|
|
1035
|
-
|
|
1036
|
-
def draw_frame(target_width)
|
|
1037
|
-
lines = render_lines(target_width)
|
|
1038
|
-
buffer = String.new(CURSOR_HOME)
|
|
1039
|
-
lines.each_with_index do |line, idx|
|
|
1040
|
-
buffer << line << CLEAR_TO_EOL
|
|
1041
|
-
buffer << "\r\n" if idx < lines.length - 1
|
|
1042
|
-
end
|
|
1043
|
-
buffer << CLEAR_TO_EOS
|
|
1044
|
-
Kernel.print(buffer)
|
|
1045
|
-
end
|
|
1046
|
-
|
|
1047
|
-
def run_interactive_loop(input_stream, target_width)
|
|
1048
|
-
draw_frame(target_width)
|
|
1049
|
-
|
|
1050
|
-
while (key = read_single_key(input_stream))
|
|
1051
|
-
break if key == "q" || key == "Q" || key == "\x03" || key == "\x04"
|
|
1052
|
-
|
|
1053
|
-
if key == "\e[A" || key == "\eOA" || key == "\xe0H" || key == "\x00H"
|
|
1054
|
-
move_up
|
|
1055
|
-
draw_frame(target_width)
|
|
1056
|
-
elsif key == "\e[B" || key == "\eOB" || key == "\xe0P" || key == "\x00P"
|
|
1057
|
-
move_down
|
|
1058
|
-
draw_frame(target_width)
|
|
1059
|
-
elsif key == "\r" || key == "\n"
|
|
1060
|
-
return @functions[@index]
|
|
1061
|
-
end
|
|
1062
|
-
end
|
|
1063
|
-
|
|
1064
|
-
nil
|
|
1065
|
-
end
|
|
1066
|
-
|
|
1067
|
-
def read_single_key(input_stream)
|
|
1068
|
-
unless input_stream.respond_to?(:tty?) && input_stream.tty?
|
|
1069
|
-
begin
|
|
1070
|
-
return input_stream.sysread(3) if input_stream.respond_to?(:sysread)
|
|
1071
|
-
return input_stream.read(1)
|
|
1072
|
-
rescue EOFError, Errno::EPIPE
|
|
1073
|
-
return nil
|
|
1074
|
-
end
|
|
1075
|
-
end
|
|
1076
|
-
|
|
1077
|
-
first_char = input_stream.getch
|
|
1078
|
-
return nil if first_char.nil?
|
|
1079
|
-
|
|
1080
|
-
if first_char == "\e"
|
|
1081
|
-
begin
|
|
1082
|
-
extra_chars = input_stream.read_nonblock(2)
|
|
1083
|
-
first_char << extra_chars
|
|
1084
|
-
rescue IO::WaitReadable, IO::EAGAINWaitReadable, EOFError
|
|
1085
|
-
end
|
|
1086
|
-
elsif first_char == "\x00" || first_char == "\xe0"
|
|
1087
|
-
begin
|
|
1088
|
-
second_char = input_stream.read_nonblock(1)
|
|
1089
|
-
first_char << second_char
|
|
1090
|
-
rescue IO::WaitReadable, IO::EAGAINWaitReadable, EOFError
|
|
1091
|
-
second_char = input_stream.getch rescue nil
|
|
1092
|
-
first_char << second_char if second_char
|
|
1093
|
-
end
|
|
1094
|
-
end
|
|
1095
|
-
|
|
1096
|
-
first_char
|
|
1097
|
-
rescue EOFError, Errno::EPIPE, Errno::ENOTTY
|
|
1098
|
-
nil
|
|
1099
|
-
end
|
|
1100
|
-
|
|
1101
|
-
def format_auto_name(raw_name)
|
|
1102
|
-
cleaned = raw_name.to_s.gsub(/[_-]+/, ' ').strip
|
|
1103
|
-
cleaned.split(' ').map(&:capitalize).join(' ')
|
|
1104
|
-
end
|
|
1105
|
-
|
|
1106
|
-
def extract_name_from_action(action)
|
|
1107
|
-
case action
|
|
1108
|
-
when Array
|
|
1109
|
-
action[0].to_s
|
|
1110
|
-
when Hash
|
|
1111
|
-
(action[:name] || action[:title] || action["name"] || action["title"] || "Opcion").to_s
|
|
1112
|
-
when Method
|
|
1113
|
-
format_auto_name(action.name)
|
|
1114
|
-
when Symbol
|
|
1115
|
-
format_auto_name(action)
|
|
1116
|
-
when Proc
|
|
1117
|
-
if action.respond_to?(:name) && action.name
|
|
1118
|
-
format_auto_name(action.name)
|
|
1119
|
-
else
|
|
1120
|
-
"Opcion"
|
|
1121
|
-
end
|
|
1122
|
-
else
|
|
1123
|
-
if action.respond_to?(:name)
|
|
1124
|
-
format_auto_name(action.name)
|
|
1125
|
-
elsif action.respond_to?(:title)
|
|
1126
|
-
action.title.to_s
|
|
1127
|
-
else
|
|
1128
|
-
format_auto_name(action)
|
|
1129
|
-
end
|
|
1130
|
-
end
|
|
1131
|
-
end
|
|
1132
|
-
|
|
1133
|
-
def extract_description_from_action(action)
|
|
1134
|
-
if action.is_a?(Array) && action.length >= 3
|
|
1135
|
-
action[2].to_s
|
|
1136
|
-
elsif action.is_a?(Hash)
|
|
1137
|
-
(action[:desc] || action[:description] || action["desc"] || action["description"]).to_s
|
|
1138
|
-
else
|
|
1139
|
-
""
|
|
1140
|
-
end
|
|
1141
|
-
end
|
|
1142
|
-
|
|
1143
|
-
def execute_action(action)
|
|
1144
|
-
case action
|
|
1145
|
-
when Method, Proc
|
|
1146
|
-
action.call
|
|
1147
|
-
when Symbol
|
|
1148
|
-
if Object.respond_to?(action, true)
|
|
1149
|
-
Object.send(action)
|
|
1150
|
-
elsif Kernel.respond_to?(action, true)
|
|
1151
|
-
Kernel.send(action)
|
|
1152
|
-
end
|
|
1153
|
-
when Array
|
|
1154
|
-
callable = action[1]
|
|
1155
|
-
if callable.is_a?(Symbol)
|
|
1156
|
-
if Object.respond_to?(callable, true)
|
|
1157
|
-
Object.send(callable)
|
|
1158
|
-
elsif Kernel.respond_to?(callable, true)
|
|
1159
|
-
Kernel.send(callable)
|
|
1160
|
-
end
|
|
1161
|
-
elsif callable.respond_to?(:call)
|
|
1162
|
-
callable.call
|
|
1163
|
-
end
|
|
1164
|
-
when Hash
|
|
1165
|
-
callable = action[:action] || action[:call] || action["action"] || action["call"]
|
|
1166
|
-
if callable.is_a?(Symbol)
|
|
1167
|
-
if Object.respond_to?(callable, true)
|
|
1168
|
-
Object.send(callable)
|
|
1169
|
-
elsif Kernel.respond_to?(callable, true)
|
|
1170
|
-
Kernel.send(callable)
|
|
1171
|
-
end
|
|
1172
|
-
elsif callable.respond_to?(:call)
|
|
1173
|
-
callable.call
|
|
1174
|
-
end
|
|
1175
|
-
else
|
|
1176
|
-
action.call if action.respond_to?(:call)
|
|
1177
|
-
end
|
|
1178
|
-
end
|
|
1179
|
-
end
|
|
1180
|
-
|
|
1181
|
-
Color = GRmenu::Color unless defined?(Color)
|
|
1182
|
-
Colors = GRmenu::Color unless defined?(Colors)
|
|
1183
|
-
C = GRmenu::Color unless defined?(C)
|
|
1184
|
-
Grmenu = GRmenu unless defined?(Grmenu)
|