fat_table 0.5.3 → 0.6.0

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.
@@ -181,41 +181,74 @@ module FatTable
181
181
  end
182
182
 
183
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
184
  #
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,
185
+ # A keyword method for adding a footer to the formatted output having the
186
+ # label +label:+ (default 'Total') placed in the column with the header
187
+ # +label_col:+ or in the first column if +label_col+ is ommitted.
188
+ # This assigns a fixed group label to be placed in the :date column:
189
+ # #+begin_src ruby
190
+ # fmtr.foot(label: "Year's Average", label_col: :date, temp: avg)
191
+ # #+end_src
192
+ #
193
+ # Besides being a fixed string, the +label:+ can also be a proc or lambda
194
+ # taking one argument, the foooter itself.
195
+ # Thus, a label such as:
196
+ #
197
+ # #+begin_src ruby
198
+ # fmtr.foot(label: -> (f) { "Average (latest year #{f.column(:date).max.year})" },
199
+ # temp: :avg)
200
+ # #+end_src
201
+ # And this would add the highest number to label, assuming the :date column
202
+ # of the footer's table had the year for each item.
203
+ #
204
+ # The remaining hash arguments apply an aggregate to the values of the
205
+ # column, which can be:
206
+ #
207
+ # 1. a symbol representing one of the builtin aggregates, i.e., :first,
208
+ # :last, :range, :sum, :count, :min, :max, :avg, :var, :pvar, :dev,
209
+ # :pdev, :any?, :all?, :none?, and :one?, or a symbol for your own
210
+ # aggregate defined as an instance method on FatTable::Column.
211
+ # 2. a fixed string, but it the string can be converted into the Column's
212
+ # type, it will be converted, so the string '3.14159' will be converted
213
+ # to 3.14159 in a Numeric column.
214
+ # 3. a value of the Column's type, so Date.today would simply be evaluated
215
+ # for a Numeric column.
216
+ # 4. most flexibly of all, a proc or lambda taking arguments: f, the
217
+ # footer object itself; c, the column (or in the case of a group
218
+ # footer, the sub-column) corresponding to the current header, and in
219
+ # the case of a group footer, k, the number of the group (0-based).
220
+ # 5. Any other value is converted to a string with #to_s.
198
221
  #
199
222
  # Examples:
200
223
  #
201
224
  # Put the label in the :dickens column of the footer and the maximum value
202
225
  # from the :alpha column in the :alpha column of the footer.
203
226
  #
204
- # fmtr.foot('Best', :dickens, alpha: :max)
227
+ # fmtr.foot(label: 'Best', label_col: :dickens, alpha: :max)
205
228
  #
206
229
  # Put the label 'Today' in the first column of the footer and today's date
207
230
  # in the :beta column.
208
231
  #
209
- # fmtr.foot('Today', beta: Date.today)
232
+ # fmtr.foot(label: 'Today', beta: Date.today)
210
233
  #
211
234
  # Put the label 'Best' in the :dickens column of the footer and the string
212
235
  # 'Tale of Two Cities' in the :alpha column of the footer. Since it can't
213
236
  # be interpreted as Boolean, Numeric, or DateTime, it is placed in the
214
237
  # footer literally.
215
238
  #
216
- # fmtr.foot('Best', :dickens, alpha: 'A Tale of Two Cities')
239
+ # fmtr.foot(label: 'Best', label_col: :dickens, alpha: 'A Tale of Two
240
+ # Cities')
241
+ #
242
+ # Use a lambda to calculate the value to be placed in the column :gamma.
243
+ #
244
+ # fmtr.foot(label: 'Gamma', beta: :avg, gamma: ->(f, c) {
245
+ # (Math.gamma(c.count) + f[:beta] } )
246
+ #
247
+ # Note that this way a footer can be made a function of the other footer
248
+ # values (using f[:other_col]) as well as the Column object corresponding
249
+ # to the lamda's column.
217
250
  #
218
- def foot(label, label_col = nil, **agg_cols)
251
+ def foot(label: 'Total', label_col: nil, **agg_cols)
219
252
  foot = Footer.new(label, table, label_col: label_col)
220
253
  agg_cols.each_pair do |h, agg|
221
254
  foot.add_value(h, agg)
@@ -255,9 +288,92 @@ module FatTable
255
288
  foot
256
289
  end
