tabulo 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.5.0
1
+ 2.6.0
@@ -3,107 +3,62 @@ 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
- corner_top_left: "+",
9
- corner_top_right: "+",
10
- corner_bottom_right: "+",
11
- corner_bottom_left: "+",
12
- edge_top: "-",
13
- edge_right: "|",
14
- edge_bottom: "-",
15
- edge_left: "|",
16
- tee_top: "+",
17
- tee_right: "+",
18
- tee_bottom: "+",
19
- tee_left: "+",
20
- divider_vertical: "|",
21
- divider_horizontal: "-",
22
- intersection: "+",
23
- },
24
- classic: {
25
- corner_top_left: "+",
26
- corner_top_right: "+",
27
- edge_top: "-",
28
- edge_right: "|",
29
- edge_left: "|",
30
- tee_top: "+",
31
- tee_right: "+",
32
- tee_left: "+",
33
- divider_vertical: "|",
34
- divider_horizontal: "-",
35
- intersection: "+",
36
- },
37
- reduced_ascii: {
38
- corner_top_left: "",
39
- corner_top_right: "",
40
- corner_bottom_right: "",
41
- corner_bottom_left: "",
42
- edge_top: "-",
43
- edge_right: "",
44
- edge_bottom: "-",
45
- edge_left: "",
46
- tee_top: " ",
47
- tee_right: "",
48
- tee_bottom: " ",
49
- tee_left: "",
50
- divider_vertical: " ",
51
- divider_horizontal: "-",
52
- intersection: " ",
53
- },
54
- reduced_modern: {
55
- corner_top_left: "",
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
@@ -147,7 +102,7 @@ module Tabulo
147
102
 
148
103
  def self.options(kind)
149
104
  opts = STYLES[kind]
150
- return opts if opts
105
+ return opts.to_h if opts
151
106
  raise InvalidBorderError
152
107
  end
153
108
 
@@ -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, padding_amount_left, padding_amount_right)
39
- total_padding_amount = padding_amount_left + padding_amount_right
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, padding_amount_left, padding_amount_right, append_truncator)
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
- if @styler.arity == 3
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, padding_amount_left, padding_amount_right, append_truncator)
76
- lpad = @padding_character * padding_amount_left
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(padding_amount_right - 1)
86
+ styled_truncation_indicator(subcell_index) + padding(@right_padding - 1)
80
87
  else
