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.
@@ -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 the
27
- # outer Hash is another Hash keyed on column names. The values of the inner
28
- # Hash are OpenStruct objects that contain the formatting instructions for
29
- # the location and column. For example, +format_at[:body][:shares].commas+
30
- # is set either true or false depending on whether the +:shares+ column in
31
- # the table body is to have grouping commas inserted in the output.
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 transformation methods on tables. They are never inherited by output
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
- # tab.footer(:shares)
159
+ # fmtr.footer(:shares)
158
160
  #
159
161
  # Change the label and sum the :price column as well
160
- # tab.footer('Grand Total', :shares, :price)
162
+ # fmtr.footer('Grand Total', :shares, :price)
161
163
  #
162
164
  # Average then show standard deviation of several columns
163
- # tab.footer.('Average', date: :avg, shares: :avg, price: :avg)
164
- # tab.footer.('Sigma', date: :dev, shares: :dev, price: :dev)
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
- # tab.footer.('Summary', :shares, date: :avg, price: :avg)
169
- def footer(label, *sum_cols, **agg_cols)
170
- label = label.to_s
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
- unless table.headers.include?(h)
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.each do |h, agg|
180
- unless table.headers.include?(h)
181
- raise UserError, "No '#{h}' column in table to #{agg} in the footer"
182
- end
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
- foot[h] = agg
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
- self
224
+ foot
188
225
  end
189
226
 
190
- # :category: Footers
227
+ # :category: Add Footers
191
228
 
192
- # Add a group footer to the table with a label given in the first parameter,
193
- # defaulting to 'Total'. After the label, you can given any number of
194
- # headers (as symbols) for columns to be summed, and then any number of hash
195
- # parameters for columns for with to apply an aggregate other than :sum. For
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
- # tab.gfooter.('Average', date: :avg, shares: :avg, price: :avg)
205
- # tab.gfooter.('Sigma', date: dev, shares: :dev, price: :dev)
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. tab.gfooter.('Summary', :shares, date: :avg, price: :avg)
209
- def gfooter(label, *sum_cols, **agg_cols)
210
- label = label.to_s
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
- unless table.headers.include?(h)
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.each do |h, agg|
220
- unless table.headers.include?(h)
221
- raise UserError, "No '#{h}' column in table for #{agg} group footer"
222
- end
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
- foot[h] = agg
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
- self
266
+ foot
228
267
  end
229
268
 
230
- # :category: Footers
269
+ # :category: Add Footers
231
270
 
232
- # Add table footer to sum the +cols+ given as header symbols.
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 table footer to average the +cols+ given as header symbols.
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 table footer to display the minimum value of the +cols+ given as
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 table footer to display the maximum value of the +cols+ given as
293
- # header symbols.
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 is no other instructions have been given.
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
- # Default formatting hash
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 = parse_string_fmt(fmts[:string])
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 = parse_nil_fmt(fmts[:nil]).first
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
- typ = location == :header ? :string : table.type(h).as_sym
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 parse_string_fmt(fmt, strict: true)
565
- format, fmt = parse_str_fmt(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 parse_str_fmt(fmt)
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 = parse_nil_fmt(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 parse_nil_fmt(fmt, _strict: true)
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 = parse_str_fmt(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 = parse_str_fmt(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[:date_fmt] = Regexp.last_match[:bdy]
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 = parse_str_fmt(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 = val.to_s(:currency, precision: prec, delimiter: delim,
919
+ result =
920
+ if istruct.post_digits < 0
921
+ val.to_s(:currency, delimiter: delim,
861
922
  unit: FatTable.currency_symbol)
862
- istruct.commas = false
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 the +table+ as either a string in the target format or as a Ruby
936
- # data structure if that is the target. In the latter case, all the cells
937
- # are converted to strings formatted according to the Formatter's formatting
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
- new_rows = []
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
- new_rows << nil if include_header_row? || grp_k.positive?
1065
- # Compute group body
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
- table.headers.each do |h|
1078
- grp_col[h] ||= Column.new(header: h)
1079
- grp_col[h] << row[h]
1143
+
1144
+ out_row = {}
1145
+ row.each_pair do |h, v|
1080
1146
  istruct = format_at[location][h]
1081
- new_row[h] = [row[h], format_cell(row[h], istruct,
1082
- decorate: decorate)]
1147
+ out_row[h] = [row[h], format_cell(row[h], istruct, decorate: decorate)]
1083
1148
  end
1084
- new_rows << [location, new_row]
1149
+ out_rows << [location, out_row]
1085
1150
  tbl_row_k += 1
1086
1151
  end
1087
- # Compute group footers
1152
+ # Format group footers
1088
1153
  gfooters.each_pair do |label, gfooter|
1089
- # Mark the beginning of a group footer
1090
- new_rows << nil
1091
- gfoot_row = {}
1092
- first_h = nil
1093
- grp_col.each_pair do |h, col|
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
- new_rows << [:gfooter, gfoot_row]
1160
+ out_rows << [:gfooter, gfoot_row]
1110
1161
  end
1111
1162
  end
1112
- new_rows
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
- new_rows = []
1120
- # Done with body, compute the table footers.
1121
- footers.each_pair do |label, footer|
1122
- # Mark the beginning of a footer
1123
- new_rows << nil
1124
- foot_row = {}
1125
- first_h = nil
1126
- table.columns.each do |col|
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
- new_rows << [:footer, foot_row]
1179
+ out_rows << [:footer, foot_row]
1146
1180
  end
1147
- new_rows
1181
+ out_rows
1148
1182
  end
1149
1183
 
1150
- # Return a hash of the maximum widths of all the given headers and rows.
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)|