fat_table 0.5.5 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f391d5e4ad9d7a4dcb303098b90f6fb42379d9df382192c4bacaf169523dab1
4
- data.tar.gz: ea75f906fcd164752a3ca2220324ad10d8438562bb31c4846bc6575723e834fa
3
+ metadata.gz: b9cce93aafbc687a599cb04bb11ead038af02ecd433b64168f904ea09707a298
4
+ data.tar.gz: d61de9b052e89b47c13afa517f1e229d05e42903ba96bb272ddb14ce81c1c563
5
5
  SHA512:
6
- metadata.gz: 2914c40a497cf24bcdba8868c4a4607ea1bdb0a94fba8caa3d6e31407ff6832621df2a0b4a8a2bb98c3b40080fed6d752f8e936f66a96270fe675e93d5a852d6
7
- data.tar.gz: dbc43f4d2bac4a2a19bc7cc90d1fbfbf739a12f0f280a543670af588cfcf2bb0df74d50cfb7ded76b851f5470fd30d4f971659cead5131f9ea15e24b492e5823
6
+ metadata.gz: 4f5418bad20549df9794fe7c3c69226d77e017433926b220282d094b66dc5b130de88f93e62d3045763aad065a5e1fb9ffbf626ee7b20e745774ee4653bad649
7
+ data.tar.gz: 31f4eee0b70bbcef9cba988dc4a2b7b00f1372fe23dd38732b34f520e6a1e2244d73f05abc9963f8b97cb5bd294e4c2ec406dbc0709d59a88355ce4f3676acd7
data/.simplecov ADDED
@@ -0,0 +1,18 @@
1
+ # -*- mode: ruby -*-
2
+
3
+ SimpleCov.start do
4
+ # any custom configs like groups and filters can be here at a central place
5
+ add_filter '/spec/'
6
+ add_filter '/tmp/'
7
+ add_group "Models", "lib/fat_table"
8
+ add_group "Core Extension", "lib/ext"
9
+ # After this many seconds between runs, old coverage stats are thrown out,
10
+ # so 3600 => 1 hour
11
+ merge_timeout 3600
12
+ # Make this true to merge rspec and cucumber coverage together
13
+ use_merging false
14
+ command_name 'Rspec'
15
+ nocov_token 'no_cover'
16
+ # Branch coverage
17
+ enable_coverage :branch
18
+ end
data/README.org CHANGED
@@ -32,7 +32,7 @@ The following is for org.
32
32
  #+end_src
33
33
 
34
34
  #+begin_EXAMPLE
35
- Current version is: 0.5.4
35
+ Current version is: 0.6.0
36
36
  #+end_EXAMPLE
37
37
 
38
38
  * Introduction
