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.
@@ -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)|