81
- padding(padding_amount_right)
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
103
+ line_index = 0
96
104
  formatted_content.split($/, -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
@@ -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 && (header_styler.arity == 2)
32
- -> (_, str, cell_data) { header_styler.call(str, cell_data.column_index) }
33
- elsif header_styler
34
- -> (_, str) { header_styler.call(str) }
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 == 3
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?
@@ -76,7 +76,8 @@ module Tabulo
76
76
  # either side of each column. If passed an Integer, then the given amount of padding is
77
77
  # applied to each side of each column. If passed a two-element Array, then the first element of the
78
78
  # Array indicates the amount of padding to apply to the left of each column, and the second
79
- # 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}.
80
81
  # @param [Integer, nil] column_width The default column width for columns in this
81
82
  # table, not excluding padding. If <tt>nil</tt>, then {DEFAULT_COLUMN_WIDTH} will be used.
82
83
  # @param [nil, #to_proc] formatter (:to_s.to_proc) The default formatter for columns in this
@@ -99,14 +100,26 @@ module Tabulo
99
100
  # will cause it to cease being valid Markdown when rendered, since Markdown engines do not generally
100
101
  # support adding a caption element (i.e. title) to tables.
101
102
  # @param [nil, #to_proc] title_styler (nil) A lambda or other callable object that will
102
- # determine the colors or other styling applied to the table title. If the table doesn't have
103
- # a title, this parameter has no effect. If passed a single-parameter callable, then that
104
- # callable will be called for each line of content within the title, and the resulting string
105
- # rendered in place of that line.
106
- # The extra width of the string returned by the <tt>title_styler</tt> is not taken into
107
- # consideration by the internal table width calculations involved in rendering the
108
- # table. Thus it can be used to apply ANSI escape codes to title content, to color the
109
- # title for example, without breaking the table formatting.
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
+ #
110
123
  # @param [nil, String] truncation_indicator Determines the character used to indicate that a
111
124
  # cell's content has been truncated. If omitted or passed <tt>nil</tt>,
112
125
  # defaults to {DEFAULT_TRUNCATION_INDICATOR}. If passed something other than <tt>nil</tt> or
@@ -142,7 +155,7 @@ module Tabulo
142
155
  @column_padding = (column_padding || DEFAULT_COLUMN_PADDING)
143
156
 
144
157
  @left_column_padding, @right_column_padding =
145
- Array === @column_padding ? @column_padding : [@column_padding, @column_padding]
158
+ (Array === @column_padding ? @column_padding : [@column_padding, @column_padding])
146
159
 
147
160
  @column_width = (column_width || DEFAULT_COLUMN_WIDTH)
148
161
  @formatter = formatter
@@ -210,7 +223,7 @@ module Tabulo
210
223
  # the column's label will also be used as its header text.
211
224
  # @param [nil, #to_proc] header_styler (nil) A lambda or other callable object that will
212
225
  # determine the colors or other styling applied to the header content. Can be passed
213
- # <tt>nil</tt>, or can be passed a callable that takes either 1 or 2 parameters:
226
+ # <tt>nil</tt>, or can be passed a callable that takes 1, 2 or 3 parameters:
214
227
  # * If passed <tt>nil</tt>, then no additional styling will be applied to the cell content
215
228
  # (other than what was already applied by the <tt>formatter</tt>).
216
229
  # * If passed a callable, then that callable will be called for each line of content within
@@ -227,9 +240,22 @@ module Tabulo
227
240
  # second parameter is an Integer representing the positional index of this header's {Column},
228
241
  # with the leftmost column having index 0, the next having index 1 etc.. This can be
229
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.
230
249
  #
231
250
  # Note that if the header content is truncated, then any <tt>header_styler</tt> will be applied to the
232
251
  # truncation indicator character as well as to the truncated content.
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.
233
259
  # @param [nil, #to_proc] styler (nil) A lambda or other callable object that will determine
234
260
  # the colors or other styling applied to the formatted value of the cell. Can be passed
235
261
  # <tt>nil</tt>, or can be passed a callable that takes either 2 or 3 parameters:
@@ -253,6 +279,12 @@ module Tabulo
253
279
  # {CellData#row_index} attribute can be inspected to determine whether the {Cell} is an
254
280
  # odd- or even-numbered {Row}, to arrange for different styling to be applied to
255
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.
256
288
  #
257
289
  # Note that if the content of a cell is truncated, then the whatever styling is applied by the
258
290
  # <tt>styler</tt> to the cell content will also be applied to the truncation indicator character.
@@ -273,10 +305,17 @@ module Tabulo
273
305
  # Table. (This is case-sensitive, but is insensitive to whether a String or Symbol is passed
274
306
  # to the label parameter.)
275
307
  def add_column(label, align_body: nil, align_header: nil, before: nil, formatter: nil,
276
- header: nil, header_styler: nil, styler: nil, width: nil, &extractor)
308
+ header: nil, header_styler: nil, padding: nil, styler: nil, width: nil, &extractor)
277
309
 
278
310
  column_label = normalize_column_label(label)
279
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
+
280
319
  if column_registry.include?(column_label)
281
320
  raise InvalidColumnLabelError, "Column label already used in this table."
282
321
  end
@@ -289,7 +328,9 @@ module Tabulo
289
328
  header: (header || label).to_s,
290
329
  header_styler: header_styler || @header_styler,
291
330
  index: column_registry.count,
331
+ left_padding: left_padding,
292
332
  padding_character: PADDING_CHARACTER,
333
+ right_padding: right_padding,
293
334
  styler: styler || @styler,
294
335
  truncation_indicator: @truncation_indicator,
295
336
  width: width || @column_width,
@@ -331,7 +372,7 @@ module Tabulo
331
372
  if column_registry.any?
332
373
  bottom_edge = horizontal_rule(:bottom)
333
374
  rows = map(&:to_s)
334
- 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])
335
376
  else
336
377
  ""
337
378
  end
