Class: FatTable::Formatter

Inherits:
Object
  • Object
show all
Defined in:
lib/fat_table/formatters/formatter.rb

Overview

A Formatter is for use in Table output routines, and provides methods for adding group and table footers to the output and instructions for how the table's cells ought to be formatted. The goal is to make subclasses of this class handle different output targets, such as aoa for an Array of Arrays (useful in Emacs org-mode code blocks), ANSI terminals, LaTeX, plain text, org mode table text, and so forth. Many of the formatting options, such as color, will be no-ops for some output targets, such as text, but will be valid nonetheless. Thus, a Formatter subclass should provide the best implementation for each formatting request available for the target. This base class will format output as pipe-separated values, but implementations provided by subclasses will override these for different output targets.

Constant Summary collapse

LOCATIONS =

Valid locations in a Table as an array of symbols.

%i[header body bfirst gfirst gfooter footer].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(table = Table.new, **options) {|_self| ... } ⇒ Formatter

Return a new Formatter for the given +table+ which must be of the class FatTable::Table. The +options+ hash can specify variants for the output for specific subclasses of Formatter. This base class outputs the +table+ as a string in the pipe-separated form, which is much like CSV except that it uses the ASCII pipe symbol +|+ to separate values rather than the comma, and therefore does not bother to quote strings since it assumes they will not contain any pipes. A new Formatter provides default formatting for all the cells in the table. If you give a block, the new Formatter is yielded to the block so that methods for formatting and adding footers can be called on it.

Yields:

  • (_self)

Yield Parameters:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/fat_table/formatters/formatter.rb', line 100

def initialize(table = Table.new, **options)
  unless table&.is_a?(Table)
    raise UserError, 'must initialize Formatter with a Table'
  end

  @table = table
  @options = options
  @footers = {}
  @gfooters = {}

  # Formatting instructions for various "locations" within the Table, as a
  # hash of hashes. The outer hash is keyed on the location, and each inner
  # hash is keyed on either a column sym or a type sym, :string, :numeric,
  # :datetime, :boolean, or :nil. The value of the inner hashes are
  # OpenStruct structs.
  @format_at = {}
  %i[header bfirst gfirst body footer gfooter].each do |loc|
    @format_at[loc] = {}
    table.headers.each do |h|
      fmt_hash = self.class.default_format
      fmt_hash[:_h] = h
      fmt_hash[:_location] = loc
      format_at[loc][h] = OpenStruct.new(fmt_hash)
    end
  end
  yield self if block_given?
end

Instance Attribute Details

#footersObject (readonly)

A Hash of the table-wide footers to be added to the output. The key is a string that is to serve as the label for the footer and inserted in the first column of the footer if that column is otherwise not populated with footer content. The value is Hash in which the keys are column symbols and the values are symbols for the aggregate method to be applied to the column to provide a value in the footer for that column. Thus, +footers['Total'][:shares]+ might be set to +:sum+ to indicate that the +:shares+ column is to be summed in the footer labeled 'Total'.



43
44
45
# File 'lib/fat_table/formatters/formatter.rb', line 43

def footers
  @footers
end

#format_atObject (readonly)

A Hash of Hashes with the outer Hash keyed on location. The value for the outer Hash is an inner Hash keyed on column names. The values of the inner Hash are OpenStruct objects that contain the formatting instructions for the location and column. For example, +format_at[:body][:shares].commas+ is set either true or false depending on whether the +:shares+ column in the table body is to have grouping commas inserted in the output.



33
34
35
# File 'lib/fat_table/formatters/formatter.rb', line 33

def format_at
  @format_at
end

#gfootersObject (readonly)

A Hash of the group footers to be added to the output. The key is a string that is to serve as the label for the footer and inserted in the first column of the footer if that column is otherwise not populated with group footer content. The value is Hash in which the keys are column symbols and the values are symbols for the aggregate method to be applied to the group's column to provide a value in the group footer for that column. Thus, +gfooters['Average'][:shares]+ might be set to +:avg+ to indicate that the +:shares+ column is to be averaged in the group footer labeled 'Average'.



54
55
56
# File 'lib/fat_table/formatters/formatter.rb', line 54