257
290
 
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)
291
+ # :category: Add Footers
292
+ #
293
+ # A keyword method for adding a group footer to the formatted output
294
+ # having the label +label:+ (default 'Total') placed in the column with
295
+ # the header +label_col:+ or in the first column if +label_col+ is
296
+ # ommitted.
297
+ #
298
+ # This assigns a fixed group label to be placed in the :date column:
299
+ # #+begin_src ruby
300
+ # fmtr.gfoot(label: "Year's Average", label_col: :date, temp: avg)
301
+ # #+end_src
302
+ #
303
+ # Besides being a fixed string, the +label:+ can also be a proc or lambda
304
+ # taking one or two arguments. In the one argument form, the argument is
305
+ # the group number k. If a second argument is specified, the foooter
306
+ # itself is passed as the argument. Thus, a label such as:
307
+ #
308
+ # #+begin_src ruby
309
+ # fmtr.gfoot(label: -> (k) { "Group #{(k+1).to_roman} Average" }, temp: :avg)
310
+ # #+end_src
311
+ # This would format the label with a roman numeral (assuming you defined a
312
+ # method to do so) for the group number.
313
+ #
314
+ # #+begin_src ruby
315
+ # fmtr.gfoot(label: -> (k, f) { "Year #{f.column(:date, k).max.year} Group #{(k+1).to_roman} Average" },
316
+ # temp: :avg)
317
+ # #+end_src
318
+ # And this would add the group's year to label, assuming the :date column
319
+ # of the footer's table had the same year for each item in the group.
320
+ #
321
+ #
322
+ # The remaining hash arguments apply an aggregate to the values
323
+ # of the column, which can be:
324
+ #
325
+ # 1. a symbol representing one of the builtin aggregates, i.e., :first,
326
+ # :last, :range, :sum, :count, :min, :max, :avg, :var, :pvar, :dev,
327
+ # :pdev, :any?, :all?, :none?, and :one?, or a symbol for your own
328
+ # aggregate defined as an instance method on FatTable::Column.
329
+ # 2. a fixed string, but it the string can be converted into the Column's
330
+ # type, it will be converted, so the string '3.14159' will be converted
331
+ # to 3.14159 in a Numeric column.
332
+ # 3. a value of the Column's type, so Date.today would simply be evaluated
333
+ # for a Numeric column.
334
+ # 4. most flexibly of all, a proc or lambda taking arguments: f, the
335
+ # footer object itself; c, the column (or in the case of a group
336
+ # footer, the sub-column) corresponding to the current header, and k,
337
+ # this group's group number (0-based).
338
+ # 5. Any other value is converted to a string with #to_s.
339
+ #
340
+ # Examples:
341
+ #
342
+ # Put the label in the :dickens column of the footer and the maximum value
343
+ # from the :alpha column in the :alpha column of the footer.
344
+ #
345
+ # #+begin_src ruby
346
+ # fmtr.gfoot(label: 'Best', label_col: :dickens, alpha: :max)
347
+ # #+end_src
348
+ #
349
+ # Put the label 'Today' in the first column of the footer and today's date
350
+ # in the :beta column.
351
+ #
352
+ # #+begin_src ruby
353
+ # fmtr.gfoot(label: 'Today', beta: Date.today)
354
+ # #+end_src
355
+ #
356
+ # Put the label 'Best' in the :dickens column of the footer and the string
357
+ # 'Tale of Two Cities' in the :alpha column of the footer. Since it can't
358
+ # be interpreted as Boolean, Numeric, or DateTime, it is placed in the
359
+ # footer literally.
360
+ #
361
+ # #+begin_src ruby
362
+ # fmtr.gfoot(label: 'Best', label_col: :dickens, alpha: 'A Tale of Two
363
+ # Cities')
364
+ # #+end_src
365
+ #
366
+ # Use a lambda to calculate the value to be placed in the column :gamma.
367
+ #
368
+ # #+begin_src ruby
369
+ # fmtr.gfoot(label: 'Gamma', beta: :avg, gamma: ->(f, c) {
370
+ # (Math.gamma(c.count) + f[:beta] } )
371
+ # #+end_src
372
+ #
373
+ # Note that this way a footer can be made a function of the other footer
374
+ # values (using f[:other_col]) as well as the Column object corresponding
375
+ # to the lamda's column.
376
+ def gfoot(label: 'Group Total', label_col: nil, **agg_cols)
261
377
  foot = Footer.new(label, table, label_col: label_col, group: true)
