tabulo 1.5.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdbbbcb7f1eb925be546ddb8b798f2c2aa949e737aa221018cfffeb25352458a
4
- data.tar.gz: 6960e851a8086dc345d4f4db2d3867893b4a0772c748336b2341dd22f9c862be
3
+ metadata.gz: 02cf8fc58fb0164abdbf0fda2d80c4487c9be3bb17ecccfefc05bdf4bd381dd4
4
+ data.tar.gz: 826091614ae6734712d7320a6ab5c5fe169555646dd0f34e398cbc6fcbc414fc
5
5
  SHA512:
6
- metadata.gz: b4488086be31ac7f659110c3f55c1a6ecce1ccfa59406ba165490622b86167bc61a666a3c42d4ae8c12a5ad4975141ea418242bcd971a4930fbc75a991797cc4
7
- data.tar.gz: 1de32be32999408268de863e94c79fed536e6844a658f135288c4469c8081d3bce7461c90ef809d67f9a2cf5a1fd6c36f323da4c64a40d20d2fff336bf65d2eb
6
+ metadata.gz: ff79da1b58dd5cfe72214041b271a962ae43ad398cd00e4ac42b10af113128ade8e4a9026a09144f8d1dabffcb511c54740d4358d8ba57fcde060f81c7512835
7
+ data.tar.gz: f9b7dbabe3dbe44732b0e914a42b5be28ec5989183f4e576b7ece3227b36cfbd7508a659f78f804153c8de2512135496c5a9dd3f165144254841153f738328b8
data/.travis.yml CHANGED
@@ -4,6 +4,6 @@ rvm:
4
4
  - 2.1.10
5
5
  - 2.2.10
6
6
  - 2.3.8
7
- - 2.4.6
8
- - 2.5.5
9
- - 2.6.3
7
+ - 2.4.9
8
+ - 2.5.7
9
+ - 2.6.5
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ### v2.0.0
4
+
5
+ #### New features
6
+
7
+ * New `border` option for `Tabulo::Table` initializer allows for better customization of border and
8
+ divider characters, using a preset list of options, viz.: `:ascii`, `:modern`, `:markdown`,
9
+ `:blank` and `:classic`. In particular, the `:modern` border option uses smoothly drawn Unicode
10
+ line characters; and the `:markdown` option renders a GitHub-flavoured Markdown table.
11
+ * `Tabulo::Table#horizontal_rule` method accepts `:top`, `:bottom` and `:middle` options to allow
12
+ the appropriate border characters to be used depending on its intended position in the table.
13
+ * When iterating a `Tabulo::Row`, it's now possible to get the formatted string value of an individual
14
+ `Tabulo::Cell`, not just its underlying "raw" value.
15
+ * Column padding can now optionally be configured separately for left and right column sides, by
16
+ passing a 2-element Array to the `column_padding` option of the `Tabulo::Table` initializer.
17
+
18
+ #### Breaking changes
19
+
20
+ * A `Tabulo::Row` is now a collection of `Tabulo::Cell`, not a collection of underlying "raw"
21
+ values. This makes it easier to get at both formatted string values and underlying "raw" values of
22
+ `Cell`s when traversing a `Row`. To get at the raw underlying value, call `Tabulo::Cell#value`.
23
+ * Remove deprecated `columns` option from `Tabulo::Table` initializer
24
+ (existing `cols` positional parameter now renamed to `columns`).
25
+ * Remove deprecated `shrinkwrap!` method (use `pack` instead).
26
+ * By default, table now has a border line at the bottom. Pass `:classic` to the `border` option of
27
+ the `Tabulo::Table` initializer to get the old behaviour.
28
+ * Removal of `horizontal_rule_character`, `vertical_rule_character` and `intersection` character
29
+ options from `Tabulo::Table` initializer, and from `Tabulo::Table#transpose` method. Use the
30
+ `border` option instead.
31
+
32
+ #### Other noteworthy changes
33
+
34
+ * Test coverage is now at exactly 100%
35
+ * `hirb` gem now mentioned in README
36
+
3
37
  ### v1.5.1
4
38
 
5
39
  * Dependency version upgrades
data/README.md CHANGED
@@ -12,38 +12,17 @@ Tabulo is a terminal table generator for Ruby.
12
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
- ## Overview
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.
16
17
 
17
- *Quick API:*
18
-
19
- ```
20
- > puts Tabulo::Table.new(User.all, :id, :first_name, :last_name)
21
- +--------------+--------------+--------------+
22
- | id | first_name | last_name |
23
- +--------------+--------------+--------------+
24
- | 1 | John | Citizen |
25
- | 2 | Jane | Doe |
26
18
  ```
27
-
28
- *Full API:*
29
-
30
- ```ruby
31
- underyling_enumerable = [1, 2, 50000000]
32
-
33
- table = Tabulo::Table.new(underlying_enumerable) do |t|
34
- t.add_column("N") { |n| n }
35
- t.add_column("Doubled") { |n| n * 2 }
36
- end
37
- ```
38
-
39
- ```
40
- > puts table
41
- +--------------+--------------+
42
- | N | Doubled |
43
- +--------------+--------------+
44
- | 1 | 2 |
45
- | 2 | 4 |
46
- | 5000000 | 10000000 |
19
+ > puts Tabulo::Table.new(User.all, :id, :first_name, :last_name, border: :modern).pack
20
+ ┌────┬────────────┬───────────┐
21
+ │ id │ first_name │ last_name │
22
+ ├────┼────────────┼───────────┤
23
+ │ 1 John │ Citizen │
24
+ │ 2 │ Jane │ Doe │
25
+ └────┴────────────┴───────────┘
47
26
  ```
48
27
 
49
28
  ## Features
@@ -62,10 +41,10 @@ end
62
41
  underlying cell values.