def gfooters
  @gfooters
end

#optionsObject (readonly)

Options given to the Formatter constructor that allow variants for specific Formatters.



24
25
26
# File 'lib/fat_table/formatters/formatter.rb', line 24

def options
  @options
end

#tableObject (readonly)

The table that is the subject of the Formatter.



20
21
22
# File 'lib/fat_table/formatters/formatter.rb', line 20

def table
  @table
end

Class Method Details

.default_formatObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/fat_table/formatters/formatter.rb', line 59

def self.default_format
  {
    nil_text: '',
    case: :none,
    alignment: :left,
    bold: false,
    italic: false,
    color: 'none',
    bgcolor: 'none',
    hms: false,
    pre_digits: 0,
    post_digits: 0,
    commas: false,
    currency: false,
    datetime_fmt: '%F %H:%M:%S',
    date_fmt: '%F',
    true_text: 'T',
    false_text: 'F',
    true_color: 'none',
    true_bgcolor: 'none',
    false_color: 'none',
    false_bgcolor: 'none',
    underline: false,
    blink: false,
    _h: nil,
    _location: nil
  }
end

Instance Method Details

Add a footer to average the +cols+ given as header symbols.



405
406
407
408
409
410
411
# File 'lib/fat_table/formatters/formatter.rb', line 405

def avg_footer(*cols)
  hsh = {}
  cols.each do |c|
    hsh[c] = :avg
  end
  footer('Average', **hsh)
end

#avg_gfooter(*cols) ⇒ Object

Add a group footer to average the +cols+ given as header symbols.



416
417
418
419
420
421
422
# File 'lib/fat_table/formatters/formatter.rb', line 416

def avg_gfooter(*cols)
  hsh = {}
  cols.each do |c|
    hsh[c] = :avg
  end
  gfooter('Group Average', **hsh)
end

#decorate_string(str, _istruct) ⇒ Object

Add LaTeX control sequences, ANSI terminal escape codes, or other decorations to string to decorate it with the given attributes. None of the decorations may affect the displayed width of the string. Return the decorated string.



987
988
989
# File 'lib/fat_table/formatters/formatter.rb', line 987

def decorate_string(str, _istruct)
  str
end

#foot(label: 'Total', label_col: nil, **agg_cols) ⇒ Object

:category: Add Footers

A keyword method for adding a footer to the formatted output having the label +label:+ (default 'Total') placed in the column with the header +label_col:+ or in the first column if +label_col+ is ommitted. This assigns a fixed group label to be placed in the :date column:

+begin_src ruby

fmtr.foot(label: "Year's Average", label_col: :date, temp: avg)

+end_src

Besides being a fixed string, the +label:+ can also be a proc or lambda taking one argument, the foooter itself. Thus, a label such as:

+begin_src ruby

fmtr.foot(label: -> (f) { "Average (latest year #{f.column(:date).max.year})" },
           temp: :avg)

+end_src

And this would add the highest number to label, assuming the :date column of the footer's table had the year for each item.

The remaining hash arguments apply an aggregate to the values of the column, which can be:

  1. a symbol representing one of the builtin aggregates, i.e., :first, :last, :range, :sum, :count, :min, :max, :avg, :var, :pvar, :dev, :pdev, :any?, :all?, :none?, and :one?, or a symbol for your own aggregate defined as an instance method on FatTable::Column.
  2. a fixed string, but it the string can be converted into the Column's type, it will be converted, so the string '3.14159' will be converted to 3.14159 in a Numeric column.
  3. a value of the Column's type, so Date.today would simply be evaluated for a Numeric column.
  4. most flexibly of all, a proc or lambda taking arguments: f, the footer object itself; c, the column (or in the case of a group footer, the sub-column) corresponding to the current header, and in the case of a group footer, k, the number of the group (0-based).
  5. Any other value is converted to a string with #to_s.

Examples:

Put the label in the :dickens column of the footer and the maximum value from the :alpha column in the :alpha column of the footer.

fmtr.foot(label: 'Best', label_col: :dickens, alpha: :max)

Put the label 'Today' in the first column of the footer and today's date in the :beta column.

