tabulo 0.6.2 → 0.6.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +11 -5
- data/VERSION +1 -1
- data/lib/tabulo.rb +1 -0
- data/lib/tabulo/column.rb +2 -13
- data/lib/tabulo/exceptions.rb +5 -0
- data/lib/tabulo/row.rb +3 -3
- data/lib/tabulo/table.rb +60 -28
- data/lib/tabulo/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6756f8ae92c219bb324358e9046e86ec756d3923
|
4
|
+
data.tar.gz: de3d6bedfcbea40b81d607d00ca07be75e4277a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b5f10a9d672d15083bf96cb7d6f5e8a21030e98318fc13cd98d82ec9c22f5d83ea25d65cb792273c5840db056b7b6a0a961d71676a10bac430f5f874517521b6
|
7
|
+
data.tar.gz: c46bcc143a0447af136aab26c7dc960b1c4f53afdf72e27a57522428dab7d936ccc95cd8b2a4bbe19726100fd28de4547f978ed1280be9a491bc88bc9a18547d
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -226,7 +226,12 @@ table will not overflow your terminal horizontally.
|
|
226
226
|
|
227
227
|
Note that shrinkwrapping necessarily involves traversing the entire collection up front as
|
228
228
|
the maximum cell width needs to be calculated for each column. You may not want to do this
|
229
|
-
if the collection is very large.
|
229
|
+
if the collection is very large. Note also the effect of `shrinkwrap!` is to fix the column widths
|
230
|
+
as appropriate to the formatted cell contents given the state of the underlying collection
|
231
|
+
_at the point of shrinkwrapping_. If the underlying collection changes between that point, and when
|
232
|
+
the table is printed, then the columns will _not_ be resized yet again on printing. This is a
|
233
|
+
consequence of the table always being essentially a "live view" on the underlying collection:
|
234
|
+
formatted contents are never cached within the table itself.
|
230
235
|
|
231
236
|
<a name="overflow-handling"></a>
|
232
237
|
#### Overflow handling
|
@@ -368,8 +373,9 @@ end.to_enum # <-- make an Enumerator
|
|
368
373
|
```
|
369
374
|
|
370
375
|
Note the use of `.find_each`: we can start printing the table without having to load the entire
|
371
|
-
underlying collection. (
|
372
|
-
|
376
|
+
underlying collection. (This is negated if we [shrinkwrap](#shrinkwrap) the table, however, since
|
377
|
+
in that case the entire collection must be traversed up front in order for column widths to be
|
378
|
+
calculated.)
|
373
379
|
|
374
380
|
## Development
|
375
381
|
|
@@ -391,11 +397,11 @@ License](http://opensource.org/licenses/MIT).
|
|
391
397
|
[Dependency Status]: https://gemnasium.com/matt-harvey/tabulo
|
392
398
|
[Coverage Status]: https://coveralls.io/r/matt-harvey/tabulo
|
393
399
|
[Code Climate]: https://codeclimate.com/github/matt-harvey/tabulo
|
394
|
-
[Documentation]: http://www.rubydoc.info/
|
400
|
+
[Documentation]: http://www.rubydoc.info/github/matt-harvey/tabulo
|
395
401
|
|
396
402
|
[GV img]: https://img.shields.io/gem/v/tabulo.svg?style=plastic
|
397
403
|
[BS img]: https://img.shields.io/travis/matt-harvey/tabulo.svg?style=plastic
|
398
404
|
[DS img]: https://img.shields.io/gemnasium/matt-harvey/tabulo.svg?style=plastic
|
399
405
|
[CS img]: https://img.shields.io/coveralls/matt-harvey/tabulo.svg?style=plastic
|
400
406
|
[CC img]: https://img.shields.io/codeclimate/github/matt-harvey/tabulo.svg?style=plastic
|
401
|
-
[DC img]: https://img.shields.io/badge/docs-
|
407
|
+
[DC img]: https://img.shields.io/badge/docs-latest-blue.svg?style=plastic
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.6.
|
1
|
+
0.6.3
|
data/lib/tabulo.rb
CHANGED
data/lib/tabulo/column.rb
CHANGED
@@ -4,13 +4,10 @@ module Tabulo
|
|
4
4
|
class Column
|
5
5
|
|
6
6
|
attr_accessor :width
|
7
|
-
attr_reader :header
|
7
|
+
attr_reader :header
|
8
8
|
|
9
|
-
|
10
|
-
def initialize(label:, header:, width:, align_header:, align_body:,
|
11
|
-
formatter:, extractor:)
|
9
|
+
def initialize(header:, width:, align_header:, align_body:, formatter:, extractor:)
|
12
10
|
|
13
|
-
@label = label
|
14
11
|
@header = header
|
15
12
|
@width = width
|
16
13
|
@align_header = align_header
|
@@ -19,17 +16,14 @@ module Tabulo
|
|
19
16
|
@extractor = extractor
|
20
17
|
end
|
21
18
|
|
22
|
-
# @!visibility private
|
23
19
|
def header_subcells
|
24
20
|
infilled_subcells(@header, @align_header)
|
25
21
|
end
|
26
22
|
|
27
|
-
# @!visibility private
|
28
23
|
def horizontal_rule
|
29
24
|
Table::HORIZONTAL_RULE_CHARACTER * width
|
30
25
|
end
|
31
26
|
|
32
|
-
# @!visibility private
|
33
27
|
def body_subcells(source)
|
34
28
|
cell_datum = body_cell_value(source)
|
35
29
|
formatted_content = @formatter.call(cell_datum)
|
@@ -37,19 +31,16 @@ module Tabulo
|
|
37
31
|
infilled_subcells(formatted_content, real_alignment)
|
38
32
|
end
|
39
33
|
|
40
|
-
# @!visibility private
|
41
34
|
def formatted_cell_content(source)
|
42
35
|
@formatter.call(body_cell_value(source))
|
43
36
|
end
|
44
37
|
|
45
|
-
# @!visibility private
|
46
38
|
def body_cell_value(source)
|
47
39
|
@extractor.call(source)
|
48
40
|
end
|
49
41
|
|
50
42
|
private
|
51
43
|
|
52
|
-
# @!visibility private
|
53
44
|
def infilled_subcells(str, real_alignment)
|
54
45
|
str.split($/, -1).flat_map do |substr|
|
55
46
|
num_subsubcells = [1, (substr.length.to_f / width).ceil].max
|
@@ -59,7 +50,6 @@ module Tabulo
|
|
59
50
|
end
|
60
51
|
end
|
61
52
|
|
62
|
-
# @!visibility private
|
63
53
|
def align_cell_content(content, real_alignment)
|
64
54
|
padding = [@width - content.length, 0].max
|
65
55
|
left_padding, right_padding =
|
@@ -76,7 +66,6 @@ module Tabulo
|
|
76
66
|
"#{' ' * left_padding}#{content}#{' ' * right_padding}"
|
77
67
|
end
|
78
68
|
|
79
|
-
# @!visibility private
|
80
69
|
def infer_alignment(cell_datum)
|
81
70
|
case cell_datum
|
82
71
|
when Numeric
|
data/lib/tabulo/row.rb
CHANGED
@@ -22,7 +22,7 @@ module Tabulo
|
|
22
22
|
# cell.class # => Fixnum, => FalseClass
|
23
23
|
# end
|
24
24
|
def each
|
25
|
-
@table.
|
25
|
+
@table.column_registry.each do |label, column|
|
26
26
|
yield column.body_cell_value(@source)
|
27
27
|
end
|
28
28
|
end
|
@@ -31,7 +31,7 @@ module Tabulo
|
|
31
31
|
# any column headers that appear just above it in the {Table} (depending on where this Row is
|
32
32
|
# in the {Table} and how the {Table} was configured with respect to header frequency).
|
33
33
|
def to_s
|
34
|
-
if @table.
|
34
|
+
if @table.column_registry.any?
|
35
35
|
@table.formatted_body_row(@source, with_header: @with_header)
|
36
36
|
else
|
37
37
|
""
|
@@ -45,7 +45,7 @@ module Tabulo
|
|
45
45
|
# row = table.first
|
46
46
|
# row.to_h # => { :itself => 1, :even? => false }
|
47
47
|
def to_h
|
48
|
-
@table.
|
48
|
+
@table.column_registry.map { |label, column| [label, column.body_cell_value(@source)] }.to_h
|
49
49
|
end
|
50
50
|
end
|
51
51
|
end
|
data/lib/tabulo/table.rb
CHANGED
@@ -6,19 +6,30 @@ module Tabulo
|
|
6
6
|
class Table
|
7
7
|
include Enumerable
|
8
8
|
|
9
|
+
# @!visibility private
|
9
10
|
DEFAULT_COLUMN_WIDTH = 12
|
11
|
+
|
12
|
+
# @!visibility private
|
10
13
|
HORIZONTAL_RULE_CHARACTER = "-"
|
14
|
+
|
15
|
+
# @!visibility private
|
11
16
|
VERTICAL_RULE_CHARACTER = "|"
|
17
|
+
|
18
|
+
# @!visibility private
|
12
19
|
CORNER_CHARACTER = "+"
|
20
|
+
|
21
|
+
# @!visibility private
|
13
22
|
PADDING_CHARACTER = " "
|
23
|
+
|
24
|
+
# @!visibility private
|
14
25
|
TRUNCATION_INDICATOR = "~"
|
15
26
|
|
16
27
|
# @!visibility private
|
17
|
-
attr_reader :
|
28
|
+
attr_reader :column_registry
|
18
29
|
|
19
30
|
# @param [Enumerable] sources the underlying Enumerable from which the table will derive its data
|
20
|
-
# @param [Array[Symbol]] columns Specifies the initial columns.
|
21
|
-
# Each element of the Array will be used to create a column whose content is
|
31
|
+
# @param [Array[Symbol]] columns Specifies the initial columns. The Symbols provided must
|
32
|
+
# be unique. Each element of the Array will be used to create a column whose content is
|
22
33
|
# created by calling the corresponding method on each element of sources. Note
|
23
34
|
# the {#add_column} method is a much more flexible way to set up columns on the table.
|
24
35
|
# @param [Fixnum, nil] column_width The default column width for columns in this
|
@@ -36,6 +47,7 @@ module Tabulo
|
|
36
47
|
# be wrapped for as many rows as required to accommodate it. If passed a Fixnum N (> 0), content will be
|
37
48
|
# wrapped up to N rows and then truncated thereafter.
|
38
49
|
# @return [Table] a new Table
|
50
|
+
# @raise [InvalidColumnLabelError] if non-unique Symbols are provided to columns.
|
39
51
|
def initialize(sources, columns: [], column_width: nil, header_frequency: :start,
|
40
52
|
wrap_header_cells_to: nil, wrap_body_cells_to: nil)
|
41
53
|
|
@@ -46,7 +58,7 @@ module Tabulo
|
|
46
58
|
|
47
59
|
@default_column_width = (column_width || DEFAULT_COLUMN_WIDTH)
|
48
60
|
|
49
|
-
@
|
61
|
+
@column_registry = { }
|
50
62
|
columns.each { |item| add_column(item) }
|
51
63
|
|
52
64
|
yield self if block_given?
|
@@ -81,24 +93,33 @@ module Tabulo
|
|
81
93
|
# that will be passed each of the Table sources to determine the value in each cell of this
|
82
94
|
# column. If this is not provided, then the column label will be treated as a method to be
|
83
95
|
# called on each source item to determine each cell's value.
|
96
|
+
# @raise [InvalidColumnLabelError] if label has already been used for another column in this
|
97
|
+
# Table. (This is case-sensitive, but is insensitive to whether a String or Symbol is passed
|
98
|
+
# to the label parameter.)
|
84
99
|
def add_column(label, header: nil, align_header: :center, align_body: nil,
|
85
100
|
width: nil, formatter: :to_s.to_proc, &extractor)
|
86
101
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
102
|
+
column_label = label.to_sym
|
103
|
+
|
104
|
+
if column_registry.include?(column_label)
|
105
|
+
raise InvalidColumnLabelError, "Column label already used in this table."
|
106
|
+
end
|
107
|
+
|
108
|
+
@column_registry[column_label] =
|
109
|
+
Column.new(
|
110
|
+
header: (header || label).to_s,
|
111
|
+
align_header: align_header,
|
112
|
+
align_body: align_body,
|
113
|
+
width: (width || @default_column_width),
|
114
|
+
formatter: formatter,
|
115
|
+
extractor: (extractor || label.to_proc)
|
116
|
+
)
|
96
117
|
end
|
97
118
|
|
98
119
|
# @return [String] a graphical "ASCII" representation of the Table, suitable for
|
99
120
|
# display in a fixed-width font.
|
100
121
|
def to_s
|
101
|
-
if
|
122
|
+
if column_registry.any?
|
102
123
|
join_lines(map(&:to_s))
|
103
124
|
else
|
104
125
|
""
|
@@ -131,7 +152,7 @@ module Tabulo
|
|
131
152
|
|
132
153
|
# @return [String] an "ASCII" graphical representation of the Table column headers.
|
133
154
|
def formatted_header
|
134
|
-
cells =
|
155
|
+
cells = column_registry.map { |label, column| column.header_subcells }
|
135
156
|
format_row(cells, @wrap_header_cells_to)
|
136
157
|
end
|
137
158
|
|
@@ -144,7 +165,9 @@ module Tabulo
|
|
144
165
|
# end
|
145
166
|
#
|
146
167
|
def horizontal_rule
|
147
|
-
inner =
|
168
|
+
inner = column_registry.map do |label, column|
|
169
|
+
surround(column.horizontal_rule, HORIZONTAL_RULE_CHARACTER)
|
170
|
+
end
|
148
171
|
surround_join(inner, CORNER_CHARACTER)
|
149
172
|
end
|
150
173
|
|
@@ -157,6 +180,11 @@ module Tabulo
|
|
157
180
|
# be traversed and all the column extractors and formatters to be applied in order
|
158
181
|
# to calculate the required widths.
|
159
182
|
#
|
183
|
+
# Note also that this method causes column widths to be fixed as appropriate to the
|
184
|
+
# formatted cell contents given the state of the source Enumerable at the point it
|
185
|
+
# is called. If the source Enumerable changes between that point, and the point when
|
186
|
+
# the Table is printed, then columns will *not* be resized yet again on printing.
|
187
|
+
#
|
160
188
|
# @param [nil, Numeric] max_table_width (nil) If provided, stops the total table
|
161
189
|
# width (including padding and borders) from expanding beyond this number of characters.
|
162
190
|
# Width is deducted from columns if required to achieve this, with one character progressively
|
@@ -170,36 +198,41 @@ module Tabulo
|
|
170
198
|
# shrink itself.
|
171
199
|
# @return [Table] the Table itself
|
172
200
|
def shrinkwrap!(max_table_width: nil)
|
173
|
-
return self if
|
201
|
+
return self if column_registry.none?
|
174
202
|
|
175
203
|
wrapped_width = -> (str) { str.split($/).map(&:length).max || 1 }
|
176
204
|
|
177
|
-
|
205
|
+
column_registry.each do |label, column|
|
178
206
|
column.width = wrapped_width.call(column.header)
|
179
207
|
end
|
180
208
|
|
181
209
|
@sources.each do |source|
|
182
|
-
|
210
|
+
column_registry.each do |label, column|
|
183
211
|
width = wrapped_width.call(column.formatted_cell_content(source))
|
184
212
|
column.width = width if width > column.width
|
185
213
|
end
|
186
214
|
end
|
187
215
|
|
188
216
|
if max_table_width
|
189
|
-
total_columns_width =
|
190
|
-
|
191
|
-
|
217
|
+
total_columns_width = column_registry.inject(0) do |sum, label_with_column|
|
218
|
+
_label, column = label_with_column
|
219
|
+
sum + column.width
|
220
|
+
end
|
221
|
+
total_padding = column_registry.count * 2
|
222
|
+
total_borders = column_registry.count + 1
|
192
223
|
unadjusted_table_width = total_columns_width + total_padding + total_borders
|
193
224
|
|
194
225
|
# Ensure max table width is at least wide enough to accommodate table borders and padding
|
195
226
|
# and one character of content.
|
196
|
-
min_table_width = total_padding + total_borders +
|
227
|
+
min_table_width = total_padding + total_borders + column_registry.count
|
197
228
|
max_table_width = min_table_width if min_table_width > max_table_width
|
198
229
|
|
199
230
|
required_reduction = [unadjusted_table_width - max_table_width, 0].max
|
200
231
|
|
201
232
|
required_reduction.times do
|
202
|
-
|
233
|
+
_first_label, first_column = column_registry.first
|
234
|
+
widest_column = column_registry.inject(first_column) do |widest, label_with_column|
|
235
|
+
_label, column = label_with_column
|
203
236
|
column.width >= widest.width ? column : widest
|
204
237
|
end
|
205
238
|
|
@@ -212,7 +245,7 @@ module Tabulo
|
|
212
245
|
|
213
246
|
# @!visibility private
|
214
247
|
def formatted_body_row(source, with_header: false)
|
215
|
-
cells =
|
248
|
+
cells = column_registry.map { |label, column| column.body_subcells(source) }
|
216
249
|
inner = format_row(cells, @wrap_body_cells_to)
|
217
250
|
if with_header
|
218
251
|
join_lines([horizontal_rule, formatted_header, horizontal_rule, inner])
|
@@ -245,7 +278,7 @@ module Tabulo
|
|
245
278
|
row_height = ([wrap_cells_to, cells.map(&:size).max].compact.min || 1)
|
246
279
|
|
247
280
|
subrows = (0...row_height).map do |subrow_index|
|
248
|
-
subrow_components = cells.map
|
281
|
+
subrow_components = cells.zip(column_registry.values).map do |cell, column|
|
249
282
|
num_subcells = cell.size
|
250
283
|
cell_truncated = (num_subcells > row_height)
|
251
284
|
append_truncator = (cell_truncated && subrow_index + 1 == row_height)
|
@@ -257,8 +290,7 @@ module Tabulo
|
|
257
290
|
if subrow_index < num_subcells
|
258
291
|
cell[subrow_index]
|
259
292
|
else
|
260
|
-
|
261
|
-
PADDING_CHARACTER * column_width
|
293
|
+
PADDING_CHARACTER * column.width
|
262
294
|
end
|
263
295
|
|
264
296
|
"#{lpad}#{inner}#{rpad}"
|
data/lib/tabulo/version.rb
CHANGED
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.6.
|
4
|
+
version: 0.6.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Harvey
|
@@ -159,6 +159,7 @@ files:
|
|
159
159
|
- bin/setup
|
160
160
|
- lib/tabulo.rb
|
161
161
|
- lib/tabulo/column.rb
|
162
|
+
- lib/tabulo/exceptions.rb
|
162
163
|
- lib/tabulo/row.rb
|
163
164
|
- lib/tabulo/table.rb
|
164
165
|
- lib/tabulo/version.rb
|