natty-ui 0.5.3 → 0.7.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/.yardopts +9 -0
- data/README.md +3 -5
- data/examples/attributes.rb +161 -0
- data/examples/basic.rb +8 -9
- data/examples/illustration.png +0 -0
- data/examples/list_in_columns.rb +43 -0
- data/examples/progress.rb +45 -61
- data/examples/query.rb +34 -23
- data/lib/natty-ui/ansi.rb +69 -60
- data/lib/natty-ui/ansi_wrapper.rb +68 -32
- data/lib/natty-ui/features.rb +36 -0
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui/wrapper/ask.rb +4 -4
- data/lib/natty-ui/wrapper/element.rb +6 -3
- data/lib/natty-ui/wrapper/list_in_columns.rb +81 -0
- data/lib/natty-ui/wrapper/mixins.rb +3 -3
- data/lib/natty-ui/wrapper/progress.rb +13 -8
- data/lib/natty-ui/wrapper/query.rb +7 -7
- data/lib/natty-ui/wrapper/request.rb +43 -0
- data/lib/natty-ui/wrapper/section.rb +4 -4
- data/lib/natty-ui/wrapper/task.rb +3 -5
- data/lib/natty-ui/wrapper.rb +12 -7
- data/lib/natty-ui.rb +22 -18
- data/lib/natty_ui.rb +3 -0
- metadata +11 -6
- data/examples/colors.rb +0 -7
- data/examples/illustration.svg +0 -1
- data/lib/natty-ui/wrapper/features.rb +0 -20
data/lib/natty-ui/ansi.rb
CHANGED
@@ -60,17 +60,23 @@ module NattyUI
|
|
60
60
|
def cursor_left(columns = nil) = "\e[#{columns}D"
|
61
61
|
|
62
62
|
# @param lines [Integer] number of lines
|
63
|
-
# @return [String] ANSI code to move the cursor to beginning of the line
|
63
|
+
# @return [String] ANSI code to move the cursor to beginning of the line
|
64
|
+
# some lines down
|
64
65
|
def cursor_line_down(lines = nil) = "\e[#{lines}E"
|
65
66
|
|
66
67
|
# @param lines [Integer] number of lines
|
67
|
-
# @return [String] ANSI code to move the cursor to beginning of the line
|
68
|
+
# @return [String] ANSI code to move the cursor to beginning of the line
|
69
|
+
# some lines up
|
68
70
|
def cursor_line_up(lines = nil) = "\e[#{lines}F"
|
69
71
|
|
70
72
|
# @param columns [Integer] number of columns
|
71
|
-
# @return [String] ANSI code to move the cursor to
|
73
|
+
# @return [String] ANSI code to move the cursor to given column
|
72
74
|
def cursor_column(columns = nil) = "\e[#{columns}G"
|
73
75
|
|
76
|
+
# @return [String] ANSI code positioning the cursor on right hand side of
|
77
|
+
# the terminal
|
78
|
+
def cursor_right_aligned = "\e[9999G\e[D\e[C"
|
79
|
+
|
74
80
|
# @return [String] ANSI code to hide the cursor
|
75
81
|
def cursor_hide = "\e[?25l"
|
76
82
|
|
@@ -107,6 +113,14 @@ module NattyUI
|
|
107
113
|
attributes.empty? ? "#{obj}" : "#{attributes}#{obj}#{"\e[0m" if reset}"
|
108
114
|
end
|
109
115
|
|
116
|
+
# Remove ANSI attribtes from given string.
|
117
|
+
#
|
118
|
+
# @see embellish
|
119
|
+
#
|
120
|
+
# @param str [#to_s] string to be modified
|
121
|
+
# @return [String] string without ANSI attributes
|
122
|
+
def blemish(str) = str.to_s.gsub(/(\x1b\[(?~[a-zA-Z])[a-zA-Z])/, '')
|
123
|
+
|
110
124
|
# Combine given ANSI `attributes`.
|
111
125
|
#
|
112
126
|
# ANSI attribute names are:
|
@@ -119,55 +133,57 @@ module NattyUI
|
|
119
133
|
# `fraktur_off`, `underline_off`, `blink_off`, `proportional`, `spacing`,
|
120
134
|
# `invert_off`, `reverse_off`, `reveal`, `strike_off`, `proportional_off`,
|
121
135
|
# `spacing_off`, `framed`, `encircled`, `overlined`, `framed_off`,
|
122
|
-
# `encircled_off`, `overlined_off
|
136
|
+
# `encircled_off`, `overlined_off`.
|
123
137
|
#
|
124
|
-
# Colors can specified by their name for ANSI 4-bit colors:
|
138
|
+
# Colors can specified by their name for ANSI 3-bit and 4-bit colors:
|
125
139
|
# `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`,
|
126
|
-
# `default`, `bright_black`, `bright_red`, `bright_green`,
|
127
|
-
# `bright_blue`, `bright_magenta`, `bright_cyan`,
|
140
|
+
# `default`, `bright_black`, `bright_red`, `bright_green`,
|
141
|
+
# `bright_yellow`, `bright_blue`, `bright_magenta`, `bright_cyan`,
|
142
|
+
# `bright_white`.
|
128
143
|
#
|
129
|
-
# For 8-bit ANSI colors
|
130
|
-
# `i0`...`i255`.
|
144
|
+
# For 8-bit ANSI colors use 2-digit hexadecimal values `00`...`ff`.
|
131
145
|
#
|
132
|
-
# To use RGB ANSI colors
|
133
|
-
#
|
146
|
+
# To use RGB ANSI colors (24-bit colors) specify 3-digit or 6-digit
|
147
|
+
# hexadecimal values `000`...`fff` or `000000`...`ffffff`.
|
148
|
+
# This represent the `RRGGBB` values (or `RGB` for short version) like you
|
149
|
+
# may known from CSS color notation.
|
134
150
|
#
|
135
151
|
# To use a color as background color prefix the color attribute with `bg_`
|
136
152
|
# or `on_`.
|
137
153
|
#
|
138
154
|
# To use a color as underline color prefix the color attribute with `ul_`.
|
139
155
|
#
|
140
|
-
# To make it more clear a color attribute
|
141
|
-
# color the
|
156
|
+
# To make it more clear a color attribute have to be used as foreground
|
157
|
+
# color the color value can be prefixed with `fg_`.
|
142
158
|
#
|
143
159
|
# @example Valid Foreground Color Attributes
|
144
160
|
# Ansi[:yellow]
|
145
|
-
# Ansi[
|
146
|
-
# Ansi[
|
161
|
+
# Ansi['#fab']
|
162
|
+
# Ansi['#00aa00']
|
147
163
|
# Ansi[:fg_fab]
|
148
164
|
# Ansi[:fg_00aa00]
|
149
|
-
# Ansi[:
|
150
|
-
# Ansi[:
|
165
|
+
# Ansi[:af]
|
166
|
+
# Ansi[:fg_af]
|
151
167
|
#
|
152
168
|
# @example Valid Background Color Attributes
|
153
169
|
# Ansi[:bg_yellow]
|
154
170
|
# Ansi[:bg_fab]
|
155
171
|
# Ansi[:bg_00aa00]
|
156
172
|
# Ansi['bg#00aa00']
|
157
|
-
# Ansi[:
|
173
|
+
# Ansi[:bg_af]
|
158
174
|
#
|
159
175
|
# Ansi[:on_yellow]
|
160
176
|
# Ansi[:on_fab]
|
161
177
|
# Ansi[:on_00aa00]
|
162
178
|
# Ansi['on#00aa00']
|
163
|
-
# Ansi[:
|
179
|
+
# Ansi[:on_af]
|
164
180
|
#
|
165
181
|
# @example Valid Underline Color Attributes
|
166
|
-
# Ansi[:underline, :
|
182
|
+
# Ansi[:underline, :ul_yellow]
|
167
183
|
# Ansi[:underline, :ul_fab]
|
168
184
|
# Ansi[:underline, :ul_00aa00]
|
169
185
|
# Ansi[:underline, 'ul#00aa00']
|
170
|
-
# Ansi[:underline, :
|
186
|
+
# Ansi[:underline, :ul_fa]
|
171
187
|
# Ansi[:underline, :ul_bright_yellow]
|
172
188
|
#
|
173
189
|
# @example Combined attributes:
|
@@ -182,7 +198,7 @@ module NattyUI
|
|
182
198
|
.map do |arg|
|
183
199
|
case arg
|
184
200
|
when Symbol, String
|
185
|
-
ATTRIBUTES[arg] ||
|
201
|
+
ATTRIBUTES[arg] || color(arg) || invalid_argument(arg)
|
186
202
|
when (0..255)
|
187
203
|
"38;5;#{arg}"
|
188
204
|
when (256..512)
|
@@ -211,11 +227,10 @@ module NattyUI
|
|
211
227
|
# @return [String] combined ANSI attributes
|
212
228
|
# @return [nil] when string does not contain valid attributes
|
213
229
|
def try_convert(attributes)
|
214
|
-
attributes = attributes.to_s.split
|
215
|
-
return if attributes.empty?
|
230
|
+
return if (attributes = attributes.to_s.split).empty?
|
216
231
|
"\e[#{
|
217
232
|
attributes
|
218
|
-
.map { |arg| ATTRIBUTES[arg] ||
|
233
|
+
.map { |arg| ATTRIBUTES[arg] || color(arg) || return }
|
219
234
|
.join(';')
|
220
235
|
}m"
|
221
236
|
end
|
@@ -230,41 +245,37 @@ module NattyUI
|
|
230
245
|
)
|
231
246
|
end
|
232
247
|
|
233
|
-
def
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
248
|
+
def color(val)
|
249
|
+
val = val.to_s.downcase
|
250
|
+
base =
|
251
|
+
if val.delete_prefix!('fg')
|
252
|
+
val.delete_prefix!(':') || val.delete_prefix!('_')
|
253
|
+
'38;'
|
254
|
+
elsif val.delete_prefix!('ul')
|
255
|
+
val.delete_prefix!(':') || val.delete_prefix!('_')
|
256
|
+
'58;'
|
257
|
+
elsif val.delete_prefix!('bg') || val.delete_prefix!('on')
|
258
|
+
val.delete_prefix!(':') || val.delete_prefix!('_')
|
259
|
+
'48;'
|
260
|
+
else
|
261
|
+
'38;'
|
262
|
+
end
|
263
|
+
val.delete_prefix!('#')
|
264
|
+
case val.size
|
265
|
+
when 2
|
266
|
+
"#{base}5;#{val.hex}" if /\A[[:xdigit:]]+\z/.match?(val)
|
267
|
+
when 3
|
268
|
+
if /\A[[:xdigit:]]+\z/.match?(val)
|
269
|
+
"#{base}2;#{(val[0] * 2).hex};#{(val[1] * 2).hex};#{
|
270
|
+
(val[2] * 2).hex
|
271
|
+
}"
|
272
|
+
end
|
273
|
+
when 6
|
274
|
+
if /\A[[:xdigit:]]+\z/.match?(val)
|
275
|
+
"#{base}2;#{val[0, 2].hex};#{val[2, 2].hex};#{val[4, 2].hex}"
|
276
|
+
end
|
253
277
|
end
|
254
278
|
end
|
255
|
-
|
256
|
-
def number(base, str)
|
257
|
-
index = str.to_i
|
258
|
-
"#{base};5;#{index}" if index >= 0 && index <= 255
|
259
|
-
end
|
260
|
-
|
261
|
-
def hex_rgb_short(base, str)
|
262
|
-
"#{base};2;#{(str[0] * 2).hex};#{(str[1] * 2).hex};#{(str[2] * 2).hex}"
|
263
|
-
end
|
264
|
-
|
265
|
-
def hex_rgb(base, str)
|
266
|
-
"#{base};2;#{str[0, 2].hex};#{str[2, 2].hex};#{str[4, 2].hex}"
|
267
|
-
end
|
268
279
|
end
|
269
280
|
|
270
281
|
ATTRIBUTES =
|
@@ -282,7 +293,6 @@ module NattyUI
|
|
282
293
|
rapid_blink: 6,
|
283
294
|
# ---
|
284
295
|
invert: 7,
|
285
|
-
reverse: 7,
|
286
296
|
# ---
|
287
297
|
conceal: 8,
|
288
298
|
hide: 8,
|
@@ -319,7 +329,6 @@ module NattyUI
|
|
319
329
|
spacing: 26,
|
320
330
|
# ---
|
321
331
|
invert_off: 27,
|
322
|
-
reverse_off: 27,
|
323
332
|
# ---
|
324
333
|
reveal: 28,
|
325
334
|
# ---
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'io/console'
|
4
|
-
require_relative 'wrapper'
|
5
3
|
require_relative 'ansi'
|
4
|
+
require_relative 'wrapper'
|
6
5
|
|
7
6
|
module NattyUI
|
8
7
|
class AnsiWrapper < Wrapper
|
@@ -23,10 +22,7 @@ module NattyUI
|
|
23
22
|
|
24
23
|
protected
|
25
24
|
|
26
|
-
def embellish(obj)
|
27
|
-
obj = NattyUI.embellish(obj)
|
28
|
-
obj.empty? ? nil : obj
|
29
|
-
end
|
25
|
+
def embellish(obj) = (obj = NattyUI.embellish(obj)).empty? ? nil : obj
|
30
26
|
|
31
27
|
def temp_func
|
32
28
|
count = @lines_written
|
@@ -69,6 +65,7 @@ module NattyUI
|
|
69
65
|
task: 117
|
70
66
|
}.compare_by_identity.freeze
|
71
67
|
end
|
68
|
+
private_constant :Message
|
72
69
|
|
73
70
|
class Section < Section
|
74
71
|
def temporary
|
@@ -97,6 +94,7 @@ module NattyUI
|
|
97
94
|
@prefix = Ansi.embellish(@prefix, *prefix_attr)
|
98
95
|
end
|
99
96
|
end
|
97
|
+
private_constant :Section
|
100
98
|
|
101
99
|
class Heading < Heading
|
102
100
|
protected
|
@@ -109,20 +107,52 @@ module NattyUI
|
|
109
107
|
PREFIX = Ansi[39].freeze
|
110
108
|
MSG = Ansi[:bold, 231].freeze
|
111
109
|
end
|
110
|
+
private_constant :Heading
|
112
111
|
|
113
|
-
class Framed <
|
112
|
+
class Framed < Section
|
114
113
|
protected
|
115
114
|
|
115
|
+
def initialize(parent, title:, type:, **opts)
|
116
|
+
@parent = parent
|
117
|
+
title = title.to_s.tr("\r\n\t", '')
|
118
|
+
topl, topr, botl, botr, hor, vert = *components(type)
|
119
|
+
width = available_width
|
120
|
+
rcount = [width - _plain_width(title) - 6, 0].max
|
121
|
+
parent.puts(
|
122
|
+
"#{COLOR}#{topl}#{hor}#{hor}#{Ansi.reset} " \
|
123
|
+
"#{TITLE_ATTR}#{title}#{Ansi.reset} " \
|
124
|
+
"#{COLOR}#{hor * rcount}#{topr}#{Ansi.reset}"
|
125
|
+
)
|
126
|
+
@bottom = "#{COLOR}#{botl}#{hor * (width - 2)}#{botr}#{Ansi.reset}"
|
127
|
+
vert = "#{COLOR}#{vert}#{Ansi.reset}"
|
128
|
+
super(
|
129
|
+
parent,
|
130
|
+
prefix: "#{vert} ",
|
131
|
+
suffix:
|
132
|
+
"#{Ansi.cursor_right_aligned}" \
|
133
|
+
"#{Ansi.cursor_left(suffix_width)}#{vert}",
|
134
|
+
**opts
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
def suffix = "#{super} "
|
139
|
+
def finish = parent.puts(@bottom)
|
140
|
+
|
116
141
|
def components(type)
|
117
|
-
|
118
|
-
[
|
119
|
-
"#{Ansi[39]}#{top_start}#{Ansi[:bold, 231]}",
|
120
|
-
"#{Ansi[:reset, 39]}#{top_suffix}#{Ansi.reset}",
|
121
|
-
Ansi.embellish(left, 39),
|
122
|
-
Ansi.embellish(bottom, 39)
|
123
|
-
]
|
142
|
+
COMPONENTS[type] || raise(ArgumentError, "invalid frame type - #{type}")
|
124
143
|
end
|
144
|
+
|
145
|
+
COLOR = Ansi[39].freeze
|
146
|
+
TITLE_ATTR = Ansi[:bold, 231].freeze
|
147
|
+
COMPONENTS = {
|
148
|
+
rounded: %w[╭ ╮ ╰ ╯ ─ │],
|
149
|
+
simple: %w[┌ ┐ └ ┘ ─ │],
|
150
|
+
heavy: %w[┏ ┓ ┗ ┛ ━ ┃],
|
151
|
+
semi: %w[┍ ┑ ┕ ┙ ━ │],
|
152
|
+
double: %w[╔ ╗ ╚ ╝ ═ ║]
|
153
|
+
}.compare_by_identity.freeze
|
125
154
|
end
|
155
|
+
private_constant :Framed
|
126
156
|
|
127
157
|
class Ask < Ask
|
128
158
|
protected
|
@@ -135,6 +165,16 @@ module NattyUI
|
|
135
165
|
|
136
166
|
PREFIX = "#{Ansi[:bold, :italic, 220]}▶︎#{Ansi[:reset, 220]}".freeze
|
137
167
|
end
|
168
|
+
private_constant :Ask
|
169
|
+
|
170
|
+
class Request < Request
|
171
|
+
def prompt(question) = "#{prefix}#{PREFIX} #{question}#{Ansi.reset} "
|
172
|
+
def finish = (wrapper.stream << FINISH).flush
|
173
|
+
|
174
|
+
PREFIX = "#{Ansi[:bold, :italic, 220]}▶︎#{Ansi[:reset, 220]}".freeze
|
175
|
+
FINISH = (Ansi.cursor_line_up + Ansi.line_erase_to_end).freeze
|
176
|
+
end
|
177
|
+
private_constant :Request
|
138
178
|
|
139
179
|
class Query < Query
|
140
180
|
protected
|
@@ -146,47 +186,41 @@ module NattyUI
|
|
146
186
|
|
147
187
|
PROMPT = Ansi.embellish(':', :bold, 220).freeze
|
148
188
|
end
|
189
|
+
private_constant :Query
|
149
190
|
|
150
191
|
class Task < Message
|
151
192
|
include ProgressAttributes
|
152
193
|
include TaskMethods
|
153
194
|
end
|
195
|
+
private_constant :Task
|
154
196
|
|
155
197
|
class Progress < Progress
|
156
198
|
protected
|
157
199
|
|
158
|
-
def
|
200
|
+
def draw(title)
|
159
201
|
@prefix = "#{prefix}#{TITLE_PREFIX}#{title}#{Ansi.reset} "
|
160
|
-
(wrapper.stream << @prefix).flush
|
202
|
+
(wrapper.stream << @prefix << Ansi.cursor_hide).flush
|
161
203
|
@prefix = "#{Ansi.line_clear}#{@prefix}"
|
162
|
-
if @max_value
|
163
|
-
|
164
|
-
|
165
|
-
@prefix << INDICATOR_ATTRIBUTE
|
166
|
-
@indicator = 0
|
167
|
-
end
|
204
|
+
return @prefix << BAR_COLOR if @max_value
|
205
|
+
@prefix << INDICATOR_ATTRIBUTE
|
206
|
+
@indicator = 0
|
168
207
|
end
|
169
208
|
|
170
|
-
def draw_final = (wrapper.stream << Ansi.line_clear).flush
|
171
|
-
|
172
209
|
def redraw
|
173
210
|
(wrapper.stream << @prefix << (@max_value ? fullbar : indicator)).flush
|
174
211
|
end
|
175
212
|
|
213
|
+
def end_draw = (wrapper.stream << ERASE).flush
|
176
214
|
def indicator = '─╲│╱'[(@indicator += 1) % 4]
|
215
|
+
# def indicator = '⣷⣯⣟⡿⢿⣻⣽⣾'[(@indicator += 1) % 8]
|
177
216
|
|
178
217
|
def fullbar
|
179
218
|
percent = @value / @max_value
|
180
219
|
count = (30 * percent).to_i
|
220
|
+
mv = max_value.to_i.to_s
|
181
221
|
"#{'█' * count}#{BAR_BACK}#{'▁' * (30 - count)}" \
|
182
|
-
"#{BAR_INK} #{
|
183
|
-
|
184
|
-
'%<value>.0f/%<max_value>.0f (%<percent>.2f%%)',
|
185
|
-
value: @value,
|
186
|
-
max_value: @max_value,
|
187
|
-
percent: percent * 100
|
188
|
-
)
|
189
|
-
}"
|
222
|
+
"#{BAR_INK} #{value.to_i.to_s.rjust(mv.size)}/#{mv} " \
|
223
|
+
"(#{(percent * 100).round(2).to_s.rjust(6)})"
|
190
224
|
end
|
191
225
|
|
192
226
|
TITLE_PREFIX = "#{Ansi[:bold, :italic, 117]}➔#{Ansi[:reset, 117]} ".freeze
|
@@ -194,7 +228,9 @@ module NattyUI
|
|
194
228
|
BAR_COLOR = Ansi[39, 295].freeze
|
195
229
|
BAR_BACK = Ansi[236, 492].freeze
|
196
230
|
BAR_INK = Ansi[:bold, 255, :on_default].freeze
|
231
|
+
ERASE = (Ansi.line_clear + Ansi.cursor_show).freeze
|
197
232
|
end
|
233
|
+
private_constant :Progress
|
198
234
|
|
199
235
|
PAGE_BEGIN =
|
200
236
|
"#{Ansi.reset}#{Ansi.cursor_save_pos}#{Ansi.screen_save}" \
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NattyUI
|
4
|
+
#
|
5
|
+
# Features of {NattyUI} - methods to display natty elements.
|
6
|
+
#
|
7
|
+
module Features
|
8
|
+
# Print a horizontal rule
|
9
|
+
#
|
10
|
+
# @param [#to_s] symbol string to build the horizontal rule
|
11
|
+
# @return [Wrapper, Wrapper::Element] itself
|
12
|
+
def hr(symbol = '═')
|
13
|
+
symbol = symbol.to_s
|
14
|
+
size = _plain_width(symbol)
|
15
|
+
return self if size.zero?
|
16
|
+
msg = symbol * ((available_width - 1) / size)
|
17
|
+
return puts(msg, prefix: Ansi[39], suffix: Ansi.reset) if wrapper.ansi?
|
18
|
+
puts(msg)
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def _plain_width(str) = NattyUI.display_width(NattyUI.plain(str))
|
24
|
+
def _blemish_width(str) = NattyUI.display_width(Ansi.blemish(str))
|
25
|
+
|
26
|
+
def _element(type, ...)
|
27
|
+
wrapper.class.const_get(type).__send__(:new, self).__send__(:_call, ...)
|
28
|
+
end
|
29
|
+
|
30
|
+
def _section(owner, type, args, **opts, &block)
|
31
|
+
sec = wrapper.class.const_get(type).__send__(:new, owner, **opts)
|
32
|
+
sec.puts(*args) if args && !args.empty?
|
33
|
+
block ? sec.__send__(:_call, &block) : sec
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/natty-ui/version.rb
CHANGED
data/lib/natty-ui/wrapper/ask.rb
CHANGED
@@ -17,7 +17,7 @@ module NattyUI
|
|
17
17
|
# when true
|
18
18
|
# sec.info('Yeah!!')
|
19
19
|
# when false
|
20
|
-
# sec.write("That's
|
20
|
+
# sec.write("That's pity!")
|
21
21
|
# else
|
22
22
|
# sec.failed('You should have an opinion!')
|
23
23
|
# end
|
@@ -59,7 +59,7 @@ module NattyUI
|
|
59
59
|
def read(yes, no)
|
60
60
|
while true
|
61
61
|
char = NattyUI.in_stream.getch
|
62
|
-
return if "\
|
62
|
+
return if "\3\4\e".include?(char)
|
63
63
|
return true if yes.include?(char)
|
64
64
|
return false if no.include?(char)
|
65
65
|
end
|
@@ -67,9 +67,9 @@ module NattyUI
|
|
67
67
|
|
68
68
|
def grab(yes, no)
|
69
69
|
yes = yes.to_s.chars.uniq
|
70
|
+
raise(ArgumentError, ':yes can not be empty') if yes.empty?
|
70
71
|
no = no.to_s.chars.uniq
|
71
|
-
raise(ArgumentError, ':
|
72
|
-
raise(ArgumentError, ':no can not be emoty') if no.empty?
|
72
|
+
raise(ArgumentError, ':no can not be empty') if no.empty?
|
73
73
|
return yes, no if (yes & no).empty?
|
74
74
|
raise(ArgumentError, 'chars in :yes and :no can not be intersect')
|
75
75
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'features'
|
3
|
+
require_relative '../features'
|
4
4
|
|
5
5
|
module NattyUI
|
6
6
|
class Wrapper
|
@@ -36,7 +36,10 @@ module NattyUI
|
|
36
36
|
protected
|
37
37
|
|
38
38
|
def prefix = "#{@parent.__send__(:prefix)}#{@prefix}"
|
39
|
-
def suffix = "#{@parent.__send__(:suffix)}
|
39
|
+
def suffix = "#{@suffix}#{@parent.__send__(:suffix)}"
|
40
|
+
def prefix_width = _blemish_width(prefix)
|
41
|
+
def suffix_width = _blemish_width(suffix)
|
42
|
+
def available_width = wrapper.screen_columns - prefix_width - suffix_width
|
40
43
|
def finish = nil
|
41
44
|
|
42
45
|
def wrapper
|
@@ -46,7 +49,7 @@ module NattyUI
|
|
46
49
|
@wrapper
|
47
50
|
end
|
48
51
|
|
49
|
-
def initialize(parent) = (@parent = parent)
|
52
|
+
def initialize(parent, **_) = (@parent = parent)
|
50
53
|
|
51
54
|
def _close(state)
|
52
55
|
return self if @status
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'element'
|
4
|
+
|
5
|
+
module NattyUI
|
6
|
+
module Features
|
7
|
+
#
|
8
|
+
# Print items of a given list as columns.
|
9
|
+
# In the default compact format columns may have diffrent widths and the
|
10
|
+
# list items are ordered column-wise.
|
11
|
+
# The non-compact format prints all columns in same width and order the list
|
12
|
+
# items row-wise.
|
13
|
+
#
|
14
|
+
# @param [Array<#to_s>] args items to print
|
15
|
+
# @param [Boolean] compact whether to use compact format
|
16
|
+
# @return [Wrapper, Wrapper::Element] itself
|
17
|
+
def ls(*args, compact: true) = _element(:ListInColumns, args, compact)
|
18
|
+
end
|
19
|
+
|
20
|
+
class Wrapper
|
21
|
+
#
|
22
|
+
# An {Element} to print items of a given list as columns.
|
23
|
+
#
|
24
|
+
# @see Features#ls
|
25
|
+
class ListInColumns < Element
|
26
|
+
protected
|
27
|
+
|
28
|
+
def _call(list, compact)
|
29
|
+
list.flatten!
|
30
|
+
return parent if list.empty?
|
31
|
+
list.map! { |item| Item.new(item = item.to_s, _plain_width(item)) }
|
32
|
+
if compact
|
33
|
+
each_compacted(list, available_width) { |line| parent.puts(line) }
|
34
|
+
else
|
35
|
+
each(list, available_width) { |line| parent.puts(line) }
|
36
|
+
end
|
37
|
+
parent
|
38
|
+
end
|
39
|
+
|
40
|
+
def each(list, max_width)
|
41
|
+
width = list.max_by(&:width).width + 3
|
42
|
+
list.each_slice(max_width / width) do |slice|
|
43
|
+
yield(slice.map { |item| item.to_s(width) }.join)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def each_compacted(list, max_width)
|
48
|
+
found, widths = find_columns(list, max_width)
|
49
|
+
fill(found[-1], found[0].size)
|
50
|
+
found.transpose.each do |row|
|
51
|
+
row = row.each_with_index.map { |item, col| item&.to_s(widths[col]) }
|
52
|
+
yield(row.join)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_columns(list, max_width)
|
57
|
+
found = [list]
|
58
|
+
widths = [list.max_by(&:width).width]
|
59
|
+
1.upto(list.size - 1) do |slice_size|
|
60
|
+
candidate = list.each_slice(list.size / slice_size).to_a
|
61
|
+
cwidths = candidate.map { |ary| ary.max_by(&:width).width + 3 }
|
62
|
+
cwidths[-1] -= 3
|
63
|
+
break if cwidths.sum > max_width
|
64
|
+
found = candidate
|
65
|
+
widths = cwidths
|
66
|
+
end
|
67
|
+
[found, widths]
|
68
|
+
end
|
69
|
+
|
70
|
+
def fill(ary, size)
|
71
|
+
diff = size - ary.size
|
72
|
+
ary.fill(nil, ary.size, diff) if diff.positive?
|
73
|
+
end
|
74
|
+
|
75
|
+
Item =
|
76
|
+
Data.define(:str, :width) do
|
77
|
+
def to_s(in_width) = "#{str}#{' ' * (in_width - width)}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -12,7 +12,7 @@ module NattyUI
|
|
12
12
|
#
|
13
13
|
module ProgressAttributes
|
14
14
|
# @attribute [r] completed?
|
15
|
-
# @return [Boolean] whether the task completed
|
15
|
+
# @return [Boolean] whether the task completed successfully
|
16
16
|
def completed? = (@status == :completed)
|
17
17
|
|
18
18
|
# @attribute [r] failed?
|
@@ -49,9 +49,9 @@ module NattyUI
|
|
49
49
|
redraw
|
50
50
|
end
|
51
51
|
|
52
|
-
#
|
52
|
+
# Maximum value.
|
53
53
|
#
|
54
|
-
# @return [Float]
|
54
|
+
# @return [Float] maximum value
|
55
55
|
# @return [nil] when no max_value was configured
|
56
56
|
attr_reader :max_value
|
57
57
|
|
@@ -29,17 +29,17 @@ module NattyUI
|
|
29
29
|
|
30
30
|
protected
|
31
31
|
|
32
|
-
def initialize(parent, title:, max_value:, **
|
33
|
-
super(parent)
|
32
|
+
def initialize(parent, title:, max_value:, **opts)
|
33
|
+
super(parent, **opts)
|
34
34
|
@final_text = [title]
|
35
35
|
@max_value = [0, max_value.to_f].max if max_value
|
36
36
|
@value = 0
|
37
37
|
@progress = 0
|
38
|
-
|
38
|
+
draw(title)
|
39
39
|
end
|
40
40
|
|
41
|
-
def
|
42
|
-
def
|
41
|
+
def draw(title) = (wrapper.stream << prefix << "➔ #{title} ").flush
|
42
|
+
def end_draw = (wrapper.stream << "\n")
|
43
43
|
|
44
44
|
def redraw
|
45
45
|
return (wrapper.stream << '.').flush unless @max_value
|
@@ -50,10 +50,15 @@ module NattyUI
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def finish
|
53
|
-
|
53
|
+
end_draw
|
54
54
|
return @parent.failed(*@final_text) if failed?
|
55
|
-
|
56
|
-
|
55
|
+
_section(
|
56
|
+
@parent,
|
57
|
+
:Message,
|
58
|
+
@final_text,
|
59
|
+
title: @final_text.shift,
|
60
|
+
symbol: @status = :completed
|
61
|
+
)
|
57
62
|
end
|
58
63
|
end
|
59
64
|
end
|