natty-ui 0.6.0 → 0.8.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 +12 -0
- data/README.md +25 -48
- data/examples/24bit-colors.rb +23 -0
- data/examples/3bit-colors.rb +13 -0
- data/examples/8bit-colors.rb +31 -0
- data/examples/attributes.rb +41 -134
- data/examples/demo.rb +217 -0
- data/examples/illustration.png +0 -0
- data/examples/illustration.rb +26 -0
- data/examples/list_in_columns.rb +10 -17
- data/examples/progress.rb +24 -29
- data/examples/query.rb +22 -21
- data/examples/table.rb +18 -0
- data/lib/natty-ui/ansi.rb +111 -92
- data/lib/natty-ui/ansi_wrapper.rb +93 -161
- data/lib/natty-ui/features.rb +12 -19
- data/lib/natty-ui/version.rb +2 -2
- data/lib/natty-ui/wrapper/ask.rb +26 -20
- data/lib/natty-ui/wrapper/element.rb +19 -23
- data/lib/natty-ui/wrapper/framed.rb +23 -18
- data/lib/natty-ui/wrapper/heading.rb +26 -53
- data/lib/natty-ui/wrapper/horizontal_rule.rb +37 -0
- data/lib/natty-ui/wrapper/list_in_columns.rb +66 -10
- data/lib/natty-ui/wrapper/message.rb +20 -27
- data/lib/natty-ui/{mixins.rb → wrapper/mixins.rb} +2 -2
- data/lib/natty-ui/wrapper/progress.rb +11 -9
- data/lib/natty-ui/wrapper/query.rb +37 -30
- data/lib/natty-ui/wrapper/quote.rb +25 -0
- data/lib/natty-ui/wrapper/request.rb +27 -5
- data/lib/natty-ui/wrapper/section.rb +42 -40
- data/lib/natty-ui/wrapper/table.rb +298 -0
- data/lib/natty-ui/wrapper/task.rb +9 -12
- data/lib/natty-ui/wrapper.rb +117 -44
- data/lib/natty-ui.rb +48 -50
- data/lib/natty_ui.rb +3 -0
- metadata +18 -9
- data/examples/basic.rb +0 -62
@@ -7,24 +7,12 @@ module NattyUI
|
|
7
7
|
# Creates a default section and prints given arguments as lines
|
8
8
|
# into the section.
|
9
9
|
#
|
10
|
-
# @param [Array<#to_s>] args objects to print
|
10
|
+
# @param [Array<#to_s>] args optional objects to print
|
11
11
|
# @yieldparam [Wrapper::Section] section the created section
|
12
12
|
# @return [Object] the result of the code block
|
13
13
|
# @return [Wrapper::Section] itself, when no code block is given
|
14
|
-
def section(*args, &block)
|
15
|
-
_section(self, :Section, args, prefix: ' ', suffix: ' ', &block)
|
16
|
-
end
|
14
|
+
def section(*args, &block) = _section(:Section, args, prefix: ' ', &block)
|
17
15
|
alias sec section
|
18
|
-
|
19
|
-
# Creates a quotation section and prints given arguments as lines
|
20
|
-
# into the section.
|
21
|
-
#
|
22
|
-
# @param (see #section)
|
23
|
-
# @yieldparam (see #section)
|
24
|
-
# @return (see #section)
|
25
|
-
def quote(*args, &block)
|
26
|
-
_section(self, :Section, args, prefix: '▍ ', prefix_attr: 39, &block)
|
27
|
-
end
|
28
16
|
end
|
29
17
|
|
30
18
|
class Wrapper
|
@@ -34,47 +22,53 @@ module NattyUI
|
|
34
22
|
# A section can contain other elements and sections.
|
35
23
|
#
|
36
24
|
# @see Features#section
|
37
|
-
# @see Features#quote
|
38
25
|
class Section < Element
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
26
|
+
# @return [Integer] available columns count within the section
|
27
|
+
def available_width
|
28
|
+
@available_width ||=
|
29
|
+
@parent.available_width - @prefix_width - @suffix_width
|
30
|
+
end
|
43
31
|
|
44
|
-
# Print given arguments
|
45
|
-
# Optionally limit the line width to given `max_width`.
|
32
|
+
# Print given arguments line-wise into the section.
|
46
33
|
#
|
47
|
-
# @overload puts(
|
34
|
+
# @overload puts(...)
|
48
35
|
# @param [#to_s] ... objects to print
|
49
|
-
# @param [Integer, nil] max_width maximum line width
|
50
|
-
# @comment @param [#to_s, nil] prefix line prefix
|
51
|
-
# @comment @param [#to_s, nil] suffix line suffix
|
52
36
|
# @return [Section] itself
|
53
|
-
def puts(*args,
|
37
|
+
def puts(*args, **kwargs)
|
54
38
|
return self if @status
|
55
39
|
@parent.puts(
|
56
40
|
*args,
|
57
|
-
|
58
|
-
|
59
|
-
suffix:
|
41
|
+
prefix: "#{@prefix}#{kwargs[:prefix]}",
|
42
|
+
prefix_width: @prefix_width + kwargs[:prefix_width].to_i,
|
43
|
+
suffix: "#{kwargs[:suffix]}#{@suffix}",
|
44
|
+
suffix_width: @suffix_width + kwargs[:suffix_width].to_i
|
60
45
|
)
|
61
46
|
self
|
62
47
|
end
|
63
|
-
alias add puts
|
64
48
|
|
65
|
-
#
|
49
|
+
# Print given arguments into the section.
|
66
50
|
#
|
67
|
-
# @
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
51
|
+
# @overload print(...)
|
52
|
+
# @param [#to_s] ... objects to print
|
53
|
+
# @return [Section] itself
|
54
|
+
def print(*args, **kwargs)
|
55
|
+
return self if @status
|
56
|
+
@parent.print(
|
57
|
+
*args,
|
58
|
+
prefix: "#{@prefix}#{kwargs[:prefix]}",
|
59
|
+
prefix_width: @prefix_width + kwargs[:prefix_width].to_i,
|
60
|
+
suffix: "#{kwargs[:suffix]}#{@suffix}",
|
61
|
+
suffix_width: @suffix_width + kwargs[:suffix_width].to_i
|
74
62
|
)
|
75
63
|
self
|
76
64
|
end
|
77
65
|
|
66
|
+
# Add at least one empty line
|
67
|
+
#
|
68
|
+
# @param [#to_i] lines count of lines
|
69
|
+
# @return [Section] itself
|
70
|
+
def space(lines = 1) = puts("\n" * [1, lines.to_i].max)
|
71
|
+
|
78
72
|
# @note The screen manipulation is only available in ANSI mode see {#ansi?}
|
79
73
|
#
|
80
74
|
# Resets the part of the screen written below the current output line when
|
@@ -90,12 +84,20 @@ module NattyUI
|
|
90
84
|
# @return [Object] block result
|
91
85
|
def temporary = block_given? ? yield(self) : self
|
92
86
|
|
87
|
+
# @!visibility private
|
88
|
+
def inspect = @status ? "#{_to_s[..-2]} status=#{@status}>" : _to_s
|
89
|
+
|
93
90
|
protected
|
94
91
|
|
95
|
-
def initialize(
|
92
|
+
def initialize(
|
93
|
+
parent,
|
94
|
+
prefix:,
|
95
|
+
prefix_width: NattyUI.display_width(prefix)
|
96
|
+
)
|
96
97
|
super(parent)
|
97
98
|
@prefix = prefix
|
98
|
-
@
|
99
|
+
@prefix_width = prefix_width
|
100
|
+
@suffix_width = 0
|
99
101
|
end
|
100
102
|
end
|
101
103
|
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'element'
|
3
|
+
|
4
|
+
module NattyUI
|
5
|
+
module Features
|
6
|
+
#
|
7
|
+
# Table view of data.
|
8
|
+
#
|
9
|
+
# @note Tables do not support text attributes yet and are still under
|
10
|
+
# construction. This means table features are not complete defined and
|
11
|
+
# may change in near future.
|
12
|
+
#
|
13
|
+
# @overload table(type: simple)
|
14
|
+
# Construct and display a table.
|
15
|
+
#
|
16
|
+
# @param [Symbol] type frame type;
|
17
|
+
# valid types are `:simple`, `:heavy`, `:semi`, `:double`
|
18
|
+
# @yieldparam [Table] table construction helper
|
19
|
+
# @return [Wrapper::Section, Wrapper] it's parent object
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# ui.table do |table|
|
23
|
+
# table.add('name', 'price', 'origin')
|
24
|
+
# table.add('apple', '1$', 'California')
|
25
|
+
# table.add('banana', '2$', 'Brasil')
|
26
|
+
# table.add('kiwi', '1.5$', 'Newzeeland')
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # output:
|
30
|
+
# # name │ price │ origin
|
31
|
+
# # ───────┼───────┼───────────
|
32
|
+
# # apple │ 1$ │ California
|
33
|
+
# # ───────┼───────┼───────────
|
34
|
+
# # banana │ 2$ │ Brasil
|
35
|
+
# # ───────┼───────┼───────────
|
36
|
+
# # kiwi │ 1.5$ │ Newzeeland
|
37
|
+
#
|
38
|
+
# @overload table(*args, type: simple)
|
39
|
+
# Display the given arrays as rows of a table.
|
40
|
+
#
|
41
|
+
# @param [Array<#to_s>] args one or more arrays representing rows of the table
|
42
|
+
# @param [Symbol] type frame type;
|
43
|
+
# valid types are `:simple`, `:heavy`, `:semi`, `:double`
|
44
|
+
# @return [Wrapper::Section, Wrapper] it's parent object
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# ui.table(
|
48
|
+
# %w[name price origin],
|
49
|
+
# %w[apple 1$ California],
|
50
|
+
# %w[banana 2$ Brasil],
|
51
|
+
# %w[kiwi 1.5$ Newzeeland]
|
52
|
+
# )
|
53
|
+
def table(*table, type: :simple)
|
54
|
+
table = Table.new(*table)
|
55
|
+
yield(table) if block_given?
|
56
|
+
_element(:Table, table.rows, type)
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Table-like display of key/value pairs.
|
61
|
+
#
|
62
|
+
# @param [#to_s] seperator
|
63
|
+
# @param [Hash<#to_s,#to_s>] kwargs
|
64
|
+
# @return [Wrapper::Section, Wrapper] it's parent object
|
65
|
+
#
|
66
|
+
# @example
|
67
|
+
# ui.pairs(apple: '1$', banana: '2$', kiwi: '1.5$')
|
68
|
+
#
|
69
|
+
# # output:
|
70
|
+
# # apple: 1$
|
71
|
+
# # banana: 2$
|
72
|
+
# # kiwi: 1.5$
|
73
|
+
#
|
74
|
+
def pairs(seperator = ': ', **kwargs)
|
75
|
+
_element(:Pairs, Table.new(**kwargs).rows, seperator)
|
76
|
+
end
|
77
|
+
|
78
|
+
class Table
|
79
|
+
attr_reader :rows
|
80
|
+
|
81
|
+
def add_row(*columns)
|
82
|
+
@rows << columns
|
83
|
+
self
|
84
|
+
end
|
85
|
+
alias add add_row
|
86
|
+
|
87
|
+
def add_col(*columns)
|
88
|
+
columns.each_with_index do |col, row_idx|
|
89
|
+
(@rows[row_idx] ||= []) << col
|
90
|
+
end
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
def initialize(*args, **kwargs)
|
95
|
+
@rows = []
|
96
|
+
args.each { add_row(*_1) }
|
97
|
+
kwargs.each_pair { add_row(*_1) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
private_constant :Table
|
101
|
+
end
|
102
|
+
|
103
|
+
class Wrapper
|
104
|
+
# An {Element} to print a table.
|
105
|
+
#
|
106
|
+
# @see Features#table
|
107
|
+
class Table < Element
|
108
|
+
protected
|
109
|
+
|
110
|
+
def call(rows, type)
|
111
|
+
TableGenerator.each_line(
|
112
|
+
rows,
|
113
|
+
@parent.available_width - 1,
|
114
|
+
ORNAMENTS[type] ||
|
115
|
+
raise(ArgumentError, "invalid table type - #{type.inspect}"),
|
116
|
+
Ansi[39],
|
117
|
+
Ansi::RESET
|
118
|
+
) { |line| @parent.puts(line) }
|
119
|
+
@parent
|
120
|
+
end
|
121
|
+
|
122
|
+
def coloring = [nil, nil]
|
123
|
+
|
124
|
+
ORNAMENTS = {
|
125
|
+
rounded: '│─┼',
|
126
|
+
simple: '│─┼',
|
127
|
+
heavy: '┃━╋',
|
128
|
+
double: '║═╬',
|
129
|
+
semi: '║╴╫'
|
130
|
+
}.compare_by_identity.freeze
|
131
|
+
end
|
132
|
+
|
133
|
+
# An {Element} to print key/value pairs.
|
134
|
+
#
|
135
|
+
# @see Features#pairs
|
136
|
+
class Pairs < Element
|
137
|
+
protected
|
138
|
+
|
139
|
+
def call(rows, seperator)
|
140
|
+
TableGenerator.each_simple_line(
|
141
|
+
rows,
|
142
|
+
@parent.available_width - 1,
|
143
|
+
seperator,
|
144
|
+
NattyUI.plain(seperator, ansi: false)[-1] == ' '
|
145
|
+
) { |line| @parent.puts(line) }
|
146
|
+
@parent
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class TableGenerator
|
151
|
+
def self.each_line(rows, max_width, ornament, opref, osuff)
|
152
|
+
return if rows.empty?
|
153
|
+
gen = new(rows, max_width, 3)
|
154
|
+
return unless gen.ok?
|
155
|
+
last_row = 0
|
156
|
+
col_div = " #{opref}#{ornament[0]}#{osuff} "
|
157
|
+
row_div = "#{ornament[1]}#{ornament[2]}#{ornament[1]}"
|
158
|
+
row_div =
|
159
|
+
"#{opref}#{gen.widths.map { ornament[1] * _1 }.join(row_div)}#{osuff}"
|
160
|
+
gen.each do |line, number|
|
161
|
+
if last_row != number
|
162
|
+
last_row = number
|
163
|
+
yield(row_div)
|
164
|
+
end
|
165
|
+
yield(line.join(col_div))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.each_simple_line(rows, max_width, col_div, first_right)
|
170
|
+
return if rows.empty?
|
171
|
+
gen = new(rows, max_width, NattyUI.display_width(col_div))
|
172
|
+
return unless gen.ok?
|
173
|
+
gen.aligns[0] = :right if first_right
|
174
|
+
gen.each { yield(_1.join(col_div)) }
|
175
|
+
end
|
176
|
+
|
177
|
+
attr_reader :widths, :aligns
|
178
|
+
|
179
|
+
def initialize(rows, max_width, col_div_size)
|
180
|
+
@rows =
|
181
|
+
rows.map do |row|
|
182
|
+
row.map do |col|
|
183
|
+
col = NattyUI.embellish(col).each_line(chomp: true).to_a
|
184
|
+
col.empty? ? col << '' : col
|
185
|
+
end
|
186
|
+
end
|
187
|
+
@max_width = max_width
|
188
|
+
@col_div_size = col_div_size
|
189
|
+
@widths = create_widths.freeze
|
190
|
+
@aligns = Array.new(@widths.size, :left)
|
191
|
+
end
|
192
|
+
|
193
|
+
def ok? = (@widths != nil)
|
194
|
+
|
195
|
+
def each
|
196
|
+
return unless @widths
|
197
|
+
col_empty = @widths.map { ' ' * _1 }
|
198
|
+
@rows.each_with_index do |row, row_idx|
|
199
|
+
row
|
200
|
+
.max_by(&:size)
|
201
|
+
.size
|
202
|
+
.times do |line_nr|
|
203
|
+
col_idx = -1
|
204
|
+
yield(
|
205
|
+
@widths.map do |col_width|
|
206
|
+
cell = row[col_idx += 1] or next col_empty[col_idx]
|
207
|
+
next col_empty[col_idx] if (line = cell[line_nr]).nil?
|
208
|
+
align(line.to_s, col_width, @aligns[col_idx])
|
209
|
+
end,
|
210
|
+
row_idx
|
211
|
+
)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
|
218
|
+
def align(str, width, alignment)
|
219
|
+
return str unless (width -= NattyUI.display_width(str)).positive?
|
220
|
+
return str << (' ' * width) if alignment == :left
|
221
|
+
(' ' * width) << str
|
222
|
+
end
|
223
|
+
|
224
|
+
def create_widths
|
225
|
+
matrix = create_matrix
|
226
|
+
col_widths = find_col_widths(matrix)
|
227
|
+
adjusted = adjusted_widths(col_widths)
|
228
|
+
return if adjusted.empty? # nothing to draw
|
229
|
+
return adjusted if col_widths == adjusted # all fine
|
230
|
+
if (size = adjusted.size) != col_widths.size
|
231
|
+
@rows.map! { _1.take(size) }
|
232
|
+
matrix.map! { _1.take(size) }
|
233
|
+
col_widths = col_widths.take(size)
|
234
|
+
end
|
235
|
+
diff = diff(col_widths, adjusted)
|
236
|
+
@rows.each_with_index do |row, row_idx|
|
237
|
+
diff.each do |col_idx|
|
238
|
+
adjust_to = adjusted[col_idx]
|
239
|
+
next if matrix[row_idx][col_idx] <= adjust_to
|
240
|
+
ary = NattyUI.each_line(*row[col_idx], max_width: adjust_to).to_a
|
241
|
+
ary.pop if ary.last.empty?
|
242
|
+
row[col_idx] = ary
|
243
|
+
end
|
244
|
+
end
|
245
|
+
adjusted
|
246
|
+
end
|
247
|
+
|
248
|
+
def create_matrix
|
249
|
+
ret =
|
250
|
+
@rows.map do |row|
|
251
|
+
row.map { |col| col.map { NattyUI.display_width(_1) }.max }
|
252
|
+
end
|
253
|
+
cc = ret.max_by(&:size).size
|
254
|
+
ret.each { (add = cc - _1.size).nonzero? and _1.fill(0, _1.size, add) }
|
255
|
+
end
|
256
|
+
|
257
|
+
def find_col_widths(matrix)
|
258
|
+
ret = nil
|
259
|
+
matrix.each do |row|
|
260
|
+
next ret = row.dup unless ret
|
261
|
+
row.each_with_index do |size, idx|
|
262
|
+
hs = ret[idx]
|
263
|
+
ret[idx] = size if hs < size
|
264
|
+
end
|
265
|
+
end
|
266
|
+
ret
|
267
|
+
end
|
268
|
+
|
269
|
+
def adjusted_widths(col_widths)
|
270
|
+
ret = col_widths.dup
|
271
|
+
if ret.sum <=
|
272
|
+
(left = @max_width - (@col_div_size * (col_widths.size - 1)))
|
273
|
+
return ret
|
274
|
+
end
|
275
|
+
indexed = ret.each_with_index.to_a
|
276
|
+
# TODO: optimize this!
|
277
|
+
until ret.sum <= left
|
278
|
+
indexed.sort! { |b, a| (a[0] <=> b[0]).nonzero? || (a[1] <=> b[1]) }
|
279
|
+
pair = indexed[0]
|
280
|
+
next ret[pair[1]] = pair[0] if (pair[0] -= 1).nonzero?
|
281
|
+
indexed.shift
|
282
|
+
return [] if indexed.empty?
|
283
|
+
ret.pop
|
284
|
+
end
|
285
|
+
ret
|
286
|
+
end
|
287
|
+
|
288
|
+
def diff(col_widths, adjusted)
|
289
|
+
ret = []
|
290
|
+
col_widths.each_with_index do |val, idx|
|
291
|
+
ret << idx if val != adjusted[idx]
|
292
|
+
end
|
293
|
+
ret
|
294
|
+
end
|
295
|
+
end
|
296
|
+
private_constant :TableGenerator
|
297
|
+
end
|
298
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'section'
|
4
|
-
require_relative '
|
4
|
+
require_relative 'mixins'
|
5
5
|
|
6
6
|
module NattyUI
|
7
7
|
module Features
|
@@ -15,31 +15,28 @@ module NattyUI
|
|
15
15
|
# @return [Object] the result of the code block
|
16
16
|
# @return [Wrapper::Task] itself, when no code block is given
|
17
17
|
def task(title, *args, &block)
|
18
|
-
_section(
|
18
|
+
_section(:Task, args, title: title, &block)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
module TaskMethods
|
23
23
|
protected
|
24
24
|
|
25
|
-
def initialize(parent, title
|
26
|
-
@
|
27
|
-
@temp = wrapper.temporary
|
25
|
+
def initialize(parent, title:)
|
26
|
+
@temp = parent.wrapper.temporary
|
28
27
|
@final_text = [title]
|
29
|
-
super(parent, title: title,
|
28
|
+
super(parent, title: title, glyph: :task)
|
30
29
|
end
|
31
30
|
|
32
31
|
def finish
|
33
|
-
|
34
|
-
|
35
|
-
@temp.call
|
36
|
-
end
|
32
|
+
return @parent.failed(*@final_text) if failed?
|
33
|
+
@temp.call
|
37
34
|
_section(
|
38
|
-
@parent,
|
39
35
|
:Message,
|
40
36
|
@final_text,
|
37
|
+
owner: @parent,
|
41
38
|
title: @final_text.shift,
|
42
|
-
|
39
|
+
glyph: @status = :completed
|
43
40
|
)
|
44
41
|
end
|
45
42
|
end
|
data/lib/natty-ui/wrapper.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'io/console'
|
4
|
+
require_relative 'ansi'
|
4
5
|
require_relative 'wrapper/ask'
|
5
6
|
require_relative 'wrapper/framed'
|
6
7
|
require_relative 'wrapper/heading'
|
8
|
+
require_relative 'wrapper/horizontal_rule'
|
7
9
|
require_relative 'wrapper/list_in_columns'
|
8
10
|
require_relative 'wrapper/message'
|
9
11
|
require_relative 'wrapper/progress'
|
10
12
|
require_relative 'wrapper/query'
|
13
|
+
require_relative 'wrapper/quote'
|
11
14
|
require_relative 'wrapper/request'
|
12
15
|
require_relative 'wrapper/section'
|
16
|
+
require_relative 'wrapper/table'
|
13
17
|
require_relative 'wrapper/task'
|
14
18
|
|
15
19
|
module NattyUI
|
@@ -28,61 +32,60 @@ module NattyUI
|
|
28
32
|
|
29
33
|
# @attribute [r] screen_size
|
30
34
|
# @return [[Integer, Integer]] screen size as rows and columns
|
31
|
-
def screen_size
|
32
|
-
return @stream.winsize if @ws
|
33
|
-
[ENV['LINES'].to_i.nonzero? || 24, ENV['COLUMNS'].to_i.nonzero? || 80]
|
34
|
-
end
|
35
|
+
def screen_size = (@screen_size ||= determine_screen_size)
|
35
36
|
|
36
37
|
# @attribute [r] screen_rows
|
37
38
|
# @return [Integer] number of screen rows
|
38
|
-
def screen_rows
|
39
|
-
@ws ? @stream.winsize[0] : (ENV['LINES'].to_i.nonzero? || 24)
|
40
|
-
end
|
39
|
+
def screen_rows = screen_size[0]
|
41
40
|
|
42
41
|
# @attribute [r] screen_columns
|
43
42
|
# @return [Integer] number of screen columns
|
44
|
-
def screen_columns
|
45
|
-
@ws ? @stream.winsize[-1] : (ENV['COLUMNS'].to_i.nonzero? || 80)
|
46
|
-
end
|
43
|
+
def screen_columns = screen_size[1]
|
47
44
|
|
48
45
|
# @!group Tool functions
|
49
46
|
|
50
|
-
# Print given arguments
|
51
|
-
# Optionally limit the line width to given `max_width`.
|
47
|
+
# Print given arguments line-wise to the output stream.
|
52
48
|
#
|
53
|
-
# @overload puts(
|
49
|
+
# @overload puts(...)
|
54
50
|
# @param [#to_s] ... objects to print
|
55
|
-
# @param [Integer, nil] max_width maximum line width
|
56
|
-
# @comment @param [#to_s, nil] prefix line prefix
|
57
|
-
# @comment @param [#to_s, nil] suffix line suffix
|
58
51
|
# @return [Wrapper] itself
|
59
|
-
def puts(*args,
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
52
|
+
def puts(*args, **kwargs)
|
53
|
+
args = prepare_print(args, kwargs)
|
54
|
+
@lines_written += args.size
|
55
|
+
@stream.puts(args)
|
56
|
+
@stream.flush
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
# Print given arguments to the output stream.
|
61
|
+
#
|
62
|
+
# @overload print(...)
|
63
|
+
# @param [#to_s] ... objects to print
|
64
|
+
# @return [Wrapper] itself
|
65
|
+
def print(*args, **kwargs)
|
66
|
+
args = prepare_print(args, kwargs).to_a
|
67
|
+
@lines_written += args.size - 1
|
68
|
+
@stream.print(args.join("\n"))
|
69
69
|
@stream.flush
|
70
70
|
self
|
71
71
|
end
|
72
|
-
alias add puts
|
73
72
|
|
74
73
|
# Add at least one empty line
|
75
74
|
#
|
76
75
|
# @param [#to_i] lines count of lines
|
77
76
|
# @return [Wrapper] itself
|
78
77
|
def space(lines = 1)
|
79
|
-
lines = [lines.to_i
|
80
|
-
@stream << ("\n" * lines)
|
78
|
+
lines = [1, lines.to_i].max
|
79
|
+
(@stream << ("\n" * lines)).flush
|
81
80
|
@lines_written += lines
|
82
|
-
@stream.flush
|
83
81
|
self
|
84
82
|
end
|
85
83
|
|
84
|
+
# Clear Screen
|
85
|
+
#
|
86
|
+
# @return [Wrapper] itself
|
87
|
+
def cls = self
|
88
|
+
|
86
89
|
# @note The screen manipulation is only available in ANSI mode see {#ansi?}
|
87
90
|
#
|
88
91
|
# Saves current screen, deletes all screen content and moves the cursor
|
@@ -137,11 +140,47 @@ module NattyUI
|
|
137
140
|
# @!visibility private
|
138
141
|
alias inspect to_s
|
139
142
|
|
143
|
+
# @attribute [r] wrapper
|
144
|
+
# @return [Wrapper] self
|
145
|
+
alias wrapper itself
|
146
|
+
|
147
|
+
# @!visibility private
|
148
|
+
alias available_width screen_columns
|
149
|
+
|
150
|
+
# @!visibility private
|
151
|
+
def prefix = ''
|
152
|
+
|
153
|
+
# @return [Array<Symbol>] available glyph names
|
154
|
+
def glyph_names = GLYPHS.keys
|
155
|
+
|
156
|
+
#
|
157
|
+
# Get a pre-defined glyph
|
158
|
+
#
|
159
|
+
# @param [Symbol] name glyph name
|
160
|
+
# @return [String] the named glyph
|
161
|
+
# @return [nil] when glyph is not defined
|
162
|
+
def glyph(name) = GLYPHS[name]
|
163
|
+
|
140
164
|
protected
|
141
165
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
166
|
+
def prepare_print(args, kwargs)
|
167
|
+
prefix = kwargs[:prefix] and prefix = NattyUI.plain(prefix, ansi: false)
|
168
|
+
suffix = kwargs[:suffix] and suffix = NattyUI.plain(suffix, ansi: false)
|
169
|
+
return ["#{prefix}#{suffix}"] if args.empty?
|
170
|
+
NattyUI
|
171
|
+
.each_line(
|
172
|
+
*args.map! { NattyUI.plain(_1, ansi: false) },
|
173
|
+
max_width: max_with(prefix, suffix, kwargs)
|
174
|
+
)
|
175
|
+
.map { "#{prefix}#{_1}#{suffix}" }
|
176
|
+
end
|
177
|
+
|
178
|
+
def max_with(prefix, suffix, kwargs)
|
179
|
+
mw = kwargs[:max_width] and return mw
|
180
|
+
mw = screen_columns
|
181
|
+
mw -= kwargs[:prefix_width] || NattyUI.display_width(prefix) if prefix
|
182
|
+
mw -= kwargs[:suffix_width] || NattyUI.display_width(suffix) if suffix
|
183
|
+
mw
|
145
184
|
end
|
146
185
|
|
147
186
|
def temp_func
|
@@ -154,21 +193,55 @@ module NattyUI
|
|
154
193
|
def initialize(stream)
|
155
194
|
@stream = stream
|
156
195
|
@lines_written = 0
|
157
|
-
@ws = stream.respond_to?(:winsize) && stream.winsize&.all?(&:positive?)
|
158
|
-
rescue Errno::ENOTTY
|
159
|
-
@ws = false
|
160
196
|
end
|
161
197
|
|
162
|
-
|
163
|
-
def prefix = nil
|
164
|
-
alias suffix prefix
|
198
|
+
private_class_method :new
|
165
199
|
|
166
|
-
|
167
|
-
alias suffix_width prefix_width
|
168
|
-
alias width prefix_width
|
200
|
+
private
|
169
201
|
|
170
|
-
|
202
|
+
def determine_screen_size
|
203
|
+
return @stream.winsize if @ws
|
204
|
+
if @ws.nil?
|
205
|
+
ret = try_fetch_winsize
|
206
|
+
if ret
|
207
|
+
@ws = true
|
208
|
+
Signal.trap('WINCH') { @screen_size = nil }
|
209
|
+
return ret
|
210
|
+
end
|
211
|
+
@ws = false
|
212
|
+
end
|
213
|
+
[ENV['LINES'].to_i.nonzero? || 24, ENV['COLUMNS'].to_i.nonzero? || 80]
|
214
|
+
end
|
171
215
|
|
172
|
-
|
216
|
+
def try_fetch_winsize
|
217
|
+
return unless @stream.respond_to?(:winsize)
|
218
|
+
ret = @stream.winsize
|
219
|
+
ret&.all?(&:positive?) ? ret : nil
|
220
|
+
rescue SystemCallError
|
221
|
+
nil
|
222
|
+
end
|
223
|
+
|
224
|
+
GLYPHS = {
|
225
|
+
default: "#{Ansi[:bold, 255]}•#{Ansi::RESET}",
|
226
|
+
information: "#{Ansi[:bold, 119]}𝒊#{Ansi::RESET}",
|
227
|
+
warning: "#{Ansi[:bold, 221]}!#{Ansi::RESET}",
|
228
|
+
error: "#{Ansi[:bold, 208]}𝙓#{Ansi::RESET}",
|
229
|
+
completed: "#{Ansi[:bold, 82]}✓#{Ansi::RESET}",
|
230
|
+
failed: "#{Ansi[:bold, 196]}𝑭#{Ansi::RESET}",
|
231
|
+
task: "#{Ansi[:bold, 39]}➔#{Ansi::RESET}",
|
232
|
+
query: "#{Ansi[:bold, 39]}▸#{Ansi::RESET}"
|
233
|
+
}.compare_by_identity.freeze
|
234
|
+
|
235
|
+
# GLYPHS = {
|
236
|
+
# default: '●',
|
237
|
+
# information: '🅸 ',
|
238
|
+
# warning: '🆆 ',
|
239
|
+
# error: '🅴 ',
|
240
|
+
# completed: '✓',
|
241
|
+
# failed: '🅵 ',
|
242
|
+
# task: '➔',
|
243
|
+
# query: '🆀 '
|
244
|
+
# }.compare_by_identity.freeze
|
245
|
+
private_constant :GLYPHS
|
173
246
|
end
|
174
247
|
end
|