fmtr.foot(label: 'Today', beta: Date.today)

Put the label 'Best' in the :dickens column of the footer and the string 'Tale of Two Cities' in the :alpha column of the footer. Since it can't be interpreted as Boolean, Numeric, or DateTime, it is placed in the footer literally.

fmtr.foot(label: 'Best', label_col: :dickens, alpha: 'A Tale of Two Cities')

Use a lambda to calculate the value to be placed in the column :gamma.

fmtr.foot(label: 'Gamma', beta: :avg, gamma: ->(f, c) { (Math.gamma(c.count) + f[:beta] } )

Note that this way a footer can be made a function of the other footer values (using f[:other_col]) as well as the Column object corresponding to the lamda's column.



254
255
256
257
258
259
260
261
# File 'lib/fat_table/formatters/formatter.rb', line 254

def foot(label: 'Total', label_col: nil, **agg_cols)
  foot = Footer.new(label, table, label_col: label_col)
  agg_cols.each_pair do |h, agg|
    foot.add_value(h, agg)
  end
  @footers[label] = foot
  foot
end

Add Footer methods

A Table may have any number of footers and any number of group footers. Footers are not part of the table's data and never participate in any of the operation methods on tables. They are never inherited by output tables from input tables in any of the transformation methods.

When output, a table footer will appear at the bottom of the table, and a group footer will appear at the bottom of each group.

Each footer must have a label, usually a string such as 'Total', to identify the purpose of the footer, and the label must be distinct among all footers of the same type. That is you may have a table footer labeled 'Total' and a group footer labeled 'Total', but you may not have two table footers with that label. If the first column of the table is not included in the footer, the footer's label will be placed there, otherwise, there will be no label output. The footers are accessible with the #footers method, which returns a hash indexed by the label converted to a symbol. The symbol is reconverted to a title-cased string on output.

Note that by adding footers or gfooters to the table, you are only stating what footers you want on output of the table. No actual calculation is performed until the table is output.

Add a table footer to the table with a label given in the first parameter, defaulting to 'Total'. After the label, you can given any number of headers (as symbols) for columns to be summed, and then any number of hash parameters for columns for with to apply an aggregate other than :sum. For example, these are valid footer definitions.

Just sum the shares column with a label of 'Total' fmtr.footer(:shares)

Change the label and sum the :price column as well fmtr.footer('Grand Total', :shares, :price)

Average then show standard deviation of several columns fmtr.footer.('Average', date: :avg, shares: :avg, price: :avg) fmtr.footer.('Sigma', date: :dev, shares: :dev, price: :dev)

Do some sums and some other aggregates: sum shares, average date and price. fmtr.footer.('Summary', :shares, date: :avg, price: :avg)



174
175
176
177
178
179
180
181
182
183
184
# File 'lib/fat_table/formatters/formatter.rb', line 174

def footer(label, label_col = nil, *sum_cols, **agg_cols)
  foot = Footer.new(label, table, label_col: label_col)
  sum_cols.each do |h|
    foot.add_value(h, :sum)
  end
  agg_cols.each_pair do |h, agg|
    foot.add_value(h, agg)
  end
  @footers[label] = foot
  foot
end

#format(**fmts) ⇒ Object

Set the formatting for all 6 "location" specifiers for the table: (1) the headline, :header, (2) the first row of the body, :bfirst, (3) the first row of each group, :gfirst, (4) all of the body rows, :body, (5) the footer rows, :footer, and (6) the group footer rows, :gfooter.



584
585
586
587
588
589
# File 'lib/fat_table/formatters/formatter.rb', line 584

def format(**fmts)
  %i[header bfirst gfirst body footer gfooter].each do |loc|
    format_for(loc, **fmts)
  end
  self
end

#format_cell(val, istruct, width: nil, decorate: false) ⇒ Object

Convert a value to a string based on the instructions in istruct, depending on the type of val. "Formatting," which changes the content of the string, such as adding commas, is always performed, except alignment which is only performed when the width parameter is non-nil. "Decorating", which changes the appearance without changing the content, is performed only if the decorate parameter is true. Priority: lowest to highest: type, location, column_name



946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
# File 'lib/fat_table/formatters/formatter.rb', line 946

def format_cell(val, istruct, width: nil, decorate: false)
  case val
  when Numeric
    str = format_numeric(val, istruct)
    str = format_string(str, istruct, width)
    decorate ? decorate_string(str, istruct) : str
  when DateTime, Date
    str = format_datetime(val, istruct)
    str = format_string(str, istruct, width)
    decorate ? decorate_string(str, istruct) : str
  when TrueClass
    str = format_boolean(val, istruct)
    str = format_string(str, istruct, width)
    true_istruct = istruct.dup
    true_istruct.color = istruct.true_color
    true_istruct.bgcolor = istruct.true_bgcolor
    decorate ? decorate_string(str, true_istruct) : str
  when FalseClass
    str = format_boolean(val, istruct)
    str = format_string(str, istruct, width)
    false_istruct = istruct.dup
    false_istruct.color = istruct.false_color
    false_istruct.bgcolor = istruct.false_bgcolor
    decorate ? decorate_string(str, false_istruct) : str
  when NilClass
    str = istruct.nil_text
    str = format_string(str, istruct, width)
    decorate ? decorate_string(str, istruct) : str
  when String
    str = format_string(val, istruct, width)
    decorate ? decorate_string(str, istruct) : str
  else
    raise UserError,
          "cannot format value '#{val}' of class #{val.class}"
  end
end

#format_for(location, **fmts) ⇒ Object

Define a formatting directives for the given location. The following are the valid +location+ symbols.

:header:: instructions for the headers of the table,

:bfirst:: instructions for the first row in the body of the table,

:gfirst:: instructions for the cells in the first row of a group, to the extent not governed by :bfirst.

:body:: instructions for the cells in the body of the table, to the extent they are not governed by :bfirst or :gfirst.

:gfooter:: instructions for the cells of a group footer, and

:footer:: instructions for the cells of a footer.

Formatting directives are specified with hash arguments where the keys are either

  1. the name of a table column in symbol form, or

  2. the name of a column type in symbol form, i.e., :string, :numeric, or :datetime, :boolean, or :nil (for empty cells or untyped columns).

The value given for the hash arguments should be strings that contain "directives" on how elements of that column or of that type are to be formatted on output. Formatting directives for a column name take precedence over those specified by type. And more specific locations take precedence over less specific ones.

For example, the first line of a table is part of :body, :gfirst, and :bfirst, but since its identity as the first row of the table is the most specific (there is only one of those, there may be many rows that qualify as :gfirst, and even more that qualify as :body rows) any :bfirst specification would have priority over :gfirst or :body.

For purposes of formatting, all headers are considered of the :string type and all nil cells are considered to be of the :nilclass type. All other cells have the type of the column to which they belong, including all cells in group or table footers. See ::format for details on formatting directives.

Set the formatting for the given location.



637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
# File 'lib/fat_table/formatters/formatter.rb', line 637

def format_for(location, **fmts)
  unless LOCATIONS.include?(location)
    raise UserError, "unknown format location '#{location}'"
  end

  fmts = fmts.transform_keys do |k|
    if k == :nilclass
      :nil
    elsif k == :date
      :datetime
    else
      k
    end
  end

  @format_at[location] ||= {}
  table.headers.each do |h|
    # Build the inner hash of formatting instructions for this column h,
    # beginning with the default formatting hash or any existing inner
    # hash.
    format_h =
      if format_at[location][h].empty?
        default_format
      else
        format_at[location][h].to_h
      end

    # Merge in string and nil formatting for this column h, but not in
    # header location.  Header is always typed a string, so it will get
    # formatted in type-based formatting below. And headers are never nil.
    unless location == :header
      if fmts.key?(:string)
        typ_fmt = parse_fmt_string(fmts[:string])
        format_h = format_h.merge(typ_fmt)
      end
      if fmts.key?(:nil)
        typ_fmt = parse_nilclass_fmt(fmts[:nil], strict: false).first
        format_h = format_h.merge(typ_fmt)
      end
    end

    # Merge in formatting instructions for column h based on the column
    # name, or if there is no formatting instructions for the column by
    # name, merge in the formatting instructions based on the column's
    # type.  Insist on only the string type for the header location.
    typ = (location == :header ? :string : table.type(h).as_sym)
    parse_typ_method_name = 'parse_' + typ.to_s + '_fmt'
    if fmts[h]
      # Merge in column formatting
      col_fmt = send(parse_typ_method_name, fmts[h],
                     strict: location != :header).first
      format_h = format_h.merge(col_fmt)
    elsif fmts.key?(typ)
      # Merge in type-based formatting
      typ_fmt = send(parse_typ_method_name, fmts[typ]).first
      format_h = format_h.merge(typ_fmt)
    end

    # Copy :body formatting for column h to :bfirst and :gfirst if they
    # still have the default formatting. Can be overridden with a
    # format_for call with those locations.
    if location == :body
      format_h.each_pair do |k, v|
        if format_at[:bfirst][h].send(k) == self.class.default_format[k]
          format_at[:bfirst][h].send("#{k}=", v)
        end
        if format_at[:gfirst][h].send(k) == self.class.default_format[k]
          format_at[:gfirst][h].send("#{k}=", v)
        end
      end
    elsif location == :gfirst
      # Copy :gfirst formatting to :bfirst if it is still the default
      format_h.each_pair do |k, v|
        if format_at[:bfirst][h].send(k) == self.class.default_format[k]
          format_at[:bfirst][h].send("#{k}=", v)
        end
      end
    end

    # Record its origin (using leading underscore so not to clash with any
    # headers named h or location) and convert to struct
    format_h[:_h] = h
    format_h[:_location] = location
    format_at[location][h] = OpenStruct.new(format_h)
  end
  self
end

#gfoot(label: 'Group Total', label_col: nil, **agg_cols) ⇒ Object

:category: Add Footers

A keyword method for adding a group footer to the formatted output having the label +label:+ (default 'Total') placed in the column with the header +label_col:+ or in the first column if +label_col+ is ommitted.

This assigns a fixed group label to be placed in the :date column:

+begin_src ruby

fmtr.gfoot(label: "Year's Average", label_col: :date, temp: avg)

+end_src

Besides being a fixed string, the +label:+ can also be a proc or lambda taking one or two arguments. In the one argument form, the argument is the group number k. If a second argument is specified, the foooter itself is passed as the argument. Thus, a label such as:

+begin_src ruby

fmtr.gfoot(label: -> (k) { "Group #FatTable::Formatter.(k+1)(k+1).to_roman Average" }, temp: :avg)

+end_src

This would format the label with a roman numeral (assuming you defined a method to do so) for the group number.

+begin_src ruby

fmtr.gfoot(label: -> (k, f) { "Year #{f.column(:date, k).max.year} Group #{(k+1).to_roman} Average" },
           temp: :avg)

+end_src

And this would add the group's year to label, assuming the :date column of the footer's table had the same year for each item in the group.

The remaining hash arguments apply an aggregate to the values of the column, which can be:

  1. a symbol representing one of the builtin aggregates, i.e., :first, :last, :range, :sum, :count, :min, :max, :avg, :var, :pvar, :dev, :pdev, :any?, :all?, :none?, and :one?, or a symbol for your own aggregate defined as an instance method on FatTable::Column.
  2. a fixed string, but it the string can be converted into the Column's type, it will be converted, so the string '3.14159' will be converted to 3.14159 in a Numeric column.
  3. a value of the Column's type, so Date.today would simply be evaluated for a Numeric column.
  4. most flexibly of all, a proc or lambda taking arguments: f, the footer object itself; c, the column (or in the case of a group footer, the sub-column) corresponding to the current header, and k, this group's group number (0-based).
  5. Any other value is converted to a string with #to_s.

Examples:

Put the label in the :dickens column of the footer and the maximum value from the :alpha column in the :alpha column of the footer.

+begin_src ruby

fmtr.gfoot(label: 'Best', label_col: :dickens, alpha: :max)

+end_src

Put the label 'Today' in the first column of the footer and today's date in the :beta column.

+begin_src ruby

fmtr.gfoot(label: 'Today', beta: Date.today)

+end_src

Put the label 'Best' in the :dickens column of the footer and the string 'Tale of Two Cities' in the :alpha column of the footer. Since it can't be interpreted as Boolean, Numeric, or DateTime, it is placed in the footer literally.

+begin_src ruby

fmtr.gfoot(label: 'Best', label_col: :dickens, alpha: 'A Tale of Two Cities')

+end_src

Use a lambda to calculate the value to be placed in the column :gamma.

+begin_src ruby

fmtr.gfoot(label: 'Gamma', beta: :avg, gamma: ->(f, c) { (Math.gamma(c.count) + f[:beta] } )

+end_src

Note that this way a footer can be made a function of the other footer values (using f[:other_col]) as well as the Column object corresponding to the lamda's column.



379
380
381
382
383
384
385
386
# File 'lib/fat_table/formatters/formatter.rb', line 379

def gfoot(label: 'Group Total', label_col: nil, **agg_cols)
  foot = Footer.new(label, table, label_col: label_col, group: true)
  agg_cols.each_pair do |h, agg|
    foot.add_value(h, agg)
  end
  @gfooters[label] = foot
  foot
end

#gfooter(label, label_col = nil, *sum_cols, **agg_cols) ⇒ Object

Add a group footer to the output with a label given in the first parameter, defaulting to 'Total'. After the label, you can given any number of headers (as symbols) for columns to be summed, and then any number of hash parameters for columns for with to apply an aggregate other than :sum. For example, these are valid gfooter definitions.

Just sum the shares column with a label of 'Total' tab.gfooter(:shares)

Change the label and sum the :price column as well tab.gfooter('Total', :shares, :price)

Average then show standard deviation of several columns fmtr.gfooter.('Average', date: :avg, shares: :avg, price: :avg) fmtr.gfooter.('Sigma', date: dev, shares: :dev, price: :dev)

Do some sums and some other aggregates: sum shares, average date and price. fmtr.gfooter.('Summary', :shares, date: :avg, price: :avg)



282
283
284
285
286
287
288
289
290
291
292
# File 'lib/fat_table/formatters/formatter.rb', line 282

def gfooter(label, label_col = nil, *sum_cols, **agg_cols)
  foot = Footer.new(label, table, label_col: label_col, group: true)
  sum_cols.each do |h|
    foot.add_value(h, :sum)
  end
  agg_cols.each_pair do |h, agg|
    foot.add_value(h, agg)
  end
  @gfooters[label] = foot
  foot
end

Add a footer to display the maximum value of the +cols+ given as header symbols.



452
453
454
455
456
457
458
# File 'lib/fat_table/formatters/formatter.rb', line 452

def max_footer(*cols)
  hsh = {}
  cols.each do |c|
    hsh[c] = :max
  end
  footer('Maximum', **hsh)
end

#max_gfooter(*cols) ⇒ Object

Add a group footer to display the maximum value of the +cols+ given as header symbols.



464
465
466
467
468
469
470
# File 'lib/fat_table/formatters/formatter.rb', line 464

def max_gfooter(*cols)
  hsh = {}
  cols.each do |c|
    hsh[c] = :max
  end
  gfooter('Group Maximum', **hsh)
end

Add a footer to display the minimum value of the +cols+ given as header symbols.



428
429
430
431
432
433
434
# File 'lib/fat_table/formatters/formatter.rb', line 428

def min_footer(*cols)
  hsh = {}
  cols.each do |c|
    hsh[c] = :min
  end
  footer('Minimum', **hsh)
end

#min_gfooter(*cols) ⇒ Object

Add a group footer to display the minimum value of the +cols+ given as header symbols.



440
441
442
443
444
445
446
# File 'lib/fat_table/formatters/formatter.rb', line 440

def min_gfooter(*cols)
  hsh = {}
  cols.each do |c|
    hsh[c] = :min
  end
  gfooter('Group Minimum', **hsh)
end

#outputObject

Return a representation of the +table+, along with all footers and group footers, as either a string in the target format or as a Ruby data structure if that is the target. In the latter case, all the cells are converted to strings formatted according to the Formatter's formatting directives given in Formatter.format_for or Formatter.format.



1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
# File 'lib/fat_table/formatters/formatter.rb', line 1133

def output
  # If there are neither headers nor any rows in the table, return an
  # empty string.
  return '' if table.empty? && table.headers.empty?

  # This results in a hash of two-element arrays. The key
  # is the header and the value is an array of the header and formatted
  # header. We do the latter so the structure parallels the structure for
  # rows explained next.
  formatted_headers = build_formatted_headers

  # These produce an array with each element representing a row of the
  # table. Each element of the array is a two-element array. The location of
  # the row in the table (:bfirst, :body, :gfooter, etc.) is the first
  # element and a hash of the row is the second element. The keys for the
  # hash are the row headers as in the Table, but the values are two element
  # arrays as well. First is the raw, unformatted value of the cell, the
  # second is a string of the first value formatted according to the
  # instructions for the column and location in which it appears. The
  # formatting done on this pass is only formatting that affects the
  # contents of the cells, such as inserting commas, that would affect the
  # width of the columns as displayed. We keep both the raw value and
  # unformatted value around because we have to make two passes over the
  # table if there is any alignment, and we want to know the type of the raw
  # element for the second pass of formatting for type-specific formatting
  # (e.g., true_color, false_color, etc.).
  new_rows = build_formatted_body
  new_rows += build_formatted_footers

  # Having formatted the cells, we can now compute column widths so we can
  # do any alignment called for if this is a Formatter that performs its own
  # alignment. On this pass, we also decorate the cells with colors, bold,
  # etc.
  if aligned?
    widths = width_map(formatted_headers, new_rows)
    table.headers.each do |h|
      fmt_h = formatted_headers[h].last
      istruct = format_at[:header][h]
      formatted_headers[h] =
        [h, format_cell(fmt_h, istruct, width: widths[h], decorate: true)]
    end
    aligned_rows = []
    new_rows.each do |loc_row|
      if loc_row.nil?
        aligned_rows << nil
        next
      end
      loc, row = *loc_row
      aligned_row = {}
      row.each_pair do |h, (val, _fmt_v)|
        istruct = format_at[loc][h]
        aligned_row[h] =
          [val, format_cell(val, istruct, width: widths[h], decorate: true)]
      end
      aligned_rows << [loc, aligned_row]
    end
    new_rows = aligned_rows
  end

  # Now that the contents of the output table cells have been computed and
  # alignment applied, we can actually construct the table using the methods
  # for constructing table parts, pre_table, etc. We expect that these will
  # be overridden by subclasses of Formatter for specific output targets. In
  # any event, the result is a single string (or ruby object if eval is true
  # for the Formatter) representing the table in the syntax of the output
  # target.
  result = ''
  result += pre_table
  if include_header_row?
    result += pre_header(widths)
    result += pre_row
    cells = []
    formatted_headers.each_pair do |h, (_v, fmt_v)|
      cells << pre_cell(h) + quote_cell(fmt_v) + post_cell
    end
    result += cells.join(inter_cell)
    result += post_row
    result += post_header(widths)
  end
  new_rows.each do |loc_row|
    if loc_row.nil?
      result += hline(widths)
      next
    end

    _loc, row = *loc_row
    result += pre_row
    cells = []
    row.each_pair do |h, (_v, fmt_v)|
      cells << pre_cell(h) + quote_cell(fmt_v) + post_cell
    end
    result += cells.join(inter_cell)
    result += post_row
  end
  result += (widths)
  result += post_table

  # If this Formatter targets a ruby data structure (e.g., AoaFormatter), we
  # eval the string to get the object.
  evaluate? ? eval(result) : result
end

Add a footer to sum the +cols+ given as header symbols.



391
392
393
# File 'lib/fat_table/formatters/formatter.rb', line 391

def sum_footer(*cols)
  footer('Total', nil, *cols)
end

#sum_gfooter(*cols) ⇒ Object

Add a group footer to sum the +cols+ given as header symbols.



398
399
400
# File 'lib/fat_table/formatters/formatter.rb', line 398

def sum_gfooter(*cols)
  gfooter('Group Total', nil, *cols)
end