fat_table 0.5.3 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.org +136 -67
- data/TODO.org +28 -1
- data/lib/ext/array.rb +17 -0
- data/lib/fat_table/column.rb +94 -56
- data/lib/fat_table/convert.rb +18 -9
- data/lib/fat_table/evaluator.rb +6 -8
- data/lib/fat_table/footer.rb +73 -33
- data/lib/fat_table/formatters/formatter.rb +153 -31
- data/lib/fat_table/formatters/org_formatter.rb +3 -3
- data/lib/fat_table/formatters/term_formatter.rb +3 -3
- data/lib/fat_table/formatters/text_formatter.rb +3 -3
- data/lib/fat_table/table.rb +12 -5
- data/lib/fat_table/version.rb +1 -1
- metadata +5 -5
@@ -181,41 +181,74 @@ module FatTable
|
|
181
181
|
end
|
182
182
|
|
183
183
|
# :category: Add Footers
|
184
|
-
|
185
|
-
# A simpler method for adding a footer to the formatted output having the
|
186
|
-
# label +label+ placed in the column with the header +label_col+ or in the
|
187
|
-
# first column if +label_col+ is ommitted. The remaining hash arguments
|
188
|
-
# apply an aggregate to the values of the column, which can be:
|
189
|
-
#
|
190
|
-
# (1) a symbol representing one of the builtin aggregates, i.e., :first,
|
191
|
-
# :last, :range, :sum, :count, :min, :max, :avg, :var, :pvar, :dev, :pdev,
|
192
|
-
# :any?, :all?, :none?, and :one?,
|
193
184
|
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
185
|
+
# A keyword method for adding a footer to the formatted output having the
|
186
|
+
# label +label:+ (default 'Total') placed in the column with the header
|
187
|
+
# +label_col:+ or in the first column if +label_col+ is ommitted.
|
188
|
+
# This assigns a fixed group label to be placed in the :date column:
|
189
|
+
# #+begin_src ruby
|
190
|
+
# fmtr.foot(label: "Year's Average", label_col: :date, temp: avg)
|
191
|
+
# #+end_src
|
192
|
+
#
|
193
|
+
# Besides being a fixed string, the +label:+ can also be a proc or lambda
|
194
|
+
# taking one argument, the foooter itself.
|
195
|
+
# Thus, a label such as:
|
196
|
+
#
|
197
|
+
# #+begin_src ruby
|
198
|
+
# fmtr.foot(label: -> (f) { "Average (latest year #{f.column(:date).max.year})" },
|
199
|
+
# temp: :avg)
|
200
|
+
# #+end_src
|
201
|
+
# And this would add the highest number to label, assuming the :date column
|
202
|
+
# of the footer's table had the year for each item.
|
203
|
+
#
|
204
|
+
# The remaining hash arguments apply an aggregate to the values of the
|
205
|
+
# column, which can be:
|
206
|
+
#
|
207
|
+
# 1. a symbol representing one of the builtin aggregates, i.e., :first,
|
208
|
+
# :last, :range, :sum, :count, :min, :max, :avg, :var, :pvar, :dev,
|
209
|
+
# :pdev, :any?, :all?, :none?, and :one?, or a symbol for your own
|
210
|
+
# aggregate defined as an instance method on FatTable::Column.
|
211
|
+
# 2. a fixed string, but it the string can be converted into the Column's
|
212
|
+
# type, it will be converted, so the string '3.14159' will be converted
|
213
|
+
# to 3.14159 in a Numeric column.
|
214
|
+
# 3. a value of the Column's type, so Date.today would simply be evaluated
|
215
|
+
# for a Numeric column.
|
216
|
+
# 4. most flexibly of all, a proc or lambda taking arguments: f, the
|
217
|
+
# footer object itself; c, the column (or in the case of a group
|
218
|
+
# footer, the sub-column) corresponding to the current header, and in
|
219
|
+
# the case of a group footer, k, the number of the group (0-based).
|
220
|
+
# 5. Any other value is converted to a string with #to_s.
|
198
221
|
#
|
199
222
|
# Examples:
|
200
223
|
#
|
201
224
|
# Put the label in the :dickens column of the footer and the maximum value
|
202
225
|
# from the :alpha column in the :alpha column of the footer.
|
203
226
|
#
|
204
|
-
# fmtr.foot('Best', :dickens, alpha: :max)
|
227
|
+
# fmtr.foot(label: 'Best', label_col: :dickens, alpha: :max)
|
205
228
|
#
|
206
229
|
# Put the label 'Today' in the first column of the footer and today's date
|
207
230
|
# in the :beta column.
|
208
231
|
#
|
209
|
-
# fmtr.foot('Today', beta: Date.today)
|
232
|
+
# fmtr.foot(label: 'Today', beta: Date.today)
|
210
233
|
#
|
211
234
|
# Put the label 'Best' in the :dickens column of the footer and the string
|
212
235
|
# 'Tale of Two Cities' in the :alpha column of the footer. Since it can't
|
213
236
|
# be interpreted as Boolean, Numeric, or DateTime, it is placed in the
|
214
237
|
# footer literally.
|
215
238
|
#
|
216
|
-
# fmtr.foot('Best', :dickens, alpha: 'A Tale of Two
|
239
|
+
# fmtr.foot(label: 'Best', label_col: :dickens, alpha: 'A Tale of Two
|
240
|
+
# Cities')
|
241
|
+
#
|
242
|
+
# Use a lambda to calculate the value to be placed in the column :gamma.
|
243
|
+
#
|
244
|
+
# fmtr.foot(label: 'Gamma', beta: :avg, gamma: ->(f, c) {
|
245
|
+
# (Math.gamma(c.count) + f[:beta] } )
|
246
|
+
#
|
247
|
+
# Note that this way a footer can be made a function of the other footer
|
248
|
+
# values (using f[:other_col]) as well as the Column object corresponding
|
249
|
+
# to the lamda's column.
|
217
250
|
#
|
218
|
-
def foot(label, label_col
|
251
|
+
def foot(label: 'Total', label_col: nil, **agg_cols)
|
219
252
|
foot = Footer.new(label, table, label_col: label_col)
|
220
253
|
agg_cols.each_pair do |h, agg|
|
221
254
|
foot.add_value(h, agg)
|
@@ -255,9 +288,92 @@ module FatTable
|
|
255
288
|
foot
|
256
289
|
end
|
257
290
|
|
258
|
-
# Add
|
259
|
-
#
|
260
|
-
|
291
|
+
# :category: Add Footers
|
292
|
+
#
|
293
|
+
# A keyword method for adding a group footer to the formatted output
|
294
|
+
# having the label +label:+ (default 'Total') placed in the column with
|
295
|
+
# the header +label_col:+ or in the first column if +label_col+ is
|
296
|
+
# ommitted.
|
297
|
+
#
|
298
|
+
# This assigns a fixed group label to be placed in the :date column:
|
299
|
+
# #+begin_src ruby
|
300
|
+
# fmtr.gfoot(label: "Year's Average", label_col: :date, temp: avg)
|
301
|
+
# #+end_src
|
302
|
+
#
|
303
|
+
# Besides being a fixed string, the +label:+ can also be a proc or lambda
|
304
|
+
# taking one or two arguments. In the one argument form, the argument is
|
305
|
+
# the group number k. If a second argument is specified, the foooter
|
306
|
+
# itself is passed as the argument. Thus, a label such as:
|
307
|
+
#
|
308
|
+
# #+begin_src ruby
|
309
|
+
# fmtr.gfoot(label: -> (k) { "Group #{(k+1).to_roman} Average" }, temp: :avg)
|
310
|
+
# #+end_src
|
311
|
+
# This would format the label with a roman numeral (assuming you defined a
|
312
|
+
# method to do so) for the group number.
|
313
|
+
#
|
314
|
+
# #+begin_src ruby
|
315
|
+
# fmtr.gfoot(label: -> (k, f) { "Year #{f.column(:date, k).max.year} Group #{(k+1).to_roman} Average" },
|
316
|
+
# temp: :avg)
|
317
|
+
# #+end_src
|
318
|
+
# And this would add the group's year to label, assuming the :date column
|
319
|
+
# of the footer's table had the same year for each item in the group.
|
320
|
+
#
|
321
|
+
#
|
322
|
+
# The remaining hash arguments apply an aggregate to the values
|
323
|
+
# of the column, which can be:
|
324
|
+
#
|
325
|
+
# 1. a symbol representing one of the builtin aggregates, i.e., :first,
|
326
|
+
# :last, :range, :sum, :count, :min, :max, :avg, :var, :pvar, :dev,
|
327
|
+
# :pdev, :any?, :all?, :none?, and :one?, or a symbol for your own
|
328
|
+
# aggregate defined as an instance method on FatTable::Column.
|
329
|
+
# 2. a fixed string, but it the string can be converted into the Column's
|
330
|
+
# type, it will be converted, so the string '3.14159' will be converted
|
331
|
+
# to 3.14159 in a Numeric column.
|
332
|
+
# 3. a value of the Column's type, so Date.today would simply be evaluated
|
333
|
+
# for a Numeric column.
|
334
|
+
# 4. most flexibly of all, a proc or lambda taking arguments: f, the
|
335
|
+
# footer object itself; c, the column (or in the case of a group
|
336
|
+
# footer, the sub-column) corresponding to the current header, and k,
|
337
|
+
# this group's group number (0-based).
|
338
|
+
# 5. Any other value is converted to a string with #to_s.
|
339
|
+
#
|
340
|
+
# Examples:
|
341
|
+
#
|
342
|
+
# Put the label in the :dickens column of the footer and the maximum value
|
343
|
+
# from the :alpha column in the :alpha column of the footer.
|
344
|
+
#
|
345
|
+
# #+begin_src ruby
|
346
|
+
# fmtr.gfoot(label: 'Best', label_col: :dickens, alpha: :max)
|
347
|
+
# #+end_src
|
348
|
+
#
|
349
|
+
# Put the label 'Today' in the first column of the footer and today's date
|
350
|
+
# in the :beta column.
|
351
|
+
#
|
352
|
+
# #+begin_src ruby
|
353
|
+
# fmtr.gfoot(label: 'Today', beta: Date.today)
|
354
|
+
# #+end_src
|
355
|
+
#
|
356
|
+
# Put the label 'Best' in the :dickens column of the footer and the string
|
357
|
+
# 'Tale of Two Cities' in the :alpha column of the footer. Since it can't
|
358
|
+
# be interpreted as Boolean, Numeric, or DateTime, it is placed in the
|
359
|
+
# footer literally.
|
360
|
+
#
|
361
|
+
# #+begin_src ruby
|
362
|
+
# fmtr.gfoot(label: 'Best', label_col: :dickens, alpha: 'A Tale of Two
|
363
|
+
# Cities')
|
364
|
+
# #+end_src
|
365
|
+
#
|
366
|
+
# Use a lambda to calculate the value to be placed in the column :gamma.
|
367
|
+
#
|
368
|
+
# #+begin_src ruby
|
369
|
+
# fmtr.gfoot(label: 'Gamma', beta: :avg, gamma: ->(f, c) {
|
370
|
+
# (Math.gamma(c.count) + f[:beta] } )
|
371
|
+
# #+end_src
|
372
|
+
#
|
373
|
+
# Note that this way a footer can be made a function of the other footer
|
374
|
+
# values (using f[:other_col]) as well as the Column object corresponding
|
375
|
+
# to the lamda's column.
|
376
|
+
def gfoot(label: 'Group Total', label_col: nil, **agg_cols)
|
261
377
|
foot = Footer.new(label, table, label_col: label_col, group: true)
|
262
378
|
agg_cols.each_pair do |h, agg|
|
263
379
|
foot.add_value(h, agg)
|
@@ -532,7 +648,7 @@ module FatTable
|
|
532
648
|
valid_keys = table.headers + %i[string numeric datetime boolean nil]
|
533
649
|
invalid_keys = (fmts.keys - valid_keys).uniq
|
534
650
|
unless invalid_keys.empty?
|
535
|
-
msg = "invalid #{location} column or type: #{invalid_keys.join(',')}"
|
651
|
+
msg = "invalid #{location} column or type: #{invalid_keys.join(', ')}"
|
536
652
|
raise UserError, msg
|
537
653
|
end
|
538
654
|
|
@@ -562,20 +678,21 @@ module FatTable
|
|
562
678
|
end
|
563
679
|
end
|
564
680
|
|
565
|
-
# Merge in formatting for column h based on the column
|
566
|
-
#
|
681
|
+
# Merge in formatting instructions for column h based on the column
|
682
|
+
# name, or if there is no formatting instructions for the column by
|
683
|
+
# name, merge in the formatting instructions based on the column's
|
684
|
+
# type. Insist on only the string type for the header location.
|
567
685
|
typ = (location == :header ? :string : table.type(h).as_sym)
|
568
686
|
parse_typ_method_name = 'parse_' + typ.to_s + '_fmt'
|
569
|
-
if fmts.key?(typ)
|
570
|
-
# Merge in type-based formatting
|
571
|
-
typ_fmt = send(parse_typ_method_name, fmts[typ]).first
|
572
|
-
format_h = format_h.merge(typ_fmt)
|
573
|
-
end
|
574
687
|
if fmts[h]
|
575
688
|
# Merge in column formatting
|
576
689
|
col_fmt = send(parse_typ_method_name, fmts[h],
|
577
690
|
strict: location != :header).first
|
578
691
|
format_h = format_h.merge(col_fmt)
|
692
|
+
elsif fmts.key?(typ)
|
693
|
+
# Merge in type-based formatting
|
694
|
+
typ_fmt = send(parse_typ_method_name, fmts[typ]).first
|
695
|
+
format_h = format_h.merge(typ_fmt)
|
579
696
|
end
|
580
697
|
|
581
698
|
# Copy :body formatting for column h to :bfirst and :gfirst if they
|
@@ -1003,9 +1120,14 @@ module FatTable
|
|
1003
1120
|
# converted to strings formatted according to the Formatter's formatting
|
1004
1121
|
# directives given in Formatter.format_for or Formatter.format.
|
1005
1122
|
def output
|
1006
|
-
#
|
1007
|
-
#
|
1008
|
-
|
1123
|
+
# If there are neither headers nor any rows in the table, return an
|
1124
|
+
# empty string.
|
1125
|
+
return '' if table.empty? && table.headers.empty?
|
1126
|
+
|
1127
|
+
# This results in a hash of two-element arrays. The key
|
1128
|
+
# is the header and the value is an array of the header and formatted
|
1129
|
+
# header. We do the latter so the structure parallels the structure for
|
1130
|
+
# rows explained next.
|
1009
1131
|
formatted_headers = build_formatted_headers
|
1010
1132
|
|
1011
1133
|
# These produce an array with each element representing a row of the
|
@@ -20,7 +20,7 @@ module FatTable
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def pre_header(widths)
|
23
|
-
result = '|'
|
23
|
+
result = +'|'
|
24
24
|
widths.each_value do |w|
|
25
25
|
result += '-' * (w + 2) + '+'
|
26
26
|
end
|
@@ -53,7 +53,7 @@ module FatTable
|
|
53
53
|
end
|
54
54
|
|
55
55
|
def hline(widths)
|
56
|
-
result = '|'
|
56
|
+
result = +'|'
|
57
57
|
widths.each_value do |w|
|
58
58
|
result += '-' * (w + 2) + '+'
|
59
59
|
end
|
@@ -62,7 +62,7 @@ module FatTable
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def post_footers(widths)
|
65
|
-
result = '|'
|
65
|
+
result = +'|'
|
66
66
|
widths.each_value do |w|
|
67
67
|
result += '-' * (w + 2) + '+'
|
68
68
|
end
|
@@ -221,7 +221,7 @@ module FatTable
|
|
221
221
|
end
|
222
222
|
|
223
223
|
def pre_header(widths)
|
224
|
-
result = upper_left
|
224
|
+
result = +upper_left
|
225
225
|
widths.each_value do |w|
|
226
226
|
result += double_rule * (w + 2) + upper_tee
|
227
227
|
end
|
@@ -255,7 +255,7 @@ module FatTable
|
|
255
255
|
end
|
256
256
|
|
257
257
|
def hline(widths)
|
258
|
-
result = left_tee
|
258
|
+
result = +left_tee
|
259
259
|
widths.each_value do |w|
|
260
260
|
result += horizontal_rule * (w + 2) + single_cross
|
261
261
|
end
|
@@ -289,7 +289,7 @@ module FatTable
|
|
289
289
|
end
|
290
290
|
|
291
291
|
def post_footers(widths)
|
292
|
-
result = lower_left
|
292
|
+
result = +lower_left
|
293
293
|
widths.each_value do |w|
|
294
294
|
result += double_rule * (w + 2) + lower_tee
|
295
295
|
end
|
@@ -16,7 +16,7 @@ module FatTable
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def pre_header(widths)
|
19
|
-
result = '+'
|
19
|
+
result = +'+'
|
20
20
|
widths.each_value do |w|
|
21
21
|
result += '=' * (w + 2) + '+'
|
22
22
|
end
|
@@ -49,7 +49,7 @@ module FatTable
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def hline(widths)
|
52
|
-
result = '+'
|
52
|
+
result = +'+'
|
53
53
|
widths.each_value do |w|
|
54
54
|
result += '-' * (w + 2) + '+'
|
55
55
|
end
|
@@ -82,7 +82,7 @@ module FatTable
|
|
82
82
|
end
|
83
83
|
|
84
84
|
def post_footers(widths)
|
85
|
-
result = '+'
|
85
|
+
result = +'+'
|
86
86
|
widths.each_value do |w|
|
87
87
|
result += '=' * (w + 2) + '+'
|
88
88
|
end
|
data/lib/fat_table/table.rb
CHANGED
@@ -105,7 +105,7 @@ module FatTable
|
|
105
105
|
unless heads.empty?
|
106
106
|
heads.each do |h|
|
107
107
|
if h.to_s.end_with?('!') || @tolerant_columns.include?(h)
|
108
|
-
@columns << Column.new(header: h.to_s.sub(/!\s*\z/, ''),
|
108
|
+
@columns << Column.new(header: h.to_s.sub(/!\s*\z/, ''), type: 'String')
|
109
109
|
else
|
110
110
|
@columns << Column.new(header: h)
|
111
111
|
end
|
@@ -120,7 +120,7 @@ module FatTable
|
|
120
120
|
# though FatTable::Table objects have no instance variables, a class that
|
121
121
|
# inherits from it might.
|
122
122
|
def empty_dup
|
123
|
-
|
123
|
+
dup.__empty!
|
124
124
|
end
|
125
125
|
|
126
126
|
def __empty!
|
@@ -779,7 +779,6 @@ module FatTable
|
|
779
779
|
last_key = nil
|
780
780
|
new_rows.each_with_index do |nrow, k|
|
781
781
|
new_tab << nrow
|
782
|
-
# key = nrow.fetch_values(*sort_heads)
|
783
782
|
key = nrow.fetch_values(*key_hash.keys)
|
784
783
|
new_tab.mark_boundary(k - 1) if last_key && key != last_key
|
785
784
|
last_key = key
|
@@ -1712,11 +1711,19 @@ module FatTable
|
|
1712
1711
|
end
|
1713
1712
|
|
1714
1713
|
# The <=> operator cannot handle nils without some help. Treat a nil as
|
1715
|
-
# smaller than any other value, but equal to other nils. The two keys are
|
1716
|
-
# compared with <=>.
|
1714
|
+
# smaller than any other value, but equal to other nils. The two keys are
|
1715
|
+
# assumed to be arrays of values to be compared with <=>. Since
|
1716
|
+
# tolerant_columns permit strings to be mixed in with columns of type
|
1717
|
+
# Numeric, DateTime, and Boolean, treat strings mixed with another type
|
1718
|
+
# the same as nils.
|
1717
1719
|
def compare_with_nils(key1, key2)
|
1718
1720
|
result = nil
|
1719
1721
|
key1.zip(key2) do |k1, k2|
|
1722
|
+
if k1.is_a?(String) && !k2.is_a?(String)
|
1723
|
+
k1 = nil
|
1724
|
+
elsif !k1.is_a?(String) && k2.is_a?(String)
|
1725
|
+
k2 = nil
|
1726
|
+
end
|
1720
1727
|
if k1.nil? && k2.nil?
|
1721
1728
|
result = 0
|
1722
1729
|
next
|
data/lib/fat_table/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fat_table
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel E. Doherty
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -332,7 +332,7 @@ licenses: []
|
|
332
332
|
metadata:
|
333
333
|
allowed_push_host: https://rubygems.org
|
334
334
|
yard.run: yri
|
335
|
-
post_install_message:
|
335
|
+
post_install_message:
|
336
336
|
rdoc_options: []
|
337
337
|
require_paths:
|
338
338
|
- lib
|
@@ -348,7 +348,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
348
348
|
version: '0'
|
349
349
|
requirements: []
|
350
350
|
rubygems_version: 3.3.3
|
351
|
-
signing_key:
|
351
|
+
signing_key:
|
352
352
|
specification_version: 4
|
353
353
|
summary: Provides tools for working with tables as a data type.
|
354
354
|
test_files: []
|