63
42
  * The header row can be [repeated](#repeating-headers) at arbitrary intervals.
64
43
  * Newlines within cell content are correctly handled.
65
- * Multibyte characters are correctly handled.
66
- * Apply [colours](#colours-and-styling) and other styling to table content without breaking the table.
44
+ * Multibyte Unicode characters are correctly handled.
45
+ * Apply [colours](#colours-and-styling) and other styling to table content and borders, without breaking the table.
67
46
  * Easily [transpose](#transposition) the table, so that rows are swapped with columns.
68
- * [Customize](#additional-configuration-options) border and divider characters.
47
+ * Choose from multiple [border configurations](#borders), including Markdown, "ASCII", and smoothly joined Unicode border characters.
69
48
  * Use a [DRY initialization interface](#configuring-columns): by being "column based", it is
70
49
  designed to spare the developer the burden of syncing the ordering within the header row with that of the body rows.
71
50
 
@@ -93,8 +72,8 @@ Tabulo has also been ported to Crystal (with some modifications): see [Tablo](ht
93
72
  * [Accessing cell values](#accessing-cell-values)
94
73
  * [Accessing the underlying enumerable](#accessing-sources)
95
74
  * [Transposing rows and columns](#transposition)
96
- * [Additional configuration options](#additional-configuration-options)
97
- * [Motivation](#motivation)
75
+ * [Border configuration](#borders)
76
+ * [Comparison with other libraries](#motivation)
98
77
  * [Contributing](#contributing)
99
78
  * [License](#license)
100
79
 
@@ -162,6 +141,7 @@ table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?)
162
141
  | 1 | false | true |
163
142
  | 2 | true | false |
164
143
  | 5 | false | true |
144
+ +--------------+--------------+--------------+
165
145
  ```
166
146
 
167
147
  Columns can also be initialized using a callable to which each object will be passed to determine
@@ -184,6 +164,7 @@ end
184
164
  | 1 | 2 | true |
185
165
  | 2 | 4 | false |
186
166
  | 5 | 10 | true |
167
+ +--------------+--------------+--------------+
187
168
  ```
188
169
 
189
170
  <a name="cell-alignment"></a>
@@ -229,6 +210,7 @@ end
229
210
  +--------+-----------+
230
211
  | 1 | false |
231
212
  | 2 | true |
213
+ +--------+-----------+
232
214
  ```
233
215
 
234
216
  If you want to set the default column width for all columns of the table to something other
@@ -245,6 +227,7 @@ table = Tabulo::Table.new([1, 2], :itself, :even?, column_width: 6)
245
227
  +--------+--------+
246
228
  | 1 | false |
247
229
  | 2 | true |
230
+ +--------+--------+
248
231
  ```
249
232
 
250
233
  Widths set for individual columns always override the default column width for the table.
@@ -253,8 +236,44 @@ Widths set for individual columns always override the default column width for t
253
236
  #### Configuring padding
254
237
 
255
238
  The single character of padding either side of each column is not counted in the column width.
256
- The amount of this padding can be configured for the table as a whole, using the `column_padding`
257
- option passed to `Table.new`.
239
+ The amount of this extra padding can be configured for the table as a whole, using the `column_padding`
240
+ option passed to `Table.new`&mdash;the default value of this option being `1`.
241
+
242
+ Passing a single integer to this option causes the given amount of padding to be applied to each
243
+ side of each column. For example:
244
+
245
+ ```ruby
246
+ table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?, column_padding: 0)
247
+ ```
248
+
249
+ ```
250
+ > puts table
251
+ +------------+------------+------------+
252
+ | itself | even? | odd? |
253
+ +------------+------------+------------+
254
+ | 1| false | true |
255
+ | 2| true | false |
256
+ | 5| false | true |
257
+ +------------+------------+------------+
258
+ ```
259
+
260
+ Passing an array of _two_ integers to this option configures the left and right padding for each
261
+ column, according to the first and second element of the array, respectively. For example:
262
+
263
+ ```ruby
264
+ table = Tabulo::Table.new([1, 2, 5], :itself, :even?, :odd?, column_padding: [0, 2])
265
+ ```
266
+
267
+ ```
268
+ > puts table
269
+ +--------------+--------------+--------------+
270
+ | itself | even? | odd? |
271
+ +--------------+--------------+--------------+
272
+ | 1 | false | true |
273
+ | 2 | true | false |
274
+ | 5 | false | true |
275
+ +--------------+--------------+--------------+
276
+ ```
258
277
 
259
278
  <a name="pack"></a>
260
279
  #### Automating column widths
@@ -275,6 +294,7 @@ table.pack
275
294
  +--------+-------+
276
295
  | 1 | false |
277
296
  | 2 | true |
297
+ +--------+-------+
278
298
  ```
279
299
 
280
300
  The `pack` method returns the table itself, so you can "pack-and-print" in one go:
@@ -297,6 +317,7 @@ puts Tabulo::Table.new([1, 2], :itself, :even?).pack(max_table_width: 17)
297
317
  +-------+-------+
298
318
  | 1 | false |
299
319
  | 2 | true |
320
+ +-------+-------+
300
321
  ```
301
322
 
302
323
  Or if you simply call `pack` with no arguments (or if you explicitly call
@@ -342,6 +363,7 @@ table = Tabulo::Table.new(
342
363
  | abcdefghijkl | 26 |
343
364
  | mnopqrstuvwx | |
344
365
  | yz | |
366
+ +--------------+--------------+
345
367
  ```
346
368
 
347
369
  Wrapping behaviour is configured for the table as a whole using the `wrap_header_cells_to` option
@@ -365,6 +387,7 @@ table = Tabulo::Table.new(
365
387
  +--------------+--------------+
366
388
  | hello | 5 |
367
389
  | abcdefghijkl~| 26 |
390
+ +--------------+--------------+
368
391
  ```
369
392
 
370
393
  <a name="formatting-cell-values"></a>
@@ -393,6 +416,7 @@ end
393
416
  | 1 | 1.00 |
394
417
  | 2 | 0.50 |
395
418
  | 3 | 0.33 |
419
+ +--------------+--------------+
396
420
  ```
397
421
 
398
422
  Note the numbers in the "Reciprocal" column in this example are still right-aligned, even though
@@ -493,6 +517,7 @@ table = Tabulo::Table.new(1..10, :itself, :even?, header_frequency: 5)
493
517
  | 8 | true |
494
518
  | 9 | false |
495
519
  | 10 | true |
520
+ +--------------+--------------+
496
521
  ```
497
522
 
498
523
  <a name="enumerator"></a>
@@ -526,16 +551,21 @@ calculated.)
526
551
 
527
552
  <a name="accessing-cell-values"></a>
528
553
  ### Accessing cell values
554
+
529
555
  Each `Tabulo::Table` is an `Enumerable` of which each element is a `Tabulo::Row`. Each `Tabulo::Row`
530
- is itself an `Enumerable` comprising the underlying values of each cell. A `Tabulo::Row` can also
556
+ is itself an `Enumerable`, of `Tabulo::Cell`. The `Tabulo::Cell#value` method will return the
557
+ underlying value of the cell; while `Tabulo::Cell#formatted_value` will return its formatted content
558
+ as a string.
559
+
560
+ A `Tabulo::Row` can also
531
561
  be converted to a `Hash` for keyed access. For example:
532
562
 
533
563
  ```ruby
534
564
  table = Tabulo::Table.new(1..5, :itself, :even?, :odd?)
535
565
 
536
566
  table.each do |row|
537
- row.each { |cell| puts cell } # 1...2...3...4...5
538
- puts row.to_h[:even?] # false...true...false...true...false
567
+ row.each { |cell| puts cell.value } # 1...2...3...4...5
568
+ puts row.to_h[:even?].value # false...true...false...true...false
539
569
  end
540
570
  ```
541
571
 
@@ -553,8 +583,8 @@ end
553
583
 
554
584
  table.each do |row|
555
585
  cells = row.to_h
556
- puts cells[:Number] # 1...2...3...4...5
557
- puts cells[:doubled] # 2...4...6...8...10
586
+ puts cells[:Number].value # 1...2...3...4...5
587
+ puts cells[:doubled].value # 2...4...6...8...10
558
588
  end
559
589
  ```
560
590
 
@@ -616,59 +646,95 @@ a new table in which the rows and columns are swapped:
616
646
  | pred | -2 | -1 | 0 |
617
647
  | succ | 0 | 1 | 2 |
618
648
  | abs | 1 | 0 | 1 |
649
+ +-------+--------------+--------------+--------------+
619
650
  ```
620
651
 
621
652
  By default, a header row is added to the new table, showing the string value of the element
622
653
  represented in that column. This can be configured, however, along with other aspects of
623
654
  `transpose`'s behaviour. For details, see the
624
- [documentation](https://www.rubydoc.info/gems/tabulo/1.5.1/Tabulo/Table#transpose-instance_method).
655
+ [documentation](https://www.rubydoc.info/gems/tabulo/2.0.0/Tabulo/Table#transpose-instance_method).
625
656
 
626
- <a name="additional-configuration-options"></a>
627
- ### Additional configuration options
657
+ <a name="borders"></a>
658
+ ### Configuring borders
628
659
 
629
- The characters used for horizontal dividers, vertical dividers and corners, which default to `-`,
630
- `|` and `+` respectively, can be configured using the using the `horizontal_rule_character`,
631
- `vertical_rule_character` and `intersection_character` options passed to `Table.new`.
660
+ You can configure the kind of border and divider characters that are used when the table is printed.
661
+ This is done using the `border` option passed to `Table.new`. The options are as follows.
632
662
 
633
- The character used to indicate truncation, which defaults to `~`, can be configured using the
634
- `truncation_indicator` option passed to `Table.new`.
663
+ `:ascii`&mdash;this is the default; the table is drawn entirely with characters in the ASCII set:
635
664
 
636
- A bottom border can be added to the table when printing, as follows:
665
+ ```
666
+ > puts Tabulo::Table.new(1...3, :itself, :even?, :odd?, border: :ascii)
667
+ +--------------+--------------+--------------+
668
+ | itself | even? | odd? |
669
+ +--------------+--------------+--------------+
670
+ | 1 | false | true |
671
+ | 2 | true | false |
672
+ | 3 | false | true |
673
+ +--------------+--------------+--------------+
674
+ ```
637
675
 
638
- ```ruby
639
- puts table
640
- puts table.horizontal_rule
676
+ `:modern`&mdash;uses smoothly joined Unicode characters:
677
+
678
+ ```
679
+ > puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :modern)
680
+ ┌──────────────┬──────────────┬──────────────┐
681
+ │ itself │ even? │ odd? │
682
+ ├──────────────┼──────────────┼──────────────┤
683
+ │ 1 │ false │ true │
684
+ │ 2 │ true │ false │
685
+ │ 3 │ false │ true │
686
+ └──────────────┴──────────────┴──────────────┘
641
687
  ```
642
688
 
643
- This will output a bottom border that's appropriately sized for the table.
689
+ `:markdown`&mdash;renders a GitHub flavoured Markdown table:
644
690
 
645
- This mechanism can also be used to output a horizontal divider after each row:
691
+ ```
692
+ > puts Tabulo::Table.new(1..3, :itself, :even?, :odd?, border: :markdown)
693
+ | itself | even? | odd? |
694
+ |--------------|--------------|--------------|
695
+ | 1 | false | true |
696
+ | 2 | true | false |
697
+ | 3 | false | true |
698
+ ```
646
699
 
647
- ```ruby
648
- table = Tabulo::Table.new(1..3, :itself, :even?)
700
+ `:blank`&mdash;no border or divider characters are printed:
701
+
702
+ ```
703
+ > puts Tabulo::Table.new(1...3, :itself, :even?, :odd?, border: :blank)
704
+ itself even? odd?
705
+ 1 false true
706
+ 2 true false
707
+ 3 false true
649
708
  ```
650
709
 
710
+ `:classic`&mdash;reproduces the default behaviour in Tabulo v1; this is like the `:ascii` option,
711
+ but without a bottom border:
712
+
651
713
  ```
652
- > table.each { |row| puts row ; puts table.horizontal_rule }
653
- +--------------+--------------+
654
- | itself | even? |
655
- +--------------+--------------+
656
- | 1 | false |
657
- +--------------+--------------+
658
- | 2 | true |
659
- +--------------+--------------+
660
- | 3 | false |
661
- +--------------+--------------+
714
+ > puts Tabulo::Table.new(1...3, :itself, :even?, :odd?, border: :classic)
715
+ +--------------+--------------+--------------+
716
+ | itself | even? | odd? |
717
+ +--------------+--------------+--------------+
718
+ | 1 | false | true |
719
+ | 2 | true | false |
720
+ | 3 | false | true |
662
721
  ```
663
722
 
723
+ <a name="additional-configuration-options"></a>
724
+ ### Other configuration options
725
+
726
+ The character used to indicate truncation, which defaults to `~`, can be configured using the
727
+ `truncation_indicator` option passed to `Table.new`.
728
+
664
729
  <a name="motivation"></a>
665
- ## Motivation
730
+ ## Comparison with other libraries
666
731
 
667
- There are other terminal table generators for Ruby. Popular among these are:
732
+ There are other libraries for generating plain text tables in Ruby. Popular among these are:
668
733
 
669
734
  * [terminal-table](https://github.com/tj/terminal-table)
670
735
  * [tty-table](https://github.com/piotrmurach/tty-table)
671
736
  * [table\_print](https://github.com/arches/table_print)
737
+ * [hirb](https://github.com/cldwalker/hirb)
672
738
 
673
739
  *DISCLAIMER: My comments regarding these other libraries are based only on my own, possibly flawed
674
740
  reading of the documentation for, and experimentation with, these libraries at the time of my
@@ -676,15 +742,15 @@ writing this. Their APIs, features or documentation may well change between when
676
742
  when you read it. Please consult the libraries' own documentation for yourself, rather than relying
677
743
  on these comments.*
678
744
 
679
- While these libraries have their strengths, I personally found that for the common use case of
745
+ While these libraries have their strengths, I have personally found that, for the common use case of
680
746
  printing a table on the basis of some underlying enumerable collection (such as an ActiveRecord
681
- query result), using these libraries felt more cumbersome than it needed to be.
747
+ query result), using these libraries feels more cumbersome than it could be.
682
748
 
683
749
  For example, suppose we have called `User.all` from the Rails console, and want to print
684
750
  a table showing the email, first name, last name and ID of each user,
685
751
  with column headings. Also, we want the ID column to be right-aligned, because it's a number.
686
752
 
687
- In `terminal-table`, we could achieve this as follows:
753
+ In **terminal-table**, we could achieve this as follows:
688
754
 
689
755
  ```ruby
690
756
  rows = User.all.map { |u| [u.email, u.first_name, u.last_name, u.id] }
@@ -702,7 +768,7 @@ tables, but for tables that have many columns, or that are constructed
702
768
  dynamically based on user input or other runtime factors determining the columns
703
769
  to be included, this can be a hassle and a source of brittleness.
704
770
 
705
- `tty-table` has a somewhat different API to `terminal-table`. It offers both a
771
+ **tty-table** has a somewhat different API to `terminal-table`. It offers both a
706
772
  "row-based" and a "column-based" method of initializing a table. The row-based method
707
773
  is similar to `terminal-table`'s in that it burdens the developer with syncing the
708
774
  column ordering across multiple code locations. The "column-based" API for `tty-table`, on
@@ -729,20 +795,20 @@ this, but if there is, it doesn't seem to be documented.) So it seems we still h
729
795
  `table.align_column(3, :right)`, which again burdens us with keeping the column index
730
796
  passed to `align_column` in sync.
731
797
 
732
- Finally, there is [table\_print](https://github.com/arches/table_print). This is a
733
- handy gem for quickly tabulating ActiveRecord collections from the Rails
734
- console. `table_print` is similar to `tabulo` in that it has a column-based API, so it doesn't
735
- suffer from the multiple-source-of-knowledge issue in regards to column orderings. However, as far
736
- as I can tell, it lacks certain other useful features, such as the ability to repeat headers every N
737
- rows, the automatic alignment of columns based on cell content (numbers right, strings left), and a
738
- quick and easy way to automatically resize columns to accommodate cell content without overflowing
739
- the terminal. Also, as of the time of writing, `table_print`'s last significant commit (ignoring a
740
- deprecation warning fix in April 2018) was in March 2016.
741
-
742
- I don't mean to disparage any of these other projects&mdash;they each have their strengths&mdash;but
743
- rather to explain my motivation for developing and maintaining Tabulo despite their existence, and
744
- to provide reasons one might consider for using `tabulo` rather than another terminal table
745
- gem.
798
+ As for **table\_print**, this is a handy gem for quickly tabulating ActiveRecord
799
+ collections from the Rails console. `table_print` is similar to `tabulo` in that it has a
800
+ column-based API, so it doesn't suffer from the multiple-source-of-knowledge issue in regards to
801
+ column orderings. However, it lacks certain other useful features, such as the ability to repeat
802
+ headers every N rows, the automatic alignment of columns based on cell content (numbers right,
803
+ strings left), and a quick and easy way to automatically resize columns to accommodate cell content
804
+ without overflowing the terminal. Also, as of the time of writing, `table_print`'s last significant
805
+ commit (ignoring a deprecation warning fix in April 2018) was in March 2016.
806
+
807
+ Finally, it is worth mentioning the **hirb** library. This is similar to `table_print`, in that it's
808
+ well suited to quickly displaying ActiveRecord collections from the Rails console. However, like
809
+ `table_print`, there are certain useful features it's lacking; and using it outside the console
810
+ environment seems cumbersome. Moreover, it seems no longer to be maintained. At the time of writing,
811
+ its last commit was in March 2015.
746
812
 
747
813
  <a name="contributing"></a>
748
814
  ## Contributing
@@ -752,9 +818,9 @@ Issues and pull requests are welcome on GitHub at https://github.com/matt-harvey
752
818
  To start working on Tabulo, `git clone` and `cd` into your fork of the repo, then run `bin/setup` to
753
819
  install dependencies.
754
820
 
755
- `bin/console` will give you an interactive prompt that will allow you to experiment; and `rake spec`
756
- will run the test suite. For a list of other Rake tasks that are available in the development
757
- environment, run `rake -T`.
821
+ `bin/console` will give you an interactive prompt that will allow you to experiment; and
822
+ `bundle exec rake spec` will run the test suite. For a list of other Rake tasks that are available in
823
+ the development environment, run `bundle exec rake -T`.
758
824
 
759
825
  ## License
760
826
 
@@ -762,14 +828,14 @@ The gem is available as open source under the terms of the [MIT
762
828
  License](http://opensource.org/licenses/MIT).
763
829
 
764
830
  [Gem Version]: https://rubygems.org/gems/tabulo
765
- [Documentation]: http://www.rubydoc.info/gems/tabulo/1.5.1
831
+ [Documentation]: http://www.rubydoc.info/gems/tabulo/2.0.0
766
832
  [Build Status]: https://travis-ci.org/matt-harvey/tabulo
767
833
  [Coverage Status]: https://coveralls.io/r/matt-harvey/tabulo
768
834
  [Code Climate]: https://codeclimate.com/github/matt-harvey/tabulo
769
835
  [Awesome Ruby]: https://github.com/markets/awesome-ruby#cli-utilities
770
836
 
771
837
  [GV img]: https://img.shields.io/gem/v/tabulo.svg
772
- [DC img]: https://img.shields.io/badge/documentation-v1.5.1-blue.svg
838
+ [DC img]: https://img.shields.io/badge/documentation-v2.0.0-blue.svg
773
839
  [BS img]: https://img.shields.io/travis/matt-harvey/tabulo.svg
774
840
  [CS img]: https://img.shields.io/coveralls/matt-harvey/tabulo.svg
775
841
  [CC img]: https://codeclimate.com/github/matt-harvey/tabulo/badges/gpa.svg
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.1
1
+ 2.0.0
data/lib/tabulo.rb CHANGED
@@ -6,3 +6,4 @@ require "tabulo/version"
6
6
  require "tabulo/row"
7
7
  require "tabulo/cell"
8
8
  require "tabulo/column"
9
+ require "tabulo/border"
@@ -0,0 +1,166 @@
1
+ module Tabulo
2
+
3
+ # @!visibility private
4
+ class Border
5
+
6
+ STYLES = {
7
+ ascii: {
8
+ corner_top_left: "+",
9
+ corner_top_right: "+",
10
+ corner_bottom_right: "+",
11
+ corner_bottom_left: "+",
12
+ edge_top: "-",
13
+ edge_right: "|",
14
+ edge_bottom: "-",
15
+ edge_left: "|",
16
+ tee_top: "+",
17
+ tee_right: "+",
18
+ tee_bottom: "+",
19
+ tee_left: "+",
20
+ divider_vertical: "|",
21
+ divider_horizontal: "-",
22
+ intersection: "+",
23
+ },
24
+ classic: {
25
+ corner_top_left: "+",
26
+ corner_top_right: "+",
27
+ edge_top: "-",
28
+ edge_right: "|",
29
+ edge_left: "|",
30
+ tee_top: "+",
31
+ tee_right: "+",
32
+ tee_left: "+",
33
+ divider_vertical: "|",
34
+ divider_horizontal: "-",
35
+ intersection: "+",
36
+ },
37
+ markdown: {
38
+ corner_top_left: "",
39
+ corner_top_right: "",
40
+ corner_bottom_right: "",
41
+ corner_bottom_left: "",
42
+ edge_top: "",
43
+ edge_right: "|",
44
+ edge_bottom: "",
45
+ edge_left: "|",
46
+ tee_top: "",
47
+ tee_right: "|",
48
+ tee_bottom: "",
49
+ tee_left: "|",
50
+ divider_vertical: "|",
51
+ divider_horizontal: "-",
52
+ intersection: "|",
53
+ },
54
+ modern: {
55
+ corner_top_left: "┌",
56
+ corner_top_right: "┐",
57
+ corner_bottom_right: "┘",
58
+ corner_bottom_left: "└",
59
+ edge_top: "─",
60
+ edge_right: "│",
61
+ edge_bottom: "─",
62
+ edge_left: "│",
63
+ tee_top: "┬",
64
+ tee_right: "┤",
65
+ tee_bottom: "┴",
66
+ tee_left: "├",
67
+ divider_vertical: "│",
68
+ divider_horizontal: "─",
69
+ intersection: "┼",
70
+ },
71
+ blank: {
72
+ },
73
+ }
74
+
75
+ # @!visibility private
76
+ def self.from(initializer, styler = nil)
77
+ new(options(initializer).merge(styler: styler))
78
+ end
79
+
80
+ # @!visibility private
81
+ def horizontal_rule(column_widths, position = :bottom)
82
+ left, center, right, segment =
83
+ case position
84
+ when :top
85
+ [@corner_top_left, @tee_top, @corner_top_right, @edge_top]
86
+ when :middle
87
+ [@tee_left, @intersection, @tee_right, @divider_horizontal]
88
+ when :bottom
89
+ [@corner_bottom_left, @tee_bottom, @corner_bottom_right, @edge_bottom]
90
+ end
91
+ segments = column_widths.map { |width| segment * width }
92
+ style("#{left}#{segments.join(center)}#{right}")
93
+ end
94
+
95
+ # @!visibility private
96
+ def join_cell_contents(cells)
97
+ styled_divider_vertical = style(@divider_vertical)
98
+ styled_edge_left = style(@edge_left)
99
+ styled_edge_right = style(@edge_right)
100
+ styled_edge_left + cells.join(styled_divider_vertical) + styled_edge_right
101
+ end
102
+
103
+ private
104
+
105
+ def self.options(kind)
106
+ opts = STYLES[kind]
107
+ return opts if opts
108
+ raise InvalidBorderError
109
+ end
110
+
111
+ # @param [nil, #to_proc] styler (nil) A lambda or other callable object taking
112
+ # a single parameter, representing a section of the table's borders (which for this purpose
113
+ # include any horizontal and vertical lines inside the table), and returning a string.
114
+ # If passed <tt>nil</tt>, then no additional styling will be applied to borders. If passed a
115
+ # callable, then that callable will be called for each border section, with the
116
+ # resulting string rendered in place of that border. The extra width of the string returned by the
117
+ # {styler} is not taken into consideration by the internal table rendering calculations
118
+ # Thus it can be used to apply ANSI escape codes to border characters, to colour the borders
119
+ # for example, without breaking the table formatting.
120
+ # @return [Border] a new {Border}
121
+ def initialize(
122
+ corner_top_left: "",
123
+ corner_top_right: "",
124
+ corner_bottom_right: "",
125
+ corner_bottom_left: "",
126
+ edge_top: "",
127
+ edge_right: "",
128
+ edge_bottom: "",
129
+ edge_left: "",
130
+ tee_top: "",
131
+ tee_right: "",
132
+ tee_bottom: "",
133
+ tee_left: "",
134
+ divider_vertical: "",
135
+ divider_horizontal: "",
136
+ intersection: "",
137
+ styler: nil)
138
+
139
+ @corner_top_left = corner_top_left
140
+ @corner_top_right = corner_top_right
141
+ @corner_bottom_right = corner_bottom_right
142
+ @corner_bottom_left = corner_bottom_left
143
+
144
+ @edge_top = edge_top
145
+ @edge_right = edge_right
146
+ @edge_bottom = edge_bottom
147
+ @edge_left = edge_left
148
+
149
+ @tee_top = tee_top
150
+ @tee_right = tee_right
151
+ @tee_bottom = tee_bottom
152
+ @tee_left = tee_left
153
+
154
+ @divider_vertical = divider_vertical
155
+ @divider_horizontal = divider_horizontal
156
+
157
+ @intersection = intersection
158
+
159
+ @styler = styler
160
+ end
161
+
162
+ def style(s)
163
+ @styler ? @styler.call(s) : s
164
+ end
165
+ end
166
+ end
data/lib/tabulo/cell.rb CHANGED
@@ -2,9 +2,13 @@ require "unicode/display_width"
2
2
 
3
3
  module Tabulo
4
4
 
5
- # @!visibility private
5
+ # Represents a single cell within the body of a {Table}.
6
6
  class Cell
7
7
 
8
+ # @return the underlying value for this Cell
9
+ attr_reader :value
10
+
11
+ # @!visibility private
8
12
  def initialize(value:, formatter:, alignment:, width:, styler:, truncation_indicator:, padding_character:)
9
13
  @value = value
10
14
  @formatter = formatter
@@ -15,18 +19,23 @@ module Tabulo
15
19
  @padding_character = padding_character
16
20
  end
17
21
 
22
+ # @!visibility private
18
23
  def height
19
24
  subcells.size
20
25
  end
21
26
 
22
- def padded_truncated_subcells(target_height, padding_amount)
27
+ # @!visibility private
28
+ def padded_truncated_subcells(target_height, padding_amount_left, padding_amount_right)
29
+ total_padding_amount = padding_amount_left + padding_amount_right
23
30
  truncated = (height > target_height)
24
31
  (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)
32
+ append_truncator = (truncated && (total_padding_amount != 0) && (subcell_index + 1 == target_height))
33
+ padded_subcell(subcell_index, padding_amount_left, padding_amount_right, append_truncator)
27
34
  end
28
35
  end
29
36
 
37
+ # @return [String] the content of the Cell, after applying the formatter for this Column (but
38
+ # without applying any wrapping or the styler).
30
39
  def formatted_content
31
40
  @formatted_content ||= @formatter.call(@value)
32
41
  end
@@ -37,9 +46,14 @@ module Tabulo
37
46
  @subcells ||= calculate_subcells
38
47
  end
39
48
 
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)
49
+ def padded_subcell(subcell_index, padding_amount_left, padding_amount_right, append_truncator)
50
+ lpad = @padding_character * padding_amount_left
51
+ rpad =
52
+ if append_truncator
53
+ styled_truncation_indicator + padding(padding_amount_right - 1)
54
+ else
55
+ padding(padding_amount_right)
56
+ end
43
57
  inner = subcell_index < height ? subcells[subcell_index] : padding(@width)
44
58
  "#{lpad}#{inner}#{rpad}"
45
59
  end
@@ -3,19 +3,10 @@ module Tabulo
3
3
  # Error indicating that the label of a column is invalid.
4
4
  class InvalidColumnLabelError < StandardError; end
5
5
 
6
- # Error indicating that an attempt was made to use an invalid horizontal rule character
7
- # for the table.
8
- class InvalidHorizontalRuleCharacterError < StandardError; end
9
-
10
- # Error indicating that an attempt was made to use an invalid vertical rule character
11
- # for the table.
12
- class InvalidVerticalRuleCharacterError < StandardError; end
13
-
14
- # Error indicating that an attempt was made to use an invalid intersection character for
15
- # the table.
16
- class InvalidIntersectionCharacterError < StandardError; end
17
-
18
6
  # Error indicating that an attempt was made to use an invalid truncation indicator for
19
7
  # the table.
20
8
  class InvalidTruncationIndicatorError < StandardError; end
9
+
10
+ # Error indicating the table border configuration is invalid.
11
+ class InvalidBorderError < StandardError; end
21
12
  end
data/lib/tabulo/row.rb CHANGED
@@ -7,10 +7,10 @@ module Tabulo
7
7
  attr_reader :source
8
8
 
9
9
  # @!visibility private
10
- def initialize(table, source, with_header: true)
10
+ def initialize(table, source, header: :top)
11
11
  @table = table
12
12
  @source = source
13
- @with_header = with_header
13
+ @header = header
14
14
  end
15
15
 
16
16
  # Calls the given block once for each cell in the {Row}, passing that cell as parameter.
@@ -25,7 +25,7 @@ module Tabulo
25
25
  # end
26
26
  def each
27
27
  @table.column_registry.each do |_, column|
28
- yield column.body_cell_value(@source)
28
+ yield column.body_cell(@source)
29
29
  end
30
30
  end
31
31
 
@@ -34,20 +34,15 @@ module Tabulo
34
34
  # in the {Table} and how the {Table} was configured with respect to header frequency).
35
35
  def to_s
36
36
  if @table.column_registry.any?
37
- @table.formatted_body_row(@source, with_header: @with_header)
37
+ @table.formatted_body_row(@source, header: @header)
38
38
  else
39
39
  ""
40
40
  end
41
41
  end
42
42
 
43
- # @return a Hash representation of the {Row}, with column labels acting
44
- # as keys and the calculated cell values (before formatting) providing the values.
45
- # @example
46
- # table = Tabulo::Table.new([1, 10], columns: %i[itself even?])
47
- # row = table.first
48
- # row.to_h # => { :itself => 1, :even? => false }
43
+ # @return a Hash representation of the {Row}, with column labels acting as keys and the {Cell}s the values.
49
44
  def to_h
50
- @table.column_registry.map { |label, column| [label, column.body_cell_value(@source)] }.to_h
45
+ @table.column_registry.map { |label, column| [label, column.body_cell(@source)] }.to_h
51
46
  end
52
47
  end
53
48
  end
data/lib/tabulo/table.rb CHANGED
@@ -10,19 +10,13 @@ module Tabulo
10
10
  include Enumerable
11
11
 
12
12
  # @!visibility public
13
- DEFAULT_COLUMN_WIDTH = 12
14
-
15
- # @!visibility public
16
- DEFAULT_COLUMN_PADDING = 1
13
+ DEFAULT_BORDER = :ascii
17
14
 
18
15
  # @!visibility public
19
- DEFAULT_HORIZONTAL_RULE_CHARACTER = "-"
20
-
21
- # @!visibility public
22
- DEFAULT_VERTICAL_RULE_CHARACTER = "|"
16
+ DEFAULT_COLUMN_WIDTH = 12
23
17
 
24
18
  # @!visibility public
25
- DEFAULT_INTERSECTION_CHARACTER = "+"
19
+ DEFAULT_COLUMN_PADDING = 1
26
20
 
27
21
  # @!visibility public
28
22
  DEFAULT_TRUNCATION_INDICATOR = "~"
@@ -37,11 +31,10 @@ module Tabulo
37
31
  attr_accessor :sources
38
32
 
39
33
  # @param [Enumerable] sources the underlying Enumerable from which the table will derive its data
40
- # @param [Array[Symbol]] cols Specifies the initial columns. The Symbols provided must
34
+ # @param [Array[Symbol]] columns Specifies the initial columns. The Symbols provided must
41
35
  # be unique. Each element of the Array will be used to create a column whose content is
42
36
  # created by calling the corresponding method on each element of sources. Note
43
37
  # the {#add_column} method is a much more flexible way to set up columns on the table.
44
- # @param [Array[Symbol]] columns <b>DEPRECATED</b> Use <tt>cols</tt> instead.
45
38
  # @param [Integer, nil] column_width The default column width for columns in this
46
39
  # table, not excluding padding. If <tt>nil</tt>, then {DEFAULT_COLUMN_WIDTH} will be used.
47
40
  # @param [:start, nil, Integer] header_frequency Controls the display of column headers.
@@ -59,24 +52,15 @@ module Tabulo
59
52
  # headers), if their content is longer than the column's fixed width. If passed <tt>nil</tt>, content will
60
53
  # be wrapped for as many rows as required to accommodate it. If passed an Integer N (> 0), content will be
61
54
  # wrapped up to N rows and then truncated thereafter.
62
- # @param [nil, String] horizontal_rule_character Determines the character used to draw
63
- # horizontal lines where required in the table. If omitted or passed <tt>nil</tt>, defaults to
64
- # {DEFAULT_HORIZONTAL_RULE_CHARACTER}. If passed something other than <tt>nil</tt> or a single-character
65
- # String, raises {InvalidHorizontalRuleCharacterError}.
66
- # @param [nil, String] vertical_rule_character Determines the character used to draw
67
- # vertical lines where required in the table. If omitted or passed <tt>nil</tt>, defaults to
68
- # {DEFAULT_VERTICAL_RULE_CHARACTER}. If passed something other than <tt>nil</tt> or a single-character
69
- # String, raises {InvalidVerticalRuleCharacterError}.
70
- # @param [nil, String] intersection_character Determines the character used to draw
71
- # line intersections and corners where required in the table. If omitted or passed <tt>nil</tt>,
72
- # defaults to {DEFAULT_INTERSECTION_CHARACTER}. If passed something other than <tt>nil</tt> or
73
- # a single-character String, raises {InvalidIntersectionCharacterError}.
74
55
  # @param [nil, String] truncation_indicator Determines the character used to indicate that a
75
56
  # cell's content has been truncated. If omitted or passed <tt>nil</tt>,
76
57
  # defaults to {DEFAULT_TRUNCATION_INDICATOR}. If passed something other than <tt>nil</tt> or
77
58
  # a single-character String, raises {InvalidTruncationIndicatorError}.
78
- # @param [nil, Integer] column_padding Determines the amount of blank space with which to pad either
79
- # of each column. Defaults to 1.
59
+ # @param [nil, Integer, Array] column_padding (1) Determines the amount of blank space with which to pad
60
+ # either side of each column. If passed an Integer, then the given amount of padding is
61
+ # applied to each side of each column. If passed a two-element Array, then the first element of the
62
+ # Array indicates the amount of padding to apply to the left of each column, and the second
63
+ # element indicates the amount to apply to the right.
80
64
  # @param [:left, :right, :center] align_header (:center) Determines the alignment of header text
81
65
  # for columns in this Table. Can be overridden for individual columns using the
82
66
  # <tt>align_header</tt> option passed to {#add_column}
@@ -85,9 +69,20 @@ module Tabulo
85
69
  # using the <tt>align_body</tt> option passed to {#add_column}. If passed <tt>:auto</tt>,
86
70
  # alignment is determined by cell content, with numbers aligned right, booleans
87
71
  # center-aligned, and other values left-aligned.
72
+ # @param [:ascii, :markdown, :modern, :blank, nil] border (nil) Determines the characters used
73
+ # for the Table border, including both the characters around the outside of table, and the lines drawn
74
+ # within the table to separate columns from each other and the header row from the Table body.
75
+ # Possible values are:
76
+ # - `:ascii` Uses ASCII characters only
77
+ # - `:markdown` Produces as a GitHub-flavoured Markdown table
78
+ # - `:modern` Uses non-ASCII Unicode characters to render a border with smooth continuous lines
79
+ # - `:blank` No border characters are rendered
80
+ # - `:classic` Like `:ascii`, but does not have a horizontal line at the bottom of the
81
+ # table. This reproduces the default behaviour in `tabulo` v1.
82
+ # If <tt>nil</tt>, then {DEFAULT_BORDER} will be used.
88
83
  # @param [nil, #to_proc] border_styler (nil) A lambda or other callable object taking
89
84
  # 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).
85
+ # include any horizontal and vertical lines inside the table), and returning a string.
91
86
  # If passed <tt>nil</tt>, then no additional styling will be applied to borders. If passed a
92
87
  # callable, then that callable will be called for each border section, with the
93
88
  # resulting string rendered in place of that border. The extra width of the string returned by the
@@ -96,38 +91,36 @@ module Tabulo
96
91
  # for example, without breaking the table formatting.
97
92
  # @return [Table] a new {Table}
98
93
  # @raise [InvalidColumnLabelError] if non-unique Symbols are provided to columns.
99
- # @raise [InvalidHorizontalRuleCharacterError] if invalid argument passed to horizontal_rule_character.
100
- # @raise [InvalidVerticalRuleCharacterError] if invalid argument passed to vertical_rule_character.
101
- def initialize(sources, *cols, columns: [], column_width: nil, column_padding: nil, header_frequency: :start,
102
- wrap_header_cells_to: nil, wrap_body_cells_to: nil, horizontal_rule_character: nil,
103
- vertical_rule_character: nil, intersection_character: nil, truncation_indicator: nil,
104
- align_header: :center, align_body: :auto, border_styler: nil)
105
-
106
- if columns.any?
107
- Deprecation.warn("`columns' option to Tabulo::Table#initialize", "the variable length parameter `cols'", 2)
108
- end
94
+ # @raise [InvalidBorderError] if invalid option passed to `border` parameter.
95
+ def initialize(sources, *columns, column_width: nil, column_padding: nil, header_frequency: :start,
96
+ wrap_header_cells_to: nil, wrap_body_cells_to: nil, truncation_indicator: nil, align_header: :center,
97
+ align_body: :auto, border: nil, border_styler: nil)
109
98
 
110
99
  @sources = sources
111
100
  @header_frequency = header_frequency
112
101
  @wrap_header_cells_to = wrap_header_cells_to
113
102
  @wrap_body_cells_to = wrap_body_cells_to
114
103
  @default_column_width = (column_width || DEFAULT_COLUMN_WIDTH)
115
- @column_padding = (column_padding || DEFAULT_COLUMN_PADDING)
116
104
  @align_header = align_header
117
105
  @align_body = align_body
106
+
107
+ @column_padding = (column_padding || DEFAULT_COLUMN_PADDING)
108
+ @left_column_padding, @right_column_padding =
109
+ case @column_padding
110
+ when Array
111
+ @column_padding
112
+ else
113
+ [@column_padding, @column_padding]
114
+ end
115
+
116
+ @border = (border || DEFAULT_BORDER)
118
117
  @border_styler = border_styler
118
+ @border_instance = Border.from(@border, @border_styler)
119
119
 
120
- @horizontal_rule_character = validate_character(horizontal_rule_character,
121
- DEFAULT_HORIZONTAL_RULE_CHARACTER, InvalidHorizontalRuleCharacterError, "horizontal rule character")
122
- @vertical_rule_character = validate_character(vertical_rule_character,
123
- DEFAULT_VERTICAL_RULE_CHARACTER, InvalidVerticalRuleCharacterError, "vertical rule character")
124
- @intersection_character = validate_character(intersection_character,
125
- DEFAULT_INTERSECTION_CHARACTER, InvalidIntersectionCharacterError, "intersection character")
126
120
  @truncation_indicator = validate_character(truncation_indicator,
127
121
  DEFAULT_TRUNCATION_INDICATOR, InvalidTruncationIndicatorError, "truncation indicator")
128
122
 
129
123
  @column_registry = { }
130
- cols.each { |item| add_column(item) }
131
124
  columns.each { |item| add_column(item) }
132
125
 
133
126
  yield self if block_given?
@@ -233,7 +226,9 @@ module Tabulo
233
226
  # display in a fixed-width font.
234
227
  def to_s
235
228
  if column_registry.any?
236
- join_lines(map(&:to_s))
229
+ bottom_edge = horizontal_rule(:bottom)
230
+ rows = map(&:to_s)
231
+ bottom_edge.empty? ? join_lines(rows) : join_lines(rows + [bottom_edge])
237
232
  else
238
233
  ""
239
234
  end
@@ -250,16 +245,20 @@ module Tabulo
250
245
  # were not disabled when the Table was initialized).
251
246
  def each
252
247
  @sources.each_with_index do |source, index|
253
- include_header =
248
+ header =
254
249
  case @header_frequency
255
250
  when :start
256
- index == 0
251
+ :top if index == 0
257
252
  when Integer
258
- index % @header_frequency == 0
253
+ if index == 0
254
+ :top
255
+ elsif index % @header_frequency == 0
256
+ :middle
257
+ end
259
258
  else
260
259
  @header_frequency
261
260
  end
262
- yield body_row(source, with_header: include_header)
261
+ yield body_row(source, header: header)
263
262
  end
264
263
  end
265
264
 
@@ -269,20 +268,26 @@ module Tabulo
269
268
  format_row(cells, @wrap_header_cells_to)
270
269
  end
271
270
 
271
+ # @param [:top, :middle, :bottom] align_body (:bottom) Specifies the position
272
+ # for which the resulting horizontal dividing line is intended to be printed.
273
+ # This determines the border characters that are used to construct the line.
272
274
  # @return [String] an "ASCII" graphical representation of a horizontal
273
- # dividing line suitable for printing at any point in the table.
274
- # @example Print a horizontal divider after every row:
275
- # table.each do |row|
275
+ # dividing line suitable for printing at the top, bottom or middle of the
276
+ # table.
277
+ # @example Print a horizontal divider between each pair of rows, and again
278
+ # at the bottom:
279
+ #
280
+ # table.each_with_index do |row, i|
281
+ # puts table.horizontal_rule(:middle) unless i == 0
276
282
  # puts row
277
- # puts table.horizontal_rule
278
283
  # end
284
+ # puts table.horizontal_rule(:bottom)
279
285
  #
280
- def horizontal_rule
281
- inner = column_registry.map do |_, column|
282
- @horizontal_rule_character * (column.width + @column_padding * 2)
283
- end
284
-
285
- styled_border(surround_join(inner, @intersection_character))
286
+ # It may be that `:top`, `:middle` and `:bottom` all look the same. Whether
287
+ # this is the case depends on the characters used for the table border.
288
+ def horizontal_rule(position = :bottom)
289
+ column_widths = column_registry.map { |_, column| column.width + total_column_padding }
290
+ @border_instance.horizontal_rule(column_widths, position)
286
291
  end
287
292
 
288
293
  # Reset all the column widths so that each column is *just* wide enough to accommodate
@@ -352,9 +357,8 @@ module Tabulo
352
357
  # @param [Hash] opts Options for configuring the new, transposed {Table}.
353
358
  # The following options are the same as the keyword params for the {#initialize} method for
354
359
  # {Table}: <tt>column_width</tt>, <tt>column_padding</tt>, <tt>header_frequency</tt>,
355
- # <tt>wrap_header_cells_to</tt>, <tt>wrap_body_cells_to</tt>, <tt>horizontal_rule_character</tt>,
356
- # <tt>vertical_rule_character</tt>, <tt>intersection_character</tt>, <tt>truncation_indicator</tt>,
357
- # <tt>align_header</tt>, <tt>align_body</tt>.
360
+ # <tt>wrap_header_cells_to</tt>, <tt>wrap_body_cells_to</tt>, <tt>border</tt>,
361
+ # <tt>border_styler</tt>, <tt>truncation_indicator</tt>, <tt>align_header</tt>, <tt>align_body</tt>.
358
362
  # These are applied in the same way as documented for {#initialize}, when creating the
359
363
  # new, transposed Table. Any options not specified explicitly in the call to {#transpose}
360
364
  # will inherit their values from the original {Table} (with the exception of settings
@@ -376,12 +380,11 @@ module Tabulo
376
380
  # new Table (other than the left-most column's header, which is determined as described
377
381
  # above).
378
382
  # @return [Table] a new {Table}
379
- # @raise [InvalidHorizontalRuleCharacterError] if invalid argument passed to horizontal_rule_character.
380
- # @raise [InvalidVerticalRuleCharacterError] if invalid argument passed to vertical_rule_character.
383
+ # @raise [InvalidBorderError] if invalid argument passed to `border` parameter.
381
384
  def transpose(opts = {})
382
385
  default_opts = [:column_width, :column_padding, :header_frequency, :wrap_header_cells_to,
383
- :wrap_body_cells_to, :horizontal_rule_character, :vertical_rule_character,
384
- :intersection_character, :truncation_indicator, :align_header, :align_body].map do |sym|
386
+ :wrap_body_cells_to, :truncation_indicator, :align_header, :align_body, :border,
387
+ :border_styler].map do |sym|
385
388
  [sym, instance_variable_get("@#{sym}")]
386
389
  end.to_h
387
390
 
@@ -411,47 +414,17 @@ module Tabulo
411
414
  end
412
415
  end
413
416
 
414
- # @deprecated Use {#pack} instead.
415
- #
416
- # Reset all the column widths so that each column is *just* wide enough to accommodate
417
- # its header text as well as the formatted content of each its cells for the entire
418
- # collection, together with a single character of padding on either side of the column,
419
- # without any wrapping.
420
- #
421
- # Note that calling this method will cause the entire source Enumerable to
422
- # be traversed and all the column extractors and formatters to be applied in order
423
- # to calculate the required widths.
424
- #
425
- # Note also that this method causes column widths to be fixed as appropriate to the
426
- # formatted cell contents given the state of the source Enumerable at the point it
427
- # is called. If the source Enumerable changes between that point, and the point when
428
- # the Table is printed, then columns will *not* be resized yet again on printing.
429
- #
430
- # @param [nil, Numeric] max_table_width (nil) If provided, stops the total table
431
- # width (including padding and borders) from expanding beyond this number of characters.
432
- # If passed <tt>:auto</tt>, the table width will automatically be capped at the current
433
- # terminal width.
434
- # Width is deducted from columns if required to achieve this, with one character progressively
435
- # deducted from the width of the widest column until the target is reached. When the
436
- # table is printed, wrapping or truncation will then occur in these columns as required
437
- # (depending on how they were configured).
438
- # Note that regardless of the value passed to max_table_width, the table will always be left wide
439
- # enough to accommodate at least 1 character's width of content, 1 character of left padding and
440
- # 1 character of right padding in each column, together with border characters (1 on each side
441
- # of the table and 1 between adjacent columns). I.e. there is a certain width below width the
442
- # Table will refuse to shrink itself.
443
- # @return [Table] the Table itself
444
- def shrinkwrap!(max_table_width: nil)
445
- Deprecation.warn("`Tabulo::Table#shrinkwrap!'", "`#pack'")
446
- pack(max_table_width: max_table_width)
447
- end
448
-
449
417
  # @!visibility private
450
- def formatted_body_row(source, with_header: false)
418
+ def formatted_body_row(source, header: nil)
451
419
  cells = column_registry.map { |_, column| column.body_cell(source) }
452
420
  inner = format_row(cells, @wrap_body_cells_to)
453
- if with_header
454
- join_lines([horizontal_rule, formatted_header, horizontal_rule, inner])
421
+ if header
422
+ join_lines([
423
+ horizontal_rule(header == :top ? :top : :middle),
424
+ formatted_header,
425
+ horizontal_rule(:middle),
426
+ inner,
427
+ ].reject(&:empty?))
455
428
  else
456
429
  inner
457
430
  end
@@ -459,11 +432,16 @@ module Tabulo
459
432
 
460
433
  private
461
434
 
435
+ # @!visibility private
436
+ def total_column_padding
437
+ @left_column_padding + @right_column_padding
438
+ end
439
+
462
440
  # @!visibility private
463
441
  def shrink_to(max_table_width)
464
442
  columns = column_registry.values
465
443
  total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
466
- total_padding = column_registry.count * @column_padding * 2
444
+ total_padding = column_registry.count * total_column_padding
467
445
  total_borders = column_registry.count + 1
468
446
  unadjusted_table_width = total_columns_width + total_padding + total_borders
469
447
 
@@ -483,8 +461,8 @@ module Tabulo
483
461
  end
484
462
 
485
463
  # @!visibility private
486
- def body_row(source, with_header: false)
487
- Row.new(self, source, with_header: with_header)
464
+ def body_row(source, header: nil)
465
+ Row.new(self, source, header: header)
488
466
  end
489
467
 
490
468
  # @!visibility private
@@ -503,25 +481,14 @@ module Tabulo
503
481
  def format_row(cells, wrap_cells_to)
504
482
  max_cell_height = cells.map(&:height).max
505
483
  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) }
509
- join_lines(subrows)
510
- end
511
-
512
- # @!visibility private
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}"
520
- end
484
+ subcell_stacks = cells.map do |cell|
485
+ cell.padded_truncated_subcells(row_height, @left_column_padding, @right_column_padding)
486
+ end
487
+ subrows = subcell_stacks.transpose.map do |subrow_components|
488
+ @border_instance.join_cell_contents(subrow_components)
489
+ end
521
490
 
522
- # @!visibility private
523
- def surround_join(arr, ch)
524
- surround(arr.join(ch), ch)
491
+ join_lines(subrows)
525
492
  end
526
493
 
527
494
  # @!visibility private
@@ -1,3 +1,3 @@
1
1
  module Tabulo
2
- VERSION = "1.5.1"
2
+ VERSION = "2.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabulo
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.1
4
+ version: 2.0.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-06-23 00:00:00.000000000 Z
11
+ date: 2019-11-04 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/border.rb
190
191
  - lib/tabulo/cell.rb
191
192
  - lib/tabulo/column.rb
192
193
  - lib/tabulo/deprecation.rb