tabulo 1.4.1 → 1.5.0
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 +1 -1
- data/CHANGELOG.md +6 -0
- data/README.md +81 -7
- data/VERSION +1 -1
- data/lib/tabulo.rb +1 -0
- data/lib/tabulo/cell.rb +104 -0
- data/lib/tabulo/column.rb +33 -64
- data/lib/tabulo/table.rb +63 -40
- data/lib/tabulo/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edb6043945662b09cf500a65acec2fa51eac9c9397e7c58a890379bc174424cd
|
4
|
+
data.tar.gz: f7866a1ac405b431752ccba6a81678c9533b245384290c913bb015267ff8f7cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e67b0006b767a5dbe884bad9296636a01e1d5bdc57c6ffd2689f9315a9465e899e79e74508e2ae7c83c37869bfb3a523931accd2e5d6db346e104794a6c844ec
|
7
|
+
data.tar.gz: 663b95287868f59f2c29f75564cb660b5c1b2462984f351d6abd6ff057228fd1f8af5a3c7a99d68ed99740b90a493fe18bd9e4a7228f45dc312c929aeb7308a3
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -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
|
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
|
-
|
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
|
459
|
-
|
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.
|
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.
|
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.
|
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.
|
1
|
+
1.5.0
|
data/lib/tabulo.rb
CHANGED
data/lib/tabulo/cell.rb
ADDED
@@ -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
|
data/lib/tabulo/column.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
21
|
-
|
27
|
+
@truncation_indicator = truncation_indicator
|
28
|
+
@padding_character = padding_character
|
22
29
|
end
|
23
30
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
32
|
-
|
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
|
data/lib/tabulo/table.rb
CHANGED
@@ -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.
|
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
|
-
|
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.
|
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.
|
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
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
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
|
496
|
-
|
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
|
data/lib/tabulo/version.rb
CHANGED
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
|
+
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-
|
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
|