tabulo 1.2.2 → 1.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/.travis.yml +1 -1
- data/CHANGELOG.md +11 -0
- data/README.md +187 -38
- data/VERSION +1 -1
- data/lib/tabulo.rb +2 -1
- data/lib/tabulo/column.rb +1 -1
- data/lib/tabulo/deprecation.rb +32 -0
- data/lib/tabulo/row.rb +3 -0
- data/lib/tabulo/table.rb +113 -42
- data/lib/tabulo/version.rb +1 -1
- data/tabulo.gemspec +4 -2
- metadata +19 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6ca950eccef1e0d5bcd333ce42584fd4a95020316e80d2f684b721f0cb6ec00a
|
4
|
+
data.tar.gz: ea4c7f96ebce149b131db590fdeb75a54152576717007024556047a36004469f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f83f8889ad5f72954bc0a16a743c6ecdb00b732fdac29d179a43874eba68b021f180be981bbce82773bf4f8f0e968a92c03400cf9af74cf8a6bb23ed585cf77
|
7
|
+
data.tar.gz: f9a47229fe11cfc79ea265f4280ca84666e7e09c7d325db4e4b4feb9c2a854f76a9c1647fdcbd2eff3a5073fd327044be75758af839f227750ab1fd24aa2eccc
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### v1.3.0
|
4
|
+
|
5
|
+
* More ergonomic Table initializer, allowing you to specify columns directly as varargs rather
|
6
|
+
than as an array passed to `columns:` option (the latter is now deprecated)
|
7
|
+
* New `#pack` method to autosize table, capping total table width at width of terminal
|
8
|
+
by default (replaces `#shrinkwrap!` method, now deprecated)
|
9
|
+
* Ability to set table-level defaults for column header and body cell alignments
|
10
|
+
* Accessor methods for `source` attribute, representing the underlying collection
|
11
|
+
being tabulated, facilitating reuse of the same table to tabulate different collections
|
12
|
+
* Documentation improvements
|
13
|
+
|
3
14
|
### v1.2.2
|
4
15
|
|
5
16
|
* Improve documentation.
|
data/README.md
CHANGED
@@ -10,8 +10,21 @@
|
|
10
10
|
|
11
11
|
Tabulo is a Ruby library for generating ASCII tables.
|
12
12
|
|
13
|
+
*Quick API:*
|
14
|
+
|
15
|
+
```
|
16
|
+
> puts Tabulo::Table.new(User.all, :id, :first_name, :last_name)
|
17
|
+
+--------------+--------------+--------------+
|
18
|
+
| id | first_name | last_name |
|
19
|
+
+--------------+--------------+--------------+
|
20
|
+
| 1 | John | Citizen |
|
21
|
+
| 2 | Jane | Doe |
|
22
|
+
```
|
23
|
+
|
24
|
+
*Full API:*
|
25
|
+
|
13
26
|
```ruby
|
14
|
-
underyling_enumerable = [1, 2, 50000000]
|
27
|
+
underyling_enumerable = [1, 2, 50000000]
|
15
28
|
|
16
29
|
table = Tabulo::Table.new(underlying_enumerable) do |t|
|
17
30
|
t.add_column("N") { |n| n }
|
@@ -31,13 +44,10 @@ end
|
|
31
44
|
|
32
45
|
## Features
|
33
46
|
|
34
|
-
* A [DRY interface](#configuring-columns): by being "column based", it is designed to spare the
|
35
|
-
developer the burden of syncing the ordering within the header row with that of the body rows.
|
36
47
|
* Lets you set [fixed column widths](#fixed-column-widths), then either [wrap](#overflow-handling) or
|
37
48
|
[truncate](#overflow-handling) the overflow.
|
38
|
-
* Alternatively, [
|
39
|
-
|
40
|
-
[stop it overflowing your terminal horizontally](#max-table-width).
|
49
|
+
* Alternatively, ["pack"](#pack) the table so that each column is automatically just wide enough for its
|
50
|
+
contents, but [without overflowing the terminal horizontally](#max-table-width).
|
41
51
|
* Alignment of cell content is [configurable](#cell-alignment), but has helpful content-based defaults
|
42
52
|
(numbers right, strings left).
|
43
53
|
* Headers are [repeatable](#repeating-headers).
|
@@ -47,6 +57,8 @@ end
|
|
47
57
|
* Each `Tabulo::Row` is also an `Enumerable`, [providing access](#accessing-cell-values) to the underlying cell values.
|
48
58
|
* Tabulate any `Enumerable`: the underlying collection need not be an array.
|
49
59
|
* [Customize](#additional-configuration-options) border and divider characters.
|
60
|
+
* Use a [DRY interface](#configuring-columns): by being "column based", it is designed to spare the
|
61
|
+
developer the burden of syncing the ordering within the header row with that of the body rows.
|
50
62
|
|
51
63
|
Tabulo has also been ported to Crystal (with some modifications): see [Tablo](https://github.com/hutou/tablo).
|
52
64
|
|
@@ -54,7 +66,7 @@ Tabulo has also been ported to Crystal (with some modifications): see [Tablo](ht
|
|
54
66
|
|
55
67
|
* [Overview](#overview)
|
56
68
|
* [Features](#features)
|
57
|
-
* [Table of
|
69
|
+
* [Table of contents](#table-of-contents)
|
58
70
|
* [Installation](#installation)
|
59
71
|
* [Detailed usage](#detailed-usage)
|
60
72
|
* [Requiring the gem](#requiring-the-gem)
|
@@ -69,8 +81,9 @@ Tabulo has also been ported to Crystal (with some modifications): see [Tablo](ht
|
|
69
81
|
* [Repeating headers](#repeating-headers)
|
70
82
|
* [Using a Table Enumerator](#using-a-table-enumerator)
|
71
83
|
* [Accessing cell values](#accessing-cell-values)
|
84
|
+
* [Accessing the underlying enumerable](#accessing-sources)
|
72
85
|
* [Additional configuration options](#additional-configuration-options)
|
73
|
-
* [
|
86
|
+
* [Motivation](#motivation)
|
74
87
|
* [Contributing](#contributing)
|
75
88
|
* [License](#license)
|
76
89
|
|
@@ -114,10 +127,10 @@ table = Tabulo::Table.new([1, 2, 5]) do |t|
|
|
114
127
|
end
|
115
128
|
```
|
116
129
|
|
117
|
-
Or equivalently:
|
130
|
+
Or equivalently, using the "quick API":
|
118
131
|
|
119
132
|
```ruby
|
120
|
-
table = Tabulo::Table.new([1, 2, 5],
|
133
|
+
table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?)
|
121
134
|
```
|
122
135
|
|
123
136
|
```
|
@@ -157,11 +170,20 @@ end
|
|
157
170
|
|
158
171
|
By default, column header text is center-aligned, while the content of each body cell is aligned
|
159
172
|
according to its data type. Numbers are right-aligned, text is left-aligned, and booleans (`false`
|
160
|
-
and `true`) are center-aligned.
|
161
|
-
|
173
|
+
and `true`) are center-aligned.
|
174
|
+
|
175
|
+
This default behaviour can be set at the table level, by passing `:center`, `:left` or `:right`
|
176
|
+
to the `align_header` or `align_body` options when initializing the table:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
table = Tabulo::Table.new([1, 2], :itself, :even?, align_header: :left, align_body: :right)
|
180
|
+
```
|
181
|
+
|
182
|
+
The table-level alignment settings can be overridden for individual columns by
|
183
|
+
passing using similarly-named options passed to `add_column`, e.g.:
|
162
184
|
|
163
185
|
```ruby
|
164
|
-
table.add_column("Doubled", align_header: :
|
186
|
+
table.add_column("Doubled", align_header: :right, align_body: :left) { |n| n * 2 }
|
165
187
|
```
|
166
188
|
|
167
189
|
### Column width, wrapping and truncation
|
@@ -192,7 +214,7 @@ If you want to set the default column width for all columns of the table to some
|
|
192
214
|
than 12, use the `column_width` option when initializing the table:
|
193
215
|
|
194
216
|
```ruby
|
195
|
-
table = Tabulo::Table.new([1, 2],
|
217
|
+
table = Tabulo::Table.new([1, 2], :itself, :even?, column_width: 6)
|
196
218
|
```
|
197
219
|
|
198
220
|
```
|
@@ -213,7 +235,7 @@ The single character of padding either side of each column is not counted in the
|
|
213
235
|
The amount of this padding can be configured for the table as a whole, using the `column_padding`
|
214
236
|
option passed to `Table.new`.
|
215
237
|
|
216
|
-
<a name="
|
238
|
+
<a name="pack"></a>
|
217
239
|
#### Automating column widths
|
218
240
|
|
219
241
|
Instead of setting column widths "manually", you can tell the table to sort out the widths
|
@@ -221,8 +243,8 @@ itself, so that each column is just wide enough for its header and contents (plu
|
|
221
243
|
of padding):
|
222
244
|
|
223
245
|
```ruby
|
224
|
-
table = Tabulo::Table.new([1, 2],
|
225
|
-
table.
|
246
|
+
table = Tabulo::Table.new([1, 2], :itself, :even?)
|
247
|
+
table.pack
|
226
248
|
```
|
227
249
|
|
228
250
|
```
|
@@ -234,17 +256,17 @@ table.shrinkwrap!
|
|
234
256
|
| 2 | true |
|
235
257
|
```
|
236
258
|
|
237
|
-
The `
|
259
|
+
The `pack` method returns the table itself, so you can "pack-and-print" in one go:
|
238
260
|
|
239
261
|
```ruby
|
240
|
-
puts Tabulo::Table.new([1, 2],
|
262
|
+
puts Tabulo::Table.new([1, 2], :itself, :even?).pack
|
241
263
|
```
|
242
264
|
|
243
265
|
<a name="max-table-width"></a>
|
244
|
-
You can place an upper limit on the total width of the table when
|
266
|
+
You can manually place an upper limit on the total width of the table when packing:
|
245
267
|
|
246
268
|
```ruby
|
247
|
-
puts Tabulo::Table.new([1, 2],
|
269
|
+
puts Tabulo::Table.new([1, 2], :itself, :even?).pack(max_table_width: 17)
|
248
270
|
```
|
249
271
|
|
250
272
|
```
|
@@ -256,17 +278,23 @@ puts Tabulo::Table.new([1, 2], columns: %i[itself even?]).shrinkwrap!(max_table_
|
|
256
278
|
| 2 | true |
|
257
279
|
```
|
258
280
|
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
targetted each time—until the table will fit. This is very useful when you want to ensure the
|
263
|
-
table will not overflow your terminal horizontally.
|
281
|
+
Or if you simply call `pack` with no parameters (or if you explicitly call
|
282
|
+
`pack(max_table_width: :auto)`), the table width will automatically be capped at the
|
283
|
+
width of your terminal.
|
264
284
|
|
265
|
-
|
285
|
+
If you want the table width not to be capped at all, call `pack(max_table_width: nil)`.
|
286
|
+
|
287
|
+
If the table cannot be fit within the width of the terminal, or the specified maximum width,
|
288
|
+
then column widths are reduced as required, with wrapping or truncation then occuring as
|
289
|
+
necessary (see [Overflow handling](#overflow-handling)). Under the hood, a character of width
|
290
|
+
is deducted column by column—the widest column being targetted each time—until
|
291
|
+
the table will fit.
|
292
|
+
|
293
|
+
Note that `pack`ing the table necessarily involves traversing the entire collection up front as
|
266
294
|
the maximum cell width needs to be calculated for each column. You may not want to do this
|
267
|
-
if the collection is very large. Note also the effect of `
|
295
|
+
if the collection is very large. Note also the effect of `pack` is to fix the column widths
|
268
296
|
as appropriate to the formatted cell contents given the state of the underlying collection
|
269
|
-
_at the point of
|
297
|
+
_at the point of packing_. If the underlying collection changes between that point, and when
|
270
298
|
the table is printed, then the columns will _not_ be resized yet again on printing. This is a
|
271
299
|
consequence of the table always being essentially a "live view" on the underlying collection:
|
272
300
|
formatted contents are never cached within the table itself.
|
@@ -280,7 +308,7 @@ required:
|
|
280
308
|
```ruby
|
281
309
|
table = Tabulo::Table.new(
|
282
310
|
["hello", "abcdefghijklmnopqrstuvwxyz"],
|
283
|
-
|
311
|
+
:itself, :length
|
284
312
|
)
|
285
313
|
```
|
286
314
|
|
@@ -304,8 +332,8 @@ outputted cell content to show that truncation has occurred:
|
|
304
332
|
```ruby
|
305
333
|
table = Tabulo::Table.new(
|
306
334
|
["hello", "abcdefghijklmnopqrstuvwxyz"],
|
307
|
-
|
308
|
-
|
335
|
+
:itself, :length,
|
336
|
+
wrap_body_cells_to: 1
|
309
337
|
)
|
310
338
|
```
|
311
339
|
|
@@ -363,7 +391,7 @@ at table that's taller than your terminal.
|
|
363
391
|
E.g.:
|
364
392
|
|
365
393
|
```ruby
|
366
|
-
table = Tabulo::Table.new(1..10,
|
394
|
+
table = Tabulo::Table.new(1..10, :itself, :even?, header_frequency: 5)
|
367
395
|
```
|
368
396
|
|
369
397
|
```
|
@@ -411,7 +439,7 @@ end.to_enum # <-- make an Enumerator
|
|
411
439
|
```
|
412
440
|
|
413
441
|
Note the use of `.find_each`: we can start printing the table without having to load the entire
|
414
|
-
underlying collection. (This is negated if we [
|
442
|
+
underlying collection. (This is negated if we [pack](#pack) the table, however, since
|
415
443
|
in that case the entire collection must be traversed up front in order for column widths to be
|
416
444
|
calculated.)
|
417
445
|
|
@@ -422,7 +450,7 @@ is itself an `Enumerable` comprising the underlying the values of each cell. A `
|
|
422
450
|
also be converted to a `Hash` for keyed access. For example:
|
423
451
|
|
424
452
|
```ruby
|
425
|
-
table = Tabulo::Table.new(1..5,
|
453
|
+
table = Tabulo::Table.new(1..5, :itself, :even?, :odd?)
|
426
454
|
|
427
455
|
table.each do |row|
|
428
456
|
row.each { |cell| puts cell } # 1...2...3...4...5
|
@@ -449,6 +477,43 @@ table.each do |row|
|
|
449
477
|
end
|
450
478
|
```
|
451
479
|
|
480
|
+
<a name="accessing-sources"></a>
|
481
|
+
### Accessing the underlying enumerable
|
482
|
+
|
483
|
+
The underlying enumerable for a table can be retrieved by calling the `sources` getter:
|
484
|
+
|
485
|
+
```ruby
|
486
|
+
table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?)
|
487
|
+
```
|
488
|
+
|
489
|
+
```
|
490
|
+
> table.sources
|
491
|
+
=> [1, 2, 5]
|
492
|
+
```
|
493
|
+
|
494
|
+
There is also a corresponding setter, meaning you can reuse the same table to tabulate
|
495
|
+
a different data set, without having to reconfigure the columns and other options from scratch:
|
496
|
+
|
497
|
+
```ruby
|
498
|
+
table.sources = [50, 60]
|
499
|
+
```
|
500
|
+
|
501
|
+
```
|
502
|
+
> table.sources
|
503
|
+
=> [50, 60]
|
504
|
+
```
|
505
|
+
|
506
|
+
In addition, the element of the underlying enumerable corresponding to a particular
|
507
|
+
row can be accessed by calling the `source` method on that row:
|
508
|
+
|
509
|
+
```ruby
|
510
|
+
table.each do |row|
|
511
|
+
puts row.source # 50...60...
|
512
|
+
end
|
513
|
+
|
514
|
+
```
|
515
|
+
|
516
|
+
<a name="additional-configuration-options"></a>
|
452
517
|
### Additional configuration options
|
453
518
|
|
454
519
|
The characters used for horizontal dividers, vertical dividers and corners, which default to `-`,
|
@@ -470,7 +535,7 @@ This will output a bottom border that's appropriately sized for the table.
|
|
470
535
|
This mechanism can also be used to output a horizontal divider after each row:
|
471
536
|
|
472
537
|
```ruby
|
473
|
-
table = Tabulo::Table.new(1..3,
|
538
|
+
table = Tabulo::Table.new(1..3, :itself, :even?)
|
474
539
|
```
|
475
540
|
|
476
541
|
```
|
@@ -486,6 +551,90 @@ table = Tabulo::Table.new(1..3, columns: %i[itself even?])
|
|
486
551
|
+--------------+--------------+
|
487
552
|
```
|
488
553
|
|
554
|
+
<a name="motivation"></a>
|
555
|
+
## Motivation
|
556
|
+
|
557
|
+
There are other terminal table generators for Ruby. Popular among these are:
|
558
|
+
|
559
|
+
* [tty-table](https://github.com/piotrmurach/tty-table)
|
560
|
+
* [terminal-table](https://github.com/tj/terminal-table)
|
561
|
+
* [table\_print](https://github.com/arches/table_print)
|
562
|
+
|
563
|
+
*DISCLAIMER: My comments regarding these other libraries are based only on my own,
|
564
|
+
possibly-flawed, reading of the documentation for, and experimentation with, these libraries at the
|
565
|
+
time of my writing this. Their APIs, features or documentation may well change between when I write this, and
|
566
|
+
when you read it—possibly in ways that address the issues I describe here!*
|
567
|
+
|
568
|
+
These are excellent libraries, and each has its strengths. However, I personally found that for the
|
569
|
+
common use case of printing a table on the basis of some underlying collection (such as an
|
570
|
+
ActiveRecord query result), using these libraries in practice was more cumbersome than it needed to
|
571
|
+
be.
|
572
|
+
|
573
|
+
For example, suppose we have called `User.all` from the Rails console, and want to print
|
574
|
+
a table showing the email, first name, last name and ID of each user,
|
575
|
+
with column headings. Also, we want the ID column to be right-aligned, because it's a number.
|
576
|
+
|
577
|
+
In `terminal-table`, we could achieve this as follows:
|
578
|
+
|
579
|
+
```ruby
|
580
|
+
rows = User.all.map { |u| [u.email, u.first_name, u.last_name, u.id] }
|
581
|
+
headings = ["email", "first name", "last name", "id"]
|
582
|
+
table = Terminal::Table.new(headings: headings, rows: rows)
|
583
|
+
table.align_column(3, :right)
|
584
|
+
puts table
|
585
|
+
```
|
586
|
+
|
587
|
+
The problem here is that there is no single source of knowledge about which columns
|
588
|
+
appear, and in which order. If we want to add another column to the left of "email",
|
589
|
+
we need to amend the rows map, and the headings map, and the index passed to `align_column`.
|
590
|
+
We bear the burden of keeping these three in sync. This is not be a big deal for small one-off
|
591
|
+
tables, but for tables that have many columns, or that are constructed
|
592
|
+
dynamically based on user input or other runtime factors determining the columns
|
593
|
+
to be included, this can be a hassle and a source of brittleness.
|
594
|
+
|
595
|
+
`tty-table` has a somewhat different API to `terminal-table`. It offers both a
|
596
|
+
"row-based" and a "column-based" method of initializing a table. We won't cover
|
597
|
+
the row-based method here, but it is similar to `terminal-table`'s in that it
|
598
|
+
burdens the developer with syncing the column ordering across multiple code
|
599
|
+
locations. The "column-based" API for `tty-table`, on the other hand, looks like this:
|
600
|
+
|
601
|
+
```ruby
|
602
|
+
users = User.all
|
603
|
+
table = TTY::Table.new [
|
604
|
+
{
|
605
|
+
"email" => users.map(&:email),
|
606
|
+
"first name" => users.map(&:first_name),
|
607
|
+
"last name" => users.map(&:last_name),
|
608
|
+
"id" => users.map(&:id),
|
609
|
+
}
|
610
|
+
]
|
611
|
+
puts table
|
612
|
+
```
|
613
|
+
|
614
|
+
While this doesn't seem too bad, it does mean that the underlying collection (`users`) has to
|
615
|
+
be traversed multiple times, once for each column, which is inefficient, particularly
|
616
|
+
if the underlying collection is large. In addition, it's not clear how to pass separate
|
617
|
+
formatting information for each column when initializing in this way. (Perhaps there is a way to do
|
618
|
+
this, but if there is, it doesn't seem to be documented.) So it seems we still have to use
|
619
|
+
`table.align_column(3, :right)`, which again burdens us with keeping the column index
|
620
|
+
passed to `align_column` in sync.
|
621
|
+
|
622
|
+
Finally, there is [table\_print](https://github.com/arches/table_print). This is a
|
623
|
+
handy gem for quickly tabulating ActiveRecord collections from the Rails
|
624
|
+
console. `table_print` is similar to `tabulo` in that it has a column-based API, so it doesn't
|
625
|
+
suffer from the multiple-source-of-knowledge issue in regards to column orderings. However, as far
|
626
|
+
as I can tell, it lacks certain other useful features, such as the ability to repeat headers every N
|
627
|
+
rows, the automatic alignment of columns based on cell content (numbers right, strings left), and a
|
628
|
+
quick and easy way to automatically resize columns to accommodate cell content without overflowing
|
629
|
+
the terminal. Also, as of the time of writing, `table_print`'s last significant commit (ignoring a
|
630
|
+
deprecation warning fix in April 2018) was in March 2016.
|
631
|
+
|
632
|
+
I don't mean to disparage any of these other projects—they each have their strengths—but
|
633
|
+
rather to explain my motivation for developing and maintaining Tabulo despite their existence, and
|
634
|
+
to provide reasons one might consider for using `tabulo` rather than another terminal table
|
635
|
+
gem.
|
636
|
+
|
637
|
+
<a name="contributing"></a>
|
489
638
|
## Contributing
|
490
639
|
|
491
640
|
Issues and pull requests are welcome on GitHub at https://github.com/matt-harvey/tabulo.
|
@@ -503,13 +652,13 @@ The gem is available as open source under the terms of the [MIT
|
|
503
652
|
License](http://opensource.org/licenses/MIT).
|
504
653
|
|
505
654
|
[Gem Version]: https://rubygems.org/gems/tabulo
|
506
|
-
[Documentation]: http://www.rubydoc.info/gems/tabulo/1.
|
655
|
+
[Documentation]: http://www.rubydoc.info/gems/tabulo/1.3.0
|
507
656
|
[Build Status]: https://travis-ci.org/matt-harvey/tabulo
|
508
657
|
[Coverage Status]: https://coveralls.io/r/matt-harvey/tabulo
|
509
658
|
[Code Climate]: https://codeclimate.com/github/matt-harvey/tabulo
|
510
659
|
|
511
660
|
[GV img]: https://img.shields.io/gem/v/tabulo.svg
|
512
|
-
[DC img]: https://img.shields.io/badge/documentation-v1.
|
661
|
+
[DC img]: https://img.shields.io/badge/documentation-v1.3.0-blue.svg
|
513
662
|
[BS img]: https://img.shields.io/travis/matt-harvey/tabulo.svg
|
514
663
|
[CS img]: https://img.shields.io/coveralls/matt-harvey/tabulo.svg
|
515
664
|
[CC img]: https://codeclimate.com/github/matt-harvey/tabulo/badges/gpa.svg
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.0
|
data/lib/tabulo.rb
CHANGED
data/lib/tabulo/column.rb
CHANGED
@@ -22,7 +22,7 @@ module Tabulo
|
|
22
22
|
def body_subcells(source)
|
23
23
|
cell_datum = body_cell_value(source)
|
24
24
|
formatted_content = @formatter.call(cell_datum)
|
25
|
-
real_alignment = (@align_body
|
25
|
+
real_alignment = (@align_body == :auto ? infer_alignment(cell_datum) : @align_body)
|
26
26
|
infilled_subcells(formatted_content, real_alignment)
|
27
27
|
end
|
28
28
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Tabulo
|
2
|
+
|
3
|
+
module Deprecation
|
4
|
+
|
5
|
+
# @!visibility private
|
6
|
+
def self.skipping_warnings
|
7
|
+
@skipping_warnings ||= false
|
8
|
+
end
|
9
|
+
|
10
|
+
# @!visibility private
|
11
|
+
def self.skipping_warnings=(v)
|
12
|
+
@skipping_warnings = v
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!visibility private
|
16
|
+
def self.without_warnings
|
17
|
+
original = skipping_warnings
|
18
|
+
self.skipping_warnings = true
|
19
|
+
yield
|
20
|
+
ensure
|
21
|
+
self.skipping_warnings = original
|
22
|
+
end
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
def self.warn(deprecated, replacement, stack_level = 1)
|
26
|
+
return if skipping_warnings
|
27
|
+
|
28
|
+
kaller = Kernel.caller[stack_level]
|
29
|
+
Kernel.warn "#{kaller}: [DEPRECATION] #{deprecated} is deprecated. Please use #{replacement} instead."
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/tabulo/row.rb
CHANGED
data/lib/tabulo/table.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "tty-screen"
|
2
|
+
|
1
3
|
module Tabulo
|
2
4
|
|
3
5
|
# Represents a table primarily intended for "pretty-printing" in a fixed-width font.
|
@@ -30,11 +32,15 @@ module Tabulo
|
|
30
32
|
# @!visibility private
|
31
33
|
attr_reader :column_registry
|
32
34
|
|
35
|
+
# @return [Enumerable] the underlying enumerable from which the table derives its data
|
36
|
+
attr_accessor :sources
|
37
|
+
|
33
38
|
# @param [Enumerable] sources the underlying Enumerable from which the table will derive its data
|
34
|
-
# @param [Array[Symbol]]
|
39
|
+
# @param [Array[Symbol]] cols Specifies the initial columns. The Symbols provided must
|
35
40
|
# be unique. Each element of the Array will be used to create a column whose content is
|
36
41
|
# created by calling the corresponding method on each element of sources. Note
|
37
42
|
# the {#add_column} method is a much more flexible way to set up columns on the table.
|
43
|
+
# @param [Array[Symbol]] columns <b>DEPRECATED</b> Use {cols} instead.
|
38
44
|
# @param [Integer, nil] column_width The default column width for columns in this
|
39
45
|
# table, not excluding padding. If <tt>nil</tt>, then {DEFAULT_COLUMN_WIDTH} will be used.
|
40
46
|
# @param [:start, nil, Integer] header_frequency Controls the display of column headers.
|
@@ -70,13 +76,27 @@ module Tabulo
|
|
70
76
|
# a single-character String, raises {InvalidTruncationIndicatorError}.
|
71
77
|
# @param [nil, Integer] column_padding Determines the amount of blank space with which to pad either
|
72
78
|
# of each column. Defaults to 1.
|
73
|
-
# @
|
79
|
+
# @param [:left, :right, :center] align_header (:center) Determines the alignment of header text
|
80
|
+
# for columns in this Table. Can be overridden for individual columns using the
|
81
|
+
# <tt>align_header</tt> option passed to {#add_column}
|
82
|
+
# @param [:left, :right, :center, :auto] align_body (:auto) Determines the alignment of body cell
|
83
|
+
# (i.e. non-header) content within columns in this Table. Can be overridden for individual columns
|
84
|
+
# using the <tt>align_body</tt> option passed to {#add_column}. If passed <tt>:auto</tt>,
|
85
|
+
# alignment is determined by cell content, with numbers aligned right, booleans
|
86
|
+
# center-aligned, and other values left-aligned.
|
87
|
+
# @return [Table] a new {Table}
|
74
88
|
# @raise [InvalidColumnLabelError] if non-unique Symbols are provided to columns.
|
75
89
|
# @raise [InvalidHorizontalRuleCharacterError] if invalid argument passed to horizontal_rule_character.
|
76
90
|
# @raise [InvalidVerticalRuleCharacterError] if invalid argument passed to vertical_rule_character.
|
77
|
-
def initialize(sources, columns: [], column_width: nil, column_padding: nil, header_frequency: :start,
|
91
|
+
def initialize(sources, *cols, columns: [], column_width: nil, column_padding: nil, header_frequency: :start,
|
78
92
|
wrap_header_cells_to: nil, wrap_body_cells_to: nil, horizontal_rule_character: nil,
|
79
|
-
vertical_rule_character: nil, intersection_character: nil, truncation_indicator: nil
|
93
|
+
vertical_rule_character: nil, intersection_character: nil, truncation_indicator: nil,
|
94
|
+
align_header: :center, align_body: :auto)
|
95
|
+
|
96
|
+
if columns.any?
|
97
|
+
Deprecation.warn("`columns' option to Tabulo::Table#initialize",
|
98
|
+
"the variable length parameter `cols'", 2)
|
99
|
+
end
|
80
100
|
|
81
101
|
@sources = sources
|
82
102
|
@header_frequency = header_frequency
|
@@ -84,6 +104,8 @@ module Tabulo
|
|
84
104
|
@wrap_body_cells_to = wrap_body_cells_to
|
85
105
|
@default_column_width = (column_width || DEFAULT_COLUMN_WIDTH)
|
86
106
|
@column_padding = (column_padding || DEFAULT_COLUMN_PADDING)
|
107
|
+
@align_header = align_header
|
108
|
+
@align_body = align_body
|
87
109
|
|
88
110
|
@horizontal_rule_character = validate_character(horizontal_rule_character,
|
89
111
|
DEFAULT_HORIZONTAL_RULE_CHARACTER, InvalidHorizontalRuleCharacterError, "horizontal rule character")
|
@@ -95,6 +117,7 @@ module Tabulo
|
|
95
117
|
DEFAULT_TRUNCATION_INDICATOR, InvalidTruncationIndicatorError, "truncation indicator")
|
96
118
|
|
97
119
|
@column_registry = { }
|
120
|
+
cols.each { |item| add_column(item) }
|
98
121
|
columns.each { |item| add_column(item) }
|
99
122
|
|
100
123
|
yield self if block_given?
|
@@ -109,13 +132,18 @@ module Tabulo
|
|
109
132
|
# for this column.
|
110
133
|
# @param [nil, #to_s] header (nil) Text to be displayed in the column header. If passed nil,
|
111
134
|
# the column's label will also be used as its header text.
|
112
|
-
# @param [:left, :center, :right] align_header (
|
113
|
-
# should be aligned.
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
135
|
+
# @param [:left, :center, :right, nil] align_header (nil) Specifies how the header text
|
136
|
+
# should be aligned. If <tt>nil</tt> is passed, then the alignment is determined
|
137
|
+
# by the Table-level setting passed to the <tt>align_header</tt> (which itself defaults
|
138
|
+
# to <tt>:center</tt>). Otherwise, this option determines the alignment of the header
|
139
|
+
# content for this column.
|
140
|
+
# @param [:left, :center, :right, :auto, nil] align_body (nil) Specifies how the cell body contents
|
141
|
+
# should be aligned. If <tt>nil</tt> is passed, then the alignment is determined
|
142
|
+
# by the Table-level setting passed to the <tt>align_body</tt> option on Table initialization
|
143
|
+
# (which itself defaults to <tt>:auto</tt>). Otherwise this option determines the alignment of
|
144
|
+
# this column. If <tt>:auto</tt> is passed, the alignment is determined by the type of the cell
|
145
|
+
# value, with numbers aligned right, booleans center-aligned, and other values left-aligned.
|
146
|
+
# Note header text alignment is configured separately using the :align_header param.
|
119
147
|
# @param [Integer] width (nil) Specifies the width of the column, excluding padding. If
|
120
148
|
# nil, then the column will take the width provided by the `column_width` param
|
121
149
|
# with which the Table was initialized.
|
@@ -132,7 +160,7 @@ module Tabulo
|
|
132
160
|
# @raise [InvalidColumnLabelError] if label has already been used for another column in this
|
133
161
|
# Table. (This is case-sensitive, but is insensitive to whether a String or Symbol is passed
|
134
162
|
# to the label parameter.)
|
135
|
-
def add_column(label, header: nil, align_header:
|
163
|
+
def add_column(label, header: nil, align_header: nil, align_body: nil,
|
136
164
|
width: nil, formatter: :to_s.to_proc, &extractor)
|
137
165
|
|
138
166
|
column_label = label.to_sym
|
@@ -144,8 +172,8 @@ module Tabulo
|
|
144
172
|
@column_registry[column_label] =
|
145
173
|
Column.new(
|
146
174
|
header: (header || label).to_s,
|
147
|
-
align_header: align_header,
|
148
|
-
align_body: align_body,
|
175
|
+
align_header: align_header || @align_header,
|
176
|
+
align_body: align_body || @align_body,
|
149
177
|
width: (width || @default_column_width),
|
150
178
|
formatter: formatter,
|
151
179
|
extractor: (extractor || label.to_proc)
|
@@ -221,19 +249,21 @@ module Tabulo
|
|
221
249
|
# is called. If the source Enumerable changes between that point, and the point when
|
222
250
|
# the Table is printed, then columns will *not* be resized yet again on printing.
|
223
251
|
#
|
224
|
-
# @param [nil, Numeric] max_table_width (
|
225
|
-
# width (including padding and borders) from expanding beyond
|
252
|
+
# @param [nil, Numeric] max_table_width (:auto) With no args, or if passed <tt>:auto</tt>,
|
253
|
+
# stops the total table width (including padding and borders) from expanding beyond the
|
254
|
+
# bounds of the terminal screen.
|
255
|
+
# If passed <tt>nil</tt>, the table width will not be capped.
|
226
256
|
# Width is deducted from columns if required to achieve this, with one character progressively
|
227
257
|
# deducted from the width of the widest column until the target is reached. When the
|
228
258
|
# table is printed, wrapping or truncation will then occur in these columns as required
|
229
|
-
# (depending on how they were configured).
|
230
|
-
# max_table_width, the table will always be left wide
|
231
|
-
# 1 character's width of content, 1 character of left padding and
|
232
|
-
# in each column, together with border characters (1 on each side
|
233
|
-
# adjacent columns). I.e. there is a certain width below width the
|
234
|
-
# shrink itself.
|
259
|
+
# (depending on how they were configured).
|
260
|
+
# Note that regardless of the value passed to max_table_width, the table will always be left wide
|
261
|
+
# enough to accommodate at least 1 character's width of content, 1 character of left padding and
|
262
|
+
# 1 character of right padding in each column, together with border characters (1 on each side
|
263
|
+
# of the table and 1 between adjacent columns). I.e. there is a certain width below width the
|
264
|
+
# Table will refuse to shrink itself.
|
235
265
|
# @return [Table] the Table itself
|
236
|
-
def
|
266
|
+
def pack(max_table_width: :auto)
|
237
267
|
return self if column_registry.none?
|
238
268
|
columns = column_registry.values
|
239
269
|
|
@@ -247,30 +277,48 @@ module Tabulo
|
|
247
277
|
end
|
248
278
|
|
249
279
|
if max_table_width
|
250
|
-
|
251
|
-
|
252
|
-
total_borders = column_registry.count + 1
|
253
|
-
unadjusted_table_width = total_columns_width + total_padding + total_borders
|
254
|
-
|
255
|
-
# Ensure max table width is at least wide enough to accommodate table borders and padding
|
256
|
-
# and one character of content.
|
257
|
-
min_table_width = total_padding + total_borders + column_registry.count
|
258
|
-
max_table_width = min_table_width if min_table_width > max_table_width
|
259
|
-
|
260
|
-
required_reduction = [unadjusted_table_width - max_table_width, 0].max
|
261
|
-
|
262
|
-
required_reduction.times do
|
263
|
-
widest_column = columns.inject(columns.first) do |widest, column|
|
264
|
-
column.width >= widest.width ? column : widest
|
265
|
-
end
|
266
|
-
|
267
|
-
widest_column.width -= 1
|
268
|
-
end
|
280
|
+
max_table_width = TTY::Screen.width if max_table_width == :auto
|
281
|
+
shrink_to(max_table_width)
|
269
282
|
end
|
270
283
|
|
271
284
|
self
|
272
285
|
end
|
273
286
|
|
287
|
+
# @deprecated Use {#pack} instead.
|
288
|
+
#
|
289
|
+
# Reset all the column widths so that each column is *just* wide enough to accommodate
|
290
|
+
# its header text as well as the formatted content of each its cells for the entire
|
291
|
+
# collection, together with a single character of padding on either side of the column,
|
292
|
+
# without any wrapping.
|
293
|
+
#
|
294
|
+
# Note that calling this method will cause the entire source Enumerable to
|
295
|
+
# be traversed and all the column extractors and formatters to be applied in order
|
296
|
+
# to calculate the required widths.
|
297
|
+
#
|
298
|
+
# Note also that this method causes column widths to be fixed as appropriate to the
|
299
|
+
# formatted cell contents given the state of the source Enumerable at the point it
|
300
|
+
# is called. If the source Enumerable changes between that point, and the point when
|
301
|
+
# the Table is printed, then columns will *not* be resized yet again on printing.
|
302
|
+
#
|
303
|
+
# @param [nil, Numeric] max_table_width (nil) If provided, stops the total table
|
304
|
+
# width (including padding and borders) from expanding beyond this number of characters.
|
305
|
+
# If passed <tt>:auto</tt>, the table width will automatically be capped at the current
|
306
|
+
# terminal width.
|
307
|
+
# Width is deducted from columns if required to achieve this, with one character progressively
|
308
|
+
# deducted from the width of the widest column until the target is reached. When the
|
309
|
+
# table is printed, wrapping or truncation will then occur in these columns as required
|
310
|
+
# (depending on how they were configured).
|
311
|
+
# Note that regardless of the value passed to max_table_width, the table will always be left wide
|
312
|
+
# enough to accommodate at least 1 character's width of content, 1 character of left padding and
|
313
|
+
# 1 character of right padding in each column, together with border characters (1 on each side
|
314
|
+
# of the table and 1 between adjacent columns). I.e. there is a certain width below width the
|
315
|
+
# Table will refuse to shrink itself.
|
316
|
+
# @return [Table] the Table itself
|
317
|
+
def shrinkwrap!(max_table_width: nil)
|
318
|
+
Deprecation.warn("`Tabulo::Table#shrinkwrap!'", "`#pack'")
|
319
|
+
pack(max_table_width: max_table_width)
|
320
|
+
end
|
321
|
+
|
274
322
|
# @!visibility private
|
275
323
|
def formatted_body_row(source, with_header: false)
|
276
324
|
cells = column_registry.map { |_, column| column.body_subcells(source) }
|
@@ -284,6 +332,29 @@ module Tabulo
|
|
284
332
|
|
285
333
|
private
|
286
334
|
|
335
|
+
# @!visibility private
|
336
|
+
def shrink_to(max_table_width)
|
337
|
+
columns = column_registry.values
|
338
|
+
total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
|
339
|
+
total_padding = column_registry.count * @column_padding * 2
|
340
|
+
total_borders = column_registry.count + 1
|
341
|
+
unadjusted_table_width = total_columns_width + total_padding + total_borders
|
342
|
+
|
343
|
+
# Ensure max table width is at least wide enough to accommodate table borders and padding
|
344
|
+
# and one character of content.
|
345
|
+
min_table_width = total_padding + total_borders + column_registry.count
|
346
|
+
max_table_width = min_table_width if min_table_width > max_table_width
|
347
|
+
required_reduction = [unadjusted_table_width - max_table_width, 0].max
|
348
|
+
|
349
|
+
required_reduction.times do
|
350
|
+
widest_column = columns.inject(columns.first) do |widest, column|
|
351
|
+
column.width >= widest.width ? column : widest
|
352
|
+
end
|
353
|
+
|
354
|
+
widest_column.width -= 1
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
287
358
|
# @!visibility private
|
288
359
|
def body_row(source, with_header: false)
|
289
360
|
Row.new(self, source, with_header: with_header)
|
data/lib/tabulo/version.rb
CHANGED
data/tabulo.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Matthew Harvey"]
|
10
10
|
spec.email = ["software@matthewharvey.net"]
|
11
11
|
|
12
|
-
spec.summary = "Enumerable ASCII table"
|
13
|
-
spec.description = "Enumerable ASCII table"
|
12
|
+
spec.summary = "Enumerable ASCII terminal table"
|
13
|
+
spec.description = "Enumerable ASCII terminal table"
|
14
14
|
spec.homepage = "https://github.com/matt-harvey/tabulo"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
|
|
28
28
|
"changelog_uri" => "https://raw.githubusercontent.com/matt-harvey/tabulo/master/CHANGELOG.md"
|
29
29
|
}
|
30
30
|
|
31
|
+
spec.add_runtime_dependency "tty-screen", "0.6.5"
|
32
|
+
|
31
33
|
spec.add_development_dependency "bundler"
|
32
34
|
spec.add_development_dependency "rake", "~> 11.0"
|
33
35
|
spec.add_development_dependency "rspec", "~> 3.0"
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tabulo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.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: 2019-
|
11
|
+
date: 2019-04-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: tty-screen
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.6.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.6.5
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: bundler
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -136,7 +150,7 @@ dependencies:
|
|
136
150
|
- - "~>"
|
137
151
|
- !ruby/object:Gem::Version
|
138
152
|
version: '1.0'
|
139
|
-
description: Enumerable ASCII table
|
153
|
+
description: Enumerable ASCII terminal table
|
140
154
|
email:
|
141
155
|
- software@matthewharvey.net
|
142
156
|
executables: []
|
@@ -160,6 +174,7 @@ files:
|
|
160
174
|
- bin/setup
|
161
175
|
- lib/tabulo.rb
|
162
176
|
- lib/tabulo/column.rb
|
177
|
+
- lib/tabulo/deprecation.rb
|
163
178
|
- lib/tabulo/exceptions.rb
|
164
179
|
- lib/tabulo/row.rb
|
165
180
|
- lib/tabulo/table.rb
|
@@ -190,5 +205,5 @@ rubyforge_project:
|
|
190
205
|
rubygems_version: 2.7.6
|
191
206
|
signing_key:
|
192
207
|
specification_version: 4
|
193
|
-
summary: Enumerable ASCII table
|
208
|
+
summary: Enumerable ASCII terminal table
|
194
209
|
test_files: []
|