@@ -389,7 +430,7 @@ module Tabulo
389
430
  # It may be that `:top`, `:middle` and `:bottom` all look the same. Whether
390
431
  # this is the case depends on the characters used for the table border.
391
432
  def horizontal_rule(position = :bottom)
392
- column_widths = get_columns.map { |column| column.width + total_column_padding }
433
+ column_widths = get_columns.map { |column| column.width + column.total_padding }
393
434
  @border_instance.horizontal_rule(column_widths, position)
394
435
  end
395
436
 
@@ -425,22 +466,27 @@ module Tabulo
425
466
  # Table will refuse to shrink itself.
426
467
  # @return [Table] the Table itself
427
468
  def pack(max_table_width: :auto)
428
- get_columns.each { |column| column.width = wrapped_width(column.header) }
469
+ get_columns.each { |column| column.width = Util.wrapped_width(column.header) }
429
470
 
430
471
  @sources.each_with_index do |source, row_index|
431
472
  get_columns.each_with_index do |column, column_index|
432
473
  cell = column.body_cell(source, row_index: row_index, column_index: column_index)
433
- cell_width = wrapped_width(cell.formatted_content)
474
+ cell_width = Util.wrapped_width(cell.formatted_content)
434
475
  column.width = Util.max(column.width, cell_width)
435
476
  end
436
477
  end
437
478
 
438
- if max_table_width
439
- shrink_to(max_table_width == :auto ? TTY::Screen.width : max_table_width)
440
- end
479
+ shrink_to(max_table_width == :auto ? TTY::Screen.width : max_table_width) if max_table_width
441
480
 
442
481
  if @title
443
- expand_to(Unicode::DisplayWidth.of(@title) + total_column_padding + (@border == :blank ? 0 : 2))
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
+ )
444
490
  end
445
491
 
446
492
  self
@@ -530,33 +576,14 @@ module Tabulo
530
576
  inner = format_row(cells, @wrap_body_cells_to)
531
577
 
532
578
  if @title && header == :top
533
- join_lines([
534
- horizontal_rule(:title_top),
535
- formatted_title,
536
- horizontal_rule(:title_bottom),
537
- formatted_header,
538
- horizontal_rule(:middle),
539
- inner
540
- ].reject(&:empty?))
579
+ Util.condense_lines([horizontal_rule(:title_top), formatted_title, horizontal_rule(:title_bottom),
580
+ formatted_header, horizontal_rule(:middle), inner])
541
581
  elsif header == :top
542
- join_lines([
543
- horizontal_rule(:top),
544
- formatted_header,
545
- horizontal_rule(:middle),
546
- inner
547
- ].reject(&:empty?))
582
+ Util.condense_lines([horizontal_rule(:top), formatted_header, horizontal_rule(:middle), inner])
548
583
  elsif header
549
- join_lines([
550
- horizontal_rule(:middle),
551
- formatted_header,
552
- horizontal_rule(:middle),
553
- inner
554
- ].reject(&:empty?))
584
+ Util.condense_lines([horizontal_rule(:middle), formatted_header, horizontal_rule(:middle), inner])
555
585
  elsif divider
556
- join_lines([
557
- horizontal_rule(:middle),
558
- inner
559
- ].reject(&:empty?))
586
+ Util.condense_lines([horizontal_rule(:middle), inner])
560
587
  else
561
588
  inner
562
589
  end
@@ -593,23 +620,34 @@ module Tabulo
593
620
  # @visibility private
594
621
  def formatted_title
595
622
  columns = get_columns
596
- num_fudged_columns = columns.count - 1
597
- basic_width = columns.inject(0) { |total_width, column| total_width + column.width }
623
+
598
624
  extra_for_internal_dividers = (@border == :blank ? 0 : 1)
599
- extra_for_internal_padding = @left_column_padding + @right_column_padding
600
- extra_total = num_fudged_columns * (extra_for_internal_dividers + extra_for_internal_padding)
601
- title_cell_width = basic_width + extra_total
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
+
602
632
  styler =
603
633
  if @title_styler
604
- -> (v, s) { @title_styler.call(s) }
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
605
640
  else
606
- -> (v, s) { s }
641
+ -> (_val, str) { str }
607
642
  end
