fat_table 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []