tabulo 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e5af752ebead6db9dfc76f720fe93390e1476d8
4
- data.tar.gz: 3aaeac2dcb657193be54534bac8babbad1cb56d7
3
+ metadata.gz: 452523797b11a532c291705852d73cc00c7aa93b
4
+ data.tar.gz: 889621678407822d354bbc5aab7a086aaefdf1b4
5
5
  SHA512:
6
- metadata.gz: 1227ced88276ad18ac557b66c2bf9db675dba4b9061797c7ddf9432b14c7882eb025b6b126bfa833dd15f92bd6421e42481bed5b858f369ac8c48e811a2da544
7
- data.tar.gz: 9f68e39a20c006414d65d0239188df0ae66eb912e9e99de51ea310636d15cf2a9b0038819bbcae39f9b26982ae5f69ddcf1e410efc13cdd2c1d91cb487fbd858
6
+ metadata.gz: b5311c3013bdba6ff2e7c6a8f0a31d0373d5167c9f34aa45caf2ca1cc5d49c8fbb5c058f166daa6b0d5da41d9d516ee87fd256893d7ce6496277bac9746f0e71
7
+ data.tar.gz: 8b515f7eca75f4ba532b482d81699b711387e822668f71b9dce86a52f7e71ff27dcdc7bda955525e4d1974346272c49d28422b5c5eb85f4a981962feaedd1961
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.5.0
4
+
5
+ * Add Table#shrinkwrap! method to automate column widths so they "just fit".
6
+ * Improve documentation.
7
+ * Minor tidy-ups.
8
+
3
9
  ## v0.4.2
4
10
 
5
11
  * Improve README.
data/README.md CHANGED
@@ -26,21 +26,25 @@ end
26
26
  | 5000000 | 10000000 |
27
27
  ```
28
28
 
29
- A `Tabulo::Table` is an `Enumerable`, so you can process one row at a time:
30
-
31
- ```ruby
32
- table.each do |row|
33
- puts row
34
- # do some other thing that you want to do for each row
35
- end
36
- ```
37
-
38
- Each `Tabulo::Row` is also an `Enumerable`, which provides access to the underlying cell values:
29
+ Tabulo is flexible:
30
+
31
+ * Fix individual column widths, then either [wrap](#width-wrapping-truncation) or
32
+ [truncate](#width-wrapping-truncation) the overflow as you prefer.
33
+ * Alternatively, [shrinkwrap](#shrinkwrap) the table so that each column is just wide enough for
34
+ its contents.
35
+ * You can cap total table width when shrinkwrapping, to [stop it overflowing your terminal](#max-table-width)
36
+ horizontally and becoming an unreadable mess.
37
+ * Cell content [alignment](#cell-alignment) is configurable, but with useful defaults, with numbers
38
+ aligned right and strings left.
39
+ * Headers can be [repeated](#repeating-headers) as desired.
40
+ * A `Tabulo::Table` is an `Enumerable`, so you can [step through it](#enumerator) one row at a time,
41
+ without having to wait for the entire underlying collection to load.
42
+ * Each `Tabulo::Row` is also an `Enumerable`.
39
43
 
40
44
  ```ruby
41
45
  table.each do |row|
42
46
  row.each do |cell|
43
- # cell => 1, 2... 2, 4... etc.
47
+ # cell => 1, 2 ... 2, 4 ... etc.
44
48
  end
45
49
  end
46
50
  ```
@@ -123,6 +127,7 @@ end
123
127
  | 5 | 10 | true |
124
128
  ```
125
129
 
130
+ <a name="cell-alignment"></a>
126
131
  ### Cell alignment
127
132
 
128
133
  By default, column header text is center-aligned, while the content of each body cell is aligned
@@ -131,19 +136,20 @@ and `true`) are center-aligned. This can be customized by passing `:center`, `:l
131
136
  the `align_header` or `align_body` options of `add_column`, e.g.:
132
137
 
133
138
  ```ruby
134
- table.add_column("Doubled", align_header: :left, align_body: :left) { |n| n * 2 }
139
+ table.add_column("Doubled", align_header: :left, align_body: :left) { |n| n * 2 }
135
140
  ```
136
141
 
142
+ <a name="width-wrapping-truncation"></a>
137
143
  ### Column width, wrapping and truncation
138
144
 
139
145
  By default, column width is fixed at 12 characters, plus 1 character of padding on either side.
140
146
  This can be adjusted on a column-by-column basis using the `width` option of `add_column`:
141
147
 
142
148
  ```ruby
