tabulo 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91b39d66ae733e33e28f3c16b9a8aee6fcf734db34b404429b1bba72063c1cb8
4
- data.tar.gz: aa5b690e3b1d6ee9819e2a097c20fefffcf4559149d3574ca6d5514dab78f86e
3
+ metadata.gz: edb6043945662b09cf500a65acec2fa51eac9c9397e7c58a890379bc174424cd
4
+ data.tar.gz: f7866a1ac405b431752ccba6a81678c9533b245384290c913bb015267ff8f7cd
5
5
  SHA512:
6
- metadata.gz: 690f7c809c3dc9a47ec6e7f736f46fdbc129e21c23723d163ff6dc545c97b1b068c622ee88a943c5e858ff2f53bc59d490680b02a85b521652db8cfd6b5194ff
7
- data.tar.gz: aaec0cb5f11f9724ba084bd3af961fca69a054601d1c86b6cc8a1fdf22a193f6de12abe25b2c41838db583dff06e4397036a3bade37da4ceb71d9494b7c659e6
6
+ metadata.gz: e67b0006b767a5dbe884bad9296636a01e1d5bdc57c6ffd2689f9315a9465e899e79e74508e2ae7c83c37869bfb3a523931accd2e5d6db346e104794a6c844ec
7
+ data.tar.gz: 663b95287868f59f2c29f75564cb660b5c1b2462984f351d6abd6ff057228fd1f8af5a3c7a99d68ed99740b90a493fe18bd9e4a7228f45dc312c929aeb7308a3
@@ -6,4 +6,4 @@ rvm:
6
6
  - 2.3.8
7
7
  - 2.4.6
8
8
  - 2.5.5
9
- - 2.6.2
9
+ - 2.6.3
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ### v1.5.0
4
+
5
+ * Support use of ANSI escape sequences to add colours and
6
+ other styling to table elements without breaking the formatting.
7
+ * Major refactor, moving various computations into a new Cell class.
8
+
3
9
  ### v1.4.1
4
10
 
5
11
  * Minor documentation fix
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  Tabulo is a terminal table generator for Ruby.
11
11
 
12
- It offers a DRY, "column-centric" interface, and is designed to make it very easy it produce highly
12
+ It offers a DRY, "column-centric" interface, and is designed to make it very easy to produce highly
13
13
  readable tables, even from large and unwieldy data sets and streams.
14
14
 
15
15
  ## Overview