@@ -148,6 +148,7 @@ org-mode buffer as an org-table, ready for processing by other code blocks.
148
148
  - [[#type-and-column-priority][Type and Column priority]]
149
149
  - [[#footers][Footers]]
150
150
  - [[#adding-footers][Adding Footers]]
151
+ - [[#dynamic-labels][Dynamic Labels]]
151
152
  - [[#aggregators][Aggregators]]
152
153
  - [[#footer-objects][Footer objects]]
153
154
  - [[#footer-examples][Footer Examples]]
@@ -534,6 +535,7 @@ You can create an empty table with ~FatTable::Table.new~ or, the shorter form,
534
535
  in the added rows determine the names of the headers:
535
536
 
536
537
  #+BEGIN_SRC ruby :results silent
538
+ require 'fat_table'
537
539
  tab = FatTable.new
538
540
  tab << { a: 1, b: 2, c: "<2017-01-21>", d: 'f', e: '' }
539
541
  tab << { a: 3.14, b: 2.17, c: '[2016-01-21 Thu]', d: 'Y', e: nil }
@@ -1754,8 +1756,6 @@ available in ~ft_console~ as ~@tab_a~ and ~@tab_b~:
1754
1756
  | 2 | Engineering | 2 |
1755
1757
  | 3 | Finance | 7 |
1756
1758
  EOS
1757
-
1758
- tab_b = FatTable.from_org_string(tab_b_str)
1759
1759
  #+END_SRC
1760
1760
 
1761
1761
  Here is ~tab_a~:
@@ -2465,7 +2465,6 @@ Finance&
2465
2465
  {:id=>"3", :dept=>"Finance", :emp_id=>"7"}]
2466
2466
  #+end_EXAMPLE
2467
2467
 
2468
-
2469
2468
  *** Formatting Directives
2470
2469
  The formatting methods explained in the next section all take formatting
2471
2470
  directives as strings in which letters and other characters signify what
@@ -2706,12 +2705,27 @@ but not for other nils, such as in the last row of the ~:join_date~ column.
2706
2705
 
2707
2706
  *** Footers
2708
2707
  **** Adding Footers
2709
- You can call the ~footer,~ ~gfooter, foot, and gfoot~ methods on ~Formatter~
2710
- objects to add footers and group footers. Note that all of these methods
2711
- return a ~Footer~ object that can be accessed to extract the computed values.
2712
- All of these methods return the ~FatTable::Footer~ object so constructed. It
2713
- can be used to access the values and other attributes of the footer
2714
- computed. Their signatures are:
2708
+ You can call the ~foot~, ~gfoot~, ~footer,~ or ~gfooter~, methods on
2709
+ ~Formatter~ objects to add footers and group footers. Note that all of these
2710
+ methods return a ~Footer~ object that can be accessed to extract the computed
2711
+ values. All of these methods return the ~FatTable::Footer~ object so
2712
+ constructed. It can be used to access the values and other attributes of the
2713
+ footer computed. Their signatures are:
2714
+
2715
+ - ~foot(label: label, label_col: nil, **agg_cols)~ :: where ~label~ is a label
2716
+ to be placed in the column with header ~label_col~, or, if ommitted, in the
2717
+ first cell of the footer (unless that column is named as one of the
2718
+ ~agg_cols~, in which case the label is ignored), and ~**agg_cols~ is zero or
2719
+ more hash-like parameters with a column symbol as a key and a valid
2720
+ aggregate as the value. This causes a table-wide header to be added at the
2721
+ bottom of the table applying ~agg~, to the ~agg_cols~. A table can have any
2722
+ number of footers attached, and they will appear at the bottom of the output
2723
+ table in the order they are given.
2724
+
2725
+ - ~gfoot(label: 'Group Total', label_col: nil, **agg_cols)~ :: where the
2726
+ parameters have the same meaning as for the ~foot~ method, but results in a
2727
+ footer for each group in the table rather than the table as a whole. These
2728
+ will appear in the output table just below each group.
2715
2729
 
2716
2730
  - ~footer(label, *sum_cols, **agg_cols)~ :: where ~label~ is a label to be
2717
2731
  placed in the first cell of the footer (unless that column is named as one
@@ -2724,26 +2738,11 @@ computed. Their signatures are:
2724
2738
  number of footers attached, and they will appear at the bottom of the output
2725
2739
  table in the order they are given.
2726
2740
 
2727
- - ~foot(label, label_col, **agg_cols)~ :: where ~label~ is a label to be
2728
- placed in the column with header ~label_col~, or, if ommitted, in the first
2729
- cell of the footer (unless that column is named as one of the ~agg_cols~, in
2730
- which case the label is ignored), and ~**agg_cols~ is zero or more hash-like
2731
- parameters with a column symbol as a key and a valid aggregate as the
2732
- value. This causes a table-wide header to be added at the bottom of the
2733
- table applying ~agg~, to the ~agg_cols~. A table can have any number of
2734
- footers attached, and they will appear at the bottom of the output table in
2735
- the order they are given.
2736
-
2737
2741
  - ~gfooter(label, *sum_cols, **agg_cols)~ :: where the parameters have the
2738
2742
  same meaning as for the ~footer~ method, but results in a footer for each
2739
2743
  group in the table rather than the table as a whole. These will appear in
2740
2744
  the output table just below each group.
2741
2745
 
2742
- - ~gfoot(label, label_col, **agg_cols)~ :: where the parameters have the same
2743
- meaning as for the ~foot~ method, but results in a footer for each group in
2744
- the table rather than the table as a whole. These will appear in the output
2745
- table just below each group.
2746
-
2747
2746
  There are also a number of convenience methods for adding common footers:
2748
2747
  - ~sum_footer(*cols)~ :: Add a footer summing the given columns with the label
2749
2748
  'Total'.
@@ -2762,6 +2761,39 @@ There are also a number of convenience methods for adding common footers:
2762
2761
  - ~max_gfooter(*cols)~ :: Add a group footer showing the maximum for the given
2763
2762
  columns with the label 'Group Maximum'.
2764
2763
 
2764
+ **** Dynamic Labels
2765
+ Most of the time, you will want a fixed string as the label. However,
2766
+ especially in the case of a group footer, you might want a dynamically
2767
+ contructed label. You can use a proc or lambda for a label, and it will be
2768
+ computed for you. In the case of non-group footers, the proc takes a single
2769
+ parameter, the footer object itself. This allows you to make the label a
2770
+ function of other footer values, for example, you could make the label
2771
+ include the most recent year from the date column:
2772
+
2773
+ #+begin_src ruby
2774
+ fmtr.foot(label: -> (f) { "Average (latest year #{f.column(:date).max.year})" },
2775
+ temp: :avg)
2776
+ #+end_src
2777
+
2778
+ In the case of a group footer, the lambda or proc may take either one or qtwo parameters.
2779
+ If it takes one, the parameter is simply the 0-based number of the group:
2780
+
2781
+ #+begin_src ruby
2782
+ fmtr.gfoot(label: -> (k) { "Group #{(k+1).to_roman} Average" }, temp: :avg)
2783
+ #+end_src
2784
+ This would format the label with a roman numeral (assuming you defined a
2785
+ method to do so) for the group number.
2786
+
2787
+ If it takes two arguments, the second argument is the footer itself, as with
2788
+ non-group footers:
2789
+
2790
+ #+begin_src ruby
2791
+ fmtr.gfoot(label: -> (k, f) { "Year #{f.column(:date, k).max.year} Group #{(k+1).to_roman} Average" },
2792
+ temp: :avg)
2793
+ #+end_src
2794
+ This would add the group's year to label, assuming the :date column of the
2795
+ footer's table had the same year for each item in the group.
2796
+
2765
2797
  **** Aggregators
2766
2798
  When adding a footer with the above methods, you can specify an aggregator for
2767
2799
  each column named in the ~agg_cols~ parameter. There are several candidates
@@ -2778,6 +2810,9 @@ for what you can use for an aggregator:
2778
2810
  In the case of datetime columns, these aggrgators convert the dates to
2779
2811
  julian date numbers, perform the calculation, then convert the result back
2780
2812
  to a datetime object.
2813
+ Apart from the built-in aggrgators, you could define your own by opening the
2814
+ FatTable::Column class and adding a suitable instance method. In that
2815
+ case, the symbol could also refer to the method you defined.
2781
2816
  - String :: using a string as an aggrgegator can result in:
2782
2817
  + the string being converted to an object matching the type of the column
2783
2818
  (for example, using '$1,888' in a numeric column puts the constant number
@@ -2792,11 +2827,16 @@ for what you can use for an aggregator:
2792
2827
  - A Lambda :: finally, you can provide a lambda for performing arbitrary
2793
2828
  calculations and placing the result in the footer field. The number of
2794
2829
  arguments the lambda takes can vary:
2795
- * If the lambda is used in a group footer, it must take a single integer
2796
- argument that is set to the group number being calculated and /can/ take a
2797
- second argument for the column symbol in which it appears, or
2798
- * If the lambda is used in an ordinary footer, it either takes no arguments,
2799
- or a single argument for the column symbol in which it appears.
2830
+ * If the lambda is used in an ordinary footer column, it can take 0, 1, or 2
2831
+ arguments: (1) the first argument, if given, will be set to the
2832
+ FatTable::Column object for that column and (2) the second argument, if
2833
+ given, will be set to the Footer object itself.
2834
+ * If the lambda is used in a group footer column, it can 0, 1, 2, or 3
2835
+ arguments: (1) the first argument, if given, will be set to the group's
2836
+ 0-based index number, (2) the second argument, if given, will be set to a
2837
+ FatTable::Column object consisting of those items in the group's column,
2838
+ and (3) the third argument, if given, will be set to the Footer object
2839
+ itself.
2800
2840
 
2801
2841
  **** Footer objects
2802
2842
  Each of the methods for adding a footer to a ~Formatter~ returns a ~Footer~ object
@@ -2820,7 +2860,6 @@ their computed values. Here are the accessors available on a
2820
2860
  to the footer value for that column, nil for unused columns. Use the index
2821
2861
  ~k~ to specify which group to access in the case of a group footer.
2822
2862
 
2823
-
2824
2863
  **** Footer Examples
2825
2864
  As a reminder, here is the table, ~tab_a~ defined earlier:
2826
2865
 
@@ -2981,18 +3020,20 @@ But it can be any type. Here we pick a lottery winner from the employee ids.
2981
3020
  #+end_EXAMPLE
2982
3021
 
2983
3022
  ***** Lambdas
2984
- Perhaps the most flexible form of aggregator is a lambda form. They require 2
2985
- or 3 parameters in non-group and group footers, respectively:
2986
-
2987
- - ~->(f, c) {...}~ :: in a normal, non-group footer, you must provide for two
2988
- paramters: the first, ~f~, will be bound to the footer in which the lambda
2989
- appears and the second, ~c~, will be bound to the column header to which the
2990
- lambda is attached.
2991
- - ~->(f, c, k)~ :: in a group footer, you must provide for three paramters:
2992
- the first, ~f~, will be bound to the footer in which the lambda appears, the
2993
- second, ~c~, will be bound to the column header to which the lambda is
2994
- attached, and the third, ~k~ will be bound to the group number of the group
2995
- being evaluated.
3023
+ Perhaps the most flexible form of aggregator is a lambda form. They can take
3024
+ up to 2 or up to 3 parameters in non-group and group footers, respectively:
3025
+
3026
+ - ~->(c, f) {...}~ :: in a normal, non-group footer, you may provide for up to
3027
+ two paramters: the first, ~c~, if given, will be bound to the column header
3028
+ to which the lambda is attached and and the second, ~f~, if given will be
3029
+ bound to the footer in which the lambda appears. A lambda with no
3030
+ parameters can be provided as well if none are needed.
3031
+ - ~->(k, c, f)~ :: in a group footer, you may provide for up to three
3032
+ paramters: the the first, ~k~, if provided, will be bound to the group
3033
+ number of the group being evaluated, the second, ~c~, if provided, will be
3034
+ bound to the column header to which the lambda is attached, and the third,
3035
+ ~f~, will be bound to the footer in which the lambda appears. A lambda with
3036
+ no parameters can be provided as well if none are needed.
2996
3037
 
2997
3038
  With the first argument, the footer itself becomes available and with it all
2998
3039
  the things accessible with the footers, including the items in the current
@@ -3003,7 +3044,7 @@ Compute the summ of the squares if the items in the ~:age~ column:
3003
3044
  tab_a.to_text do |f|
3004
3045
  f.format(numeric: '0.0R,', datetime: 'd[%v]D[%v]')
3005
3046
  f.footer('Average', age: :avg, salary: :avg, join_date: :avg)
3006
- f.footer('SSQ', age: ->(f, c) { sa = f.items(c).map {|x| x * x}.sum; Math.sqrt(sa) })
3047
+ f.footer('SSQ', age: ->(c) { sa = c.items.map {|x| x * x}.sum; Math.sqrt(sa) })
3007
3048
  end
3008
3049
  #+END_SRC
3009
3050
 
@@ -3032,8 +3073,8 @@ summ of the squares if the ages in each group:
3032
3073
  tab_a.order_with('join_date.year').to_text do |f|
3033
3074
  f.format(numeric: '0.0R,', datetime: 'd[%v]D[%v]', sort_key: '0.0~,')
3034
3075
  f.footer('Average', age: :avg, salary: :avg, join_date: :avg)
3035
- f.gfooter('Group SSQ', age: ->(f, c, k) { sa = f.items(c, k).map {|x| x * x}.sum; Math.sqrt(sa) })
3036
- f.footer('Total SSQ', age: ->(f, c) { sa = f.items(c).map {|x| x * x}.sum; Math.sqrt(sa) })
3076
+ f.gfooter('Group SSQ', age: ->(k, c, f) { sa = c.items.map {|x| x * x}.sum; Math.sqrt(sa) })
3077
+ f.footer('Total SSQ', age: ->(c, f) { sa = c.items.map {|x| x * x}.sum; Math.sqrt(sa) })
3037
3078
  end
3038
3079
  #+END_SRC
3039
3080
 
@@ -3045,19 +3086,19 @@ summ of the squares if the ages in each group:
3045
3086
  +-----------+-------+-----+------------+--------+-------------+----------+
3046
3087
  | Group SSQ | | 45 | | | | |
3047
3088
  +-----------+-------+-----+------------+--------+-------------+----------+
3048
- | 1 | Paul | 32 | California | 20,000 | 13-JUL-2001 | 2001 |
3089
+ | 1 | Paul | 32 | California | 20,000 | 13-JUL-2001 | 2001 |
3049
3090
  +-----------+-------+-----+------------+--------+-------------+----------+
3050
3091
  | Group SSQ | | 32 | | | | |
3051
3092
  +-----------+-------+-----+------------+--------+-------------+----------+
3052
- | 2 | Allen | 25 | Texas | | 13-JUL-2005 | 2005 |
3053
- | 8 | Paul | 24 | Houston | 20,000 | 13-JUL-2005 | 2005 |
3054
- | 9 | James | 44 | Norway | 5,000 | 13-JUL-2005 | 2005 |
3093
+ | 2 | Allen | 25 | Texas | | 13-JUL-2005 | 2005 |
3094
+ | 8 | Paul | 24 | Houston | 20,000 | 13-JUL-2005 | 2005 |
3095
+ | 9 | James | 44 | Norway | 5,000 | 13-JUL-2005 | 2005 |
3055
3096
  +-----------+-------+-----+------------+--------+-------------+----------+
3056
3097
  | Group SSQ | | 56 | | | | |
3057
3098
  +-----------+-------+-----+------------+--------+-------------+----------+
3058
- | 3 | Teddy | 23 | Norway | 20,000 | 13-DEC-2007 | 2007 |
3059
- | 4 | Mark | 25 | Rich-Mond | 65,000 | 13-DEC-2007 | 2007 |
3060
- | 5 | David | 27 | Texas | 85,000 | 13-DEC-2007 | 2007 |
3099
+ | 3 | Teddy | 23 | Norway | 20,000 | 13-DEC-2007 | 2007 |
3100
+ | 4 | Mark | 25 | Rich-Mond | 65,000 | 13-DEC-2007 | 2007 |
3101
+ | 5 | David | 27 | Texas | 85,000 | 13-DEC-2007 | 2007 |
3061
3102
  +-----------+-------+-----+------------+--------+-------------+----------+
3062
3103
  | Group SSQ | | 43 | | | | |
3063
3104
  +-----------+-------+-----+------------+--------+-------------+----------+
data/fat_table.gemspec CHANGED
@@ -76,7 +76,6 @@ Gem::Specification.new do |spec|
76
76
  spec.add_development_dependency 'rubocop-performance'
77
77
  spec.add_development_dependency 'simplecov'
78
78
 
79
- spec.add_runtime_dependency 'activesupport', '>3.0'
80
79
  spec.add_runtime_dependency 'fat_core', '>= 4.9.0'
81
80
  spec.add_runtime_dependency 'rainbow'
82
81
  spec.add_runtime_dependency 'sequel'
@@ -173,7 +173,12 @@ module FatTable
173
173
  # Column. This makes Columns Enumerable, so all the Enumerable methods are
174
174
  # available on a Column.
175
175
  def each
176
- items.each { |itm| yield itm }
176
+ if block_given?
177
+ items.each { |itm| yield itm }
178
+ self
179
+ else
180
+ to_enum(:each)
181
+ end
177
182
  end
178
183
 
179
184
  ##########################################################################
@@ -161,13 +161,22 @@ module FatTable
161
161
  def self.convert_to_numeric(val)
162
162
  return BigDecimal(val, Float::DIG) if val.is_a?(Float)
163
163
  return val if val.is_a?(Numeric)
164
+
164
165
  # Eliminate any commas, $'s (or other currency symbol), or _'s.
165
166
  cursym = Regexp.quote(FatTable.currency_symbol)
166
167
  clean_re = /[,_#{cursym}]/
167
168
  val = val.to_s.clean.gsub(clean_re, '')
168
169
  return nil if val.blank?
170
+
169
171
  case val
170
- when /(\A[-+]?\d+\.\d*\z)|(\A[-+]?\d*\.\d+\z)/
172
+ when /\A[-+]?\d+\.\z/
173
+ # Catch quirk in Ruby's BigDecimal converter where a number ending in
174
+ # a decimal but with no digits after the decimal throws an
175
+ # ArgumentError as an invalid value for BigDecimal(). Just append a
176
+ # '0'.
177
+ val += '0'
178
+ BigDecimal(val.to_s.clean)
179
+ when /(\A[-+]?\d+\.\d+\z)|(\A[-+]?\d*\.\d+\z)/
171
180
  BigDecimal(val.to_s.clean)
172
181
  when /\A[-+]?[\d]+\z/
173
182
  val.to_i
@@ -2,7 +2,7 @@
2
2
 
3
3
  module FatTable
4
4
  class Footer
5
- attr_reader :table, :label, :label_col, :values, :group
5
+ attr_reader :table, :label_col, :values, :group
6
6
 
7
7
  ###########################################################################
8
8
  # Constructors
@@ -15,6 +15,7 @@ module FatTable
15
15
  # for the footer are added later with the #add_value method.
16
16
  def initialize(label = 'Total', table, label_col: nil, group: false)
17
17
  @label = label
18
+
18
19
  unless table.is_a?(Table)
19
20
  raise ArgumentError, 'Footer.new needs a table argument'
20
21
  end
@@ -30,14 +31,7 @@ module FatTable
30
31
  @group = group
31
32
  @group_cols = {}
32
33
  @values = {}
33
- if group
34
- @values[@label_col] = []
35
- table.number_of_groups.times do
36
- @values[@label_col] << @label
37
- end
38
- else
39
- @values[@label_col] = [@label]
40
- end
34
+ insert_labels_in_label_col
41
35
  make_accessor_methods
42
36
  end
43
37
 
@@ -71,6 +65,11 @@ module FatTable
71
65
  end
72
66
  end
73
67
 
68
+ # Return the value of the label, for the kth group if grouped.
69
+ def label(k = 0)
70
+ @values[@label_col][k]
71
+ end
72
+
74
73
  # :category: Accessors
75
74
 
76
75
  # Return the value of under the +key+ header, or if this is a group
@@ -108,8 +107,10 @@ module FatTable
108
107
  if group && k.nil?
109
108
  raise ArgumentError, 'Footer#column(h, k) missing the group number argument k'
110
109
  end
110
+
111
111
  if group
112
- k.nil? ? @group_cols[h] : @group_cols[h][k]
112
+ @group_cols[h] ||= table.group_cols(h)
113
+ @group_cols[h][k]
113
114
  else
114
115
  table.column(h)
115
116
  end
@@ -151,14 +152,13 @@ module FatTable
151
152
 
152
153
  # Evaluate the given agg for the header col and, in the case of a group
153
154
  # footer, the group k.
154
- def calc_val(agg, col, k = nil)
155
- column =
156
- if group
157
- @group_cols[col] ||= table.group_cols(col)
158
- @group_cols[col][k]
159
- else
160
- table.column(col)
161
- end
155
+ def calc_val(agg, h, k = nil)
156
+ column = column(h, k)
157
+
158
+ # Convert Date and Time objects to DateTime
159
+ if [Date, Time].include?(agg.class)
160
+ agg = agg.to_datetime
161
+ end
162
162
 
163
163
  case agg
164
164
  when Symbol
@@ -179,29 +179,77 @@ module FatTable
179
179
  when Proc
180
180
  result =
181
181
  if group
182
- unless agg.arity == 3
183
- msg = 'a lambda used in a group footer must have three arguments: (f, c, k)'
182
+ case agg.arity
183
+ when 0
184
+ agg.call
185
+ when 1
186
+ agg.call(k)
187
+ when 2
188
+ agg.call(k, column)
189
+ when 3
190
+ agg.call(k, column, self)
191
+ else
192
+ msg = 'a lambda used in a group footer may have 0 to 3 three arguments: (k, c, f)'
184
193
  raise ArgumentError, msg
185
194
  end
186
- agg.call(self, col, k)
187
195
  else
188
- unless agg.arity == 2
189
- msg = 'a lambda used in a non-group footer must have two arguments: (f, c)'
196
+ case agg.arity
197
+ when 0
198
+ agg.call
199
+ when 1
200
+ agg.call(column)
201
+ when 2
202
+ agg.call(column, self)
203
+ else
204
+ msg = 'a lambda used in a non-group footer may have 0 to 2 arguments: (c, f)'
190
205
  raise ArgumentError, msg
191
206
  end
192
- agg.call(self, col)
193
207
  end
194
- # Make sure the result returned can be inserted into footer field.
195
- case result
196
- when Symbol, String
197
- calc_val(result, col, k)
198
- when column.type.constantize
199
- result
208
+ # Pass the result back into this method as the new agg
209
+ calc_val(result, h, k)
210
+ else
211
+ agg.to_s
212
+ end
213
+ end
214
+
215
+ # Insert a possibly calculated value for the label in the appropriate
216
+ # @values column.
217
+ def insert_labels_in_label_col
218
+ if group
219
+ @values[@label_col] = []
220
+ table.number_of_groups.times do |k|
221
+ @values[@label_col] << calc_label(k)
222
+ end
223
+ else
224
+ @values[@label_col] = [calc_label]
225
+ end
226
+ end
227
+
228
+ # Calculate the label for the kth group, using k = nil for non-group
229
+ # footers. If the label is a proc, call it with the group number.
230
+ def calc_label(k = nil)
231
+ case @label
232
+ when Proc
233
+ case @label.arity
234
+ when 0
235
+ @label.call
236
+ when 1
237
+ k ? @label.call(k) : @label.call(self)
238
+ when 2
239
+ if k
240
+ @label.call(k, self)
241
+ else
242
+ raise ArgumentError, "a non-group footer label proc may only have 1 argument for the containing footer f"
243
+ end
200
244
  else
201
- raise ArgumentError, "lambda cannot return an object of class #{result.class}"
245
+ if k
246
+ raise ArgumentError, "group footer label proc may only have 0, 1, or 2 arguments for group number k and containing footer f"
247
+ else
248
+ raise ArgumentError, "a non-group footer label proc may only have 0 or 1 arguments for the containing footer f"
249
+ end
202
250
  end
203
251
  else
204
- agg
252
+ @label.to_s
205
253
  end
206
254
  end
207
255
 
@@ -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] } )
217
246
  #
218
- def foot(label, label_col = nil, **agg_cols)
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.
250
+ #
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)
@@ -530,8 +530,13 @@ module FatTable
530
530
 
531
531
  # Yield each row of the table as a Hash with the column symbols as keys.
532
532
  def each
533
- rows.each do |row|
534
- yield row
533
+ if block_given?
534
+ rows.each do |row|
535
+ yield row
536
+ end
537
+ self
538
+ else
539
+ to_enum(:each)
535
540
  end
536
541
  end
537
542
 
@@ -2,5 +2,5 @@
2
2
 
3
3
  module FatTable
4
4
  # The current version of FatTable
5
- VERSION = '0.5.5'
5
+ VERSION = '0.6.2'
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.5
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-25 00:00:00.000000000 Z
11
+ date: 2022-05-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -178,20 +178,6 @@ dependencies:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
- - !ruby/object:Gem::Dependency
182
- name: activesupport
183
- requirement: !ruby/object:Gem::Requirement
184
- requirements:
185
- - - ">"
186
- - !ruby/object:Gem::Version
187
- version: '3.0'
188
- type: :runtime
189
- prerelease: false
190
- version_requirements: !ruby/object:Gem::Requirement
191
- requirements:
192
- - - ">"
193
- - !ruby/object:Gem::Version
194
- version: '3.0'
195
181
  - !ruby/object:Gem::Dependency
196
182
  name: fat_core
197
183
  requirement: !ruby/object:Gem::Requirement
@@ -287,6 +273,7 @@ files:
287
273
  - ".gitignore"
288
274
  - ".rspec"
289
275
  - ".rubocop.yml"
276
+ - ".simplecov"
290
277
  - ".travis.yml"
291
278
  - ".yardopts"
292
279
  - Gemfile