fat_table 0.3.3 → 0.5.1
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 +4 -4
- data/.rspec +2 -1
- data/.rubocop.yml +3 -5
- data/README.org +1334 -457
- data/README.rdoc +1 -2
- data/TODO.org +17 -10
- data/examples/create_trans.sql +14 -0
- data/examples/quick.pdf +0 -0
- data/examples/quick.png +0 -0
- data/examples/quick.ppm +0 -0
- data/examples/quick.tex +8 -0
- data/examples/quick_small.png +0 -0
- data/examples/quicktable.tex +123 -0
- data/examples/trades.db +0 -0
- data/examples/trans.csv +13 -0
- data/fat_table.gemspec +2 -2
- data/lib/ext/array.rb +15 -0
- data/lib/fat_table/column.rb +71 -208
- data/lib/fat_table/convert.rb +173 -0
- data/lib/fat_table/evaluator.rb +7 -0
- data/lib/fat_table/footer.rb +228 -0
- data/lib/fat_table/formatters/formatter.rb +200 -163
- data/lib/fat_table/formatters/latex_formatter.rb +9 -7
- data/lib/fat_table/table.rb +229 -57
- data/lib/fat_table/version.rb +1 -1
- data/lib/fat_table.rb +5 -2
- data/md/README.md +1 -2
- metadata +30 -18
@@ -23,12 +23,13 @@ module FatTable
|
|
23
23
|
# specific Formatters.
|
24
24
|
attr_reader :options
|
25
25
|
|
26
|
-
# A Hash of Hashes with the outer Hash keyed on location. The value for
|
27
|
-
# outer Hash is
|
28
|
-
# Hash are OpenStruct objects that contain the formatting
|
29
|
-
# the location and column. For example,
|
30
|
-
# is set either true or false depending
|
31
|
-
# the table body is to have grouping
|
26
|
+
# A Hash of Hashes with the outer Hash keyed on location. The value for
|
27
|
+
# the outer Hash is an inner Hash keyed on column names. The values of
|
28
|
+
# the inner Hash are OpenStruct objects that contain the formatting
|
29
|
+
# instructions for the location and column. For example,
|
30
|
+
# +format_at[:body][:shares].commas+ is set either true or false depending
|
31
|
+
# on whether the +:shares+ column in the table body is to have grouping
|
32
|
+
# commas inserted in the output.
|
32
33
|
attr_reader :format_at
|
33
34
|
|
34
35
|
# A Hash of the table-wide footers to be added to the output. The key is a
|
@@ -102,6 +103,7 @@ module FatTable
|
|
102
103
|
@options = options
|
103
104
|
@footers = {}
|
104
105
|
@gfooters = {}
|
106
|
+
|
105
107
|
# Formatting instructions for various "locations" within the Table, as a
|
106
108
|
# hash of hashes. The outer hash is keyed on the location, and each inner
|
107
109
|
# hash is keyed on either a column sym or a type sym, :string, :numeric,
|
@@ -120,14 +122,14 @@ module FatTable
|
|
120
122
|
yield self if block_given?
|
121
123
|
end
|
122
124
|
|
123
|
-
# :category: Footers
|
125
|
+
# :category: Add Footers
|
124
126
|
|
125
127
|
############################################################################
|
126
|
-
# Footer methods
|
128
|
+
# Add Footer methods
|
127
129
|
#
|
128
130
|
# A Table may have any number of footers and any number of group footers.
|
129
131
|
# Footers are not part of the table's data and never participate in any of
|
130
|
-
# the
|
132
|
+
# the operation methods on tables. They are never inherited by output
|
131
133
|
# tables from input tables in any of the transformation methods.
|
132
134
|
#
|
133
135
|
# When output, a table footer will appear at the bottom of the table, and a
|
@@ -154,46 +156,81 @@ module FatTable
|
|
154
156
|
# For example, these are valid footer definitions.
|
155
157
|
#
|
156
158
|
# Just sum the shares column with a label of 'Total'
|
157
|
-
#
|
159
|
+
# fmtr.footer(:shares)
|
158
160
|
#
|
159
161
|
# Change the label and sum the :price column as well
|
160
|
-
#
|
162
|
+
# fmtr.footer('Grand Total', :shares, :price)
|
161
163
|
#
|
162
164
|
# Average then show standard deviation of several columns
|
163
|
-
#
|
164
|
-
#
|
165
|
+
# fmtr.footer.('Average', date: :avg, shares: :avg, price: :avg)
|
166
|
+
# fmtr.footer.('Sigma', date: :dev, shares: :dev, price: :dev)
|
165
167
|
#
|
166
168
|
# Do some sums and some other aggregates: sum shares, average date and
|
167
169
|
# price.
|
168
|
-
#
|
169
|
-
def footer(label, *sum_cols, **agg_cols)
|
170
|
-
|
171
|
-
foot = {}
|
170
|
+
# fmtr.footer.('Summary', :shares, date: :avg, price: :avg)
|
171
|
+
def footer(label, label_col = nil, *sum_cols, **agg_cols)
|
172
|
+
foot = Footer.new(label, table, label_col: label_col)
|
172
173
|
sum_cols.each do |h|
|
173
|
-
|
174
|
-
raise UserError, "No '#{h}' column in table to sum in the footer"
|
175
|
-
end
|
176
|
-
|
177
|
-
foot[h] = :sum
|
174
|
+
foot.add_value(h, :sum)
|
178
175
|
end
|
179
|
-
agg_cols.
|
180
|
-
|
181
|
-
|
182
|
-
|
176
|
+
agg_cols.each_pair do |h, agg|
|
177
|
+
foot.add_value(h, agg)
|
178
|
+
end
|
179
|
+
@footers[label] = foot
|
180
|
+
foot
|
181
|
+
end
|
183
182
|
|
184
|
-
|
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
|
+
#
|
194
|
+
# (2) an arbitrary value of one of the four supported types: Boolean,
|
195
|
+
# DateTime, Numeric, or String: true, false, 3.14159, 'Total debits', or a
|
196
|
+
# string that is convertible into one of these types by the usual methods
|
197
|
+
# used in contructing a table,
|
198
|
+
#
|
199
|
+
# Examples:
|
200
|
+
#
|
201
|
+
# Put the label in the :dickens column of the footer and the maximum value
|
202
|
+
# from the :alpha column in the :alpha column of the footer.
|
203
|
+
#
|
204
|
+
# fmtr.foot('Best', :dickens, alpha: :max)
|
205
|
+
#
|
206
|
+
# Put the label 'Today' in the first column of the footer and today's date
|
207
|
+
# in the :beta column.
|
208
|
+
#
|
209
|
+
# fmtr.foot('Today', beta: Date.today)
|
210
|
+
#
|
211
|
+
# Put the label 'Best' in the :dickens column of the footer and the string
|
212
|
+
# 'Tale of Two Cities' in the :alpha column of the footer. Since it can't
|
213
|
+
# be interpreted as Boolean, Numeric, or DateTime, it is placed in the
|
214
|
+
# footer literally.
|
215
|
+
#
|
216
|
+
# fmtr.foot('Best', :dickens, alpha: 'A Tale of Two Cities')
|
217
|
+
#
|
218
|
+
def foot(label, label_col = nil, **agg_cols)
|
219
|
+
foot = Footer.new(label, table, label_col: label_col)
|
220
|
+
agg_cols.each_pair do |h, agg|
|
221
|
+
foot.add_value(h, agg)
|
185
222
|
end
|
186
223
|
@footers[label] = foot
|
187
|
-
|
224
|
+
foot
|
188
225
|
end
|
189
226
|
|
190
|
-
# :category: Footers
|
227
|
+
# :category: Add Footers
|
191
228
|
|
192
|
-
# Add a group footer to the
|
193
|
-
# defaulting to 'Total'. After the label, you can given any
|
194
|
-
# headers (as symbols) for columns to be summed, and then any
|
195
|
-
# parameters for columns for with to apply an aggregate
|
196
|
-
# example, these are valid gfooter definitions.
|
229
|
+
# Add a group footer to the output with a label given in the first
|
230
|
+
# parameter, defaulting to 'Total'. After the label, you can given any
|
231
|
+
# number of headers (as symbols) for columns to be summed, and then any
|
232
|
+
# number of hash parameters for columns for with to apply an aggregate
|
233
|
+
# other than :sum. For example, these are valid gfooter definitions.
|
197
234
|
#
|
198
235
|
# Just sum the shares column with a label of 'Total' tab.gfooter(:shares)
|
199
236
|
#
|
@@ -201,49 +238,51 @@ module FatTable
|
|
201
238
|
# :shares, :price)
|
202
239
|
#
|
203
240
|
# Average then show standard deviation of several columns
|
204
|
-
#
|
205
|
-
#
|
241
|
+
# fmtr.gfooter.('Average', date: :avg, shares: :avg, price: :avg)
|
242
|
+
# fmtr.gfooter.('Sigma', date: dev, shares: :dev, price: :dev)
|
206
243
|
#
|
207
244
|
# Do some sums and some other aggregates: sum shares, average date and
|
208
|
-
# price.
|
209
|
-
def gfooter(label, *sum_cols, **agg_cols)
|
210
|
-
|
211
|
-
foot = {}
|
245
|
+
# price. fmtr.gfooter.('Summary', :shares, date: :avg, price: :avg)
|
246
|
+
def gfooter(label, label_col = nil, *sum_cols, **agg_cols)
|
247
|
+
foot = Footer.new(label, table, label_col: label_col, group: true)
|
212
248
|
sum_cols.each do |h|
|
213
|
-
|
214
|
-
raise UserError, "No '#{h}' column in table for group sum footer"
|
215
|
-
end
|
216
|
-
|
217
|
-
foot[h] = :sum
|
249
|
+
foot.add_value(h, :sum)
|
218
250
|
end
|
219
|
-
agg_cols.
|
220
|
-
|
221
|
-
|
222
|
-
|
251
|
+
agg_cols.each_pair do |h, agg|
|
252
|
+
foot.add_value(h, agg)
|
253
|
+
end
|
254
|
+
@gfooters[label] = foot
|
255
|
+
foot
|
256
|
+
end
|
223
257
|
|
224
|
-
|
258
|
+
# Add a group footer to the formatted output. This method has the same
|
259
|
+
# usage as the #foot method, but it adds group footers.
|
260
|
+
def gfoot(label, label_col = nil, **agg_cols)
|
261
|
+
foot = Footer.new(label, table, label_col: label_col, group: true)
|
262
|
+
agg_cols.each_pair do |h, agg|
|
263
|
+
foot.add_value(h, agg)
|
225
264
|
end
|
226
265
|
@gfooters[label] = foot
|
227
|
-
|
266
|
+
foot
|
228
267
|
end
|
229
268
|
|
230
|
-
# :category: Footers
|
269
|
+
# :category: Add Footers
|
231
270
|
|
232
|
-
# Add
|
271
|
+
# Add a footer to sum the +cols+ given as header symbols.
|
233
272
|
def sum_footer(*cols)
|
234
|
-
footer('Total', *cols)
|
273
|
+
footer('Total', nil, *cols)
|
235
274
|
end
|
236
275
|
|
237
|
-
# :category: Footers
|
276
|
+
# :category: Add Footers
|
238
277
|
|
239
|
-
# Add group footer to sum the +cols+ given as header symbols.
|
278
|
+
# Add a group footer to sum the +cols+ given as header symbols.
|
240
279
|
def sum_gfooter(*cols)
|
241
|
-
gfooter('Group Total', *cols)
|
280
|
+
gfooter('Group Total', nil, *cols)
|
242
281
|
end
|
243
282
|
|
244
|
-
# :category: Footers
|
283
|
+
# :category: Add Footers
|
245
284
|
|
246
|
-
# Add
|
285
|
+
# Add a footer to average the +cols+ given as header symbols.
|
247
286
|
def avg_footer(*cols)
|
248
287
|
hsh = {}
|
249
288
|
cols.each do |c|
|
@@ -252,9 +291,9 @@ module FatTable
|
|
252
291
|
footer('Average', **hsh)
|
253
292
|
end
|
254
293
|
|
255
|
-
# :category: Footers
|
294
|
+
# :category: Add Footers
|
256
295
|
|
257
|
-
# Add group footer to average the +cols+ given as header symbols.
|
296
|
+
# Add a group footer to average the +cols+ given as header symbols.
|
258
297
|
def avg_gfooter(*cols)
|
259
298
|
hsh = {}
|
260
299
|
cols.each do |c|
|
@@ -263,9 +302,9 @@ module FatTable
|
|
263
302
|
gfooter('Group Average', **hsh)
|
264
303
|
end
|
265
304
|
|
266
|
-
# :category: Footers
|
305
|
+
# :category: Add Footers
|
267
306
|
|
268
|
-
# Add
|
307
|
+
# Add a footer to display the minimum value of the +cols+ given as
|
269
308
|
# header symbols.
|
270
309
|
def min_footer(*cols)
|
271
310
|
hsh = {}
|
@@ -275,9 +314,9 @@ module FatTable
|
|
275
314
|
footer('Minimum', **hsh)
|
276
315
|
end
|
277
316
|
|
278
|
-
# :category: Footers
|
317
|
+
# :category: Add Footers
|
279
318
|
|
280
|
-
# Add group footer to display the minimum value of the +cols+ given as
|
319
|
+
# Add a group footer to display the minimum value of the +cols+ given as
|
281
320
|
# header symbols.
|
282
321
|
def min_gfooter(*cols)
|
283
322
|
hsh = {}
|
@@ -287,10 +326,10 @@ module FatTable
|
|
287
326
|
gfooter('Group Minimum', **hsh)
|
288
327
|
end
|
289
328
|
|
290
|
-
# :category: Footers
|
329
|
+
# :category: Add Footers
|
291
330
|
|
292
|
-
# Add
|
293
|
-
#
|
331
|
+
# Add a footer to display the maximum value of the +cols+ given as header
|
332
|
+
# symbols.
|
294
333
|
def max_footer(*cols)
|
295
334
|
hsh = {}
|
296
335
|
cols.each do |c|
|
@@ -299,9 +338,9 @@ module FatTable
|
|
299
338
|
footer('Maximum', **hsh)
|
300
339
|
end
|
301
340
|
|
302
|
-
# :category: Footers
|
341
|
+
# :category: Add Footers
|
303
342
|
|
304
|
-
# Add group footer to display the maximum value of the +cols+ given as
|
343
|
+
# Add a group footer to display the maximum value of the +cols+ given as
|
305
344
|
# header symbols.
|
306
345
|
def max_gfooter(*cols)
|
307
346
|
hsh = {}
|
@@ -320,7 +359,7 @@ module FatTable
|
|
320
359
|
# columns by using the column head as a key and the value as the format
|
321
360
|
# instructions. In addition, the keys, :numeric, :string, :datetime,
|
322
361
|
# :boolean, and :nil, can be used to specify the default format instructions
|
323
|
-
# for columns of the given type
|
362
|
+
# for columns of the given type if no other instructions have been given.
|
324
363
|
#
|
325
364
|
# Formatting instructions are strings, and what are valid strings depend on
|
326
365
|
# the type of the column:
|
@@ -418,6 +457,11 @@ module FatTable
|
|
418
457
|
# instructions valid for strings are available:
|
419
458
|
#
|
420
459
|
# \n\[niltext\]:: render a nil item with the given text.
|
460
|
+
|
461
|
+
# Set the formatting for all 6 "location" specifiers for the table: (1)
|
462
|
+
# the headline, :header, (2) the first row of the body, :bfirst, (3) the
|
463
|
+
# first row of each group, :gfirst, (4) all of the body rows, :body, (5)
|
464
|
+
# the footer rows, :footer, and (6) the group footer rows, :gfooter.
|
421
465
|
def format(**fmts)
|
422
466
|
%i[header bfirst gfirst body footer gfooter].each do |loc|
|
423
467
|
format_for(loc, **fmts)
|
@@ -469,11 +513,22 @@ module FatTable
|
|
469
513
|
# cells have the type of the column to which they belong, including all
|
470
514
|
# cells in group or table footers. See ::format for details on formatting
|
471
515
|
# directives.
|
516
|
+
#
|
517
|
+
# Set the formatting for the given location.
|
472
518
|
def format_for(location, **fmts)
|
473
519
|
unless LOCATIONS.include?(location)
|
474
520
|
raise UserError, "unknown format location '#{location}'"
|
475
521
|
end
|
476
522
|
|
523
|
+
fmts = fmts.transform_keys do |k|
|
524
|
+
if k == :nilclass
|
525
|
+
:nil
|
526
|
+
elsif k == :date
|
527
|
+
:datetime
|
528
|
+
else
|
529
|
+
k
|
530
|
+
end
|
531
|
+
end
|
477
532
|
valid_keys = table.headers + %i[string numeric datetime boolean nil]
|
478
533
|
invalid_keys = (fmts.keys - valid_keys).uniq
|
479
534
|
unless invalid_keys.empty?
|
@@ -483,7 +538,9 @@ module FatTable
|
|
483
538
|
|
484
539
|
@format_at[location] ||= {}
|
485
540
|
table.headers.each do |h|
|
486
|
-
#
|
541
|
+
# Build the inner hash of formatting instructions for this column h,
|
542
|
+
# beginning with the default formatting hash or any existing inner
|
543
|
+
# hash.
|
487
544
|
format_h =
|
488
545
|
if format_at[location][h].empty?
|
489
546
|
default_format.dup
|
@@ -491,37 +548,40 @@ module FatTable
|
|
491
548
|
format_at[location][h].to_h
|
492
549
|
end
|
493
550
|
|
551
|
+
# Merge in string and nil formatting for this column h, but not in
|
552
|
+
# header location. Header is always typed a string, so it will get
|
553
|
+
# formatted in type-based formatting below. And headers are never nil.
|
494
554
|
unless location == :header
|
495
|
-
# Merge in string and nil formatting, but not in header. Header is
|
496
|
-
# always typed a string, so it will get formatted in type-based
|
497
|
-
# formatting below. And headers are never nil.
|
498
555
|
if fmts.key?(:string)
|
499
|
-
typ_fmt =
|
556
|
+
typ_fmt = parse_fmt_string(fmts[:string])
|
500
557
|
format_h = format_h.merge(typ_fmt)
|
501
558
|
end
|
502
559
|
if fmts.key?(:nil)
|
503
|
-
typ_fmt =
|
560
|
+
typ_fmt = parse_nilclass_fmt(fmts[:nil], strict: false).first
|
504
561
|
format_h = format_h.merge(typ_fmt)
|
505
562
|
end
|
506
563
|
end
|
507
|
-
|
564
|
+
|
565
|
+
# Merge in formatting for column h based on the column type, or based
|
566
|
+
# on the string type for the header location.
|
567
|
+
typ = (location == :header ? :string : table.type(h).as_sym)
|
508
568
|
parse_typ_method_name = 'parse_' + typ.to_s + '_fmt'
|
509
569
|
if fmts.key?(typ)
|
510
570
|
# Merge in type-based formatting
|
511
|
-
typ_fmt = send(parse_typ_method_name, fmts[typ])
|
571
|
+
typ_fmt = send(parse_typ_method_name, fmts[typ]).first
|
512
572
|
format_h = format_h.merge(typ_fmt)
|
513
573
|
end
|
514
574
|
if fmts[h]
|
515
575
|
# Merge in column formatting
|
516
576
|
col_fmt = send(parse_typ_method_name, fmts[h],
|
517
|
-
strict: location != :header)
|
577
|
+
strict: location != :header).first
|
518
578
|
format_h = format_h.merge(col_fmt)
|
519
579
|
end
|
520
580
|
|
581
|
+
# Copy :body formatting for column h to :bfirst and :gfirst if they
|
582
|
+
# still have the default formatting. Can be overridden with a
|
583
|
+
# format_for call with those locations.
|
521
584
|
if location == :body
|
522
|
-
# Copy :body formatting for column h to :bfirst and :gfirst if they
|
523
|
-
# still have the default formatting. Can be overridden with a
|
524
|
-
# format_for call with those locations.
|
525
585
|
format_h.each_pair do |k, v|
|
526
586
|
if format_at[:bfirst][h].send(k) == default_format[k]
|
527
587
|
format_at[:bfirst][h].send("#{k}=", v)
|
@@ -561,8 +621,8 @@ module FatTable
|
|
561
621
|
# string fmt. Raise an error if it contains invalid formatting instructions.
|
562
622
|
# If fmt contains conflicting instructions, say C and L, there is no
|
563
623
|
# guarantee which will win, but it will not be considered an error to do so.
|
564
|
-
def
|
565
|
-
format, fmt =
|
624
|
+
def parse_fmt_string(fmt, strict: true)
|
625
|
+
format, fmt = parse_string_fmt(fmt)
|
566
626
|
unless fmt.blank? || !strict
|
567
627
|
raise UserError, "unrecognized string formatting instructions '#{fmt}'"
|
568
628
|
end
|
@@ -574,7 +634,7 @@ module FatTable
|
|
574
634
|
# of the instructions and the unconsumed part of the instruction string.
|
575
635
|
# This is called to cull string-based instructions from a formatting string
|
576
636
|
# intended for other types, such as numeric, etc.
|
577
|
-
def
|
637
|
+
def parse_string_fmt(fmt, strict: true)
|
578
638
|
# We parse the more complex formatting constructs first, and after each
|
579
639
|
# parse, we remove the matched construct from fmt. At the end, any
|
580
640
|
# remaining characters in fmt should be invalid.
|
@@ -587,7 +647,7 @@ module FatTable
|
|
587
647
|
fmt = fmt.sub($&, '')
|
588
648
|
end
|
589
649
|
# Nil formatting can apply to strings as well
|
590
|
-
nil_hash, fmt =
|
650
|
+
nil_hash, fmt = parse_nilclass_fmt(fmt)
|
591
651
|
fmt_hash = fmt_hash.merge(nil_hash)
|
592
652
|
if fmt =~ /u/
|
593
653
|
fmt_hash[:case] = :lower
|
@@ -636,11 +696,11 @@ module FatTable
|
|
636
696
|
# instructions and the unconsumed part of the instruction string. This is
|
637
697
|
# called to cull nil-based instructions from a formatting string intended
|
638
698
|
# for other types, such as numeric, etc.
|
639
|
-
def
|
699
|
+
def parse_nilclass_fmt(fmt, strict: true)
|
640
700
|
# We parse the more complex formatting constructs first, and after each
|
641
701
|
# parse, we remove the matched construct from fmt. At the end, any
|
642
702
|
# remaining characters in fmt should be invalid.
|
643
|
-
fmt_hash = {}
|
703
|
+
fmt_hash, fmt = strict ? [{}, fmt] : parse_string_fmt(fmt)
|
644
704
|
if fmt =~ /n\[\s*(?<bdy>[^\]]*)\s*\]/
|
645
705
|
fmt_hash[:nil_text] = Regexp.last_match[:bdy].clean
|
646
706
|
fmt = fmt.sub(Regexp.last_match[0], '')
|
@@ -656,7 +716,7 @@ module FatTable
|
|
656
716
|
# We parse the more complex formatting constructs first, and after each
|
657
717
|
# parse, we remove the matched construct from fmt. At the end, any
|
658
718
|
# remaining characters in fmt should be invalid.
|
659
|
-
fmt_hash, fmt =
|
719
|
+
fmt_hash, fmt = parse_string_fmt(fmt)
|
660
720
|
if fmt =~ /(?<pre>\d+).(?<post>\d+)/
|
661
721
|
fmt_hash[:pre_digits] = Regexp.last_match[:pre].to_i
|
662
722
|
fmt_hash[:post_digits] = Regexp.last_match[:post].to_i
|
@@ -678,7 +738,7 @@ module FatTable
|
|
678
738
|
raise UserError, "unrecognized numeric formatting instructions '#{fmt}'"
|
679
739
|
end
|
680
740
|
|
681
|
-
fmt_hash
|
741
|
+
[fmt_hash, fmt]
|
682
742
|
end
|
683
743
|
|
684
744
|
# Return a hash that reflects the datetime or string formatting instructions
|
@@ -689,20 +749,20 @@ module FatTable
|
|
689
749
|
# We parse the more complex formatting constructs first, and after each
|
690
750
|
# parse, we remove the matched construct from fmt. At the end, any
|
691
751
|
# remaining characters in fmt should be invalid.
|
692
|
-
fmt_hash, fmt =
|
752
|
+
fmt_hash, fmt = parse_string_fmt(fmt)
|
693
753
|
if fmt =~ /d\[(?<bdy>[^\]]*)\]/
|
694
754
|
fmt_hash[:date_fmt] = Regexp.last_match[:bdy]
|
695
755
|
fmt = fmt.sub(Regexp.last_match[0], '')
|
696
756
|
end
|
697
757
|
if fmt =~ /D\[(?<bdy>[^\]]*)\]/
|
698
|
-
fmt_hash[:
|
758
|
+
fmt_hash[:datetime_fmt] = Regexp.last_match[:bdy]
|
699
759
|
fmt = fmt.sub(Regexp.last_match[0], '')
|
700
760
|
end
|
701
761
|
unless fmt.blank? || !strict
|
702
762
|
msg = "unrecognized datetime formatting instructions '#{fmt}'"
|
703
763
|
raise UserError, msg
|
704
764
|
end
|
705
|
-
fmt_hash
|
765
|
+
[fmt_hash, fmt]
|
706
766
|
end
|
707
767
|
|
708
768
|
# Return a hash that reflects the boolean or string formatting instructions
|
@@ -730,7 +790,7 @@ module FatTable
|
|
730
790
|
fmt_hash[:false_bgcolor] = fbg unless fbg.blank?
|
731
791
|
fmt = fmt.sub(Regexp.last_match[0], '')
|
732
792
|
end
|
733
|
-
str_fmt_hash, fmt =
|
793
|
+
str_fmt_hash, fmt = parse_string_fmt(fmt)
|
734
794
|
fmt_hash = fmt_hash.merge(str_fmt_hash)
|
735
795
|
if fmt =~ /Y/
|
736
796
|
fmt_hash[:true_text] = 'Y'
|
@@ -751,7 +811,7 @@ module FatTable
|
|
751
811
|
raise UserError, "unrecognized boolean formatting instructions '#{fmt}'"
|
752
812
|
end
|
753
813
|
|
754
|
-
fmt_hash
|
814
|
+
[fmt_hash, fmt]
|
755
815
|
end
|
756
816
|
|
757
817
|
############################################################################
|
@@ -855,11 +915,16 @@ module FatTable
|
|
855
915
|
result = val.secs_to_hms
|
856
916
|
istruct.commas = false
|
857
917
|
elsif istruct.currency
|
858
|
-
prec = istruct.post_digits.zero? ? 2 : istruct.post_digits
|
859
918
|
delim = istruct.commas ? ',' : ''
|
860
|
-
result =
|
919
|
+
result =
|
920
|
+
if istruct.post_digits < 0
|
921
|
+
val.to_s(:currency, delimiter: delim,
|
861
922
|
unit: FatTable.currency_symbol)
|
862
|
-
|
923
|
+
else
|
924
|
+
val.to_s(:currency, precision: istruct.post_digits, delimiter: delim,
|
925
|
+
unit: FatTable.currency_symbol)
|
926
|
+
end
|
927
|
+
# istruct.commas = false
|
863
928
|
elsif istruct.pre_digits.positive?
|
864
929
|
if val.whole?
|
865
930
|
# No fractional part, ignore post_digits
|
@@ -932,9 +997,10 @@ module FatTable
|
|
932
997
|
|
933
998
|
# :category: Output
|
934
999
|
|
935
|
-
# Return
|
936
|
-
#
|
937
|
-
#
|
1000
|
+
# Return a representation of the +table+, along with all footers and group
|
1001
|
+
# footers, as either a string in the target format or as a Ruby data
|
1002
|
+
# structure if that is the target. In the latter case, all the cells are
|
1003
|
+
# converted to strings formatted according to the Formatter's formatting
|
938
1004
|
# directives given in Formatter.format_for or Formatter.format.
|
939
1005
|
def output
|
940
1006
|
# This results in a hash of two-element arrays. The key is the header and
|
@@ -1056,16 +1122,16 @@ module FatTable
|
|
1056
1122
|
# Don't decorate if this Formatter calls for alignment. It will be done
|
1057
1123
|
# in the second pass.
|
1058
1124
|
decorate = !aligned?
|
1059
|
-
|
1125
|
+
out_rows = []
|
1060
1126
|
tbl_row_k = 0
|
1061
1127
|
table.groups.each_with_index do |grp, grp_k|
|
1128
|
+
# NB: grp is an array of hashes, one for each row in the group.
|
1129
|
+
#
|
1062
1130
|
# Mark the beginning of a group if this is the first group after the
|
1063
1131
|
# header or the second or later group.
|
1064
|
-
|
1065
|
-
#
|
1066
|
-
grp_col = {}
|
1132
|
+
out_rows << nil if include_header_row? || grp_k.positive?
|
1133
|
+
# Format group body rows
|
1067
1134
|
grp.each_with_index do |row, grp_row_k|
|
1068
|
-
new_row = {}
|
1069
1135
|
location =
|
1070
1136
|
if tbl_row_k.zero?
|
1071
1137
|
:bfirst
|
@@ -1074,80 +1140,51 @@ module FatTable
|
|
1074
1140
|
else
|
1075
1141
|
:body
|
1076
1142
|
end
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1143
|
+
|
1144
|
+
out_row = {}
|
1145
|
+
row.each_pair do |h, v|
|
1080
1146
|
istruct = format_at[location][h]
|
1081
|
-
|
1082
|
-
decorate: decorate)]
|
1147
|
+
out_row[h] = [row[h], format_cell(row[h], istruct, decorate: decorate)]
|
1083
1148
|
end
|
1084
|
-
|
1149
|
+
out_rows << [location, out_row]
|
1085
1150
|
tbl_row_k += 1
|
1086
1151
|
end
|
1087
|
-
#
|
1152
|
+
# Format group footers
|
1088
1153
|
gfooters.each_pair do |label, gfooter|
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
1093
|
-
|
1094
|
-
first_h ||= h
|
1095
|
-
gfoot_row[h] =
|
1096
|
-
if gfooter[h]
|
1097
|
-
val = col.send(gfooter[h])
|
1098
|
-
istruct = format_at[:gfooter][h]
|
1099
|
-
[val, format_cell(val, istruct, decorate: decorate)]
|
1100
|
-
else
|
1101
|
-
[nil, '']
|
1102
|
-
end
|
1103
|
-
end
|
1104
|
-
if gfoot_row[first_h].last.blank?
|
1105
|
-
istruct = format_at[:gfooter][first_h]
|
1106
|
-
gfoot_row[first_h] =
|
1107
|
-
[label, format_cell(label, istruct, decorate: decorate)]
|
1154
|
+
out_rows << nil
|
1155
|
+
gfoot_row = Hash.new([nil, ''])
|
1156
|
+
gfooter.to_h(grp_k).each_pair do |h, v|
|
1157
|
+
istruct = format_at[:gfooter][h]
|
1158
|
+
gfoot_row[h] = [v, format_cell(v, istruct, decorate: decorate)]
|
1108
1159
|
end
|
1109
|
-
|
1160
|
+
out_rows << [:gfooter, gfoot_row]
|
1110
1161
|
end
|
1111
1162
|
end
|
1112
|
-
|
1163
|
+
out_rows
|
1113
1164
|
end
|
1114
1165
|
|
1115
1166
|
def build_formatted_footers
|
1116
1167
|
# Don't decorate if this Formatter calls for alignment. It will be done
|
1117
1168
|
# in the second pass.
|
1118
1169
|
decorate = !aligned?
|
1119
|
-
|
1120
|
-
|
1121
|
-
footers.each_pair do |label,
|
1122
|
-
|
1123
|
-
|
1124
|
-
|
1125
|
-
|
1126
|
-
|
1127
|
-
h = col.header
|
1128
|
-
first_h ||= h
|
1129
|
-
foot_row[h] =
|
1130
|
-
if footer[h]
|
1131
|
-
val = col.send(footer[h])
|
1132
|
-
istruct = format_at[:footer][h]
|
1133
|
-
[val, format_cell(val, istruct, decorate: decorate)]
|
1134
|
-
else
|
1135
|
-
[nil, '']
|
1136
|
-
end
|
1137
|
-
end
|
1138
|
-
# Put the label in the first column of footer unless it has been
|
1139
|
-
# formatted as part of footer.
|
1140
|
-
if foot_row[first_h].last.blank?
|
1141
|
-
istruct = format_at[:footer][first_h]
|
1142
|
-
foot_row[first_h] =
|
1143
|
-
[label, format_cell(label, istruct, decorate: decorate)]
|
1170
|
+
out_rows = []
|
1171
|
+
|
1172
|
+
footers.each_pair do |label, foot|
|
1173
|
+
out_rows << nil
|
1174
|
+
foot_row = Hash.new([nil, ''])
|
1175
|
+
foot.to_h.each_pair do |h, v|
|
1176
|
+
istruct = format_at[:gfooter][h]
|
1177
|
+
foot_row[h] = [v, format_cell(v, istruct, decorate: decorate)]
|
1144
1178
|
end
|
1145
|
-
|
1179
|
+
out_rows << [:footer, foot_row]
|
1146
1180
|
end
|
1147
|
-
|
1181
|
+
out_rows
|
1148
1182
|
end
|
1149
1183
|
|
1150
|
-
# Return a hash of the maximum widths of all the given headers and
|
1184
|
+
# Return a hash of the maximum widths of all the given headers and out
|
1185
|
+
# rows, represented as hashes keyed by column symbol, h, and with a value
|
1186
|
+
# being a 2-element array of the raw value for the cell and the formatted
|
1187
|
+
# string representation of the cell.
|
1151
1188
|
def width_map(formatted_headers, rows)
|
1152
1189
|
widths = {}
|
1153
1190
|
formatted_headers.each_pair do |h, (_v, fmt_v)|
|