tabulo 2.4.0 → 2.6.2
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 +28 -0
- data/README.md +332 -122
- data/VERSION +1 -1
- data/assets/social_media_preview/table.png +0 -0
- data/lib/tabulo/border.rb +66 -102
- data/lib/tabulo/cell.rb +28 -17
- data/lib/tabulo/cell_data.rb +2 -1
- data/lib/tabulo/column.rb +28 -5
- data/lib/tabulo/table.rb +204 -86
- data/lib/tabulo/util.rb +22 -0
- data/lib/tabulo/version.rb +1 -1
- data/tabulo.gemspec +2 -2
- metadata +8 -8
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.2
|
Binary file
|
data/lib/tabulo/border.rb
CHANGED
@@ -3,118 +3,77 @@ module Tabulo
|
|
3
3
|
# @!visibility private
|
4
4
|
class Border
|
5
5
|
|
6
|
+
Style = Struct.new(
|
7
|
+
:corner_top_left, :corner_top_right, :corner_bottom_right, :corner_bottom_left,
|
8
|
+
:edge_top, :edge_right, :edge_bottom, :edge_left,
|
9
|
+
:tee_top, :tee_right, :tee_bottom, :tee_left,
|
10
|
+
:divider_vertical, :divider_horizontal, :intersection)
|
11
|
+
|
6
12
|
STYLES = {
|
7
|
-
ascii:
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
corner_top_right: "",
|
57
|
-
corner_bottom_right: "",
|
58
|
-
corner_bottom_left: "",
|
59
|
-
edge_top: "─",
|
60
|
-
edge_right: "",
|
61
|
-
edge_bottom: "─",
|
62
|
-
edge_left: "",
|
63
|
-
tee_top: " ",
|
64
|
-
tee_right: "",
|
65
|
-
tee_bottom: " ",
|
66
|
-
tee_left: "",
|
67
|
-
divider_vertical: " ",
|
68
|
-
divider_horizontal: "─",
|
69
|
-
intersection: " ",
|
70
|
-
},
|
71
|
-
markdown: {
|
72
|
-
corner_top_left: "",
|
73
|
-
corner_top_right: "",
|
74
|
-
corner_bottom_right: "",
|
75
|
-
corner_bottom_left: "",
|
76
|
-
edge_top: "",
|
77
|
-
edge_right: "|",
|
78
|
-
edge_bottom: "",
|
79
|
-
edge_left: "|",
|
80
|
-
tee_top: "",
|
81
|
-
tee_right: "|",
|
82
|
-
tee_bottom: "",
|
83
|
-
tee_left: "|",
|
84
|
-
divider_vertical: "|",
|
85
|
-
divider_horizontal: "-",
|
86
|
-
intersection: "|",
|
87
|
-
},
|
88
|
-
modern: {
|
89
|
-
corner_top_left: "┌",
|
90
|
-
corner_top_right: "┐",
|
91
|
-
corner_bottom_right: "┘",
|
92
|
-
corner_bottom_left: "└",
|
93
|
-
edge_top: "─",
|
94
|
-
edge_right: "│",
|
95
|
-
edge_bottom: "─",
|
96
|
-
edge_left: "│",
|
97
|
-
tee_top: "┬",
|
98
|
-
tee_right: "┤",
|
99
|
-
tee_bottom: "┴",
|
100
|
-
tee_left: "├",
|
101
|
-
divider_vertical: "│",
|
102
|
-
divider_horizontal: "─",
|
103
|
-
intersection: "┼",
|
104
|
-
},
|
105
|
-
blank: {
|
106
|
-
},
|
13
|
+
ascii:
|
14
|
+
Style.new(
|
15
|
+
"+", "+", "+", "+",
|
16
|
+
"-", "|", "-", "|",
|
17
|
+
"+", "+", "+", "+",
|
18
|
+
"|", "-", "+",
|
19
|
+
),
|
20
|
+
classic:
|
21
|
+
Style.new(
|
22
|
+
"+", "+", "", "",
|
23
|
+
"-", "|", "", "|",
|
24
|
+
"+", "+", "", "+",
|
25
|
+
"|", "-", "+",
|
26
|
+
),
|
27
|
+
reduced_ascii:
|
28
|
+
Style.new(
|
29
|
+
"", "", "", "",
|
30
|
+
"-", "", "-", "",
|
31
|
+
" ", "", " ", "",
|
32
|
+
" ", "-", " ",
|
33
|
+
),
|
34
|
+
reduced_modern:
|
35
|
+
Style.new(
|
36
|
+
"", "", "", "",
|
37
|
+
"─", "", "─", "",
|
38
|
+
" ", "", " ", "",
|
39
|
+
" ", "─", " ",
|
40
|
+
),
|
41
|
+
markdown:
|
42
|
+
Style.new(
|
43
|
+
"", "", "", "",
|
44
|
+
"", "|", "", "|",
|
45
|
+
"", "|", "", "|",
|
46
|
+
"|", "-", "|",
|
47
|
+
),
|
48
|
+
modern:
|
49
|
+
Style.new(
|
50
|
+
"┌", "┐", "┘", "└",
|
51
|
+
"─", "│", "─", "│",
|
52
|
+
"┬", "┤", "┴", "├",
|
53
|
+
"│", "─", "┼",
|
54
|
+
),
|
55
|
+
blank:
|
56
|
+
Style.new(
|
57
|
+
"", "", "", "",
|
58
|
+
"", "", "", "",
|
59
|
+
"", "", "", "",
|
60
|
+
"", "", "",
|
61
|
+
),
|
107
62
|
}
|
108
63
|
|
109
64
|
# @!visibility private
|
110
65
|
def self.from(initializer, styler = nil)
|
111
|
-
new(options(initializer).merge(styler: styler))
|
66
|
+
new(**options(initializer).merge(styler: styler))
|
112
67
|
end
|
113
68
|
|
114
69
|
# @!visibility private
|
115
70
|
def horizontal_rule(column_widths, position = :bottom)
|
116
71
|
left, center, right, segment =
|
117
72
|
case position
|
73
|
+
when :title_top
|
74
|
+
[@corner_top_left, @edge_top, @corner_top_right, @edge_top]
|
75
|
+
when :title_bottom
|
76
|
+
[@tee_left, @tee_top, @tee_right, @edge_top]
|
118
77
|
when :top
|
119
78
|
[@corner_top_left, @tee_top, @corner_top_right, @edge_top]
|
120
79
|
when :middle
|
@@ -123,6 +82,11 @@ module Tabulo
|
|
123
82
|
[@corner_bottom_left, @tee_bottom, @corner_bottom_right, @edge_bottom]
|
124
83
|
end
|
125
84
|
segments = column_widths.map { |width| segment * width }
|
85
|
+
|
86
|
+
# Prevent weird bottom edge of title if segments empty but right/left not empty, as in
|
87
|
+
# Markdown border.
|
88
|
+
left = right = "" if segments.all?(&:empty?)
|
89
|
+
|
126
90
|
style("#{left}#{segments.join(center)}#{right}")
|
127
91
|
end
|
128
92
|
|
@@ -138,7 +102,7 @@ module Tabulo
|
|
138
102
|
|
139
103
|
def self.options(kind)
|
140
104
|
opts = STYLES[kind]
|
141
|
-
return opts if opts
|
105
|
+
return opts.to_h if opts
|
142
106
|
raise InvalidBorderError
|
143
107
|
end
|
144
108
|
|
data/lib/tabulo/cell.rb
CHANGED
@@ -13,7 +13,9 @@ module Tabulo
|
|
13
13
|
alignment:,
|
14
14
|
cell_data:,
|
15
15
|
formatter:,
|
16
|
+
left_padding:,
|
16
17
|
padding_character:,
|
18
|
+
right_padding:,
|
17
19
|
styler:,
|
18
20
|
truncation_indicator:,
|
19
21
|
value:,
|
@@ -21,8 +23,10 @@ module Tabulo
|
|
21
23
|
|
22
24
|
@alignment = alignment
|
23
25
|
@cell_data = cell_data
|
24
|
-
@padding_character = padding_character
|
25
26
|
@formatter = formatter
|
27
|
+
@left_padding = left_padding
|
28
|
+
@padding_character = padding_character
|
29
|
+
@right_padding = right_padding
|
26
30
|
@styler = styler
|
27
31
|
@truncation_indicator = truncation_indicator
|
28
32
|
@value = value
|
@@ -35,12 +39,12 @@ module Tabulo
|
|
35
39
|
end
|
36
40
|
|
37
41
|
# @!visibility private
|
38
|
-
def padded_truncated_subcells(target_height
|
39
|
-
total_padding_amount =
|
42
|
+
def padded_truncated_subcells(target_height)
|
43
|
+
total_padding_amount = @left_padding + @right_padding
|
40
44
|
truncated = (height > target_height)
|
41
45
|
(0...target_height).map do |subcell_index|
|
42
46
|
append_truncator = (truncated && (total_padding_amount != 0) && (subcell_index + 1 == target_height))
|
43
|
-
padded_subcell(subcell_index,
|
47
|
+
padded_subcell(subcell_index, append_truncator)
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
@@ -60,8 +64,11 @@ module Tabulo
|
|
60
64
|
end
|
61
65
|
end
|
62
66
|
|
63
|
-
def apply_styler(content)
|
64
|
-
|
67
|
+
def apply_styler(content, line_index)
|
68
|
+
case @styler.arity
|
69
|
+
when 4
|
70
|
+
@styler.call(@value, content, @cell_data, line_index)
|
71
|
+
when 3
|
65
72
|
@styler.call(@value, content, @cell_data)
|
66
73
|
else
|
67
74
|
@styler.call(@value, content)
|
@@ -72,13 +79,13 @@ module Tabulo
|
|
72
79
|
@subcells ||= calculate_subcells
|
73
80
|
end
|
74
81
|
|
75
|
-
def padded_subcell(subcell_index,
|
76
|
-
lpad = @padding_character *
|
82
|
+
def padded_subcell(subcell_index, append_truncator)
|
83
|
+
lpad = @padding_character * @left_padding
|
77
84
|
rpad =
|
78
85
|
if append_truncator
|
79
|
-
styled_truncation_indicator + padding(
|
86
|
+
styled_truncation_indicator(subcell_index) + padding(@right_padding - 1)
|
80
87
|
else
|
81
|
-
padding(
|
88
|
+
padding(@right_padding)
|
82
89
|
end
|
83
90
|
inner = subcell_index < height ? subcells[subcell_index] : padding(@width)
|
84
91
|
"#{lpad}#{inner}#{rpad}"
|
@@ -88,31 +95,35 @@ module Tabulo
|
|
88
95
|
@padding_character * amount
|
89
96
|
end
|
90
97
|
|
91
|
-
def styled_truncation_indicator
|
92
|
-
apply_styler(@truncation_indicator)
|
98
|
+
def styled_truncation_indicator(line_index)
|
99
|
+
apply_styler(@truncation_indicator, line_index)
|
93
100
|
end
|
94
101
|
|
95
102
|
def calculate_subcells
|
96
|
-
|
103
|
+
line_index = 0
|
104
|
+
formatted_content.split(Util::NEWLINE, -1).flat_map do |substr|
|
97
105
|
subsubcells, subsubcell, subsubcell_width = [], String.new(""), 0
|
98
106
|
|
99
107
|
substr.scan(/\X/).each do |grapheme_cluster|
|
100
108
|
grapheme_cluster_width = Unicode::DisplayWidth.of(grapheme_cluster)
|
101
109
|
if subsubcell_width + grapheme_cluster_width > @width
|
102
|
-
subsubcells << style_and_align_cell_content(subsubcell)
|
110
|
+
subsubcells << style_and_align_cell_content(subsubcell, line_index)
|
103
111
|
subsubcell_width = 0
|
104
112
|
subsubcell.clear
|
113
|
+
line_index += 1
|
105
114
|
end
|
106
115
|
|
107
116
|
subsubcell << grapheme_cluster
|
108
117
|
subsubcell_width += grapheme_cluster_width
|
109
118
|
end
|
110
119
|
|
111
|
-
subsubcells << style_and_align_cell_content(subsubcell)
|
120
|
+
subsubcells << style_and_align_cell_content(subsubcell, line_index)
|
121
|
+
line_index += 1
|
122
|
+
subsubcells
|
112
123
|
end
|
113
124
|
end
|
114
125
|
|
115
|
-
def style_and_align_cell_content(content)
|
126
|
+
def style_and_align_cell_content(content, line_index)
|
116
127
|
padding = Util.max(@width - Unicode::DisplayWidth.of(content), 0)
|
117
128
|
left_padding, right_padding =
|
118
129
|
case real_alignment
|
@@ -125,7 +136,7 @@ module Tabulo
|
|
125
136
|
[padding, 0]
|
126
137
|
end
|
127
138
|
|
128
|
-
"#{' ' * left_padding}#{apply_styler(content)}#{' ' * right_padding}"
|
139
|
+
"#{' ' * left_padding}#{apply_styler(content, line_index)}#{' ' * right_padding}"
|
129
140
|
end
|
130
141
|
|
131
142
|
def real_alignment
|
data/lib/tabulo/cell_data.rb
CHANGED
@@ -5,7 +5,8 @@ module Tabulo
|
|
5
5
|
# @attr source [Object] The member of this {Cell}'s {Table}'s underlying enumerable from which
|
6
6
|
# this {Cell}'s {Row} was derived.
|
7
7
|
# @attr row_index [Integer] The positional index of the {Cell}'s {Row}. The topmost {Row} of the
|
8
|
-
# {Table} has index 0, the next has index 1, etc..
|
8
|
+
# {Table} has index 0, the next has index 1, etc.. The header row(s) are not counted for the purpose
|
9
|
+
# of this numbering.
|
9
10
|
# @attr column_index [Integer] The positional index of the {Cell}'s {Column}. The leftmost {Column}
|
10
11
|
# of the {Table} has index 0, the next has index 1, etc..
|
11
12
|
CellData = Struct.new(:source, :row_index, :column_index)
|
data/lib/tabulo/column.rb
CHANGED
@@ -6,6 +6,8 @@ module Tabulo
|
|
6
6
|
attr_accessor :width
|
7
7
|
attr_reader :header
|
8
8
|
attr_reader :index
|
9
|
+
attr_reader :left_padding
|
10
|
+
attr_reader :right_padding
|
9
11
|
|
10
12
|
def initialize(
|
11
13
|
align_body:,
|
@@ -15,7 +17,9 @@ module Tabulo
|
|
15
17
|
header:,
|
16
18
|
header_styler:,
|
17
19
|
index:,
|
20
|
+
left_padding:,
|
18
21
|
padding_character:,
|
22
|
+
right_padding:,
|
19
23
|
styler:,
|
20
24
|
truncation_indicator:,
|
21
25
|
width:)
|
@@ -26,12 +30,19 @@ module Tabulo
|
|
26
30
|
@formatter = formatter
|
27
31
|
@header = header
|
28
32
|
@index = index
|
33
|
+
@left_padding = left_padding
|
34
|
+
@right_padding = right_padding
|
29
35
|
|
30
36
|
@header_styler =
|
31
|
-
if header_styler
|
32
|
-
|
33
|
-
|
34
|
-
|
37
|
+
if header_styler
|
38
|
+
case header_styler.arity
|
39
|
+
when 3
|
40
|
+
-> (_, str, cell_data, line_index) { header_styler.call(str, cell_data.column_index, line_index) }
|
41
|
+
when 2
|
42
|
+
-> (_, str, cell_data) { header_styler.call(str, cell_data.column_index) }
|
43
|
+
else
|
44
|
+
-> (_, str) { header_styler.call(str) }
|
45
|
+
end
|
35
46
|
else
|
36
47
|
-> (_, str) { str }
|
37
48
|
end
|
@@ -43,14 +54,16 @@ module Tabulo
|
|
43
54
|
end
|
44
55
|
|
45
56
|
def header_cell
|
46
|
-
if @header_styler.arity
|
57
|
+
if @header_styler.arity >= 3
|
47
58
|
cell_data = CellData.new(nil, nil, @index)
|
48
59
|
end
|
49
60
|
Cell.new(
|
50
61
|
alignment: @align_header,
|
51
62
|
cell_data: cell_data,
|
52
63
|
formatter: -> (s) { s },
|
64
|
+
left_padding: @left_padding,
|
53
65
|
padding_character: @padding_character,
|
66
|
+
right_padding: @right_padding,
|
54
67
|
styler: @header_styler,
|
55
68
|
truncation_indicator: @truncation_indicator,
|
56
69
|
value: @header,
|
@@ -66,7 +79,9 @@ module Tabulo
|
|
66
79
|
alignment: @align_body,
|
67
80
|
cell_data: cell_data,
|
68
81
|
formatter: @formatter,
|
82
|
+
left_padding: @left_padding,
|
69
83
|
padding_character: @padding_character,
|
84
|
+
right_padding: @right_padding,
|
70
85
|
styler: @styler,
|
71
86
|
truncation_indicator: @truncation_indicator,
|
72
87
|
value: body_cell_value(source, row_index: row_index, column_index: column_index),
|
@@ -82,6 +97,14 @@ module Tabulo
|
|
82
97
|
end
|
83
98
|
end
|
84
99
|
|
100
|
+
def padded_width
|
101
|
+
width + total_padding
|
102
|
+
end
|
103
|
+
|
104
|
+
def total_padding
|
105
|
+
@left_padding + @right_padding
|
106
|
+
end
|
107
|
+
|
85
108
|
private
|
86
109
|
|
87
110
|
def body_cell_data_required?
|
data/lib/tabulo/table.rb
CHANGED
@@ -35,21 +35,26 @@ module Tabulo
|
|
35
35
|
# be unique. Each element of the Array will be used to create a column whose content is
|
36
36
|
# created by calling the corresponding method on each element of sources. Note
|
37
37
|
# the {#add_column} method is a much more flexible way to set up columns on the table.
|
38
|
-
# @param [:left, :right, :center, :auto] align_body
|
38
|
+
# @param [:left, :right, :center, :auto] align_body Determines the alignment of body cell
|
39
39
|
# (i.e. non-header) content within columns in this Table. Can be overridden for individual columns
|
40
40
|
# using the <tt>align_body</tt> option passed to {#add_column}. If passed <tt>:auto</tt>,
|
41
41
|
# alignment is determined by cell content, with numbers aligned right, booleans
|
42
42
|
# center-aligned, and other values left-aligned.
|
43
|
-
# @param [:left, :right, :center] align_header
|
43
|
+
# @param [:left, :right, :center] align_header 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 [:
|
46
|
+
# @param [:left, :right, :center] align_header Determines the alignment of the table
|
47
|
+
# title, if present.
|
48
|
+
# @param [:ascii, :markdown, :modern, :blank, nil] border 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
|
@@ -58,7 +63,7 @@ module Tabulo
|
|
58
63
|
# borders and intersection characters consisting of whitespace only
|
59
64
|
# - `:classic` Like `:ascii`, but does not have a horizontal line at the bottom of the
|
60
65
|
# table. This reproduces the default behaviour in `tabulo` v1.
|
61
|
-
# @param [nil, #to_proc] border_styler
|
66
|
+
# @param [nil, #to_proc] border_styler A lambda or other callable object taking
|
62
67
|
# a single parameter, representing a section of the table's borders (which for this purpose
|
63
68
|
# include any horizontal and vertical lines inside the table), and returning a string.
|
64
69
|
# If passed <tt>nil</tt>, then no additional styling will be applied to borders. If passed a
|
@@ -67,28 +72,54 @@ module Tabulo
|
|
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
|
-
# @param [nil, Integer, Array] column_padding
|
75
|
+
# @param [nil, Integer, Array] column_padding 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
|
-
# @param [nil, #to_proc] formatter
|
83
|
+
# @param [nil, #to_proc] formatter The default formatter for columns in this
|
78
84
|
# table. See `formatter` option of {#add_column} for details.
|
79
85
|
# @param [:start, nil, Integer] header_frequency (:start) Controls the display of column headers.
|
80
86
|
# If passed <tt>:start</tt>, headers will be shown at the top of the table only. If passed <tt>nil</tt>,
|
81
87
|
# headers will not be shown. If passed an Integer N (> 0), headers will be shown at the top of the table,
|
82
88
|
# then repeated every N rows.
|
83
|
-
# @param [nil, #to_proc] header_styler
|
89
|
+
# @param [nil, #to_proc] header_styler The default header styler for columns in this
|
84
90
|
# table. See `header_styler` option of {#add_column} for details.
|
85
|
-
# @param [nil, Integer] row_divider_frequency
|
91
|
+
# @param [nil, Integer] row_divider_frequency Controls the display of horizontal row dividers within
|
86
92
|
# the table body. If passed <tt>nil</tt>, dividers will not be shown. If passed an Integer N (> 0),
|
87
93
|
# dividers will be shown after every N rows. The characters used to form the dividers are
|
88
94
|
# determined by the `border` option, and are the same as those used to form the bottom edge of the
|
89
95
|
# header row.
|
90
|
-
# @param [nil, #to_proc] styler
|
96
|
+
# @param [nil, #to_proc] styler The default styler for columns in this table. See `styler`
|
91
97
|
# option of {#add_column} for details.
|
98
|
+
# @param [nil, String] title 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 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
|
@@ -149,24 +184,24 @@ module Tabulo
|
|
149
184
|
# a method to be called on each item in the table sources to provide the content
|
150
185
|
# for this column. If a String is passed as the label, then it will be converted to
|
151
186
|
# a Symbol for the purpose of serving as this label.
|
152
|
-
# @param [:left, :center, :right, :auto, nil] align_body
|
187
|
+
# @param [:left, :center, :right, :auto, nil] align_body Specifies how the cell body contents
|
153
188
|
# should be aligned. If <tt>nil</tt> is passed, then the alignment is determined
|
154
189
|
# by the Table-level setting passed to the <tt>align_body</tt> option on Table initialization
|
155
190
|
# (which itself defaults to <tt>:auto</tt>). Otherwise this option determines the alignment of
|
156
191
|
# this column. If <tt>:auto</tt> is passed, the alignment is determined by the type of the cell
|
157
192
|
# value, with numbers aligned right, booleans center-aligned, and other values left-aligned.
|
158
193
|
# Note header text alignment is configured separately using the :align_header param.
|
159
|
-
# @param [:left, :center, :right, nil] align_header
|
194
|
+
# @param [:left, :center, :right, nil] align_header Specifies how the header text
|
160
195
|
# should be aligned. If <tt>nil</tt> is passed, then the alignment is determined
|
161
196
|
# by the Table-level setting passed to the <tt>align_header</tt> (which itself defaults
|
162
197
|
# to <tt>:center</tt>). Otherwise, this option determines the alignment of the header
|
163
198
|
# content for this column.
|
164
|
-
# @param [Symbol, String, Integer, nil] before
|
199
|
+
# @param [Symbol, String, Integer, nil] before The label of the column before (i.e. to
|
165
200
|
# the left of) which the new column should inserted. If <tt>nil</tt> is passed, it will be
|
166
201
|
# inserted after all other columns. If there is no column with the given label, then an
|
167
202
|
# {InvalidColumnLabelError} will be raised. A non-Integer labelled column can be identified
|
168
203
|
# in either String or Symbol form for this purpose.
|
169
|
-
# @param [#to_proc] formatter
|
204
|
+
# @param [#to_proc] formatter 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
206
|
# is distinct from the extractor and the styler (see below).
|
172
207
|
# For example, if the extractor for this column generates a Date, then the formatter might format
|
@@ -184,11 +219,11 @@ module Tabulo
|
|
184
219
|
# whether the {Cell} is an odd- or even-numbered {Row}, to arrange for different formatting
|
185
220
|
# to be applied to alternating rows.
|
186
221
|
# See the documentation for {CellData} for more.
|
187
|
-
# @param [nil, #to_s] header
|
222
|
+
# @param [nil, #to_s] header Text to be displayed in the column header. If passed nil,
|
188
223
|
# the column's label will also be used as its header text.
|
189
224
|
# @param [nil, #to_proc] header_styler (nil) A lambda or other callable object that will
|
190
225
|
# determine the colors or other styling applied to the header content. Can be passed
|
191
|
-
# <tt>nil</tt>, or can be passed a callable that takes
|
226
|
+
# <tt>nil</tt>, or can be passed a callable that takes 1, 2 or 3 parameters:
|
192
227
|
# * If passed <tt>nil</tt>, then no additional styling will be applied to the cell content
|
193
228
|
# (other than what was already applied by the <tt>formatter</tt>).
|
194
229
|
# * If passed a callable, then that callable will be called for each line of content within
|
@@ -205,10 +240,23 @@ module Tabulo
|
|
205
240
|
# second parameter is an Integer representing the positional index of this header's {Column},
|
206
241
|
# with the leftmost column having index 0, the next having index 1 etc.. This can be
|
207
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.
|
208
249
|
#
|
209
250
|
# Note that if the header content is truncated, then any <tt>header_styler</tt> will be applied to the
|
210
251
|
# truncation indicator character as well as to the truncated content.
|
211
|
-
# @param [nil,
|
252
|
+
# @param [nil, Integer, Array] padding 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 A lambda or other callable object that will determine
|
212
260
|
# the colors or other styling applied to the formatted value of the cell. Can be passed
|
213
261
|
# <tt>nil</tt>, or can be passed a callable that takes either 2 or 3 parameters:
|
214
262
|
# * If passed <tt>nil</tt>, then no additional styling will be applied to the cell content
|
@@ -231,10 +279,16 @@ module Tabulo
|
|
231
279
|
# {CellData#row_index} attribute can be inspected to determine whether the {Cell} is an
|
232
280
|
# odd- or even-numbered {Row}, to arrange for different styling to be applied to
|
233
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.
|
234
288
|
#
|
235
289
|
# Note that if the content of a cell is truncated, then the whatever styling is applied by the
|
236
290
|
# <tt>styler</tt> to the cell content will also be applied to the truncation indicator character.
|
237
|
-
# @param [Integer] width
|
291
|
+
# @param [Integer] width Specifies the width of the column, excluding padding. If
|
238
292
|
# nil, then the column will take the width provided by the `column_width` param
|
239
293
|
# with which the Table was initialized.
|
240
294
|
# @param [#to_proc] extractor A block or other callable that will be passed each of the {Table}
|
@@ -251,10 +305,17 @@ module Tabulo
|
|
251
305
|
# Table. (This is case-sensitive, but is insensitive to whether a String or Symbol is passed
|
252
306
|
# to the label parameter.)
|
253
307
|
def add_column(label, align_body: nil, align_header: nil, before: nil, formatter: nil,
|
254
|
-
header: nil, header_styler: nil, styler: nil, width: nil, &extractor)
|
308
|
+
header: nil, header_styler: nil, padding: nil, styler: nil, width: nil, &extractor)
|
255
309
|
|
256
310
|
column_label = normalize_column_label(label)
|
257
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
|
+
|
258
319
|
if column_registry.include?(column_label)
|
259
320
|
raise InvalidColumnLabelError, "Column label already used in this table."
|
260
321
|
end
|
@@ -267,7 +328,9 @@ module Tabulo
|
|
267
328
|
header: (header || label).to_s,
|
268
329
|
header_styler: header_styler || @header_styler,
|
269
330
|
index: column_registry.count,
|
331
|
+
left_padding: left_padding,
|
270
332
|
padding_character: PADDING_CHARACTER,
|
333
|
+
right_padding: right_padding,
|
271
334
|
styler: styler || @styler,
|
272
335
|
truncation_indicator: @truncation_indicator,
|
273
336
|
width: width || @column_width,
|
@@ -309,7 +372,7 @@ module Tabulo
|
|
309
372
|
if column_registry.any?
|
310
373
|
bottom_edge = horizontal_rule(:bottom)
|
311
374
|
rows = map(&:to_s)
|
312
|
-
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])
|
313
376
|
else
|
314
377
|
""
|
315
378
|
end
|
@@ -339,7 +402,8 @@ module Tabulo
|
|
339
402
|
end
|
340
403
|
end
|
341
404
|
|
342
|
-
# @return [String]
|
405
|
+
# @return [String] a graphical representation of the Table column headers formatted with fixed
|
406
|
+
# width plain text.
|
343
407
|
def formatted_header
|
344
408
|
cells = get_columns.map(&:header_cell)
|
345
409
|
format_row(cells, @wrap_header_cells_to)
|
@@ -348,9 +412,11 @@ module Tabulo
|
|
348
412
|
# Produce a horizontal dividing line suitable for printing at the top, bottom or middle
|
349
413
|
# of the table.
|
350
414
|
#
|
351
|
-
# @param [:top, :middle, :bottom
|
352
|
-
# for which the resulting horizontal dividing line is intended to
|
353
|
-
# This determines the border characters that are used to construct the line.
|
415
|
+
# @param [:top, :middle, :bottom, :title_top, :title_bottom] position
|
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.
|
354
420
|
# @return [String] an "ASCII" graphical representation of a horizontal
|
355
421
|
# dividing line.
|
356
422
|
# @example Print a horizontal divider between each pair of rows, and again at the bottom:
|
@@ -364,14 +430,17 @@ module Tabulo
|
|
364
430
|
# It may be that `:top`, `:middle` and `:bottom` all look the same. Whether
|
365
431
|
# this is the case depends on the characters used for the table border.
|
366
432
|
def horizontal_rule(position = :bottom)
|
367
|
-
column_widths = get_columns.map { |column| column.width +
|
433
|
+
column_widths = get_columns.map { |column| column.width + column.total_padding }
|
368
434
|
@border_instance.horizontal_rule(column_widths, position)
|
369
435
|
end
|
370
436
|
|
371
|
-
#
|
437
|
+
# Resets all the column widths so that each column is *just* wide enough to accommodate
|
372
438
|
# its header text as well as the formatted content of each its cells for the entire
|
373
439
|
# collection, together with a single character of padding on either side of the column,
|
374
|
-
# 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.
|
375
444
|
#
|
376
445
|
# Note that calling this method will cause the entire source Enumerable to
|
377
446
|
# be traversed and all the column extractors and formatters to be applied in order
|
@@ -382,7 +451,7 @@ module Tabulo
|
|
382
451
|
# is called. If the source Enumerable changes between that point, and the point when
|
383
452
|
# the Table is printed, then columns will *not* be resized yet again on printing.
|
384
453
|
#
|
385
|
-
# @param [nil, Numeric] max_table_width
|
454
|
+
# @param [nil, Numeric] max_table_width With no args, or if passed <tt>:auto</tt>,
|
386
455
|
# stops the total table width (including padding and borders) from expanding beyond the
|
387
456
|
# bounds of the terminal screen.
|
388
457
|
# If passed <tt>nil</tt>, the table width will not be capped.
|
@@ -397,18 +466,27 @@ module Tabulo
|
|
397
466
|
# Table will refuse to shrink itself.
|
398
467
|
# @return [Table] the Table itself
|
399
468
|
def pack(max_table_width: :auto)
|
400
|
-
get_columns.each { |column| column.width = wrapped_width(column.header) }
|
469
|
+
get_columns.each { |column| column.width = Util.wrapped_width(column.header) }
|
401
470
|
|
402
471
|
@sources.each_with_index do |source, row_index|
|
403
472
|
get_columns.each_with_index do |column, column_index|
|
404
473
|
cell = column.body_cell(source, row_index: row_index, column_index: column_index)
|
405
|
-
cell_width = wrapped_width(cell.formatted_content)
|
474
|
+
cell_width = Util.wrapped_width(cell.formatted_content)
|
406
475
|
column.width = Util.max(column.width, cell_width)
|
407
476
|
end
|
408
477
|
end
|
409
478
|
|
410
|
-
if max_table_width
|
411
|
-
|
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
|
+
)
|
412
490
|
end
|
413
491
|
|
414
492
|
self
|
@@ -433,8 +511,9 @@ module Tabulo
|
|
433
511
|
# The following options are the same as the keyword params for the {#initialize} method for
|
434
512
|
# {Table}: <tt>column_width</tt>, <tt>column_padding</tt>, <tt>formatter</tt>,
|
435
513
|
# <tt>header_frequency</tt>, <tt>row_divider_frequency</tt>, <tt>wrap_header_cells_to</tt>,
|
436
|
-
# <tt>wrap_body_cells_to</tt>, <tt>border</tt>, <tt>border_styler</tt>, <tt>
|
437
|
-
# <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>.
|
438
517
|
# These are applied in the same way as documented for {#initialize}, when
|
439
518
|
# creating the new, transposed Table. Any options not specified explicitly in the call to {#transpose}
|
440
519
|
# will inherit their values from the original {Table} (with the exception of settings
|
@@ -444,13 +523,13 @@ module Tabulo
|
|
444
523
|
# new Table, which contains the names of "fields" (corresponding to the original Table's
|
445
524
|
# column headings). If this is not provided, then by default this column will be made just
|
446
525
|
# wide enough to accommodate its contents.
|
447
|
-
# @option opts [String] :field_names_header
|
526
|
+
# @option opts [String] :field_names_header By default the left-most column will have a
|
448
527
|
# blank header; but this can be overridden by passing a String to this option.
|
449
|
-
# @option opts [:left, :center, :right] :field_names_header_alignment
|
528
|
+
# @option opts [:left, :center, :right] :field_names_header_alignment Specifies how the
|
450
529
|
# header text of the left-most column (if it has header text) should be aligned.
|
451
|
-
# @option opts [:left, :center, :right] :field_names_body_alignment
|
530
|
+
# @option opts [:left, :center, :right] :field_names_body_alignment Specifies how the
|
452
531
|
# body text of the left-most column should be aligned.
|
453
|
-
# @option opts [#to_proc] :headers
|
532
|
+
# @option opts [#to_proc] :headers A lambda or other callable object that
|
454
533
|
# will be passed in turn each of the elements of the current Table's <tt>sources</tt>
|
455
534
|
# Enumerable, to determine the text to be displayed in the header of each column of the
|
456
535
|
# new Table (other than the left-most column's header, which is determined as described
|
@@ -458,9 +537,9 @@ module Tabulo
|
|
458
537
|
# @return [Table] a new {Table}
|
459
538
|
# @raise [InvalidBorderError] if invalid argument passed to `border` parameter.
|
460
539
|
def transpose(opts = {})
|
461
|
-
default_opts = [:align_body, :align_header, :border, :border_styler, :column_padding,
|
462
|
-
:formatter, :header_frequency, :row_divider_frequency, :
|
463
|
-
: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|
|
464
543
|
[sym, instance_variable_get("@#{sym}")]
|
465
544
|
end.to_h
|
466
545
|
|
@@ -496,25 +575,15 @@ module Tabulo
|
|
496
575
|
cells = get_columns.map.with_index { |c, i| c.body_cell(source, row_index: index, column_index: i) }
|
497
576
|
inner = format_row(cells, @wrap_body_cells_to)
|
498
577
|
|
499
|
-
if header == :top
|
500
|
-
|
501
|
-
horizontal_rule(:
|
502
|
-
|
503
|
-
|
504
|
-
inner
|
505
|
-
].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])
|
506
583
|
elsif header
|
507
|
-
|
508
|
-
horizontal_rule(:middle),
|
509
|
-
formatted_header,
|
510
|
-
horizontal_rule(:middle),
|
511
|
-
inner
|
512
|
-
].reject(&:empty?))
|
584
|
+
Util.condense_lines([horizontal_rule(:middle), formatted_header, horizontal_rule(:middle), inner])
|
513
585
|
elsif divider
|
514
|
-
|
515
|
-
horizontal_rule(:middle),
|
516
|
-
inner
|
517
|
-
].reject(&:empty?))
|
586
|
+
Util.condense_lines([horizontal_rule(:middle), inner])
|
518
587
|
else
|
519
588
|
inner
|
520
589
|
end
|
@@ -548,6 +617,55 @@ module Tabulo
|
|
548
617
|
@column_registry[label] = column
|
549
618
|
end
|
550
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
|
+
|
551
669
|
# @!visibility private
|
552
670
|
def normalize_column_label(label)
|
553
671
|
case label
|
@@ -558,14 +676,32 @@ module Tabulo
|
|
558
676
|
end
|
559
677
|
end
|
560
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
|
+
|
561
697
|
# @!visibility private
|
562
698
|
def shrink_to(max_table_width)
|
563
699
|
columns = get_columns
|
564
700
|
num_columns = columns.count
|
565
|
-
|
566
|
-
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 }
|
567
703
|
total_borders = num_columns + 1
|
568
|
-
unadjusted_table_width =
|
704
|
+
unadjusted_table_width = total_columns_padded_width + total_borders
|
569
705
|
|
570
706
|
# Ensure max table width is at least wide enough to accommodate table borders and padding
|
571
707
|
# and one character of content.
|
@@ -582,11 +718,6 @@ module Tabulo
|
|
582
718
|
end
|
583
719
|
end
|
584
720
|
|
585
|
-
# @!visibility private
|
586
|
-
def total_column_padding
|
587
|
-
@left_column_padding + @right_column_padding
|
588
|
-
end
|
589
|
-
|
590
721
|
# @!visibility private
|
591
722
|
#
|
592
723
|
# Formats a single header row or body row as a String.
|
@@ -604,18 +735,13 @@ module Tabulo
|
|
604
735
|
max_cell_height = cells.map(&:height).max
|
605
736
|
row_height = ([wrap_cells_to, max_cell_height].compact.min || 1)
|
606
737
|
subcell_stacks = cells.map do |cell|
|
607
|
-
cell.padded_truncated_subcells(row_height
|
738
|
+
cell.padded_truncated_subcells(row_height)
|
608
739
|
end
|
609
740
|
subrows = subcell_stacks.transpose.map do |subrow_components|
|
610
741
|
@border_instance.join_cell_contents(subrow_components)
|
611
742
|
end
|
612
743
|
|
613
|
-
join_lines(subrows)
|
614
|
-
end
|
615
|
-
|
616
|
-
# @!visibility private
|
617
|
-
def join_lines(lines)
|
618
|
-
lines.join($/) # join strings with cross-platform newline
|
744
|
+
Util.join_lines(subrows)
|
619
745
|
end
|
620
746
|
|
621
747
|
# @!visibility private
|
@@ -633,13 +759,5 @@ module Tabulo
|
|
633
759
|
c
|
634
760
|
end
|
635
761
|
|
636
|
-
# @!visibility private
|
637
|
-
# @return [Integer] the length of the longest segment of str when split by newlines
|
638
|
-
def wrapped_width(str)
|
639
|
-
segments = str.split($/)
|
640
|
-
segments.inject(1) do |longest_length_so_far, segment|
|
641
|
-
Util.max(longest_length_so_far, Unicode::DisplayWidth.of(segment))
|
642
|
-
end
|
643
|
-
end
|
644
762
|
end
|
645
763
|
end
|