643
+
608
644
  title_cell = Cell.new(
609
645
  alignment: @align_title,
610
646
  cell_data: nil,
611
647
  formatter: -> (s) { s },
648
+ left_padding: columns.first.left_padding,
612
649
  padding_character: PADDING_CHARACTER,
650
+ right_padding: columns.last.right_padding,
613
651
  styler: styler,
614
652
  truncation_indicator: @truncation_indicator,
615
653
  value: @title,
@@ -619,13 +657,13 @@ module Tabulo
619
657
  max_cell_height = cells.map(&:height).max
620
658
  row_height = ([nil, max_cell_height].compact.min || 1)
621
659
  subcell_stacks = cells.map do |cell|
622
- cell.padded_truncated_subcells(row_height, @left_column_padding, @right_column_padding)
660
+ cell.padded_truncated_subcells(row_height)
623
661
  end
624
662
  subrows = subcell_stacks.transpose.map do |subrow_components|
625
663
  @border_instance.join_cell_contents(subrow_components)
626
664
  end
627
665
 
628
- join_lines(subrows)
666
+ Util.join_lines(subrows)
629
667
  end
630
668
 
631
669
  # @!visibility private
@@ -642,10 +680,9 @@ module Tabulo
642
680
  def expand_to(min_table_width)
643
681
  columns = get_columns
644
682
  num_columns = columns.count
645
- total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
646
- total_padding = num_columns * total_column_padding
683
+ total_columns_padded_width = columns.inject(0) { |sum, column| sum + column.padded_width }
647
684
  total_borders = num_columns + 1
648
- unadjusted_table_width = total_columns_width + total_padding + total_borders
685
+ unadjusted_table_width = total_columns_padded_width + total_borders
649
686
  required_increase = Util.max(min_table_width - unadjusted_table_width, 0)
650
687
 
651
688
  required_increase.times do
@@ -661,10 +698,10 @@ module Tabulo
661
698
  def shrink_to(max_table_width)
662
699
  columns = get_columns
663
700
  num_columns = columns.count
664
- total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
665
- total_padding = num_columns * total_column_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 }
666
703
  total_borders = num_columns + 1
667
- unadjusted_table_width = total_columns_width + total_padding + total_borders
704
+ unadjusted_table_width = total_columns_padded_width + total_borders
668
705
 
669
706
  # Ensure max table width is at least wide enough to accommodate table borders and padding
670
707
  # and one character of content.
@@ -681,11 +718,6 @@ module Tabulo
681
718
  end
682
719
  end
683
720
 
684
- # @!visibility private
685
- def total_column_padding
686
- @left_column_padding + @right_column_padding
687
- end
688
-
689
721
  # @!visibility private
690
722
  #
691
723
  # Formats a single header row or body row as a String.
@@ -703,18 +735,13 @@ module Tabulo
703
735
  max_cell_height = cells.map(&:height).max
704
736
  row_height = ([wrap_cells_to, max_cell_height].compact.min || 1)
705
737
  subcell_stacks = cells.map do |cell|
706
- cell.padded_truncated_subcells(row_height, @left_column_padding, @right_column_padding)
738
+ cell.padded_truncated_subcells(row_height)
707
739
  end
708
740
  subrows = subcell_stacks.transpose.map do |subrow_components|
709
741
  @border_instance.join_cell_contents(subrow_components)
710
742
  end
711
743
 
712
- join_lines(subrows)
713
- end
714
-
715
- # @!visibility private
716
- def join_lines(lines)
717
- lines.join($/) # join strings with cross-platform newline
744
+ Util.join_lines(subrows)
718
745
  end
719
746
 
720
747
  # @!visibility private
@@ -732,13 +759,5 @@ module Tabulo
732
759
  c
733
760
  end
734
761
 
735
- # @!visibility private
736
- # @return [Integer] the length of the longest segment of str when split by newlines
737
- def wrapped_width(str)
738
- segments = str.split($/)
739
- segments.inject(1) do |longest_length_so_far, segment|
740
- Util.max(longest_length_so_far, Unicode::DisplayWidth.of(segment))
741
- end
742
- end
743
762
  end
744
763
  end