143
- table = Tabulo::Table.new([1, 2]) do |t|
144
- t.add_column(:itself, width: 6)
145
- t.add_column(:even?, width: 9)
146
- end
149
+ table = Tabulo::Table.new([1, 2]) do |t|
150
+ t.add_column(:itself, width: 6)
151
+ t.add_column(:even?, width: 9)
152
+ end
147
153
  ```
148
154
 
149
155
  ```
@@ -159,7 +165,7 @@ If you want to set the default column width for all columns of the table to some
159
165
  than 12, use the `column_width` option when initializing the table:
160
166
 
161
167
  ```ruby
162
- table = Tabulo::Table.new([1, 2], columns: %i(itself even?), column_width: 6)
168
+ table = Tabulo::Table.new([1, 2], columns: %i(itself even?), column_width: 6)
163
169
  ```
164
170
 
165
171
  ```
@@ -173,6 +179,60 @@ than 12, use the `column_width` option when initializing the table:
173
179
 
174
180
  Widths set for individual columns always override the default column width for the table.
175
181
 
182
+ <a name="shrinkwrap"></a>
183
+ ### Automating column widths
184
+
185
+ Instead of setting column widths "manually", you can tell the table to sort out the widths
186
+ itself, so that each column is just wide enough for its header and contents (plus a character
187
+ of padding):
188
+
189
+ ```ruby
190
+ table = Tabulo::Table.new([1, 2], columns: %i(itself even?))
191
+ table.shrinkwrap!
192
+ ```
193
+
194
+ ```
195
+ > puts table
196
+ +--------+-------+
197
+ | itself | even? |
198
+ +--------+-------+
199
+ | 1 | false |
200
+ | 2 | true |
201
+ ```
202
+
203
+ The `shrinkwrap!` method returns the table itself, so you can "wrap-and-print" in one go:
204
+
205
+ ```ruby
206
+ puts Tabulo::Table.new([1, 2], columns: %i(itself even?)).shrinkwrap!
207
+ ```
208
+
209
+ <a name="max-table-width"></a>
210
+ You can place an upper limit on the total width of the table when shrinkwrapping:
211
+
212
+ ```ruby
213
+ puts Tabulo::Table.new([1, 2], columns: %i(itself even?)).shrinkwrap!(max_table_width: 17)
214
+ ```
215
+
216
+ ```
217
+ +-------+-------+
218
+ | itsel | even? |
219
+ | f | |
220
+ +-------+-------+
221
+ | 1 | false |
222
+ | 2 | true |
223
+ ```
224
+
225
+ If the table cannot be fit within `max_column_width`, column widths are reduced as required, with
226
+ wrapping or truncation then occuring as necessary (see [Overflow handling](#overflow-handling)).
227
+ Under the hood, a character of width is deducted column by column&mdash;the widest column being
228
+ targetted each time&mdash;until the table will fit. This is very useful when you want to ensure the
229
+ table will not overflow your terminal horizontally.
230
+
231
+ Note that shrinkwrapping necessarily involves traversing the entire collection up front as
232
+ the maximum cell width needs to be calculated for each column. You may not want to do this
233
+ if the collection is very large.
234
+
235
+ <a name="overflow-handling"></a>
176
236
  ### Overflow handling
177
237
 
178
238
  By default, if cell contents exceed their column width, they are wrapped for as many rows as
@@ -253,6 +313,7 @@ of the underlying cell value, not the way it is formatted. This is usually the d
253
313
  Note also that the item yielded to `.each` for each cell when enumerating over a `Tabulo::Row` is
254
314
  the underlying value of that cell, not its formatted value.
255
315
 
316
+ <a name="repeating-headers"></a>
256
317
  ### Repeating headers
257
318
 
258
319
  By default, headers are only shown once, at the top of the table (`header_frequency: :start`). If
@@ -286,6 +347,7 @@ table = Tabulo::Table.new(1..10, columns: %i(itself even?), header_frequency: 5)
286
347
  | 10 | true |
287
348
  ```
288
349
 
350
+ <a name="enumerator"></a>
289
351
  ### Using a Table Enumerator