262
378
  agg_cols.each_pair do |h, agg|
263
379
  foot.add_value(h, agg)
@@ -532,7 +648,7 @@ module FatTable
532
648
  valid_keys = table.headers + %i[string numeric datetime boolean nil]
533
649
  invalid_keys = (fmts.keys - valid_keys).uniq
534
650
  unless invalid_keys.empty?
535
- msg = "invalid #{location} column or type: #{invalid_keys.join(',')}"
651
+ msg = "invalid #{location} column or type: #{invalid_keys.join(', ')}"
536
652
  raise UserError, msg
537
653
  end
538
654
 
@@ -562,20 +678,21 @@ module FatTable
562
678
  end
563
679
  end
564
680
 
565
- # Merge in formatting for column h based on the column type, or based
566
- # on the string type for the header location.
681
+ # Merge in formatting instructions for column h based on the column
682
+ # name, or if there is no formatting instructions for the column by
683
+ # name, merge in the formatting instructions based on the column's
684
+ # type. Insist on only the string type for the header location.
567
685
  typ = (location == :header ? :string : table.type(h).as_sym)
568
686
  parse_typ_method_name = 'parse_' + typ.to_s + '_fmt'
569
- if fmts.key?(typ)
570
- # Merge in type-based formatting
571
- typ_fmt = send(parse_typ_method_name, fmts[typ]).first
572
- format_h = format_h.merge(typ_fmt)
573
- end
574
687
  if fmts[h]
575
688
  # Merge in column formatting
576
689
  col_fmt = send(parse_typ_method_name, fmts[h],
577
690
  strict: location != :header).first
578
691
  format_h = format_h.merge(col_fmt)
692
+ elsif fmts.key?(typ)
693
+ # Merge in type-based formatting
694
+ typ_fmt = send(parse_typ_method_name, fmts[typ]).first
695
+ format_h = format_h.merge(typ_fmt)
579
696
  end
580
697
 
581
698
  # Copy :body formatting for column h to :bfirst and :gfirst if they
@@ -1003,9 +1120,14 @@ module FatTable
1003
1120
  # converted to strings formatted according to the Formatter's formatting
1004
1121
  # directives given in Formatter.format_for or Formatter.format.
1005
1122
  def output
1006
- # This results in a hash of two-element arrays. The key is the header and
1007
- # the value is an array of the header and formatted header. We do the
1008
- # latter so the structure parallels the structure for rows explained next.
1123
+ # If there are neither headers nor any rows in the table, return an
1124
+ # empty string.
1125
+ return '' if table.empty? && table.headers.empty?
1126
+
1127
+ # This results in a hash of two-element arrays. The key
1128
+ # is the header and the value is an array of the header and formatted
1129
+ # header. We do the latter so the structure parallels the structure for
1130
+ # rows explained next.
1009
1131
  formatted_headers = build_formatted_headers
1010
1132
 
1011
1133
  # These produce an array with each element representing a row of the
@@ -20,7 +20,7 @@ module FatTable
20
20
  end
21
21
 
22
22
  def pre_header(widths)
23
- result = '|'
23
+ result = +'|'
24
24
  widths.each_value do |w|
25
25
  result += '-' * (w + 2) + '+'
26
26
  end
@@ -53,7 +53,7 @@ module FatTable
53
53
  end
54
54
 
55
55
  def hline(widths)
56
- result = '|'
56
+ result = +'|'
57
57
  widths.each_value do |w|
58
58
  result += '-' * (w + 2) + '+'
59
59
  end
@@ -62,7 +62,7 @@ module FatTable
62
62
  end
63
63
 
64
64
  def post_footers(widths)
65
- result = '|'
65
+ result = +'|'
66
66
  widths.each_value do |w|
67
67
  result += '-' * (w + 2) + '+'
68
68
  end
@@ -221,7 +221,7 @@ module FatTable
221
221
  end
222
222
 
223
223
  def pre_header(widths)
224
- result = upper_left
224
+ result = +upper_left
225
225
  widths.each_value do |w|
226
226
  result += double_rule * (w + 2) + upper_tee
227
227
  end
@@ -255,7 +255,7 @@ module FatTable
255
255
  end
256
256
 
257
257
  def hline(widths)
258
- result = left_tee
258
+ result = +left_tee
259
259
  widths.each_value do |w|
260
260
  result += horizontal_rule * (w + 2) + single_cross
