tabulo 0.4.2 → 0.5.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
  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