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 +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
|