261
261
  end
@@ -289,7 +289,7 @@ module FatTable
289
289
  end
290
290
 
291
291
  def post_footers(widths)
292
- result = lower_left
292
+ result = +lower_left
293
293
  widths.each_value do |w|
294
294
  result += double_rule * (w + 2) + lower_tee
295
295
  end
@@ -16,7 +16,7 @@ module FatTable
16
16
  end
17
17
 
18
18
  def pre_header(widths)
19
- result = '+'
19
+ result = +'+'
20
20
  widths.each_value do |w|
21
21
  result += '=' * (w + 2) + '+'
22
22
  end
@@ -49,7 +49,7 @@ module FatTable
49
49
  end
50
50
 
51
51
  def hline(widths)
52
- result = '+'
52
+ result = +'+'
53
53
  widths.each_value do |w|
54
54
  result += '-' * (w + 2) + '+'
55
55
  end
@@ -82,7 +82,7 @@ module FatTable
82
82
  end
83
83
 
84
84
  def post_footers(widths)
85
- result = '+'
85
+ result = +'+'
86
86
  widths.each_value do |w|
87
87
  result += '=' * (w + 2) + '+'
88
88
  end
@@ -105,7 +105,7 @@ module FatTable
105
105
  unless heads.empty?
106
106
  heads.each do |h|
107
107
  if h.to_s.end_with?('!') || @tolerant_columns.include?(h)
108
- @columns << Column.new(header: h.to_s.sub(/!\s*\z/, ''), tolerant: true)
108
+ @columns << Column.new(header: h.to_s.sub(/!\s*\z/, ''), type: 'String')
109
109
  else
110
110
  @columns << Column.new(header: h)
111
111
  end
@@ -120,7 +120,7 @@ module FatTable
120
120
  # though FatTable::Table objects have no instance variables, a class that
121
121
  # inherits from it might.
122
122
  def empty_dup
123
- self.dup.__empty!
123
+ dup.__empty!
124
124
  end
125
125
 
126
126
  def __empty!
@@ -779,7 +779,6 @@ module FatTable
779
779
  last_key = nil
780
780
  new_rows.each_with_index do |nrow, k|
781
781
  new_tab << nrow
782
- # key = nrow.fetch_values(*sort_heads)
783
782
  key = nrow.fetch_values(*key_hash.keys)
784
783
  new_tab.mark_boundary(k - 1) if last_key && key != last_key
785
784
  last_key = key
@@ -1712,11 +1711,19 @@ module FatTable
1712
1711
  end
1713
1712
 
1714
1713
  # The <=> operator cannot handle nils without some help. Treat a nil as
1715
- # smaller than any other value, but equal to other nils. The two keys are assumed to be arrays of values to be
1716
- # compared with <=>.
1714
+ # smaller than any other value, but equal to other nils. The two keys are
1715
+ # assumed to be arrays of values to be compared with <=>. Since
1716
+ # tolerant_columns permit strings to be mixed in with columns of type
1717
+ # Numeric, DateTime, and Boolean, treat strings mixed with another type
1718
+ # the same as nils.
1717
1719
  def compare_with_nils(key1, key2)
1718
1720
  result = nil
1719
1721
  key1.zip(key2) do |k1, k2|
1722
+ if k1.is_a?(String) && !k2.is_a?(String)
1723
+ k1 = nil
1724
+ elsif !k1.is_a?(String) && k2.is_a?(String)
1725
+ k2 = nil
1726
+ end
1720
1727
  if k1.nil? && k2.nil?
1721
1728
  result = 0
1722
1729
  next
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FatTable
4
4
  # The current version of FatTable
5
- VERSION = '0.5.3'
5
+ VERSION = '0.6.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-24 00:00:00.000000000 Z
11
+ date: 2022-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -332,7 +332,7 @@ licenses: []
332
332
  metadata:
333
333
  allowed_push_host: https://rubygems.org
334
334
  yard.run: yri
335
- post_install_message:
335
+ post_install_message:
336
336
  rdoc_options: []
337
337
  require_paths:
338
338
  - lib
@@ -348,7 +348,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
348
348
  version: '0'
349
349
  requirements: []
350
350
  rubygems_version: 3.3.3
351
- signing_key:
351
+ signing_key:
352
352
  specification_version: 4
353
353
  summary: Provides tools for working with tables as a data type.
354
354
  test_files: []