fat_table 0.3.3 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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)|
|