@@ -63,6 +63,7 @@ end
63
63
  * The header row can be [repeated](#repeating-headers) at arbitrary intervals.
64
64
  * Newlines within cell content are correctly handled.
65
65
  * Multibyte characters are correctly handled.
66
+ * Apply [colours](#colours-and-styling) and other styling to table content without breaking the table.
66
67
  * Easily [transpose](#transposition) the table, so that rows are swapped with columns.
67
68
  * [Customize](#additional-configuration-options) border and divider characters.
68
69
  * Use a [DRY initialization interface](#configuring-columns): by being "column based", it is
@@ -86,6 +87,7 @@ Tabulo has also been ported to Crystal (with some modifications): see [Tablo](ht
86
87
  * [Automating column widths](#automating-column-widths)
87
88
  * [Overflow handling](#overflow-handling)
88
89
  * [Formatting cell values](#formatting-cell-values)
90
+ * [Colours and stying[(#colours-and-styling)
89
91
  * [Repeating headers](#repeating-headers)
90
92
  * [Using a Table Enumerator](#using-a-table-enumerator)
91
93
  * [Accessing cell values](#accessing-cell-values)
@@ -128,6 +130,15 @@ the columns you want to generate.
128
130
  A simple case involves initializing columns from symbols corresponding to methods on members of the
129
131
  underlying `Enumerable`. In this case the symbol also provides the header for each column:
130
132
 
133
+ ```ruby
134
+ table = Tabulo::Table.new([1, 2, 5])
135
+ table.add_column(:itself)
136
+ table.add_column(:even?)
137
+ table.add_column(:odd?)
138
+ ```
139
+
140
+ Alternatively, you can pass an initialization block to `new`:
141
+
131
142
  ```ruby
132
143
  table = Tabulo::Table.new([1, 2, 5]) do |t|
133
144
  t.add_column(:itself)
@@ -136,7 +147,8 @@ table = Tabulo::Table.new([1, 2, 5]) do |t|
136
147
  end
137
148
  ```
138
149
 
139
- Or equivalently, using the "quick API":
150
+ When the columns correspond to methods, you can also use the "quick API", by passing a symbol
151
+ directly to `new` for each column:
140
152
 
141
153
  ```ruby
142
154
  table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?)
@@ -355,6 +367,7 @@ table = Tabulo::Table.new(
355
367
  | abcdefghijkl~| 26 |
356
368
  ```
357
369
 
370
+ <a name="formatting-cell-values"></a>
358
371
  ### Formatting cell values
359
372
 
360
373
  While the callable passed to `add_column` determines the underyling, calculated value in each
@@ -389,6 +402,67 @@ of the underlying cell value, not the way it is formatted. This is usually the d
389
402
  Note also that the item yielded to `.each` for each cell when enumerating over a `Tabulo::Row` is
390
403
  the underlying value of that cell, not its formatted value.
391
404
 
405
+ <a name="colours-and-styling"></a>
406
+ ### Colours and styling
407
+
408
+ In most terminals, if you want print text that is coloured, or has other styles
409
+ such as underlining, you need to use ANSI escape sequences, either directly, or by means of a library
410
+ such as [Rainbow](http://github.com/sickill/rainbow) that uses them under the hood. When added
411
+ to a string, ANSI escape codes increase a string's length without increasing the width
412
+ it visually occupies in the terminal. So that Tabulo can perform the width calculations required to
413
+ render the table correctly, the `styler` option should be passed to `add_column` to apply colours
414
+ or other styling that require escape sequences.
415
+
416
+ For example, suppose you have a table to which you want to add a column that
417
+ displays `true` in green if a given number is even, or else displays `false` in red.
418
+ You can achieve this as follows using raw ANSI escape codes:
419
+
420
+ ```ruby
421
+ table.add_column(
422
+ :even?,
423
+ styler: -> (n, s) { n.even? ? "\033[32m#{s}\033[0m" : "\033[31m#{s}\033[0m" }
424
+ )
425
+ ```
426
+
427
+ Or, if you are using the [rainbow](https://github.com/sickill/rainbow) gem for colouring, you
428
+ could do the following:
429
+
430
+ ```ruby
431
+ require "rainbow"
432
+
433
+ # ...
434
+
435
+ table.add_column(
436
+ :even?,
437
+ styler: -> (n, s) { n.even? ? Rainbow(s).red : Rainbow(s).green }
438
+ )
439
+ ```
440
+
441
+ The `styler` option should be passed a callable that takes two parameters: the
442
+ first represents the element of the underlying enumerable for a given table
443
+ row; and the second the represents formatted string value of that cell, i.e. the
444
+ cell content after any processing by the [formatter](#formatting-cell-values) (if any).
445
+ If the content of a cell is wrapped over multiple lines, then the `styler` will be called
446
+ once per line, so that each line of the cell will have the escape sequence applied to it
447
+ separately (ensuring the stying doesn't bleed into neighbouring cells).
448
+
449
+ If you want to apply colours or other styling to the content of a column header, as opposed
450
+ to cells in the table body, use the `header_styler` option, e.g.:
451
+
452
+ ```ruby
453
+ table.add_column(:even?, header_styler: -> (s) { "\033[32m#{s}\033[0m" })
454
+ ```
455
+
456
+ To apply colours or other styling to the row divider, column divider, corner and border
457
+ characters of the table, use the `border_styler` option when initializing the table, e.g.:
458
+
459
+ ```ruby
460
+ table = Tabulo::Table.new(1..5, :itself, :even?, :odd?, border_styler: -> (s) { "\033[32m#{s}\033[0m" })
461
+ ```
462
+
463
+ If the content of a cell has been [truncated](#overflow-handling), then whatever colours or other styling
464
+ apply to the cell content will also be applied the truncation indicator character.
465
+
392
466
  <a name="repeating-headers"></a>
393
467
  ### Repeating headers
394
468
 
@@ -455,8 +529,8 @@ calculated.)
455
529
  <a name="accessing-cell-values"></a>
456
530
  ### Accessing cell values
457
531
  Each `Tabulo::Table` is an `Enumerable` of which each element is a `Tabulo::Row`. Each `Tabulo::Row`
458
- is itself an `Enumerable` comprising the underlying the values of each cell. A `Tabulo::Row` can
459
- also be converted to a `Hash` for keyed access. For example:
532
+ is itself an `Enumerable` comprising the underlying values of each cell. A `Tabulo::Row` can also
533
+ be converted to a `Hash` for keyed access. For example:
460
534
 
461
535
  ```ruby
462
536
  table = Tabulo::Table.new(1..5, :itself, :even?, :odd?)
@@ -549,7 +623,7 @@ a new table in which the rows and columns are swapped:
549
623
  By default, a header row is added to the new table, showing the string value of the element
550
624
  represented in that column. This can be configured, however, along with other aspects of
551
625
  `transpose`'s behaviour. For details, see the
552
- [documentation](https://www.rubydoc.info/gems/tabulo/1.4.1/Tabulo/Table#transpose-instance_method).
626
+ [documentation](https://www.rubydoc.info/gems/tabulo/1.5.0/Tabulo/Table#transpose-instance_method).
553
627
 
554
628
  <a name="additional-configuration-options"></a>
555
629
  ### Additional configuration options
@@ -690,14 +764,14 @@ The gem is available as open source under the terms of the [MIT
690
764
  License](http://opensource.org/licenses/MIT).
691
765
 
692
766
  [Gem Version]: https://rubygems.org/gems/tabulo
693
- [Documentation]: http://www.rubydoc.info/gems/tabulo/1.4.1
767
+ [Documentation]: http://www.rubydoc.info/gems/tabulo/1.5.0
694
768
  [Build Status]: https://travis-ci.org/matt-harvey/tabulo
695
769
  [Coverage Status]: https://coveralls.io/r/matt-harvey/tabulo
696
770
  [Code Climate]: https://codeclimate.com/github/matt-harvey/tabulo
697
771
  [Awesome Ruby]: https://github.com/markets/awesome-ruby#cli-utilities
698
772
 
699
773
  [GV img]: https://img.shields.io/gem/v/tabulo.svg
700
- [DC img]: https://img.shields.io/badge/documentation-v1.4.1-blue.svg
774
+ [DC img]: https://img.shields.io/badge/documentation-v1.5.0-blue.svg
701
775
  [BS img]: https://img.shields.io/travis/matt-harvey/tabulo.svg
702
776
  [CS img]: https://img.shields.io/coveralls/matt-harvey/tabulo.svg
703
777
  [CC img]: https://codeclimate.com/github/matt-harvey/tabulo/badges/gpa.svg
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.1
1
+ 1.5.0
@@ -4,4 +4,5 @@ require "tabulo/util"
4
4
  require "tabulo/table"
5
5
  require "tabulo/version"
6
6
  require "tabulo/row"
7
+ require "tabulo/cell"
7
8
  require "tabulo/column"
@@ -0,0 +1,104 @@
1
+ require "unicode/display_width"
2
+
3
+ module Tabulo
4
+
5
+ # @!visibility private
6
+ class Cell
7
+
8
+ def initialize(value:, formatter:, alignment:, width:, styler:, truncation_indicator:, padding_character:)
9
+ @value = value
10
+ @formatter = formatter
11
+ @alignment = alignment
12
+ @width = width
13
+ @styler = styler
14
+ @truncation_indicator = truncation_indicator
15
+ @padding_character = padding_character
16
+ end
17
+
18
+ def height
19
+ subcells.size
20
+ end
21
+
22
+ def padded_truncated_subcells(target_height, padding_amount)
23
+ truncated = (height > target_height)
24
+ (0...target_height).map do |subcell_index|
25
+ append_truncator = (truncated && (padding_amount != 0) && (subcell_index + 1 == target_height))
26
+ padded_subcell(subcell_index, padding_amount, append_truncator)
27
+ end
28
+ end
29
+
30
+ def formatted_content
31
+ @formatted_content ||= @formatter.call(@value)
32
+ end
33
+
34
+ private
35
+
36
+ def subcells
37
+ @subcells ||= calculate_subcells
38
+ end
39
+
40
+ def padded_subcell(subcell_index, padding_amount, append_truncator)
41
+ lpad = @padding_character * padding_amount
42
+ rpad = append_truncator ? styled_truncation_indicator + padding(padding_amount - 1) : padding(padding_amount)
43
+ inner = subcell_index < height ? subcells[subcell_index] : padding(@width)
44
+ "#{lpad}#{inner}#{rpad}"
45
+ end
46
+
47
+ def padding(amount)
48
+ @padding_character * amount
49
+ end
50
+
51
+ def styled_truncation_indicator
52
+ @styler.call(@value, @truncation_indicator)
53
+ end
54
+
55
+ def calculate_subcells
56
+ formatted_content.split($/, -1).flat_map do |substr|
57
+ subsubcells, subsubcell, subsubcell_width = [], String.new(""), 0
58
+
59
+ substr.scan(/\X/).each do |grapheme_cluster|
60
+ grapheme_cluster_width = Unicode::DisplayWidth.of(grapheme_cluster)
61
+ if subsubcell_width + grapheme_cluster_width > @width
62
+ subsubcells << style_and_align_cell_content(subsubcell)
63
+ subsubcell_width = 0
64
+ subsubcell.clear
65
+ end
66
+
67
+ subsubcell << grapheme_cluster
68
+ subsubcell_width += grapheme_cluster_width
69
+ end
70
+
71
+ subsubcells << style_and_align_cell_content(subsubcell)
72
+ end
73
+ end
74
+
75
+ def style_and_align_cell_content(content)
76
+ padding = [@width - Unicode::DisplayWidth.of(content), 0].max
77
+ left_padding, right_padding =
78
+ case real_alignment
79
+ when :center
80
+ half_padding = padding / 2
81
+ [padding - half_padding, half_padding]
82
+ when :left
83
+ [0, padding]
84
+ when :right
85
+ [padding, 0]
86
+ end
87
+
88
+ "#{' ' * left_padding}#{@styler.call(@value, content)}#{' ' * right_padding}"
89
+ end
90
+
91
+ def real_alignment
92
+ return @alignment unless @alignment == :auto
93
+
94
+ case @value
95
+ when Numeric
96
+ :right
97
+ when TrueClass, FalseClass
98
+ :center
99
+ else
100
+ :left
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,3 @@
1
- require "unicode/display_width"
2
-
3
1
  module Tabulo
4
2
 
5
3
  # @!visibility private
@@ -8,83 +6,54 @@ module Tabulo
8
6
  attr_accessor :width
9
7
  attr_reader :header
10
8
 
11
- def initialize(header:, width:, align_header:, align_body:, formatter:, extractor:)
9
+ def initialize(header:, width:, align_header:, align_body:, formatter:, extractor:, styler:,
10
+ header_styler:, truncation_indicator:, padding_character:)
11
+
12
12
  @header = header
13
13
  @width = width
14
14
  @align_header = align_header
15
15
  @align_body = align_body
16
16
  @formatter = formatter
17
17
  @extractor = extractor
18
- end
18
+ @styler = styler || -> (_, s) { s }
19
+
20
+ @header_styler =
21
+ if header_styler
22
+ -> (_, s) { header_styler.call(s) }
23
+ else
24
+ -> (_, s) { s }
25
+ end
19
26
 
20
- def header_subcells
21
- infilled_subcells(@header, @align_header)
27
+ @truncation_indicator = truncation_indicator
28
+ @padding_character = padding_character
22
29
  end
23
30
 
24
- def body_subcells(source)
25
- cell_datum = body_cell_value(source)
26
- formatted_content = @formatter.call(cell_datum)
27
- real_alignment = (@align_body == :auto ? infer_alignment(cell_datum) : @align_body)
28
- infilled_subcells(formatted_content, real_alignment)
31
+ def header_cell
32
+ Cell.new(
33
+ value: @header,
34
+ formatter: -> (s) { s },
35
+ alignment: @align_header,
36
+ width: @width,
37
+ styler: @header_styler,
38
+ truncation_indicator: @truncation_indicator,
39
+ padding_character: @padding_character,
40
+ )
29
41
  end
30
42
 
31
- def formatted_cell_content(source)
32
- @formatter.call(body_cell_value(source))
43
+ def body_cell(source)
44
+ Cell.new(
45
+ value: body_cell_value(source),
46
+ formatter: @formatter,
47
+ alignment: @align_body,
48
+ width: @width,
49
+ styler: @styler,
50
+ truncation_indicator: @truncation_indicator,
51
+ padding_character: @padding_character,
52
+ )
33
53
  end
34
54
 
35
55
  def body_cell_value(source)
36
56
  @extractor.call(source)
37
57
  end
38
-
39
- private
40
-
41
- def infilled_subcells(str, real_alignment)
42
- str.split($/, -1).flat_map do |substr|
43
- substr_grapheme_clusters = substr.scan(/\X/)
44
- subsubcells = []
45
- current_subsubcell_grapheme_clusters = []
46
- current_subsubcell_display_width = 0
47
- substr_grapheme_clusters.each do |sgc|
48
- sgc_display_width = Unicode::DisplayWidth.of(sgc)
49
- if sgc_display_width + current_subsubcell_display_width > width
50
- subsubcells << current_subsubcell_grapheme_clusters.join("")
51
- current_subsubcell_grapheme_clusters.clear
52
- current_subsubcell_display_width = 0
53
- end
54
-
55
- current_subsubcell_grapheme_clusters << sgc
56
- current_subsubcell_display_width += sgc_display_width
57
- end
58
- subsubcells << current_subsubcell_grapheme_clusters.join("")
59
- subsubcells.map { |s| align_cell_content(s, real_alignment) }
60
- end
61
- end
62
-
63
- def align_cell_content(content, real_alignment)
64
- padding = [@width - Unicode::DisplayWidth.of(content), 0].max
65
- left_padding, right_padding =
66
- case real_alignment
67
- when :center
68
- half_padding = padding / 2
69
- [padding - half_padding, half_padding]
70
- when :left
71
- [0, padding]
72
- when :right
73
- [padding, 0]
74
- end
75
-
76
- "#{' ' * left_padding}#{content}#{' ' * right_padding}"
77
- end
78
-
79
- def infer_alignment(cell_datum)
80
- case cell_datum
81
- when Numeric
82
- :right
83
- when TrueClass, FalseClass
84
- :center
85
- else
86
- :left
87
- end
88
- end
89
58
  end
90
59
  end
@@ -85,6 +85,15 @@ module Tabulo
85
85
  # using the <tt>align_body</tt> option passed to {#add_column}. If passed <tt>:auto</tt>,
86
86
  # alignment is determined by cell content, with numbers aligned right, booleans
87
87
  # center-aligned, and other values left-aligned.
88
+ # @param [nil, #to_proc] border_styler (nil) A lambda or other callable object taking
89
+ # a single parameter, representing a section of the table's borders (which for this purpose
90
+ # include any horizontal and vertical lines inside the table).
91
+ # If passed <tt>nil</tt>, then no additional styling will be applied to borders. If passed a
92
+ # callable, then that callable will be called for each border section, with the
93
+ # resulting string rendered in place of that border. The extra width of the string returned by the
94
+ # {border_styler} is not taken into consideration by the internal table rendering calculations
95
+ # Thus it can be used to apply ANSI escape codes to border characters, to colour the borders
96
+ # for example, without breaking the table formatting.
88
97
  # @return [Table] a new {Table}
89
98
  # @raise [InvalidColumnLabelError] if non-unique Symbols are provided to columns.
90
99
  # @raise [InvalidHorizontalRuleCharacterError] if invalid argument passed to horizontal_rule_character.
@@ -92,11 +101,10 @@ module Tabulo
92
101
  def initialize(sources, *cols, columns: [], column_width: nil, column_padding: nil, header_frequency: :start,
93
102
  wrap_header_cells_to: nil, wrap_body_cells_to: nil, horizontal_rule_character: nil,
94
103
  vertical_rule_character: nil, intersection_character: nil, truncation_indicator: nil,
95
- align_header: :center, align_body: :auto)
104
+ align_header: :center, align_body: :auto, border_styler: nil)
96
105
 
97
106
  if columns.any?
98
- Deprecation.warn("`columns' option to Tabulo::Table#initialize",
99
- "the variable length parameter `cols'", 2)
107
+ Deprecation.warn("`columns' option to Tabulo::Table#initialize", "the variable length parameter `cols'", 2)
100
108
  end
101
109
 
102
110
  @sources = sources
@@ -107,6 +115,7 @@ module Tabulo
107
115
  @column_padding = (column_padding || DEFAULT_COLUMN_PADDING)
108
116
  @align_header = align_header
109
117
  @align_body = align_body
118
+ @border_styler = border_styler
110
119
 
111
120
  @horizontal_rule_character = validate_character(horizontal_rule_character,
112
121
  DEFAULT_HORIZONTAL_RULE_CHARACTER, InvalidHorizontalRuleCharacterError, "horizontal rule character")
@@ -155,6 +164,34 @@ module Tabulo
155
164
  # generates a Date, then the formatter might format that Date in a particular way.
156
165
  # If no formatter is provided, then <tt>.to_s</tt> will be called on
157
166
  # the extracted value of each cell to determine its displayed content.
167
+ # @param [nil, #to_proc] styler (nil) A lambda or other callable object that will be passed
168
+ # two arguments: the calculated value of the cell (prior to the {formatter} being applied);
169
+ # and a string representing a single formatted line within the cell. For example, if the
170
+ # cell content is wrapped over three lines, then for that cell, the {styler} will be called
171
+ # three times, once for each line of content within the cell. If passed <tt>nil</tt>, then
172
+ # no additional styling will be applied to the cell content (other than what was already
173
+ # applied by the {formatter}). If passed a callable, then that callable will be called for
174
+ # each line of content within the cell, and the resulting string rendered in place of that
175
+ # line. The {styler} option differs from the {formatter} option in that the extra width of the
176
+ # string returned by {styler} is not taken into consideration by the internal table and
177
+ # cell width calculations involved in rendering the table. Thus it can be used to apply
178
+ # ANSI escape codes to cell content, to colour the cell content for example, without
179
+ # breaking the table formatting.
180
+ # Note that if the content of a cell is truncated, then the whatever styling is applied by the
181
+ # {styler} to the cell content will also be applied to the truncation indicator character.
182
+ # @param [nil, #to_proc] header_styler (nil) A lambda or other callable object taking
183
+ # a single parameter, representing a single line of within the header content for
184
+ # this column. For example, if the header cell content is wrapped over three lines, then
185
+ # the {header_styler} will be called once for each line. If passed <tt>nil</tt>, then
186
+ # no additional styling will be applied to the header cell content. If passed a callable,
187
+ # then that callable will be called for each line of content within the header cell, and the
188
+ # resulting string rendered in place of that line. The extra width of the string returned by the
189
+ # {header_styler} is not taken into consideration by the internal table and
190
+ # cell width calculations involved in rendering the table. Thus it can be used to apply
191
+ # ANSI escape codes to header cell content, to colour the cell content for example, without
192
+ # breaking the table formatting.
193
+ # Note that if the header content is truncated, then any {header_styler} will be applied to the
194
+ # truncation indicator character as well as to the truncated content.
158
195
  # @param [#to_proc] extractor A block or other callable
159
196
  # that will be passed each of the Table sources to determine the value in each cell of this
160
197
  # column. If this is not provided, then the column label will be treated as a method to be
@@ -163,7 +200,7 @@ module Tabulo
163
200
  # Table. (This is case-sensitive, but is insensitive to whether a String or Symbol is passed
164
201
  # to the label parameter.)
165
202
  def add_column(label, header: nil, align_header: nil, align_body: nil,
166
- width: nil, formatter: :to_s.to_proc, &extractor)
203
+ width: nil, formatter: :to_s.to_proc, styler: nil, header_styler: nil, &extractor)
167
204
 
168
205
  column_label =
169
206
  case label
@@ -184,7 +221,11 @@ module Tabulo
184
221
  align_body: align_body || @align_body,
185
222
  width: (width || @default_column_width),
186
223
  formatter: formatter,
187
- extractor: (extractor || label.to_proc)
224
+ extractor: (extractor || label.to_proc),
225
+ styler: styler,
226
+ header_styler: header_styler,
227
+ truncation_indicator: @truncation_indicator,
228
+ padding_character: PADDING_CHARACTER,
188
229
  )
189
230
  end
190
231
 
@@ -224,7 +265,7 @@ module Tabulo
224
265
 
225
266
  # @return [String] an "ASCII" graphical representation of the Table column headers.
226
267
  def formatted_header
227
- cells = column_registry.map { |_, column| column.header_subcells }
268
+ cells = column_registry.map { |_, column| column.header_cell }
228
269
  format_row(cells, @wrap_header_cells_to)
229
270
  end
230
271
 
@@ -240,7 +281,8 @@ module Tabulo
240
281
  inner = column_registry.map do |_, column|
241
282
  @horizontal_rule_character * (column.width + @column_padding * 2)
242
283
  end
243
- surround_join(inner, @intersection_character)
284
+
285
+ styled_border(surround_join(inner, @intersection_character))
244
286
  end
245
287
 
246
288
  # Reset all the column widths so that each column is *just* wide enough to accommodate
@@ -279,7 +321,7 @@ module Tabulo
279
321
 
280
322
  @sources.each do |source|
281
323
  columns.each do |column|
282
- width = wrapped_width(column.formatted_cell_content(source))
324
+ width = wrapped_width(column.body_cell(source).formatted_content)
283
325
  column.width = width if width > column.width
284
326
  end
285
327
  end
@@ -406,7 +448,7 @@ module Tabulo
406
448
 
407
449
  # @!visibility private
408
450
  def formatted_body_row(source, with_header: false)
409
- cells = column_registry.map { |_, column| column.body_subcells(source) }
451
+ cells = column_registry.map { |_, column| column.body_cell(source) }
410
452
  inner = format_row(cells, @wrap_body_cells_to)
411
453
  if with_header
412
454
  join_lines([horizontal_rule, formatted_header, horizontal_rule, inner])
@@ -459,41 +501,22 @@ module Tabulo
459
501
  # before truncating.
460
502
  # @return [String] the entire formatted row including all padding and borders.
461
503
  def format_row(cells, wrap_cells_to)
462
- row_height = ([wrap_cells_to, cells.map(&:size).max].compact.min || 1)
463
-
464
- subrows = (0...row_height).map do |subrow_index|
465
- subrow_components = cells.zip(column_registry.values).map do |cell, column|
466
- num_subcells = cell.size
467
- cell_truncated = (num_subcells > row_height)
468
- append_truncator = (cell_truncated && subrow_index + 1 == row_height)
469
-
470
- lpad = PADDING_CHARACTER * @column_padding
471
- rpad =
472
- if append_truncator && @column_padding != 0
473
- @truncation_indicator + PADDING_CHARACTER * (@column_padding - 1)
474
- else
475
- PADDING_CHARACTER * @column_padding
476
- end
477
-
478
- inner =
479
- if subrow_index < num_subcells
480
- cell[subrow_index]
481
- else
482
- PADDING_CHARACTER * column.width
483
- end
484
-
485
- "#{lpad}#{inner}#{rpad}"
486
- end
487
-
488
- surround_join(subrow_components, @vertical_rule_character)
489
- end
490
-
504
+ max_cell_height = cells.map(&:height).max
505
+ row_height = ([wrap_cells_to, max_cell_height].compact.min || 1)
506
+ vertical = styled_border(@vertical_rule_character)
507
+ subcell_stacks = cells.map { |cell| cell.padded_truncated_subcells(row_height, @column_padding) }
508
+ subrows = subcell_stacks.transpose.map { |subrow_components| surround_join(subrow_components, vertical) }
491
509
  join_lines(subrows)
492
510
  end
493
511
 
494
512
  # @!visibility private
495
- def surround(str, ch0)
496
- "#{ch0}#{str}#{ch0}"
513
+ def styled_border(str)
514
+ @border_styler ? @border_styler.call(str) : str
515
+ end
516
+
517
+ # @!visibility private
518
+ def surround(str, ch)
519
+ "#{ch}#{str}#{ch}"
497
520
  end
498
521
 
499
522
  # @!visibility private
@@ -1,3 +1,3 @@
1
1
  module Tabulo
2
- VERSION = "1.4.1"
2
+ VERSION = "1.5.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabulo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Harvey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-05-03 00:00:00.000000000 Z
11
+ date: 2019-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tty-screen
@@ -187,6 +187,7 @@ files:
187
187
  - bin/console
188
188
  - bin/setup
189
189
  - lib/tabulo.rb
190
+ - lib/tabulo/cell.rb
190
191
  - lib/tabulo/column.rb
191
192
  - lib/tabulo/deprecation.rb
192
193
  - lib/tabulo/exceptions.rb