tabulo 2.2.0 → 2.3.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/CHANGELOG.md +9 -1
- data/README.md +208 -62
- data/VERSION +1 -1
- data/lib/tabulo/column.rb +25 -16
- data/lib/tabulo/row.rb +2 -2
- data/lib/tabulo/table.rb +82 -22
- data/lib/tabulo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87e2e8bb108886b594c80a32396ab586c90792ae6e5efeb5769e457b13d6c96d
|
4
|
+
data.tar.gz: 03c93ac0d5419fb654244ab574d9e7aa43263f40ca71e008f6d43772a76ffece
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae04026433b44638cd777f483fb0e8b0494370cc71fb84c5bc91cc38c65c92aa4e68183b48db1e92e53b3e3ede6b680975190d2626510f0ad31b6f651bb9538d
|
7
|
+
data.tar.gz: 5a543577d7d678ccee445789d588147a778fa45899876872749ad1ced9119e99369b43b1e7b642d2f9eae27c41d2e904de9d7765513b9b813824deac5e8d958c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### v2.3.0
|
4
|
+
|
5
|
+
* Provide `#remove_column` method.
|
6
|
+
* Provide `before` option to `#add_column`, to allow insertion of column into non-final position.
|
7
|
+
* Provide `styler` and `header_styler` options in table initializer, to enable default stylers
|
8
|
+
to be set for all columns.
|
9
|
+
* Documentation improvements and code tidy-ups.
|
10
|
+
|
3
11
|
### v2.2.0
|
4
12
|
|
5
13
|
* New `column_formatter` option on `Tabulo::Table` initializer, enabling the table's default column
|
@@ -15,7 +23,7 @@
|
|
15
23
|
|
16
24
|
### v2.1.0
|
17
25
|
|
18
|
-
* New
|
26
|
+
* New `reduced_ascii` and `reduced_modern` border options
|
19
27
|
* Fix `column_width` option not properly inherited from original table by the new table created
|
20
28
|
by calling #transpose.
|
21
29
|
|
data/README.md
CHANGED
@@ -7,13 +7,9 @@
|
|
7
7
|
[![Code Climate][CC img]][Code Climate]
|
8
8
|
[![Awesome][AR img]][Awesome Ruby]
|
9
9
|
|
10
|
-
Tabulo is a terminal table generator for Ruby.
|
10
|
+
Tabulo is a terminal table generator for Ruby. It is both highly configurable and very easy to use.
|
11
11
|
|
12
|
-
|
13
|
-
readable tables, even from large and unwieldy data sets and streams.
|
14
|
-
|
15
|
-
Tabulo is both easy to use and feature rich, allowing you to quickly tabulate any enumerable
|
16
|
-
collection, with options for automatic column sizing, a variety of border styles, and more.
|
12
|
+
_Quick API:_
|
17
13
|
|
18
14
|
```
|
19
15
|
> puts Tabulo::Table.new(User.all, :id, :first_name, :last_name, border: :modern).pack
|
@@ -25,12 +21,35 @@ collection, with options for automatic column sizing, a variety of border styles
|
|
25
21
|
└────┴────────────┴───────────┘
|
26
22
|
```
|
27
23
|
|
24
|
+
_Full API:_
|
25
|
+
|
26
|
+
```
|
27
|
+
table = Tabulo::Table.new(User.all, border: :modern) do |t|
|
28
|
+
t.add_column("ID", &:id)
|
29
|
+
t.add_column("First name", &:first_name)
|
30
|
+
t.add_column("Last name") { |user| user.last_name.upcase }
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
```
|
35
|
+
> puts table.pack
|
36
|
+
┌────┬────────────┬───────────┐
|
37
|
+
│ ID │ First name │ Last name │
|
38
|
+
├────┼────────────┼───────────┤
|
39
|
+
│ 1 │ John │ CITIZEN │
|
40
|
+
│ 2 │ Jane │ DOE │
|
41
|
+
└────┴────────────┴───────────┘
|
42
|
+
```
|
43
|
+
|
28
44
|
## Features
|
29
45
|
|
46
|
+
* Presents a [DRY initialization interface](#configuring-columns): by being “column based” rather than
|
47
|
+
“row based”, it spares the developer the burden of syncing the ordering within the header row
|
48
|
+
with that of the body rows.
|
30
49
|
* Lets you set [fixed column widths](#fixed-column-widths), then either [wrap](#overflow-handling)
|
31
50
|
or [truncate](#overflow-handling) the overflow.
|
32
|
-
* Alternatively, [
|
33
|
-
its contents, but [without overflowing the terminal horizontally](#max-table-width).
|
51
|
+
* Alternatively, you can [“pack”](#pack) the table so that each column is automatically just
|
52
|
+
wide enough for its contents, but [without overflowing the terminal horizontally](#max-table-width).
|
34
53
|
* Alignment of cell content is [configurable](#cell-alignment), but has helpful content-based
|
35
54
|
defaults (numbers right, strings left).
|
36
55
|
* Tabulate any `Enumerable`: the underlying collection need not be an array.
|
@@ -44,9 +63,8 @@ collection, with options for automatic column sizing, a variety of border styles
|
|
44
63
|
* Multibyte Unicode characters are correctly handled.
|
45
64
|
* Apply [colours](#colours-and-styling) and other styling to table content and borders, without breaking the table.
|
46
65
|
* Easily [transpose](#transposition) the table, so that rows are swapped with columns.
|
47
|
-
* Choose from multiple [border configurations](#borders), including Markdown,
|
48
|
-
|
49
|
-
designed to spare the developer the burden of syncing the ordering within the header row with that of the body rows.
|
66
|
+
* Choose from multiple [border configurations](#borders), including Markdown, “ASCII”, and smoothly
|
67
|
+
joined Unicode border characters.
|
50
68
|
|
51
69
|
Tabulo has also been ported to Crystal (with some modifications): see [Tablo](https://github.com/hutou/tablo).
|
52
70
|
|
@@ -65,7 +83,11 @@ Tabulo has also been ported to Crystal (with some modifications): see [Tablo](ht
|
|
65
83
|
* [Automating column widths](#automating-column-widths)
|
66
84
|
* [Overflow handling](#overflow-handling)
|
67
85
|
* [Formatting cell values](#formatting-cell-values)
|
68
|
-
* [Colours and styling](#colours-and-styling)
|
86
|
+
* [Colours and other styling](#colours-and-styling)
|
87
|
+
* [Styling cell content](#styling-cell-content)
|
88
|
+
* [Styling column headers](#styling-column-headers)
|
89
|
+
* [Setting default styles](#default-styles)
|
90
|
+
* [Styling borders](#border-styling)
|
69
91
|
* [Repeating headers](#repeating-headers)
|
70
92
|
* [Using a Table Enumerator](#using-a-table-enumerator)
|
71
93
|
* [Accessing cell values](#accessing-cell-values)
|
@@ -73,13 +95,14 @@ Tabulo has also been ported to Crystal (with some modifications): see [Tablo](ht
|
|
73
95
|
* [Transposing rows and columns](#transposition)
|
74
96
|
* [Border configuration](#borders)
|
75
97
|
* [Row dividers](#dividers)
|
98
|
+
* [Using a table as a snapshot rather than as a dynamic view](#freezing-a-table)
|
76
99
|
* [Comparison with other libraries](#motivation)
|
77
100
|
* [Contributing](#contributing)
|
78
101
|
* [License](#license)
|
79
102
|
|
80
103
|
## Installation
|
81
104
|
|
82
|
-
Add this line to your application
|
105
|
+
Add this line to your application’s Gemfile:
|
83
106
|
|
84
107
|
```ruby
|
85
108
|
gem 'tabulo'
|
@@ -126,7 +149,7 @@ table = Tabulo::Table.new([1, 2, 5]) do |t|
|
|
126
149
|
end
|
127
150
|
```
|
128
151
|
|
129
|
-
When the columns correspond to methods, you can also use the
|
152
|
+
When the columns correspond to methods, you can also use the “quick API”, by passing a symbol
|
130
153
|
directly to `new` for each column:
|
131
154
|
|
132
155
|
```ruby
|
@@ -167,6 +190,30 @@ end
|
|
167
190
|
+--------------+--------------+--------------+
|
168
191
|
```
|
169
192
|
|
193
|
+
By default, each new column is added to the right of all the other columns so far added to the
|
194
|
+
table. However, if you want to insert a new column into some other position, you can use the
|
195
|
+
`before` option, passing the label of the column to the left of which you want the new column to be added:
|
196
|
+
|
197
|
+
```ruby
|
198
|
+
table = Table::Table.new([1, 2, 3], :itself, :odd?)
|
199
|
+
table.add_column(:even?, before: :odd?)
|
200
|
+
```
|
201
|
+
|
202
|
+
```
|
203
|
+
> puts table
|
204
|
+
+--------------+--------------+--------------+
|
205
|
+
| itself | even? | odd? |
|
206
|
+
+--------------+--------------+--------------+
|
207
|
+
| 1 | false | true |
|
208
|
+
| 2 | true | false |
|
209
|
+
| 5 | false | true |
|
210
|
+
+--------------+--------------+--------------+
|
211
|
+
```
|
212
|
+
|
213
|
+
There is also a `#remove_column` method, for deleting an existing column from a table; see the
|
214
|
+
[documentation](https://www.rubydoc.info/gems/tabulo/2.3.0/Tabulo/Table#remove_column-instance_method)
|
215
|
+
for details.
|
216
|
+
|
170
217
|
<a name="cell-alignment"></a>
|
171
218
|
### Cell alignment
|
172
219
|
|
@@ -278,7 +325,7 @@ table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?, column_padding: [0,
|
|
278
325
|
<a name="pack"></a>
|
279
326
|
#### Automating column widths
|
280
327
|
|
281
|
-
Instead of setting column widths
|
328
|
+
Instead of setting column widths “manually”, you can tell the table to sort out the widths
|
282
329
|
itself, so that each column is just wide enough for its header and contents (plus a character
|
283
330
|
of padding):
|
284
331
|
|
@@ -297,7 +344,7 @@ table.pack
|
|
297
344
|
+--------+-------+
|
298
345
|
```
|
299
346
|
|
300
|
-
The `pack` method returns the table itself, so you can
|
347
|
+
The `pack` method returns the table itself, so you can “pack-and-print” in one go:
|
301
348
|
|
302
349
|
```ruby
|
303
350
|
puts Tabulo::Table.new([1, 2], :itself, :even?).pack
|
@@ -334,12 +381,15 @@ the table will fit.
|
|
334
381
|
|
335
382
|
Note that `pack`ing the table necessarily involves traversing the entire collection up front as
|
336
383
|
the maximum cell width needs to be calculated for each column. You may not want to do this
|
337
|
-
if the collection is very large.
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
384
|
+
if the collection is very large.
|
385
|
+
|
386
|
+
Note also the effect of `pack` is to fix the column widths as appropriate to the formatted cell
|
387
|
+
contents given the state of the underlying collection _at the point of packing_. If the underlying
|
388
|
+
collection changes between that point, and when the table is printed, then the columns will _not_ be
|
389
|
+
resized yet again on printing. This is a consequence of the table always being essentially a
|
390
|
+
“live view” on the underlying collection: formatted contents are never cached within the
|
391
|
+
table itself. There are [ways around this](#freezing-a-table), however, if this is not the desired
|
392
|
+
behaviour—see [below](#freezing-a-table).
|
343
393
|
|
344
394
|
<a name="overflow-handling"></a>
|
345
395
|
#### Overflow handling
|
@@ -397,10 +447,10 @@ The character used to indicate truncation, which defaults to `~`, can be configu
|
|
397
447
|
### Formatting cell values
|
398
448
|
|
399
449
|
While the callable passed to `add_column` determines the underyling, calculated value in each
|
400
|
-
cell of the column, there is a separate concept, of a
|
401
|
-
be visually displayed. By default, `.to_s` is called on the underlying cell value to
|
402
|
-
it; however, you can format it differently by passing another callable to the
|
403
|
-
of `add_column`:
|
450
|
+
cell of the column, there is a separate concept, of a “formatter”, that determines how
|
451
|
+
that value will be visually displayed. By default, `.to_s` is called on the underlying cell value to
|
452
|
+
“format” it; however, you can format it differently by passing another callable to the
|
453
|
+
`formatter` option of `add_column`:
|
404
454
|
|
405
455
|
```ruby
|
406
456
|
table = Tabulo::Table.new(1..3) do |t|
|
@@ -422,7 +472,7 @@ end
|
|
422
472
|
+--------------+--------------+
|
423
473
|
```
|
424
474
|
|
425
|
-
Note the numbers in the
|
475
|
+
Note the numbers in the “Reciprocal” column in this example are still right-aligned, even though
|
426
476
|
the callable passed to `formatter` returns a String. Default cell alignment is determined by the type
|
427
477
|
of the underlying cell value, not the way it is formatted. This is usually the desired result.
|
428
478
|
|
@@ -452,7 +502,10 @@ Formatters set for individual columns on calling `#add_column` always override t
|
|
452
502
|
the table.
|
453
503
|
|
454
504
|
<a name="colours-and-styling"></a>
|
455
|
-
### Colours and styling
|
505
|
+
### Colours and other styling
|
506
|
+
|
507
|
+
<a name="styling-cell-content"></a>
|
508
|
+
#### Styling cell content
|
456
509
|
|
457
510
|
In most terminals, if you want to print text that is coloured, or has certain other styles such as
|
458
511
|
underlining, you need to use ANSI escape sequences, either directly, or by means of a library such
|
@@ -485,13 +538,19 @@ table.add_column(
|
|
485
538
|
)
|
486
539
|
```
|
487
540
|
|
488
|
-
The `styler` option should be passed a callable that takes two parameters: the
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
541
|
+
The `styler` option should be passed a callable that takes two parameters: the first represents
|
542
|
+
the underlying value of the cell (in this case a boolean indicating whether the number is even);
|
543
|
+
and the second represents the formatted string value of that cell, i.e. the cell content after
|
544
|
+
any processing by the [formatter](#formatting-cell-values). If the content of a cell is wrapped
|
545
|
+
over multiple lines, then the `styler` will be called once per line, so that each line of the
|
546
|
+
cell will have the escape sequence applied to it separately (ensuring the styling doesn’t bleed
|
547
|
+
into neighbouring cells).
|
548
|
+
|
549
|
+
If the content of a cell has been [truncated](#overflow-handling), then whatever colours or other styling
|
550
|
+
apply to the cell content will also be applied the truncation indicator character.
|
551
|
+
|
552
|
+
<a name="styling-column-headers"></a>
|
553
|
+
#### Styling column headers
|
495
554
|
|
496
555
|
If you want to apply colours or other styling to the content of a column header, as opposed
|
497
556
|
to cells in the table body, use the `header_styler` option, e.g.:
|
@@ -500,23 +559,39 @@ to cells in the table body, use the `header_styler` option, e.g.:
|
|
500
559
|
table.add_column(:even?, header_styler: -> (s) { "\033[32m#{s}\033[0m" })
|
501
560
|
```
|
502
561
|
|
503
|
-
|
504
|
-
|
562
|
+
<a name="default-styles"></a>
|
563
|
+
#### Setting default styles
|
564
|
+
|
565
|
+
By default, no styling is applied to the headers or body content of a column unless configured to do
|
566
|
+
so via the `header_styler` or `styler` option when calling `add_column` for that particular column.
|
567
|
+
It is possible to apply styling by default to _all_ columns in a table, however, as the table initializer
|
568
|
+
also accepts both a `header_styler` and a `styler` option. For example, if you want all the header text
|
569
|
+
in the table to be green, you could do:
|
505
570
|
|
506
571
|
```ruby
|
507
|
-
table = Tabulo::Table.new(1..5, :itself, :even?, :odd?,
|
572
|
+
table = Tabulo::Table.new(1..5, :itself, :even?, :odd?, header_styler: -> (s) { "\033[32m#{s}\033[0m" })
|
508
573
|
```
|
509
574
|
|
510
|
-
|
511
|
-
|
575
|
+
Now, all columns in the table will have automatically have green header text, unless overridden
|
576
|
+
by another header styler being passed to `#add_column`.
|
577
|
+
|
578
|
+
<a name="border-styling"></a>
|
579
|
+
#### Styling borders
|
580
|
+
|
581
|
+
Styling can also be applied to borders and dividing lines, using the `border_styler` option when
|
582
|
+
initializing the table, e.g.:
|
583
|
+
|
584
|
+
```ruby
|
585
|
+
table = Tabulo::Table.new(1..5, :itself, :even?, :odd?, border_styler: -> (s) { "\033[32m#{s}\033[0m" })
|
586
|
+
```
|
512
587
|
|
513
588
|
<a name="repeating-headers"></a>
|
514
589
|
### Repeating headers
|
515
590
|
|
516
591
|
By default, headers are only shown once, at the top of the table (`header_frequency: :start`). If
|
517
592
|
`header_frequency` is passed `nil`, headers are not shown at all; or, if passed an `Integer` N,
|
518
|
-
headers are shown at the top and then repeated every N rows. This can be handy when you
|
519
|
-
at table that
|
593
|
+
headers are shown at the top and then repeated every N rows. This can be handy when you’re looking
|
594
|
+
at table that’s taller than your terminal.
|
520
595
|
|
521
596
|
E.g.:
|
522
597
|
|
@@ -548,7 +623,7 @@ table = Tabulo::Table.new(1..10, :itself, :even?, header_frequency: 5)
|
|
548
623
|
<a name="enumerator"></a>
|
549
624
|
### Using a Table Enumerator
|
550
625
|
|
551
|
-
Because it
|
626
|
+
Because it’s an `Enumerable`, a `Tabulo::Table` can also give you an `Enumerator`,
|
552
627
|
which is useful when you want to step through rows one at a time. In a Rails console,
|
553
628
|
for example, you might do this:
|
554
629
|
|
@@ -596,8 +671,8 @@ end
|
|
596
671
|
|
597
672
|
The first argument to `add_column` always provides the key for the purpose of accessing the `Hash`
|
598
673
|
form of a `Tabulo::Row`. (If the provided argument was a `String`, it will be converted to a
|
599
|
-
`Symbol` for purposes of accessing this `Hash`.) This key serves as a sort of
|
600
|
-
the column; and it need not be the same as the column header. If we want the header to be different
|
674
|
+
`Symbol` for purposes of accessing this `Hash`.) This key serves as a sort of “logical label”
|
675
|
+
for the column; and it need not be the same as the column header. If we want the header to be different
|
601
676
|
to the label, we can achieve this using the `header` option to `add_column`:
|
602
677
|
|
603
678
|
```ruby
|
@@ -676,8 +751,8 @@ a new table in which the rows and columns are swapped:
|
|
676
751
|
|
677
752
|
By default, a header row is added to the new table, showing the string value of the element
|
678
753
|
represented in that column. This can be configured, however, along with other aspects of
|
679
|
-
`transpose
|
680
|
-
[documentation](https://www.rubydoc.info/gems/tabulo/2.
|
754
|
+
`transpose`’s behaviour. For details, see the
|
755
|
+
[documentation](https://www.rubydoc.info/gems/tabulo/2.3.0/Tabulo/Table#transpose-instance_method).
|
681
756
|
|
682
757
|
<a name="borders"></a>
|
683
758
|
### Configuring borders
|
@@ -813,6 +888,76 @@ If you want a line before every row, pass `1` to `row_divider_frequency`. For ex
|
|
813
888
|
└──────────────┴──────────────┴──────────────┘
|
814
889
|
```
|
815
890
|
|
891
|
+
<a name="freezing-a-table"></a>
|
892
|
+
### Using a table as a snapshot rather than as a dynamic view
|
893
|
+
|
894
|
+
The nature of a `Tabulo::Table` is that of a dynamic view onto the underlying `sources` enumerable
|
895
|
+
from which it was initialized (or which was subsequently assigned to its `sources` attribute). That
|
896
|
+
is, if the contents of the `sources` enumerable change subsequent to initialization of or assignment to
|
897
|
+
`sources`, then the table when printed will show the `sources` as they are at the time of printing,
|
898
|
+
not as they were at the time of initialization or assignment. For example:
|
899
|
+
|
900
|
+
```ruby
|
901
|
+
arr = [1, 2]
|
902
|
+
table = Tabulo::Table.new(arr, :itself, :even?, :odd?)
|
903
|
+
```
|
904
|
+
|
905
|
+
```
|
906
|
+
> puts table
|
907
|
+
+--------------+--------------+--------------+
|
908
|
+
| itself | even? | odd? |
|
909
|
+
+--------------+--------------+--------------+
|
910
|
+
| 1 | false | true |
|
911
|
+
| 2 | true | false |
|
912
|
+
+--------------+--------------+--------------+
|
913
|
+
```
|
914
|
+
|
915
|
+
```ruby
|
916
|
+
arr << 3
|
917
|
+
```
|
918
|
+
|
919
|
+
```
|
920
|
+
> puts table
|
921
|
+
+--------------+--------------+--------------+
|
922
|
+
| itself | even? | odd? |
|
923
|
+
+--------------+--------------+--------------+
|
924
|
+
| 1 | false | true |
|
925
|
+
| 2 | true | false |
|
926
|
+
| 3 | false | true |
|
927
|
+
+--------------+--------------+--------------+
|
928
|
+
```
|
929
|
+
|
930
|
+
In this example, even though no direct mutations have been made to `table`, the result
|
931
|
+
of calling `puts table` has changed, in virtue of a mutation on the underyling enumerable `arr`.
|
932
|
+
|
933
|
+
A similar behaviour can be seen when `sources` is an ActiveRecord query, and there
|
934
|
+
are changes to the relevant database table(s) such that the result of the query changes. This is
|
935
|
+
worth bearing in mind when calling [`pack`](#pack) on a table, since if the `sources` enumerable
|
936
|
+
changes between `pack`ing and printing, then the column widths calculated by the `pack` method
|
937
|
+
may no longer be “just right” given the changed `sources`.
|
938
|
+
|
939
|
+
If this is not the desired behaviour, there are ways around this. For example, if dealing with an
|
940
|
+
ActiveRecord relation, you can convert the query to a plain array before initializing the table:
|
941
|
+
|
942
|
+
```ruby
|
943
|
+
sources = User.all.to_a
|
944
|
+
table = Tabulo::Table.new(sources, :id, :first_name, :last_name)
|
945
|
+
table.pack
|
946
|
+
puts table
|
947
|
+
```
|
948
|
+
|
949
|
+
Passing an `Array` rather than the ActiveRecord query directly means that if there are changes to
|
950
|
+
the content of the `users` database table, these will not be reflected in the rendered content of
|
951
|
+
the `Tabulo::Table` (unless some of the `Tabulo::Table` columns are based on callables that perform
|
952
|
+
further database queries when called…).
|
953
|
+
|
954
|
+
Note that it is also possible simply to store the string value of a table for later use,
|
955
|
+
rather than the table itself:
|
956
|
+
|
957
|
+
```ruby
|
958
|
+
rendered_table = Tabulo::Table.new(1..10, :itself, :even?, :odd?).pack.to_s
|
959
|
+
```
|
960
|
+
|
816
961
|
<a name="motivation"></a>
|
817
962
|
## Comparison with other libraries
|
818
963
|
|
@@ -826,7 +971,7 @@ There are other libraries for generating plain text tables in Ruby. Popular amon
|
|
826
971
|
*DISCLAIMER: My comments regarding these other libraries are based only on my own, possibly flawed
|
827
972
|
reading of the documentation for, and experimentation with, these libraries at the time of my
|
828
973
|
writing this. Their APIs, features or documentation may well change between when I write this, and
|
829
|
-
when you read it. Please consult the libraries
|
974
|
+
when you read it. Please consult the libraries’ own documentation for yourself, rather than relying
|
830
975
|
on these comments.*
|
831
976
|
|
832
977
|
While these libraries have their strengths, I have personally found that, for the common use case of
|
@@ -835,7 +980,7 @@ query result), using these libraries feels more cumbersome than it could be.
|
|
835
980
|
|
836
981
|
For example, suppose we have called `User.all` from the Rails console, and want to print
|
837
982
|
a table showing the email, first name, last name and ID of each user,
|
838
|
-
with column headings. Also, we want the ID column to be right-aligned, because it
|
983
|
+
with column headings. Also, we want the ID column to be right-aligned, because it’s a number.
|
839
984
|
|
840
985
|
In **terminal-table**, we could achieve this as follows:
|
841
986
|
|
@@ -848,7 +993,7 @@ puts table
|
|
848
993
|
```
|
849
994
|
|
850
995
|
The problem here is that there is no single source of knowledge about which columns
|
851
|
-
appear, and in which order. If we want to add another column to the left of
|
996
|
+
appear, and in which order. If we want to add another column to the left of “email”,
|
852
997
|
we need to amend the rows array, and the headings array, and the index passed to `align_column`.
|
853
998
|
We bear the burden of keeping these three in sync. This is not be a big deal for small one-off
|
854
999
|
tables, but for tables that have many columns, or that are constructed
|
@@ -856,9 +1001,9 @@ dynamically based on user input or other runtime factors determining the columns
|
|
856
1001
|
to be included, this can be a hassle and a source of brittleness.
|
857
1002
|
|
858
1003
|
**tty-table** has a somewhat different API to `terminal-table`. It offers both a
|
859
|
-
|
860
|
-
is similar to `terminal-table
|
861
|
-
column ordering across multiple code locations. The
|
1004
|
+
“row-based” and a “column-based” method of initializing a table. The row-based method
|
1005
|
+
is similar to `terminal-table`’s in that it burdens the developer with syncing the
|
1006
|
+
column ordering across multiple code locations. The “column-based” API for `tty-table`, on
|
862
1007
|
the other hand, seems to avoid this problem. One way of using it is like this:
|
863
1008
|
|
864
1009
|
```ruby
|
@@ -874,26 +1019,27 @@ table = TTY::Table.new [
|
|
874
1019
|
puts table
|
875
1020
|
```
|
876
1021
|
|
877
|
-
While this doesn
|
1022
|
+
While this doesn’t seem too bad, it does mean that the underlying collection (`users`) has to
|
878
1023
|
be traversed multiple times, once for each column, which is inefficient, particularly
|
879
|
-
if the underlying collection is large. In addition, it
|
1024
|
+
if the underlying collection is large. In addition, it’s not clear how to pass separate
|
880
1025
|
formatting information for each column when initializing in this way. (Perhaps there is a way to do
|
881
|
-
this, but if there is, it doesn
|
1026
|
+
this, but if there is, it doesn’t seem to be documented.) So it seems we still have to use
|
882
1027
|
`table.align_column(3, :right)`, which again burdens us with keeping the column index
|
883
1028
|
passed to `align_column` in sync.
|
884
1029
|
|
885
1030
|
As for **table\_print**, this is a handy gem for quickly tabulating ActiveRecord
|
886
1031
|
collections from the Rails console. `table_print` is similar to `tabulo` in that it has a
|
887
|
-
column-based API, so it doesn
|
1032
|
+
column-based API, so it doesn’t suffer from the multiple-source-of-knowledge issue in regards to
|
888
1033
|
column orderings. However, it lacks certain other useful features, such as the ability to repeat
|
889
1034
|
headers every N rows, the automatic alignment of columns based on cell content (numbers right,
|
890
1035
|
strings left), and a quick and easy way to automatically resize columns to accommodate cell content
|
891
|
-
without overflowing the terminal. Also, as of the time of writing, `table_print
|
1036
|
+
without overflowing the terminal. Also, as of the time of writing, `table_print`’s last significant
|
892
1037
|
commit (ignoring a deprecation warning fix in April 2018) was in March 2016.
|
893
1038
|
|
894
|
-
Finally, it is worth mentioning the **hirb** library. This is similar to `table_print`, in that
|
1039
|
+
Finally, it is worth mentioning the **hirb** library. This is similar to `table_print`, in that
|
1040
|
+
it’s
|
895
1041
|
well suited to quickly displaying ActiveRecord collections from the Rails console. However, like
|
896
|
-
`table_print`, there are certain useful features it
|
1042
|
+
`table_print`, there are certain useful features it’s lacking; and using it outside the console
|
897
1043
|
environment seems cumbersome. Moreover, it seems no longer to be maintained. At the time of writing,
|
898
1044
|
its last commit was in March 2015.
|
899
1045
|
|
@@ -915,14 +1061,14 @@ The gem is available as open source under the terms of the [MIT
|
|
915
1061
|
License](http://opensource.org/licenses/MIT).
|
916
1062
|
|
917
1063
|
[Gem Version]: https://rubygems.org/gems/tabulo
|
918
|
-
[Documentation]: http://www.rubydoc.info/gems/tabulo/2.
|
1064
|
+
[Documentation]: http://www.rubydoc.info/gems/tabulo/2.3.0
|
919
1065
|
[Build Status]: https://travis-ci.org/matt-harvey/tabulo
|
920
1066
|
[Coverage Status]: https://coveralls.io/r/matt-harvey/tabulo
|
921
1067
|
[Code Climate]: https://codeclimate.com/github/matt-harvey/tabulo
|
922
1068
|
[Awesome Ruby]: https://github.com/markets/awesome-ruby#cli-utilities
|
923
1069
|
|
924
1070
|
[GV img]: https://img.shields.io/gem/v/tabulo.svg
|
925
|
-
[DC img]: https://img.shields.io/badge/documentation-v2.
|
1071
|
+
[DC img]: https://img.shields.io/badge/documentation-v2.3.0-blue.svg
|
926
1072
|
[BS img]: https://img.shields.io/travis/matt-harvey/tabulo.svg
|
927
1073
|
[CS img]: https://img.shields.io/coveralls/matt-harvey/tabulo.svg
|
928
1074
|
[CC img]: https://codeclimate.com/github/matt-harvey/tabulo/badges/gpa.svg
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.3.0
|
data/lib/tabulo/column.rb
CHANGED
@@ -6,16 +6,23 @@ module Tabulo
|
|
6
6
|
attr_accessor :width
|
7
7
|
attr_reader :header
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
|
9
|
+
def initialize(
|
10
|
+
align_body:,
|
11
|
+
align_header:,
|
12
|
+
extractor:,
|
13
|
+
formatter:,
|
14
|
+
header:,
|
15
|
+
header_styler:,
|
16
|
+
padding_character:,
|
17
|
+
styler:,
|
18
|
+
truncation_indicator:,
|
19
|
+
width:)
|
11
20
|
|
12
|
-
@header = header
|
13
|
-
@width = width
|
14
|
-
@align_header = align_header
|
15
21
|
@align_body = align_body
|
16
|
-
@
|
22
|
+
@align_header = align_header
|
17
23
|
@extractor = extractor
|
18
|
-
@
|
24
|
+
@formatter = formatter
|
25
|
+
@header = header
|
19
26
|
|
20
27
|
@header_styler =
|
21
28
|
if header_styler
|
@@ -24,31 +31,33 @@ module Tabulo
|
|
24
31
|
-> (_, s) { s }
|
25
32
|
end
|
26
33
|
|
27
|
-
@truncation_indicator = truncation_indicator
|
28
34
|
@padding_character = padding_character
|
35
|
+
@styler = styler || -> (_, s) { s }
|
36
|
+
@truncation_indicator = truncation_indicator
|
37
|
+
@width = width
|
29
38
|
end
|
30
39
|
|
31
40
|
def header_cell
|
32
41
|
Cell.new(
|
33
|
-
value: @header,
|
34
|
-
formatter: -> (s) { s },
|
35
42
|
alignment: @align_header,
|
36
|
-
|
43
|
+
formatter: -> (s) { s },
|
44
|
+
padding_character: @padding_character,
|
37
45
|
styler: @header_styler,
|
38
46
|
truncation_indicator: @truncation_indicator,
|
39
|
-
|
47
|
+
value: @header,
|
48
|
+
width: @width,
|
40
49
|
)
|
41
50
|
end
|
42
51
|
|
43
52
|
def body_cell(source)
|
44
53
|
Cell.new(
|
45
|
-
value: body_cell_value(source),
|
46
|
-
formatter: @formatter,
|
47
54
|
alignment: @align_body,
|
48
|
-
|
55
|
+
formatter: @formatter,
|
56
|
+
padding_character: @padding_character,
|
49
57
|
styler: @styler,
|
50
58
|
truncation_indicator: @truncation_indicator,
|
51
|
-
|
59
|
+
value: body_cell_value(source),
|
60
|
+
width: @width,
|
52
61
|
)
|
53
62
|
end
|
54
63
|
|
data/lib/tabulo/row.rb
CHANGED
@@ -7,11 +7,11 @@ module Tabulo
|
|
7
7
|
attr_reader :source
|
8
8
|
|
9
9
|
# @!visibility private
|
10
|
-
def initialize(table, source,
|
10
|
+
def initialize(table, source, divider: false, header: :top)
|
11
11
|
@table = table
|
12
12
|
@source = source
|
13
|
-
@header = header
|
14
13
|
@divider = divider
|
14
|
+
@header = header
|
15
15
|
end
|
16
16
|
|
17
17
|
# Calls the given block once for each {Cell} in the {Row}, passing that {Cell} as parameter.
|
data/lib/tabulo/table.rb
CHANGED
@@ -74,17 +74,21 @@ module Tabulo
|
|
74
74
|
# element indicates the amount to apply to the right.
|
75
75
|
# @param [Integer, nil] column_width The default column width for columns in this
|
76
76
|
# table, not excluding padding. If <tt>nil</tt>, then {DEFAULT_COLUMN_WIDTH} will be used.
|
77
|
-
# @param [nil, #to_proc] formatter (:to_s.to_proc) The default
|
78
|
-
#
|
77
|
+
# @param [nil, #to_proc] formatter (:to_s.to_proc) The default formatter for columns in this
|
78
|
+
# table. See `formatter` option of {#add_column} for details.
|
79
79
|
# @param [:start, nil, Integer] header_frequency (:start) Controls the display of column headers.
|
80
80
|
# If passed <tt>:start</tt>, headers will be shown at the top of the table only. If passed <tt>nil</tt>,
|
81
81
|
# headers will not be shown. If passed an Integer N (> 0), headers will be shown at the top of the table,
|
82
82
|
# then repeated every N rows.
|
83
|
+
# @param [nil, #to_proc] header_styler (nil) The default header styler for columns in this
|
84
|
+
# table. See `header_styler` option of {#add_column} for details.
|
83
85
|
# @param [nil, Integer] row_divider_frequency (nil) Controls the display of horizontal row dividers within
|
84
86
|
# the table body. If passed <tt>nil</tt>, dividers will not be shown. If passed an Integer N (> 0),
|
85
87
|
# dividers will be shown after every N rows. The characters used to form the dividers are
|
86
88
|
# determined by the `border` option, and are the same as those used to form the bottom edge of the
|
87
89
|
# header row.
|
90
|
+
# @param [nil, #to_proc] styler (nil) The default styler for columns in this table. See `styler`
|
91
|
+
# option of {#add_column} for details.
|
88
92
|
# @param [nil, String] truncation_indicator Determines the character used to indicate that a
|
89
93
|
# cell's content has been truncated. If omitted or passed <tt>nil</tt>,
|
90
94
|
# defaults to {DEFAULT_TRUNCATION_INDICATOR}. If passed something other than <tt>nil</tt> or
|
@@ -114,7 +118,9 @@ module Tabulo
|
|
114
118
|
column_width: nil,
|
115
119
|
formatter: :to_s.to_proc,
|
116
120
|
header_frequency: :start,
|
121
|
+
header_styler: nil,
|
117
122
|
row_divider_frequency: nil,
|
123
|
+
styler: nil,
|
118
124
|
truncation_indicator: nil,
|
119
125
|
wrap_body_cells_to: nil,
|
120
126
|
wrap_header_cells_to: nil)
|
@@ -139,6 +145,7 @@ module Tabulo
|
|
139
145
|
@column_width = (column_width || DEFAULT_COLUMN_WIDTH)
|
140
146
|
@formatter = formatter
|
141
147
|
@header_frequency = header_frequency
|
148
|
+
@header_styler = header_styler
|
142
149
|
@row_divider_frequency = row_divider_frequency
|
143
150
|
@truncation_indicator = validate_character(truncation_indicator,
|
144
151
|
DEFAULT_TRUNCATION_INDICATOR, InvalidTruncationIndicatorError, "truncation indicator")
|
@@ -171,6 +178,11 @@ module Tabulo
|
|
171
178
|
# by the Table-level setting passed to the <tt>align_header</tt> (which itself defaults
|
172
179
|
# to <tt>:center</tt>). Otherwise, this option determines the alignment of the header
|
173
180
|
# content for this column.
|
181
|
+
# @param [Symbol, String, Integer, nil] before (nil) The label of the column before (i.e. to
|
182
|
+
# the left of) which the new column should inserted. If <tt>nil</tt> is passed, it will be
|
183
|
+
# inserted after all other columns. If there is no column with the given label, then an
|
184
|
+
# {InvalidColumnLabelError} will be raised. A non-Integer labelled column can be identified
|
185
|
+
# in either String or Symbol form for this purpose.
|
174
186
|
# @param [#to_proc] formatter (nil) A lambda or other callable object that
|
175
187
|
# will be passed the calculated value of each cell to determine how it should be displayed. This
|
176
188
|
# is distinct from the extractor (see below). For example, if the extractor for this column
|
@@ -222,6 +234,7 @@ module Tabulo
|
|
222
234
|
label,
|
223
235
|
align_body: nil,
|
224
236
|
align_header: nil,
|
237
|
+
before: nil,
|
225
238
|
formatter: nil,
|
226
239
|
header: nil,
|
227
240
|
header_styler: nil,
|
@@ -241,19 +254,43 @@ module Tabulo
|
|
241
254
|
raise InvalidColumnLabelError, "Column label already used in this table."
|
242
255
|
end
|
243
256
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
+
column = Column.new(
|
258
|
+
align_body: align_body || @align_body,
|
259
|
+
align_header: align_header || @align_header,
|
260
|
+
extractor: extractor || label.to_proc,
|
261
|
+
formatter: formatter || @formatter,
|
262
|
+
header: (header || label).to_s,
|
263
|
+
header_styler: header_styler || @header_styler,
|
264
|
+
padding_character: PADDING_CHARACTER,
|
265
|
+
styler: styler || @styler,
|
266
|
+
truncation_indicator: @truncation_indicator,
|
267
|
+
width: width || @column_width,
|
268
|
+
)
|
269
|
+
|
270
|
+
add_column_before(column, column_label, before)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Removes the column identifed by the passed label.
|
274
|
+
#
|
275
|
+
# @example
|
276
|
+
# table = Table.new(1..10, :itself, :even?, :odd?)
|
277
|
+
# table.add_column(:even2, header: "even?") { |n| n.even? }
|
278
|
+
# table.remove_column(:even2)
|
279
|
+
# table.remove_column(:odd?)
|
280
|
+
#
|
281
|
+
# @param [Symbol, String, Integer] label The unique identifier for the column to be removed,
|
282
|
+
# corresponding to the label that was passed as the first parameter to {#add_column} (or was
|
283
|
+
# used in the table initializer) when the column was originally added. For columns that were
|
284
|
+
# originally added with a String or Symbol label, either a String or Symbol form of that label
|
285
|
+
# can be passed to {#remove_column}, indifferently. For example, if the label passed to
|
286
|
+
# {#add_column} had been `"height"`, then that column could be removed by passing either
|
287
|
+
# `"height"` or `:height` to {#remove_column}. (However, if an Integer was originally passed
|
288
|
+
# as the label to {#add_column}, then only that same Integer, as an Integer, can be passed to
|
289
|
+
# {#remove_column} to remove that column.)
|
290
|
+
# @return [true, false] If the label identifies a column in the table, then the column will be
|
291
|
+
# removed and true will be returned; otherwise no column will be removed, and false will be returned.
|
292
|
+
def remove_column(label)
|
293
|
+
!!column_registry.delete(Integer === label ? label : label.to_sym)
|
257
294
|
end
|
258
295
|
|
259
296
|
# @return [String] a graphical "ASCII" representation of the Table, suitable for
|
@@ -420,15 +457,15 @@ module Tabulo
|
|
420
457
|
# @return [Table] a new {Table}
|
421
458
|
# @raise [InvalidBorderError] if invalid argument passed to `border` parameter.
|
422
459
|
def transpose(opts = {})
|
423
|
-
default_opts = [:
|
424
|
-
:
|
425
|
-
:
|
460
|
+
default_opts = [:align_body, :align_header, :border, :border_styler, :column_padding, :column_width,
|
461
|
+
:formatter, :header_frequency, :row_divider_frequency, :truncation_indicator, :wrap_body_cells_to,
|
462
|
+
:wrap_header_cells_to].map do |sym|
|
426
463
|
[sym, instance_variable_get("@#{sym}")]
|
427
464
|
end.to_h
|
428
465
|
|
429
466
|
initializer_opts = default_opts.merge(Util.slice_hash(opts, *default_opts.keys))
|
430
|
-
default_extra_opts = {
|
431
|
-
|
467
|
+
default_extra_opts = { field_names_body_alignment: :right, field_names_header: "",
|
468
|
+
field_names_header_alignment: :right, field_names_width: nil, headers: :to_s.to_proc }
|
432
469
|
extra_opts = default_extra_opts.merge(Util.slice_hash(opts, *default_extra_opts.keys))
|
433
470
|
|
434
471
|
# The underlying enumerable for the new table, is the columns of the original table.
|
@@ -440,8 +477,9 @@ module Tabulo
|
|
440
477
|
width_opt = extra_opts[:field_names_width]
|
441
478
|
field_names_width = (width_opt.nil? ? fields.map { |f| f.header.length }.max : width_opt)
|
442
479
|
|
443
|
-
t.add_column(:dummy,
|
444
|
-
extra_opts[:field_names_header_alignment],
|
480
|
+
t.add_column(:dummy, align_body: extra_opts[:field_names_body_alignment],
|
481
|
+
align_header: extra_opts[:field_names_header_alignment], header: extra_opts[:field_names_header],
|
482
|
+
width: field_names_width, &:header)
|
445
483
|
|
446
484
|
# Add a column to the new table for each of the original table's sources
|
447
485
|
sources.each_with_index do |source, i|
|
@@ -476,6 +514,28 @@ module Tabulo
|
|
476
514
|
|
477
515
|
private
|
478
516
|
|
517
|
+
# @!visibility private
|
518
|
+
def add_column_before(column, label, before)
|
519
|
+
if before == nil
|
520
|
+
@column_registry[label] = column
|
521
|
+
else
|
522
|
+
old_column_entries = @column_registry.to_a
|
523
|
+
new_column_entries = []
|
524
|
+
found = false
|
525
|
+
old_column_entries.each do |entry|
|
526
|
+
if entry[0] == before
|
527
|
+
found = true
|
528
|
+
new_column_entries << [label, column]
|
529
|
+
end
|
530
|
+
new_column_entries << entry
|
531
|
+
end
|
532
|
+
unless found
|
533
|
+
raise InvalidColumnLabelError, "There is no column with label #{before}"
|
534
|
+
end
|
535
|
+
@column_registry = new_column_entries.to_h
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
479
539
|
# @!visibility private
|
480
540
|
def total_column_padding
|
481
541
|
@left_column_padding + @right_column_padding
|
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: 2.
|
4
|
+
version: 2.3.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: 2020-
|
11
|
+
date: 2020-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: tty-screen
|