290
352
 
291
353
  Because it's an `Enumerable`, a `Tabulo::Table` can also give you an `Enumerator`,
data/lib/tabulo/column.rb CHANGED
@@ -3,7 +3,8 @@ module Tabulo
3
3
  # @!visibility private
4
4
  class Column
5
5
 
6
- attr_reader :label, :width
6
+ attr_accessor :width
7
+ attr_reader :header, :label
7
8
 
8
9
  # @!visibility private
9
10
  def initialize(options)
@@ -28,9 +29,14 @@ module Tabulo
28
29
  # @!visibility private
29
30
  def body_cell(source)
30
31
  cell_datum = body_cell_value(source)
31
- formatted_cell_content = @formatter.call(cell_datum)
32
- real_alignment = @align_body || infer_alignment(cell_datum)
33
- align_cell_content(formatted_cell_content, real_alignment)
32
+ formatted_content = @formatter.call(cell_datum)
33
+ real_alignment = (@align_body || infer_alignment(cell_datum))
34
+ align_cell_content(formatted_content, real_alignment)
35
+ end
36
+
37
+ # @!visibility private
38
+ def formatted_cell_content(source)
39
+ @formatter.call(body_cell_value(source))
34
40
  end
35
41
 
36
42
  # @!visibility private
data/lib/tabulo/table.rb CHANGED
@@ -148,6 +148,64 @@ module Tabulo
148
148
  format_row(false, HORIZONTAL_RULE_CHARACTER, CORNER_CHARACTER, &:horizontal_rule)
149
149
  end
150
150
 
151
+ # Reset all the column widths so that each column is *just* wide enough to accommodate
152
+ # its header text as well as the formatted content of each its cells for the entire
153
+ # collection, together with a single character of padding on either side of the column,
154
+ # without any wrapping.
155
+ #
156
+ # Note that calling this method will cause the entire source Enumerable to
157
+ # be traversed and all the column extractors and formatters to be applied in order
158
+ # to calculate the required widths.
159
+ #
160
+ # @param [Hash] options
161
+ # @option options [String] :max_table_width (nil) If provided, stops the total table
162
+ # width (including padding and borders) from expanding beyond this number of characters.
163
+ # Width is deducted from columns if required to achieve this, with one character progressively
164
+ # deducted from the width of the widest column until the target is reached. When the
165
+ # table is printed, wrapping or truncation will then occur in these columns as required
166
+ # (depending on how they were configured).
167
+ #
168
+ # @return [Table] the Table itself
169
+ def shrinkwrap!(options = { })
170
+ return self if columns.none?
171
+ max_table_width = options[:max_table_width]
172
+
173
+ header_widths = columns.map { |c| c.header.length }
174
+
175
+ column_widths = @sources.inject(header_widths) do |widths, source|
176
+ columns.map { |c| c.formatted_cell_content(source).length }.zip(widths).map(&:max)
177
+ end
178
+
179
+ columns.zip(column_widths).each do |column, width|
180
+ column.width = width
181
+ end
182
+
183
+ if max_table_width
184
+ total_columns_width = columns.inject(0) { |sum, column| sum + column.width }
185
+ total_padding = columns.count * 2
186
+ total_borders = columns.count + 1
187
+ unadjusted_table_width = total_columns_width + total_padding + total_borders
188
+
189
+ # Ensure max table width is at least wide enough to accommodate table borders and padding
190
+ # and one character of content.
191
+ # TODO Document this behaviour.
192
+ min_table_width = total_padding + total_borders + columns.count
193
+ max_table_width = min_table_width if min_table_width > max_table_width
194
+
195
+ required_reduction = [unadjusted_table_width - max_table_width, 0].max
196
+
197
+ required_reduction.times do
198
+ widest_column = columns.inject(columns.first) do |widest, column|
199
+ column.width >= widest.width ? column : widest
200
+ end
201
+
202
+ widest_column.width -= 1
203
+ end
204
+ end
205
+
206
+ self
207
+ end
208
+
151
209
  # @!visibility private
152
210
  def formatted_body_row(source, options = { with_header: false })
153
211
  inner = format_row { |column| column.body_cell(source) }
@@ -1,3 +1,3 @@
1
1
  module Tabulo
2
- VERSION = "0.4.2"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tabulo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew Harvey