natty-ui 0.10.0 → 0.11.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 +4 -11
- data/examples/24bit-colors.rb +8 -16
- data/examples/3bit-colors.rb +1 -1
- data/examples/8bit-colors.rb +15 -11
- data/examples/animate.rb +31 -11
- data/examples/attributes.rb +16 -14
- data/examples/attributes_list.rb +14 -0
- data/examples/demo.rb +4 -4
- data/examples/illustration.rb +52 -24
- data/examples/ls.rb +4 -4
- data/examples/message.rb +11 -9
- data/examples/progress.rb +2 -2
- data/examples/query.rb +1 -1
- data/examples/read_key.rb +3 -3
- data/examples/table.rb +23 -23
- data/lib/natty-ui/animation/binary.rb +37 -0
- data/lib/natty-ui/animation/default.rb +42 -0
- data/lib/natty-ui/animation/matrix.rb +55 -0
- data/lib/natty-ui/animation/rainbow.rb +28 -0
- data/lib/natty-ui/animation/type_writer.rb +44 -0
- data/lib/natty-ui/animation.rb +69 -0
- data/lib/natty-ui/ansi/constants.rb +1 -1
- data/lib/natty-ui/ansi.rb +46 -26
- data/lib/natty-ui/ansi_wrapper.rb +44 -51
- data/lib/natty-ui/key_map.rb +72 -49
- data/lib/natty-ui/preload.rb +1 -1
- data/lib/natty-ui/text.rb +76 -103
- data/lib/natty-ui/version.rb +1 -1
- data/lib/natty-ui/wrapper/animate.rb +1 -1
- data/lib/natty-ui/wrapper/element.rb +18 -10
- data/lib/natty-ui/wrapper/features.rb +0 -8
- data/lib/natty-ui/wrapper/framed.rb +4 -4
- data/lib/natty-ui/wrapper/list_in_columns.rb +1 -1
- data/lib/natty-ui/wrapper/section.rb +18 -18
- data/lib/natty-ui/wrapper/table.rb +432 -171
- data/lib/natty-ui/wrapper.rb +37 -20
- data/lib/natty-ui.rb +14 -10
- metadata +10 -8
- data/lib/natty-ui/line_animation/default.rb +0 -36
- data/lib/natty-ui/line_animation/matrix.rb +0 -29
- data/lib/natty-ui/line_animation/rainbow.rb +0 -31
- data/lib/natty-ui/line_animation/type_writer.rb +0 -45
- data/lib/natty-ui/line_animation.rb +0 -49
@@ -7,43 +7,15 @@ module NattyUI
|
|
7
7
|
#
|
8
8
|
# Table view of data.
|
9
9
|
#
|
10
|
-
# @note Tables do not support text attributes yet and are still under
|
11
|
-
# construction. This means table features are not complete defined and
|
12
|
-
# may change in near future.
|
13
|
-
#
|
14
10
|
# Defined values for `type` are
|
15
11
|
# :double, :heavy, :semi, :simple
|
16
12
|
#
|
17
|
-
# @overload table(type: simple)
|
18
|
-
# Construct and display a table.
|
19
|
-
#
|
20
|
-
# @param [Symbol] type frame type
|
21
|
-
# @yieldparam [Table] table construction helper
|
22
|
-
# @return [Wrapper::Section, Wrapper] it's parent object
|
23
|
-
#
|
24
|
-
# @example
|
25
|
-
# ui.table do |table|
|
26
|
-
# table.add('name', 'price', 'origin')
|
27
|
-
# table.add('apple', '1$', 'California')
|
28
|
-
# table.add('banana', '2$', 'Brasil')
|
29
|
-
# table.add('kiwi', '1.5$', 'Newzeeland')
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# # output:
|
33
|
-
# # name │ price │ origin
|
34
|
-
# # ───────┼───────┼───────────
|
35
|
-
# # apple │ 1$ │ California
|
36
|
-
# # ───────┼───────┼───────────
|
37
|
-
# # banana │ 2$ │ Brasil
|
38
|
-
# # ───────┼───────┼───────────
|
39
|
-
# # kiwi │ 1.5$ │ Newzeeland
|
40
|
-
#
|
41
|
-
# @overload table(*args, type: simple)
|
13
|
+
# @overload table(*args, type: simple, expand: false)
|
42
14
|
# Display the given arrays as rows of a table.
|
43
15
|
#
|
44
|
-
# @param [
|
16
|
+
# @param [#map<#map<#to_s>>] args one or more arrays representing rows of the table
|
45
17
|
# @param [Symbol] type frame type
|
46
|
-
# @
|
18
|
+
# @param [false, true. :equal] expand
|
47
19
|
#
|
48
20
|
# @example
|
49
21
|
# ui.table(
|
@@ -52,10 +24,45 @@ module NattyUI
|
|
52
24
|
# %w[banana 2$ Brasil],
|
53
25
|
# %w[kiwi 1.5$ Newzeeland]
|
54
26
|
# )
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
27
|
+
#
|
28
|
+
# # name │ price │ origin
|
29
|
+
# # ───────┼───────┼───────────
|
30
|
+
# # apple │ 1$ │ California
|
31
|
+
# # ───────┼───────┼───────────
|
32
|
+
# # banana │ 2$ │ Brasil
|
33
|
+
# # ───────┼───────┼───────────
|
34
|
+
# # kiwi │ 1.5$ │ Newzeeland
|
35
|
+
#
|
36
|
+
# @overload table(type: simple, expand: false)
|
37
|
+
# Construct and display a table.
|
38
|
+
#
|
39
|
+
# @param [Symbol] type frame type
|
40
|
+
# @param [false, true. :equal] expand
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# ui.table(type: :heavy, expand: true) do |table|
|
44
|
+
# table.add('name', 'price', 'origin', style: 'bold green')
|
45
|
+
# table.add('apple', '1$', 'California')
|
46
|
+
# table.add('banana', '2$', 'Brasil')
|
47
|
+
# table.add('kiwi', '1.5$', 'Newzeeland')
|
48
|
+
# table.align_column(0, :right).align_row(0, :center)
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# # name ┃ price ┃ origin
|
52
|
+
# # ━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
53
|
+
# # apple ┃ 1$ ┃ California
|
54
|
+
# # ━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
55
|
+
# # banana ┃ 2$ ┃ Brasil
|
56
|
+
# # ━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
57
|
+
# # kiwi ┃ 1.5$ ┃ Newzeeland
|
58
|
+
#
|
59
|
+
# @yield [Table] table construction helper
|
60
|
+
# @return [Wrapper::Section, Wrapper] it's parent object
|
61
|
+
def table(*table, type: :simple, expand: false)
|
62
|
+
type = NattyUI.frame(type)
|
63
|
+
table = Table.create(*table)
|
64
|
+
yield(table) if block_given?
|
65
|
+
_element(:Table, table, type, expand)
|
59
66
|
end
|
60
67
|
|
61
68
|
#
|
@@ -74,28 +81,280 @@ module NattyUI
|
|
74
81
|
# # kiwi: 1.5$
|
75
82
|
#
|
76
83
|
def pairs(seperator = ': ', **kwargs)
|
77
|
-
_element(:Pairs, kwargs
|
84
|
+
kwargs.empty? ? self : _element(:Pairs, kwargs, seperator)
|
78
85
|
end
|
79
86
|
|
87
|
+
#
|
88
|
+
# Helper class to define a table layout.
|
89
|
+
# @see #table
|
90
|
+
#
|
80
91
|
class Table
|
81
|
-
|
92
|
+
# @!visibility private
|
93
|
+
def self.create(*table)
|
94
|
+
table = table[0] if table.size == 1 && table[0].respond_to?(:each)
|
95
|
+
return table if table.is_a?(self.class)
|
96
|
+
ret = new
|
97
|
+
table.each { ret.add_row(*_1) }
|
98
|
+
ret
|
99
|
+
end
|
100
|
+
|
101
|
+
# @!visibility private
|
102
|
+
def initialize = @rows = []
|
103
|
+
|
104
|
+
# @return [Integer] count of rows
|
105
|
+
def row_count = @rows.size
|
106
|
+
|
107
|
+
# @return [Integer] count of columns
|
108
|
+
def col_count = @rows.max_by { _1&.size || 0 }&.size || 0
|
82
109
|
|
83
|
-
|
84
|
-
|
110
|
+
# Get the {Cell} at a table position.
|
111
|
+
#
|
112
|
+
# @param [Integer] row row index
|
113
|
+
# @param [Integer] col column index
|
114
|
+
# @return [Cell] at row/column
|
115
|
+
# @return [nil] if no cell defined at row/column
|
116
|
+
def [](row, col) = @rows.dig(row, col)
|
117
|
+
|
118
|
+
# Change {Cell} or (text) value at a table position.
|
119
|
+
#
|
120
|
+
# @example change {Cell} at row 2, column 3
|
121
|
+
# table[2, 3] = table.cell('Hello World', align: right, style: 'bold')
|
122
|
+
# @example change text at row 2, column 3
|
123
|
+
# table[2, 3] = 'Hello Ruby!'
|
124
|
+
# @example delete {Cell} at row 2, column 3
|
125
|
+
# table[2, 3] = nil
|
126
|
+
#
|
127
|
+
# @param [Integer] row row index
|
128
|
+
# @param [Integer] col column index
|
129
|
+
# @param [Cell, #to_s, nil] value Cell or text to use at specified position
|
130
|
+
# @return [Cell, #to_s, nil] the value
|
131
|
+
def []=(row, col, value)
|
132
|
+
row = (@rows[row] ||= [])
|
133
|
+
if value.nil? || value.is_a?(Cell)
|
134
|
+
row[col] = value
|
135
|
+
else
|
136
|
+
cell = row[col]
|
137
|
+
cell ? cell.value = value : row[col] = Cell.new(value, nil, nil)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Create a new cell.
|
142
|
+
#
|
143
|
+
# @example create a {Cell} with right aligned bold text "Hello World"
|
144
|
+
# table[2, 3] = table.cell('Hello World', align: right, style: 'bold')
|
145
|
+
#
|
146
|
+
# @param [#to_s] value text value
|
147
|
+
# @param [:left, :right, :center] align text alignment
|
148
|
+
# @param [String] style text style; see {Ansi.try_convert}
|
149
|
+
# @return [Cell] a new cell
|
150
|
+
def cell(value, align: :left, style: nil) = Cell.new(value, align, style)
|
151
|
+
|
152
|
+
# Add a new row to the table.
|
153
|
+
#
|
154
|
+
# @example add a row with three right-aligned columns
|
155
|
+
# table.add_row('One', 'Two', 'Three', align: :right)
|
156
|
+
#
|
157
|
+
# @param [#map] columns Enumerable-like object containing column texts
|
158
|
+
# @param [:left, :right, :center] align text alignment
|
159
|
+
# @param [String] style text style; see {Ansi.try_convert}
|
160
|
+
# @return [Table] itself
|
161
|
+
def add_row(*columns, align: nil, style: nil)
|
162
|
+
if columns.size == 1 && columns[0].respond_to?(:map)
|
163
|
+
columns = columns[0]
|
164
|
+
end
|
165
|
+
columns =
|
166
|
+
columns.map { |cell| as_cell(cell, align, style) if cell }.to_a
|
167
|
+
@rows << (columns.empty? ? nil : columns)
|
85
168
|
self
|
86
169
|
end
|
87
170
|
alias add add_row
|
88
171
|
|
89
|
-
|
90
|
-
|
91
|
-
|
172
|
+
# Add a new column to the table.
|
173
|
+
#
|
174
|
+
# @example add a column of three rows with bold styled text
|
175
|
+
# table.add_column('One', 'Two', 'Three', style: :bold)
|
176
|
+
#
|
177
|
+
# @param [#map] rows Enumerable-like object containing texts for each row
|
178
|
+
# @param [:left, :right, :center] align text alignment
|
179
|
+
# @param [String] style text style; see {Ansi.try_convert}
|
180
|
+
# @return [Table] itself
|
181
|
+
def add_column(*rows, align: nil, style: nil)
|
182
|
+
rows = rows[0] if rows.size == 1 && rows[0].respond_to?(:map)
|
183
|
+
row_idx = -1
|
184
|
+
rows.each do |cell|
|
185
|
+
(@rows[row_idx += 1] ||= []) << as_cell(cell, align, style)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Change style of one or more rows.
|
190
|
+
#
|
191
|
+
# @example define bold red text style for the first row
|
192
|
+
# table.style_row(0, 'bold red')
|
193
|
+
# @example define yellow text style for the first three rows
|
194
|
+
# table.style_row(0..2, 'yellow')
|
195
|
+
# @example define green text style for rows 3, 4 and 7
|
196
|
+
# table.style_row([3, 4, 7], 'green')
|
197
|
+
#
|
198
|
+
# @param [Integer, Enumerable<Integer>] row index of row(s) to change
|
199
|
+
# @param [String, nil] style text style; see {Ansi.try_convert}
|
200
|
+
# @return [Table] itself
|
201
|
+
def style_row(row, style)
|
202
|
+
if row.is_a?(Integer)
|
203
|
+
row = [row]
|
204
|
+
elsif !row.is_a?(Enumerable)
|
205
|
+
raise(TypeError, "invalid row value - #{row}")
|
206
|
+
end
|
207
|
+
row.each { |r| @rows[r]&.each { _1&.style = style } }
|
208
|
+
self
|
209
|
+
end
|
210
|
+
|
211
|
+
# Change style of one or more columns.
|
212
|
+
#
|
213
|
+
# @example define bold red text style for the first column
|
214
|
+
# table.style_column(0, 'bold red')
|
215
|
+
# @example define yellow text style for the first three columns
|
216
|
+
# table.style_column(0..2, 'yellow')
|
217
|
+
# @example define green text style for columns with index 3, 4 and 7
|
218
|
+
# table.style_column([3, 4, 7], 'green')
|
219
|
+
#
|
220
|
+
# @param [Integer, Enumerable<Integer>] column index of column(s) to change
|
221
|
+
# @param [String, nil] style text style; see {Ansi.try_convert}
|
222
|
+
# @return [Table] itself
|
223
|
+
def style_column(column, style)
|
224
|
+
if column.is_a?(Integer)
|
225
|
+
column = [column]
|
226
|
+
elsif !column.is_a?(Enumerable)
|
227
|
+
raise(TypeError, "invalid column value - #{column}")
|
228
|
+
end
|
229
|
+
@rows.each { |row| column.each { row[_1]&.style = style } }
|
230
|
+
self
|
231
|
+
end
|
232
|
+
|
233
|
+
# Change text alignment of one or more rows.
|
234
|
+
#
|
235
|
+
# @example align first row right
|
236
|
+
# table.align_row(0, :right)
|
237
|
+
# @example center first three rows
|
238
|
+
# table.align_row(0..2, :center)
|
239
|
+
# @example center the rows with index 3, 4 and 7
|
240
|
+
# table.align_row([3, 4, 7], :center)
|
241
|
+
#
|
242
|
+
# @param [Integer, Enumerable<Integer>] row index of row(s) to change
|
243
|
+
# @param [:left, :right, :center] alignment
|
244
|
+
# @return [Table] itself
|
245
|
+
def align_row(row, alignment)
|
246
|
+
if row.is_a?(Integer)
|
247
|
+
row = [row]
|
248
|
+
elsif !row.is_a?(Enumerable)
|
249
|
+
raise(TypeError, "invalid row value - #{row}")
|
250
|
+
end
|
251
|
+
row.each { |r| @rows[r]&.each { _1&.align = alignment } }
|
252
|
+
self
|
253
|
+
end
|
254
|
+
|
255
|
+
# Change text alignment of one or more column.
|
256
|
+
#
|
257
|
+
# @example align first column right
|
258
|
+
# table.align_column(0, :right)
|
259
|
+
# @example center first three columns
|
260
|
+
# table.align_column(0..2, :center)
|
261
|
+
# @example center the columns with index 3, 4 and 7
|
262
|
+
# table.align_column([3, 4, 7], :center)
|
263
|
+
#
|
264
|
+
# @param [Integer, Enumerable<Integer>] column index of column(s) to change
|
265
|
+
# @param [:left, :right, :center] alignment
|
266
|
+
# @return [Table] itself
|
267
|
+
def align_column(column, alignment)
|
268
|
+
if column.is_a?(Integer)
|
269
|
+
column = [column]
|
270
|
+
elsif !column.is_a?(Enumerable)
|
271
|
+
raise(TypeError, "invalid column value - #{column}")
|
92
272
|
end
|
273
|
+
@rows.each { |row| column.each { row[_1]&.align = alignment } }
|
93
274
|
self
|
94
275
|
end
|
95
276
|
|
96
|
-
|
277
|
+
# Convert the table to the compactest (two-dimensional) array
|
278
|
+
# representation.
|
279
|
+
#
|
280
|
+
# @return [Array<Array<Cell>>]
|
281
|
+
def to_a
|
282
|
+
ret = []
|
283
|
+
ridx = -1
|
284
|
+
@rows.each do |row|
|
285
|
+
ridx += 1
|
286
|
+
next unless row
|
287
|
+
count = 0
|
288
|
+
row =
|
289
|
+
row.map do |cell|
|
290
|
+
next unless cell
|
291
|
+
next if cell.value.empty?
|
292
|
+
count += 1
|
293
|
+
cell.dup
|
294
|
+
end
|
295
|
+
ret[ridx] = row if count.positive?
|
296
|
+
end
|
297
|
+
ret
|
298
|
+
end
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
def as_cell(value, align, style)
|
303
|
+
return Cell.new(value, align, style) unless value.is_a?(Cell)
|
304
|
+
cell = value.dup
|
305
|
+
cell.align = align if align
|
306
|
+
cell.style = style if style
|
307
|
+
cell
|
308
|
+
end
|
309
|
+
|
310
|
+
def initialize_copy(*)
|
311
|
+
super
|
312
|
+
@rows = to_a
|
313
|
+
end
|
314
|
+
|
315
|
+
class Cell
|
316
|
+
# @return [String, nil] text value of the cell
|
317
|
+
attr_reader :value
|
318
|
+
|
319
|
+
attr_writer :align, :style
|
320
|
+
|
321
|
+
# @!visibility private
|
322
|
+
attr_accessor :tag
|
323
|
+
|
324
|
+
# @!visibility private
|
325
|
+
def initialize(value, align, style)
|
326
|
+
@value = value.to_s
|
327
|
+
@align = align
|
328
|
+
@style = style
|
329
|
+
end
|
330
|
+
|
331
|
+
# @attribute [r] align
|
332
|
+
# @return [:left, :right, :center] text alignment
|
333
|
+
def align = ALIGNMENT[@align]
|
334
|
+
|
335
|
+
# @attribute [r] style
|
336
|
+
# @return [String, nil] text style; see {Ansi.try_convert}
|
337
|
+
def style
|
338
|
+
@style_ ||= Ansi.try_convert(@style)
|
339
|
+
end
|
340
|
+
|
341
|
+
def value=(value)
|
342
|
+
@value = value.to_s
|
343
|
+
end
|
344
|
+
|
345
|
+
private
|
346
|
+
|
347
|
+
def initialize_copy(*)
|
348
|
+
super
|
349
|
+
@value = @value.dup
|
350
|
+
@tag = nil
|
351
|
+
end
|
352
|
+
|
353
|
+
alignment = { left: :left, right: :right, center: :center }
|
354
|
+
alignment.default = :left
|
355
|
+
ALIGNMENT = alignment.compare_by_identity.freeze
|
356
|
+
end
|
97
357
|
end
|
98
|
-
private_constant :Table
|
99
358
|
end
|
100
359
|
|
101
360
|
class Wrapper
|
@@ -105,27 +364,15 @@ module NattyUI
|
|
105
364
|
class Table < Element
|
106
365
|
protected
|
107
366
|
|
108
|
-
def call(
|
109
|
-
|
110
|
-
|
367
|
+
def call(table, frame, enlarge)
|
368
|
+
TableGen.each_line(
|
369
|
+
table.to_a,
|
111
370
|
@parent.available_width - 1,
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
Ansi::RESET
|
116
|
-
) { @parent.puts(_1) }
|
371
|
+
frame,
|
372
|
+
enlarge
|
373
|
+
) { |line| @parent.puts(line) }
|
117
374
|
@parent
|
118
375
|
end
|
119
|
-
|
120
|
-
def coloring = [nil, nil]
|
121
|
-
|
122
|
-
ORNAMENTS = {
|
123
|
-
rounded: '│─┼',
|
124
|
-
simple: '│─┼',
|
125
|
-
heavy: '┃━╋',
|
126
|
-
double: '║═╬',
|
127
|
-
semi: '║╴╫'
|
128
|
-
}.compare_by_identity.freeze
|
129
376
|
end
|
130
377
|
|
131
378
|
# An {Element} to print key/value pairs.
|
@@ -134,160 +381,174 @@ module NattyUI
|
|
134
381
|
class Pairs < Element
|
135
382
|
protected
|
136
383
|
|
137
|
-
def call(
|
138
|
-
|
139
|
-
|
384
|
+
def call(kwargs, seperator)
|
385
|
+
TableGen.each_line_simple(
|
386
|
+
kwargs.map do |k, v|
|
387
|
+
[
|
388
|
+
Features::Table::Cell.new(k, :right, nil),
|
389
|
+
Features::Table::Cell.new(v, :left, nil)
|
390
|
+
]
|
391
|
+
end,
|
140
392
|
@parent.available_width - 1,
|
141
|
-
seperator
|
142
|
-
Text.plain(seperator)[-1] == ' '
|
393
|
+
seperator
|
143
394
|
) { @parent.puts(_1) }
|
144
395
|
@parent
|
145
396
|
end
|
146
397
|
end
|
147
398
|
|
148
|
-
class
|
149
|
-
|
150
|
-
|
151
|
-
|
399
|
+
class TableGen
|
400
|
+
COLOR = Ansi[39]
|
401
|
+
|
402
|
+
def self.each_line(table, max_width, frame, expand)
|
403
|
+
gen = new(table, max_width, 3, expand)
|
152
404
|
return unless gen.ok?
|
153
|
-
|
154
|
-
|
155
|
-
row_div = "#{ornament[1]}#{ornament[2]}#{ornament[1]}"
|
405
|
+
col_div = "#{Ansi::RESET} #{COLOR}#{frame[4]}#{Ansi::RESET} "
|
406
|
+
row_div = "#{frame[5]}#{frame[6]}#{frame[5]}"
|
156
407
|
row_div =
|
157
|
-
"#{
|
158
|
-
|
408
|
+
"#{COLOR}#{
|
409
|
+
gen.widths.map { frame[5] * _1 }.join(row_div)
|
410
|
+
}#{Ansi::RESET}"
|
411
|
+
last_row = 0
|
412
|
+
gen.each do |number, line|
|
159
413
|
if last_row != number
|
160
414
|
last_row = number
|
161
415
|
yield(row_div)
|
162
416
|
end
|
163
|
-
yield(line.join(col_div))
|
417
|
+
yield(line.join(col_div) + Ansi::RESET)
|
164
418
|
end
|
165
419
|
end
|
166
420
|
|
167
|
-
def self.
|
168
|
-
|
169
|
-
gen = new(rows, max_width, Text.width(col_div))
|
421
|
+
def self.each_line_simple(table, max_width, seperator)
|
422
|
+
gen = new(table, max_width, Text.width(seperator), false)
|
170
423
|
return unless gen.ok?
|
171
|
-
|
172
|
-
gen.each { yield(
|
424
|
+
col_div = "#{COLOR}#{seperator}#{Ansi::RESET}"
|
425
|
+
gen.each { yield(_2.join(col_div)) }
|
173
426
|
end
|
174
427
|
|
175
|
-
attr_reader :widths
|
428
|
+
attr_reader :widths
|
176
429
|
|
177
|
-
def initialize(
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
col = Text.embellish(col).each_line(chomp: true).to_a
|
182
|
-
col.empty? ? col << '' : col
|
183
|
-
end
|
184
|
-
end
|
430
|
+
def initialize(table, max_width, coldiv_size, expand)
|
431
|
+
return if coldiv_size > max_width # wtf
|
432
|
+
@table = table
|
433
|
+
@col_count = table.max_by { _1&.size || 0 }&.size || 0
|
185
434
|
@max_width = max_width
|
186
|
-
@
|
187
|
-
|
188
|
-
|
435
|
+
@widths = determine_widths
|
436
|
+
sum = @widths.sum
|
437
|
+
space = @max_width - ((@col_count - 1) * coldiv_size)
|
438
|
+
if space < sum
|
439
|
+
@widths = reduce_widths(sum, space, coldiv_size)
|
440
|
+
adjust!
|
441
|
+
elsif expand && sum < space
|
442
|
+
if expand == :equal
|
443
|
+
equal_widths(space)
|
444
|
+
else
|
445
|
+
enlarge_widths(sum, space)
|
446
|
+
end
|
447
|
+
adjust!
|
448
|
+
end
|
449
|
+
@empties = @widths.map { ' ' * _1 }
|
189
450
|
end
|
190
451
|
|
191
|
-
def ok? =
|
452
|
+
def ok? = @empties != nil
|
192
453
|
|
193
454
|
def each
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
row
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
455
|
+
ridx = -1
|
456
|
+
@table.each do |row|
|
457
|
+
ridx += 1
|
458
|
+
next yield(ridx, @empties) unless row
|
459
|
+
line_count = row.map { _1 ? _1.tag.lines.size : 0 }.max
|
460
|
+
line_count.times do |lidx|
|
461
|
+
cidx = -1
|
462
|
+
yield(
|
463
|
+
ridx,
|
464
|
+
@empties.map do |spacer|
|
465
|
+
cell = row[cidx += 1] or next spacer
|
466
|
+
str, str_size = cell.tag.lines[lidx]
|
467
|
+
next spacer unless str_size
|
468
|
+
str_fmt(cell.align, cell.style, str, str_size, spacer.size)
|
469
|
+
end
|
470
|
+
)
|
471
|
+
end
|
211
472
|
end
|
473
|
+
nil
|
212
474
|
end
|
213
475
|
|
214
476
|
private
|
215
477
|
|
216
|
-
def align
|
217
|
-
return str unless (
|
218
|
-
return
|
219
|
-
|
478
|
+
def str_fmt(align, style, str, str_size, size)
|
479
|
+
return "#{style}#{str}" unless (size -= str_size).positive?
|
480
|
+
return "#{style}#{' ' * size}#{str}" if align == :right
|
481
|
+
if align == :center
|
482
|
+
right = size / 2
|
483
|
+
return "#{style}#{' ' * (size - right)}#{str}#{' ' * right}"
|
484
|
+
end
|
485
|
+
"#{style}#{str}#{' ' * size}"
|
220
486
|
end
|
221
487
|
|
222
|
-
def
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
end
|
233
|
-
diff = diff(col_widths, adjusted)
|
234
|
-
@rows.each_with_index do |row, row_idx|
|
235
|
-
diff.each do |col_idx|
|
236
|
-
adjust_to = adjusted[col_idx]
|
237
|
-
next if matrix[row_idx][col_idx] <= adjust_to
|
238
|
-
row[col_idx] = Text.as_embellished_lines_min(
|
239
|
-
row[col_idx],
|
240
|
-
adjust_to
|
241
|
-
)
|
488
|
+
def adjust!
|
489
|
+
@table.each do |row|
|
490
|
+
next unless row
|
491
|
+
cidx = -1
|
492
|
+
row.each do |cell|
|
493
|
+
cidx += 1
|
494
|
+
next unless cell
|
495
|
+
width = @widths[cidx]
|
496
|
+
next if cell.tag.value <= width
|
497
|
+
cell.tag.lines = Text.as_lines([cell.value], width)
|
242
498
|
end
|
243
499
|
end
|
244
|
-
adjusted
|
245
500
|
end
|
246
501
|
|
247
|
-
def
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
502
|
+
def enlarge_widths(sum, space)
|
503
|
+
@widths.map! { ((((100.0 * _1) / sum) * space) / 100).round }
|
504
|
+
return if (diff = space - @widths.sum).zero?
|
505
|
+
@widths[
|
506
|
+
@widths.index(diff.negative? ? @widths.max : @widths.min)
|
507
|
+
] += diff
|
252
508
|
end
|
253
509
|
|
254
|
-
def
|
255
|
-
|
256
|
-
|
257
|
-
next ret = row.dup unless ret
|
258
|
-
row.each_with_index do |size, idx|
|
259
|
-
hs = ret[idx]
|
260
|
-
ret[idx] = size if hs < size
|
261
|
-
end
|
262
|
-
end
|
263
|
-
ret
|
510
|
+
def equal_widths(space)
|
511
|
+
@widths = Array.new(@widths.size, space / @widths.size)
|
512
|
+
@widths[-1] += space - @widths.sum
|
264
513
|
end
|
265
514
|
|
266
|
-
def
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
515
|
+
def reduce_widths(sum, space, coldiv_size)
|
516
|
+
ws = @widths.dup
|
517
|
+
until sum <= space
|
518
|
+
max = ws.max
|
519
|
+
if max == 1
|
520
|
+
ws = @widths.take(ws.size - 1)
|
521
|
+
sum = ws.sum
|
522
|
+
space += coldiv_size
|
523
|
+
else
|
524
|
+
while (idx = ws.rindex(max)) && (sum > space)
|
525
|
+
ws[idx] -= 1
|
526
|
+
sum -= 1
|
527
|
+
end
|
528
|
+
end
|
279
529
|
end
|
280
|
-
|
530
|
+
ws
|
281
531
|
end
|
282
532
|
|
283
|
-
def
|
284
|
-
ret =
|
285
|
-
|
286
|
-
|
533
|
+
def determine_widths
|
534
|
+
ret = Array.new(@col_count, 1)
|
535
|
+
@table.each do |row|
|
536
|
+
next unless row
|
537
|
+
cidx = -1
|
538
|
+
row.each do |cell|
|
539
|
+
cidx += 1
|
540
|
+
next unless cell
|
541
|
+
cell.tag = Tag.new(Text.as_lines([cell.value], @max_width))
|
542
|
+
width = cell.tag.value = cell.tag.lines.max_by(&:last).last
|
543
|
+
ret[cidx] = width if ret[cidx] < width
|
544
|
+
end
|
287
545
|
end
|
288
546
|
ret
|
289
547
|
end
|
548
|
+
|
549
|
+
Tag = Struct.new(:lines, :value)
|
290
550
|
end
|
291
|
-
|
551
|
+
|
552
|
+
private_constant :TableGen
|
292
553
|
end
|
293
554
|
end
|