tabulo 2.3.3 → 2.6.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/.travis.yml +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +330 -96
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/assets/social_media_preview/table.png +0 -0
- data/lib/tabulo.rb +1 -0
- data/lib/tabulo/border.rb +66 -102
- data/lib/tabulo/cell.rb +56 -19
- data/lib/tabulo/cell_data.rb +14 -0
- data/lib/tabulo/column.rb +52 -6
- data/lib/tabulo/row.rb +8 -5
- data/lib/tabulo/table.rb +268 -110
- data/lib/tabulo/util.rb +20 -0
- data/lib/tabulo/version.rb +1 -1
- data/tabulo.gemspec +3 -3
- metadata +11 -10
data/lib/tabulo/row.rb
CHANGED
@@ -7,11 +7,12 @@ module Tabulo
|
|
7
7
|
attr_reader :source
|
8
8
|
|
9
9
|
# @!visibility private
|
10
|
-
def initialize(table, source, divider
|
10
|
+
def initialize(table, source, divider:, header:, index:)
|
11
11
|
@table = table
|
12
12
|
@source = source
|
13
13
|
@divider = divider
|
14
14
|
@header = header
|
15
|
+
@index = index
|
15
16
|
end
|
16
17
|
|
17
18
|
# Calls the given block once for each {Cell} in the {Row}, passing that {Cell} as parameter.
|
@@ -23,8 +24,8 @@ module Tabulo
|
|
23
24
|
# puts cell.value # => 1, => false
|
24
25
|
# end
|
25
26
|
def each
|
26
|
-
@table.column_registry.
|
27
|
-
yield column.body_cell(@source)
|
27
|
+
@table.column_registry.each_with_index do |(_, column), column_index|
|
28
|
+
yield column.body_cell(@source, row_index: @index, column_index: column_index)
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -34,7 +35,7 @@ module Tabulo
|
|
34
35
|
# and divider frequency).
|
35
36
|
def to_s
|
36
37
|
if @table.column_registry.any?
|
37
|
-
@table.formatted_body_row(@source, header: @header,
|
38
|
+
@table.formatted_body_row(@source, divider: @divider, header: @header, index: @index)
|
38
39
|
else
|
39
40
|
""
|
40
41
|
end
|
@@ -42,7 +43,9 @@ module Tabulo
|
|
42
43
|
|
43
44
|
# @return a Hash representation of the {Row}, with column labels acting as keys and the {Cell}s the values.
|
44
45
|
def to_h
|
45
|
-
@table.column_registry.map
|
46
|
+
@table.column_registry.map.with_index do |(label, column), column_index|
|
47
|
+
[label, column.body_cell(@source, row_index: @index, column_index: column_index)]
|
48
|
+
end.to_h
|
46
49
|
end
|
47
50
|
end
|
48
51
|
end
|
data/lib/tabulo/table.rb
CHANGED
@@ -43,13 +43,18 @@ module Tabulo
|
|
43
43
|
# @param [:left, :right, :center] align_header (:center) Determines the alignment of header text
|
44
44
|
# for columns in this Table. Can be overridden for individual columns using the
|
45
45
|
# <tt>align_header</tt> option passed to {#add_column}
|
46
|
+
# @param [:left, :right, :center] align_header (:center) Determines the alignment of the table
|
47
|
+
# title, if present.
|
46
48
|
# @param [:ascii, :markdown, :modern, :blank, nil] border (nil) Determines the characters used
|
47
49
|
# for the Table border, including both the characters around the outside of table, and the lines drawn
|
48
50
|
# within the table to separate columns from each other and the header row from the Table body.
|
49
51
|
# If <tt>nil</tt>, then the value of {DEFAULT_BORDER} will be used.
|
50
52
|
# Possible values are:
|
51
53
|
# - `:ascii` Uses ASCII characters only
|
52
|
-
# - `:markdown` Produces a GitHub-flavoured Markdown table
|
54
|
+
# - `:markdown` Produces a GitHub-flavoured Markdown table. Note: Using the `title`
|
55
|
+
# option in combination with this border type will cause the rendered
|
56
|
+
# table not to be valid Markdown, since Markdown engines do not generally
|
57
|
+
# support adding a caption element (i.e. title) to tables.
|
53
58
|
# - `:modern` Uses non-ASCII Unicode characters to render a border with smooth continuous lines
|
54
59
|
# - `:blank` No border characters are rendered
|
55
60
|
# - `:reduced_ascii` Like `:ascii`, but without left or right borders, and with internal vertical
|
@@ -64,14 +69,15 @@ module Tabulo
|
|
64
69
|
# If passed <tt>nil</tt>, then no additional styling will be applied to borders. If passed a
|
65
70
|
# callable, then that callable will be called for each border section, with the
|
66
71
|
# resulting string rendered in place of that border. The extra width of the string returned by the
|
67
|
-
#
|
72
|
+
# <tt>border_styler</tt> is not taken into consideration by the internal table rendering calculations
|
68
73
|
# Thus it can be used to apply ANSI escape codes to border characters, to colour the borders
|
69
74
|
# for example, without breaking the table formatting.
|
70
75
|
# @param [nil, Integer, Array] column_padding (1) Determines the amount of blank space with which to pad
|
71
76
|
# either side of each column. If passed an Integer, then the given amount of padding is
|
72
77
|
# applied to each side of each column. If passed a two-element Array, then the first element of the
|
73
78
|
# Array indicates the amount of padding to apply to the left of each column, and the second
|
74
|
-
# element indicates the amount to apply to the right.
|
79
|
+
# element indicates the amount to apply to the right. This setting can be overridden for
|
80
|
+
# individual columns using the `padding` option of {#add_column}.
|
75
81
|
# @param [Integer, nil] column_width The default column width for columns in this
|
76
82
|
# table, not excluding padding. If <tt>nil</tt>, then {DEFAULT_COLUMN_WIDTH} will be used.
|
77
83
|
# @param [nil, #to_proc] formatter (:to_s.to_proc) The default formatter for columns in this
|
@@ -89,6 +95,31 @@ module Tabulo
|
|
89
95
|
# header row.
|
90
96
|
# @param [nil, #to_proc] styler (nil) The default styler for columns in this table. See `styler`
|
91
97
|
# option of {#add_column} for details.
|
98
|
+
# @param [nil, String] title (nil) If passed a String, will arrange for a title to be shown at the top
|
99
|
+
# of the table. Note: If the `border` option is set to `:markdown`, adding a title to the table
|
100
|
+
# will cause it to cease being valid Markdown when rendered, since Markdown engines do not generally
|
101
|
+
# support adding a caption element (i.e. title) to tables.
|
102
|
+
# @param [nil, #to_proc] title_styler (nil) A lambda or other callable object that will
|
103
|
+
# determine the colors or other styling applied to the table title. Can be passed
|
104
|
+
# <tt>nil</tt>, or can be passed a callable that takes either 1 or 2 parametes:
|
105
|
+
# * If passed <tt>nil</tt>, then no additional styling will be applied to the title.
|
106
|
+
# * If passed a callable, then that callable will be called for each line of
|
107
|
+
# the title, and the resulting string rendered in place of that line.
|
108
|
+
# The extra width of the string returned by the <tt>title_styler</tt> is not taken into
|
109
|
+
# consideration by the internal table and cell width calculations involved in rendering the
|
110
|
+
# table. Thus it can be used to apply ANSI escape codes to title content, to color the
|
111
|
+
# content for example, without breaking the table formatting.
|
112
|
+
# * If the passed callable takes 1 parameter, then the first parameter is a string
|
113
|
+
# representing a single line within the title. For example, if the title
|
114
|
+
# is wrapped over three lines, then the <tt>title_styler</tt> will be called
|
115
|
+
# three times, once for each line of content.
|
116
|
+
# * If the passed callable takes 2 parameters, then the first parameter is as above, and the
|
117
|
+
# second parameter is an Integer representing the index of the line within the
|
118
|
+
# title that is currently being styled. For example, if the title is wrapped over 3
|
119
|
+
# lines, then the callable will be called first with a line index of 0, to style the first line,
|
120
|
+
# then with a line index of 1, to style the second line, and finally with a line index of 2, for
|
121
|
+
# the third and final wrapped line of the cell.
|
122
|
+
#
|
92
123
|
# @param [nil, String] truncation_indicator Determines the character used to indicate that a
|
93
124
|
# cell's content has been truncated. If omitted or passed <tt>nil</tt>,
|
94
125
|
# defaults to {DEFAULT_TRUNCATION_INDICATOR}. If passed something other than <tt>nil</tt> or
|
@@ -107,22 +138,24 @@ module Tabulo
|
|
107
138
|
# @return [Table] a new {Table}
|
108
139
|
# @raise [InvalidColumnLabelError] if non-unique Symbols are provided to columns.
|
109
140
|
# @raise [InvalidBorderError] if invalid option passed to `border` parameter.
|
110
|
-
def initialize(sources, *columns, align_body: :auto, align_header: :center,
|
111
|
-
border_styler: nil, column_padding: nil, column_width: nil, formatter: :to_s.to_proc,
|
141
|
+
def initialize(sources, *columns, align_body: :auto, align_header: :center, align_title: :center,
|
142
|
+
border: nil, border_styler: nil, column_padding: nil, column_width: nil, formatter: :to_s.to_proc,
|
112
143
|
header_frequency: :start, header_styler: nil, row_divider_frequency: nil, styler: nil,
|
113
|
-
|
144
|
+
title: nil, title_styler: nil, truncation_indicator: nil, wrap_body_cells_to: nil,
|
145
|
+
wrap_header_cells_to: nil)
|
114
146
|
|
115
147
|
@sources = sources
|
116
148
|
|
117
149
|
@align_body = align_body
|
118
150
|
@align_header = align_header
|
151
|
+
@align_title = align_title
|
119
152
|
@border = (border || DEFAULT_BORDER)
|
120
153
|
@border_styler = border_styler
|
121
154
|
@border_instance = Border.from(@border, @border_styler)
|
122
155
|
@column_padding = (column_padding || DEFAULT_COLUMN_PADDING)
|
123
156
|
|
124
157
|
@left_column_padding, @right_column_padding =
|
125
|
-
Array === @column_padding ? @column_padding : [@column_padding, @column_padding]
|
158
|
+
(Array === @column_padding ? @column_padding : [@column_padding, @column_padding])
|
126
159
|
|
127
160
|
@column_width = (column_width || DEFAULT_COLUMN_WIDTH)
|
128
161
|
@formatter = formatter
|
@@ -130,6 +163,8 @@ module Tabulo
|
|
130
163
|
@header_styler = header_styler
|
131
164
|
@row_divider_frequency = row_divider_frequency
|
132
165
|
@styler = styler
|
166
|
+
@title = title
|
167
|
+
@title_styler = title_styler
|
133
168
|
@truncation_indicator = validate_character(truncation_indicator,
|
134
169
|
DEFAULT_TRUNCATION_INDICATOR, InvalidTruncationIndicatorError, "truncation indicator")
|
135
170
|
@wrap_body_cells_to = wrap_body_cells_to
|
@@ -168,56 +203,119 @@ module Tabulo
|
|
168
203
|
# in either String or Symbol form for this purpose.
|
169
204
|
# @param [#to_proc] formatter (nil) A lambda or other callable object that
|
170
205
|
# will be passed the calculated value of each cell to determine how it should be displayed. This
|
171
|
-
# is distinct from the extractor (see below).
|
172
|
-
# generates a Date, then the formatter might format
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
206
|
+
# is distinct from the extractor and the styler (see below).
|
207
|
+
# For example, if the extractor for this column generates a Date, then the formatter might format
|
208
|
+
# that Date in a particular way.
|
209
|
+
# * If <tt>nil</tt> is provided, then the callable that was passed to the `formatter` option
|
210
|
+
# of the table itself on its creation (see {#initialize}) (which itself defaults to
|
211
|
+
# `:to_s.to_proc`), will be used as the formatter for the column.
|
212
|
+
# * If a 1-parameter callable is passed, then this callable will be called with the calculated
|
213
|
+
# value of the cell; it should then return a String, and this String will be displayed as
|
214
|
+
# the formatted value of the cell.
|
215
|
+
# * If a 2-parameter callable is passed, then the first parameter represents the calculated
|
216
|
+
# value of the cell, and the second parameter is a {CellData} instance, containing
|
217
|
+
# additional information about the cell that may be relevant to what formatting should
|
218
|
+
# be applied. For example, the {CellData#row_index} attribute can be inspected to determine
|
219
|
+
# whether the {Cell} is an odd- or even-numbered {Row}, to arrange for different formatting
|
220
|
+
# to be applied to alternating rows.
|
221
|
+
# See the documentation for {CellData} for more.
|
176
222
|
# @param [nil, #to_s] header (nil) Text to be displayed in the column header. If passed nil,
|
177
223
|
# the column's label will also be used as its header text.
|
178
|
-
# @param [nil, #to_proc] header_styler (nil) A lambda or other callable object
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
# then that callable will be called for each line of content within
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
187
|
-
#
|
188
|
-
#
|
189
|
-
#
|
224
|
+
# @param [nil, #to_proc] header_styler (nil) A lambda or other callable object that will
|
225
|
+
# determine the colors or other styling applied to the header content. Can be passed
|
226
|
+
# <tt>nil</tt>, or can be passed a callable that takes 1, 2 or 3 parameters:
|
227
|
+
# * If passed <tt>nil</tt>, then no additional styling will be applied to the cell content
|
228
|
+
# (other than what was already applied by the <tt>formatter</tt>).
|
229
|
+
# * If passed a callable, then that callable will be called for each line of content within
|
230
|
+
# the header cell, and the resulting string rendered in place of that line.
|
231
|
+
# The extra width of the string returned by the <tt>header_styler</tt> is not taken into
|
232
|
+
# consideration by the internal table and cell width calculations involved in rendering the
|
233
|
+
# table. Thus it can be used to apply ANSI escape codes to header cell content, to color the
|
234
|
+
# cell content for example, without breaking the table formatting.
|
235
|
+
# * If the passed callable takes 1 parameter, then the first parameter is a string
|
236
|
+
# representing a single formatted line within the header cell. For example, if the header
|
237
|
+
# cell content is wrapped over three lines, then the <tt>header_styler</tt> will be called
|
238
|
+
# three times for that header cell, once for each line of content.
|
239
|
+
# * If the passed callable takes 2 parameters, then the first parameter is as above, and the
|
240
|
+
# second parameter is an Integer representing the positional index of this header's {Column},
|
241
|
+
# with the leftmost column having index 0, the next having index 1 etc.. This can be
|
242
|
+
# used, for example, to apply different styles to alternating {Column}s.
|
243
|
+
# * If the passed callable takes 3 parameters, then the first and second parameters are as above,
|
244
|
+
# and the third parameter is an Integer representing the index of the line within the
|
245
|
+
# header cell that is currently being styled. For example, if the cell content is wrapped over 3
|
246
|
+
# lines, then the callable will be called first with a line index of 0, to style the first line,
|
247
|
+
# then with a line index of 1, to style the second line, and finally with a line index of 2, for
|
248
|
+
# the third and final wrapped line of the cell.
|
249
|
+
#
|
250
|
+
# Note that if the header content is truncated, then any <tt>header_styler</tt> will be applied to the
|
190
251
|
# truncation indicator character as well as to the truncated content.
|
191
|
-
# @param [nil,
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
252
|
+
# @param [nil, Integer, Array] padding (nil) Determines the amount of blank space with which to
|
253
|
+
# pad either side of the column. If passed nil, then the `column_padding` setting of the
|
254
|
+
# {Table} will determine the column's padding. (See {#initialize}.) Otherwise, this option
|
255
|
+
# overrides, for this column, the `column_padding` that was set at the table level: if passed an Integer,
|
256
|
+
# then the given amount of padding is applied to either side of the column; or if passed a two-element Array,
|
257
|
+
# then the first element of the Array indicates the amount of padding to apply to the left of the column,
|
258
|
+
# and the second element indicates the amount to apply to the right.
|
259
|
+
# @param [nil, #to_proc] styler (nil) A lambda or other callable object that will determine
|
260
|
+
# the colors or other styling applied to the formatted value of the cell. Can be passed
|
261
|
+
# <tt>nil</tt>, or can be passed a callable that takes either 2 or 3 parameters:
|
262
|
+
# * If passed <tt>nil</tt>, then no additional styling will be applied to the cell content
|
263
|
+
# (other than what was already applied by the <tt>formatter</tt>).
|
264
|
+
# * If passed a callable, then that callable will be called for each line of content within
|
265
|
+
# the cell, and the resulting string rendered in place of that line.
|
266
|
+
# The <tt>styler</tt> option differs from the <tt>formatter</tt> option in that the extra width of the
|
267
|
+
# string returned by <tt>styler</tt> is not taken into consideration by the internal table and
|
268
|
+
# cell width calculations involved in rendering the table. Thus it can be used to apply
|
269
|
+
# ANSI escape codes to cell content, to color the cell content for example, without
|
270
|
+
# breaking the table formatting.
|
271
|
+
# * If the passed callable takes 2 parameters, then the first parameter is the calculated
|
272
|
+
# value of the cell (prior to the <tt>formatter</tt> being applied); and the second parameter is
|
273
|
+
# a string representing a single formatted line within the cell. For example, if the cell
|
274
|
+
# content is wrapped over three lines, then for that cell, the <tt>styler</tt> will be called
|
275
|
+
# three times, once for each line of content within the cell.
|
276
|
+
# * If the passed callable takes 3 parameters, then the first two parameters are as above,
|
277
|
+
# and the third parameter is a {CellData} instance, containing additional information
|
278
|
+
# about the cell that may be relevant to what styles should be applied. For example, the
|
279
|
+
# {CellData#row_index} attribute can be inspected to determine whether the {Cell} is an
|
280
|
+
# odd- or even-numbered {Row}, to arrange for different styling to be applied to
|
281
|
+
# alternating rows. See the documentation for {CellData} for more.
|
282
|
+
# * If the passed callable takes 4 parameters, then the first three parameters are as above,
|
283
|
+
# and the fourth parameter is an Integer representing the index of the line within the
|
284
|
+
# cell that is currently being styled. For example, if the cell content is wrapped over 3
|
285
|
+
# lines, then the callable will be called first with a line index of 0, to style the first
|
286
|
+
# line, then with a line index of 1, to style the second line, and finally with a line
|
287
|
+
# index of 2, to style the third and final wrapped line of the cell.
|
288
|
+
#
|
204
289
|
# Note that if the content of a cell is truncated, then the whatever styling is applied by the
|
205
|
-
#
|
290
|
+
# <tt>styler</tt> to the cell content will also be applied to the truncation indicator character.
|
206
291
|
# @param [Integer] width (nil) Specifies the width of the column, excluding padding. If
|
207
292
|
# nil, then the column will take the width provided by the `column_width` param
|
208
293
|
# with which the Table was initialized.
|
209
|
-
# @param [#to_proc] extractor A block or other callable
|
210
|
-
#
|
211
|
-
#
|
212
|
-
#
|
294
|
+
# @param [#to_proc] extractor A block or other callable that will be passed each of the {Table}
|
295
|
+
# sources to determine the value in each cell of this column.
|
296
|
+
# * If this is not provided, then the column label will be treated as a method to be called on
|
297
|
+
# each source item to determine each cell's value.
|
298
|
+
# * If provided a single-parameter callable, then this callable will be passed each of the
|
299
|
+
# {Table} sources to determine the cell value for each row in this column.
|
300
|
+
# * If provided a 2-parameter callable, then for each of the {Table} sources, this callable
|
301
|
+
# will be passed the source, and the row index, to determine the cell value for that row.
|
302
|
+
# For this purpose, the first body row (not counting the header row) has an index of 0,
|
303
|
+
# the next an index of 1, etc..
|
213
304
|
# @raise [InvalidColumnLabelError] if label has already been used for another column in this
|
214
305
|
# Table. (This is case-sensitive, but is insensitive to whether a String or Symbol is passed
|
215
306
|
# to the label parameter.)
|
216
307
|
def add_column(label, align_body: nil, align_header: nil, before: nil, formatter: nil,
|
217
|
-
header: nil, header_styler: nil, styler: nil, width: nil, &extractor)
|
308
|
+
header: nil, header_styler: nil, padding: nil, styler: nil, width: nil, &extractor)
|
218
309
|
|
219
310
|
column_label = normalize_column_label(label)
|
220
311
|
|
312
|
+
left_padding, right_padding =
|
313
|
+
if padding
|
314
|
+
Array === padding ? padding : [padding, padding]
|
315
|
+
else
|
316
|
+
[@left_column_padding, @right_column_padding]
|
317
|
+
end
|
318
|
+
|
221
319
|
if column_registry.include?(column_label)
|
222
320
|
raise InvalidColumnLabelError, "Column label already used in this table."
|
223
321
|
end
|
@@ -229,7 +327,10 @@ module Tabulo
|
|
229
327
|
formatter: formatter || @formatter,
|
230
328
|
header: (header || label).to_s,
|
231
329
|
header_styler: header_styler || @header_styler,
|
330
|
+
index: column_registry.count,
|
331
|
+
left_padding: left_padding,
|
232
332
|
padding_character: PADDING_CHARACTER,
|
333
|
+
right_padding: right_padding,
|
233
334
|
styler: styler || @styler,
|
234
335
|
truncation_indicator: @truncation_indicator,
|
235
336
|
width: width || @column_width,
|
@@ -271,7 +372,7 @@ module Tabulo
|
|
271
372
|
if column_registry.any?
|
272
373
|
bottom_edge = horizontal_rule(:bottom)
|
273
374
|
rows = map(&:to_s)
|
274
|
-
bottom_edge.empty? ? join_lines(rows) : join_lines(rows + [bottom_edge])
|
375
|
+
bottom_edge.empty? ? Util.join_lines(rows) : Util.join_lines(rows + [bottom_edge])
|
275
376
|
else
|
276
377
|
""
|
277
378
|
end
|
@@ -297,24 +398,28 @@ module Tabulo
|
|
297
398
|
|
298
399
|
show_divider = @row_divider_frequency && (index != 0) && Util.divides?(@row_divider_frequency, index)
|
299
400
|
|
300
|
-
yield Row.new(self, source, header: header, divider: show_divider)
|
401
|
+
yield Row.new(self, source, header: header, divider: show_divider, index: index)
|
301
402
|
end
|
302
403
|
end
|
303
404
|
|
304
|
-
# @return [String]
|
405
|
+
# @return [String] a graphical representation of the Table column headers formatted with fixed
|
406
|
+
# width plain text.
|
305
407
|
def formatted_header
|
306
408
|
cells = get_columns.map(&:header_cell)
|
307
409
|
format_row(cells, @wrap_header_cells_to)
|
308
410
|
end
|
309
411
|
|
310
|
-
#
|
311
|
-
#
|
312
|
-
#
|
412
|
+
# Produce a horizontal dividing line suitable for printing at the top, bottom or middle
|
413
|
+
# of the table.
|
414
|
+
#
|
415
|
+
# @param [:top, :middle, :bottom, :title_top, :title_bottom] position (:bottom)
|
416
|
+
# Specifies the position for which the resulting horizontal dividing line is intended to
|
417
|
+
# be printed. This determines the border characters that are used to construct the line.
|
418
|
+
# The `:title_top` and `:title_bottom` options are used internally for adding borders
|
419
|
+
# above and below the table title text.
|
313
420
|
# @return [String] an "ASCII" graphical representation of a horizontal
|
314
|
-
# dividing line
|
315
|
-
#
|
316
|
-
# @example Print a horizontal divider between each pair of rows, and again
|
317
|
-
# at the bottom:
|
421
|
+
# dividing line.
|
422
|
+
# @example Print a horizontal divider between each pair of rows, and again at the bottom:
|
318
423
|
#
|
319
424
|
# table.each_with_index do |row, i|
|
320
425
|
# puts table.horizontal_rule(:middle) unless i == 0
|
@@ -325,14 +430,17 @@ module Tabulo
|
|
325
430
|
# It may be that `:top`, `:middle` and `:bottom` all look the same. Whether
|
326
431
|
# this is the case depends on the characters used for the table border.
|
327
432
|
def horizontal_rule(position = :bottom)
|
328
|
-
column_widths = get_columns.map { |column| column.width +
|
433
|
+
column_widths = get_columns.map { |column| column.width + column.total_padding }
|
329
434
|
@border_instance.horizontal_rule(column_widths, position)
|
330
435
|
end
|
331
436
|
|
332
|
-
#
|
437
|
+
# Resets all the column widths so that each column is *just* wide enough to accommodate
|
333
438
|
# its header text as well as the formatted content of each its cells for the entire
|
334
439
|
# collection, together with a single character of padding on either side of the column,
|
335
|
-
# without any wrapping.
|
440
|
+
# without any wrapping. In addition, if the table has a title but is not wide enough to
|
441
|
+
# accommodate (without wrapping) the title text (with a character of padding either side),
|
442
|
+
# widens the columns roughly evenly until the table as a whole is just wide enough to
|
443
|
+
# accommodate the title text.
|
336
444
|
#
|
337
445
|
# Note that calling this method will cause the entire source Enumerable to
|
338
446
|
# be traversed and all the column extractors and formatters to be applied in order
|
@@ -358,17 +466,27 @@ module Tabulo
|
|
358
466
|
# Table will refuse to shrink itself.
|
359
467
|
# @return [Table] the Table itself
|
360
468
|
def pack(max_table_width: :auto)
|
361
|
-
get_columns.each { |column| column.width = wrapped_width(column.header) }
|
469
|
+
get_columns.each { |column| column.width = Util.wrapped_width(column.header) }
|
362
470
|
|
363
|
-
@sources.
|
364
|
-
get_columns.
|
365
|
-
|
471
|
+
@sources.each_with_index do |source, row_index|
|
472
|
+
get_columns.each_with_index do |column, column_index|
|
473
|
+
cell = column.body_cell(source, row_index: row_index, column_index: column_index)
|
474
|
+
cell_width = Util.wrapped_width(cell.formatted_content)
|
366
475
|
column.width = Util.max(column.width, cell_width)
|
367
476
|
end
|
368
477
|
end
|
369
478
|
|
370
|
-
if max_table_width
|
371
|
-
|
479
|
+
shrink_to(max_table_width == :auto ? TTY::Screen.width : max_table_width) if max_table_width
|
480
|
+
|
481
|
+
if @title
|
482
|
+
border_edge_width = (@border == :blank ? 0 : 2)
|
483
|
+
columns = get_columns
|
484
|
+
expand_to(
|
485
|
+
Unicode::DisplayWidth.of(@title) +
|
486
|
+
columns.first.left_padding +
|
487
|
+
columns.last.right_padding +
|
488
|
+
border_edge_width
|
489
|
+
)
|
372
490
|
end
|
373
491
|
|
374
492
|
self
|
@@ -393,8 +511,9 @@ module Tabulo
|
|
393
511
|
# The following options are the same as the keyword params for the {#initialize} method for
|
394
512
|
# {Table}: <tt>column_width</tt>, <tt>column_padding</tt>, <tt>formatter</tt>,
|
395
513
|
# <tt>header_frequency</tt>, <tt>row_divider_frequency</tt>, <tt>wrap_header_cells_to</tt>,
|
396
|
-
# <tt>wrap_body_cells_to</tt>, <tt>border</tt>, <tt>border_styler</tt>, <tt>
|
397
|
-
# <tt>align_header</tt>, <tt>align_body</tt
|
514
|
+
# <tt>wrap_body_cells_to</tt>, <tt>border</tt>, <tt>border_styler</tt>, <tt>title</tt>,
|
515
|
+
# <tt>title_styler</tt>, <tt>truncation_indicator</tt>, <tt>align_header</tt>, <tt>align_body</tt>,
|
516
|
+
# <tt>align_title</tt>.
|
398
517
|
# These are applied in the same way as documented for {#initialize}, when
|
399
518
|
# creating the new, transposed Table. Any options not specified explicitly in the call to {#transpose}
|
400
519
|
# will inherit their values from the original {Table} (with the exception of settings
|
@@ -418,9 +537,9 @@ module Tabulo
|
|
418
537
|
# @return [Table] a new {Table}
|
419
538
|
# @raise [InvalidBorderError] if invalid argument passed to `border` parameter.
|
420
539
|
def transpose(opts = {})
|
421
|
-
default_opts = [:align_body, :align_header, :border, :border_styler, :column_padding,
|
422
|
-
:formatter, :header_frequency, :row_divider_frequency, :
|
423
|
-
:wrap_header_cells_to].map do |sym|
|
540
|
+
default_opts = [:align_body, :align_header, :align_title, :border, :border_styler, :column_padding,
|
541
|
+
:column_width, :formatter, :header_frequency, :row_divider_frequency, :title, :title_styler,
|
542
|
+
:truncation_indicator, :wrap_body_cells_to, :wrap_header_cells_to].map do |sym|
|
424
543
|
[sym, instance_variable_get("@#{sym}")]
|
425
544
|
end.to_h
|
426
545
|
|
@@ -445,36 +564,26 @@ module Tabulo
|
|
445
564
|
# Add a column to the new table for each of the original table's sources
|
446
565
|
sources.each_with_index do |source, i|
|
447
566
|
t.add_column(i, header: extra_opts[:headers].call(source)) do |original_column|
|
448
|
-
original_column.body_cell_value(source)
|
567
|
+
original_column.body_cell_value(source, row_index: i, column_index: original_column.index)
|
449
568
|
end
|
450
569
|
end
|
451
570
|
end
|
452
571
|
end
|
453
572
|
|
454
573
|
# @!visibility private
|
455
|
-
def formatted_body_row(source, header:, divider:)
|
456
|
-
cells = get_columns.map { |
|
574
|
+
def formatted_body_row(source, header:, divider:, index:)
|
575
|
+
cells = get_columns.map.with_index { |c, i| c.body_cell(source, row_index: index, column_index: i) }
|
457
576
|
inner = format_row(cells, @wrap_body_cells_to)
|
458
577
|
|
459
|
-
if header == :top
|
460
|
-
|
461
|
-
horizontal_rule(:
|
462
|
-
|
463
|
-
|
464
|
-
inner
|
465
|
-
].reject(&:empty?))
|
578
|
+
if @title && header == :top
|
579
|
+
Util.condense_lines([horizontal_rule(:title_top), formatted_title, horizontal_rule(:title_bottom),
|
580
|
+
formatted_header, horizontal_rule(:middle), inner])
|
581
|
+
elsif header == :top
|
582
|
+
Util.condense_lines([horizontal_rule(:top), formatted_header, horizontal_rule(:middle), inner])
|
466
583
|
elsif header
|
467
|
-
|
468
|
-
horizontal_rule(:middle),
|
469
|
-
formatted_header,
|
470
|
-
horizontal_rule(:middle),
|
471
|
-
inner
|
472
|
-
].reject(&:empty?))
|
584
|
+
Util.condense_lines([horizontal_rule(:middle), formatted_header, horizontal_rule(:middle), inner])
|
473
585
|
elsif divider
|
474
|
-
|
475
|
-
horizontal_rule(:middle),
|
476
|
-
inner
|
477
|
-
].reject(&:empty?))
|
586
|
+
Util.condense_lines([horizontal_rule(:middle), inner])
|
478
587
|
else
|
479
588
|
inner
|
480
589
|
end
|
@@ -508,6 +617,55 @@ module Tabulo
|
|
508
617
|
@column_registry[label] = column
|
509
618
|
end
|
510
619
|
|
620
|
+
# @visibility private
|
621
|
+
def formatted_title
|
622
|
+
columns = get_columns
|
623
|
+
|
624
|
+
extra_for_internal_dividers = (@border == :blank ? 0 : 1)
|
625
|
+
|
626
|
+
title_cell_width = columns.inject(0) do |total_width, column|
|
627
|
+
total_width + column.padded_width + extra_for_internal_dividers
|
628
|
+
end
|
629
|
+
|
630
|
+
title_cell_width -= (columns.first.left_padding + columns.last.right_padding + extra_for_internal_dividers)
|
631
|
+
|
632
|
+
styler =
|
633
|
+
if @title_styler
|
634
|
+
case @title_styler.arity
|
635
|
+
when 1
|
636
|
+
-> (_val, str) { @title_styler.call(str) }
|
637
|
+
when 2
|
638
|
+
-> (_val, str, _cell_data, line_index) { @title_styler.call(str, line_index) }
|
639
|
+
end
|
640
|
+
else
|
641
|
+
-> (_val, str) { str }
|
642
|
+
end
|
643
|
+
|
644
|
+
title_cell = Cell.new(
|
645
|
+
alignment: @align_title,
|
646
|
+
cell_data: nil,
|
647
|
+
formatter: -> (s) { s },
|
648
|
+
left_padding: columns.first.left_padding,
|
649
|
+
padding_character: PADDING_CHARACTER,
|
650
|
+
right_padding: columns.last.right_padding,
|
651
|
+
styler: styler,
|
652
|
+
truncation_indicator: @truncation_indicator,
|
653
|
+
value: @title,
|
654
|
+
width: title_cell_width
|
655
|
+
)
|
656
|
+
cells = [title_cell]
|
657
|
+
max_cell_height = cells.map(&:height).max
|
658
|
+
row_height = ([nil, max_cell_height].compact.min || 1)
|
659
|
+
subcell_stacks = cells.map do |cell|
|
660
|
+
cell.padded_truncated_subcells(row_height)
|
661
|
+
end
|
662
|
+
subrows = subcell_stacks.transpose.map do |subrow_components|
|
663
|
+
@border_instance.join_cell_contents(subrow_components)
|
664
|
+
end
|
665
|
+
|
666
|
+
Util.join_lines(subrows)
|
667
|
+
end
|
668
|
+
|
511
669
|
# @!visibility private
|
512
670
|
def normalize_column_label(label)
|
513
671
|
case label
|
@@ -518,14 +676,32 @@ module Tabulo
|
|
518
676
|
end
|
519
677
|
end
|
520
678
|
|
679
|
+
# @!visibility private
|
680
|
+
def expand_to(min_table_width)
|
681
|
+
columns = get_columns
|
682
|
+
num_columns = columns.count
|
683
|
+
total_columns_padded_width = columns.inject(0) { |sum, column| sum + column.padded_width }
|
684
|
+
total_borders = num_columns + 1
|
685
|
+
unadjusted_table_width = total_columns_padded_width + total_borders
|
686
|
+
required_increase = Util.max(min_table_width - unadjusted_table_width, 0)
|
687
|
+
|
688
|
+
required_increase.times do
|
689
|
+
narrowest_column = columns.inject(columns.first) do |narrowest, column|
|
690
|
+
column.width <= narrowest.width ? column : narrowest
|
691
|
+
end
|
692
|
+
|
693
|
+
narrowest_column.width += 1
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
521
697
|
# @!visibility private
|
522
698
|
def shrink_to(max_table_width)
|
523
699
|
columns = get_columns
|
524
700
|
num_columns = columns.count
|
525
|
-
|
526
|
-
total_padding =
|
701
|
+
total_columns_padded_width = columns.inject(0) { |sum, column| sum + column.padded_width }
|
702
|
+
total_padding = columns.inject(0) { |sum, column| sum + column.total_padding }
|
527
703
|
total_borders = num_columns + 1
|
528
|
-
unadjusted_table_width =
|
704
|
+
unadjusted_table_width = total_columns_padded_width + total_borders
|
529
705
|
|
530
706
|
# Ensure max table width is at least wide enough to accommodate table borders and padding
|
531
707
|
# and one character of content.
|
@@ -542,11 +718,6 @@ module Tabulo
|
|
542
718
|
end
|
543
719
|
end
|
544
720
|
|
545
|
-
# @!visibility private
|
546
|
-
def total_column_padding
|
547
|
-
@left_column_padding + @right_column_padding
|
548
|
-
end
|
549
|
-
|
550
721
|
# @!visibility private
|
551
722
|
#
|
552
723
|
# Formats a single header row or body row as a String.
|
@@ -564,18 +735,13 @@ module Tabulo
|
|
564
735
|
max_cell_height = cells.map(&:height).max
|
565
736
|
row_height = ([wrap_cells_to, max_cell_height].compact.min || 1)
|
566
737
|
subcell_stacks = cells.map do |cell|
|
567
|
-
cell.padded_truncated_subcells(row_height
|
738
|
+
cell.padded_truncated_subcells(row_height)
|
568
739
|
end
|
569
740
|
subrows = subcell_stacks.transpose.map do |subrow_components|
|
570
741
|
@border_instance.join_cell_contents(subrow_components)
|
571
742
|
end
|
572
743
|
|
573
|
-
join_lines(subrows)
|
574
|
-
end
|
575
|
-
|
576
|
-
# @!visibility private
|
577
|
-
def join_lines(lines)
|
578
|
-
lines.join($/) # join strings with cross-platform newline
|
744
|
+
Util.join_lines(subrows)
|
579
745
|
end
|
580
746
|
|
581
747
|
# @!visibility private
|
@@ -593,13 +759,5 @@ module Tabulo
|
|
593
759
|
c
|
594
760
|
end
|
595
761
|
|
596
|
-
# @!visibility private
|
597
|
-
# @return [Integer] the length of the longest segment of str when split by newlines
|
598
|
-
def wrapped_width(str)
|
599
|
-
segments = str.split($/)
|
600
|
-
segments.inject(1) do |longest_length_so_far, segment|
|
601
|
-
Util.max(longest_length_so_far, Unicode::DisplayWidth.of(segment))
|
602
|
-
end
|
603
|
-
end
|
604
762
|
end
|
605
763
|
end
|