fat_table 0.5.4 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.org +126 -62
- data/TODO.org +28 -1
- data/lib/fat_table/column.rb +82 -38
- data/lib/fat_table/convert.rb +4 -8
- data/lib/fat_table/footer.rb +81 -33
- data/lib/fat_table/formatters/formatter.rb +145 -24
- data/lib/fat_table/formatters/org_formatter.rb +3 -3
- data/lib/fat_table/formatters/term_formatter.rb +3 -3
- data/lib/fat_table/formatters/text_formatter.rb +3 -3
- data/lib/fat_table/table.rb +9 -4
- data/lib/fat_table/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f045f2196dd5d6ad7584b877d847945f99cc8079b86b8a810af8528d2c18074c
|
4
|
+
data.tar.gz: cb8f031c4f48d472af658f111221524882f5d2bb6574858b1e988e2384d7ba56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f906e8841e268263635dc4231573b07cb48cd48b1f33fd2a0d7812e65f1e9720ac8f8556129601b702a828c5c6a7a7a04c00344016c2ebe0004a892d1f524ab9
|
7
|
+
data.tar.gz: 694f65455ed63c1304f0bc626192c2770968278ec0599c0e9c5f57c5fcb091a351ed4e09d28fe6cbda12278f1f9ed669e128339e6e99b4fc2a1a21467d182892
|
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.
|
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]]
|
@@ -442,11 +443,26 @@ or nil. There are only five permissible types for a ~Column~:
|
|
442
443
|
5. *NilClass* (for the undetermined column type).
|
443
444
|
|
444
445
|
When a ~Table~ is constructed from an external source, all ~Columns~ start out
|
445
|
-
having a type of ~NilClass~, that is, their type is as yet undetermined. When
|
446
|
-
string or object of one of the four determined types is added to a ~Column
|
447
|
-
|
448
|
-
|
449
|
-
|
446
|
+
having a type of ~NilClass~, that is, their type is as yet undetermined. When
|
447
|
+
a string or object of one of the four determined types is added to a ~Column~
|
448
|
+
and it can be converted into one of the permissible types, it fixes the type
|
449
|
+
of the column, and all further items added to the ~Column~ must either be
|
450
|
+
~nil~ (indicating no value) or be capable of being coerced to the column's
|
451
|
+
type. Otherwise, ~FatTable~ raises an exception.
|
452
|
+
|
453
|
+
The strictness of requiring all items to be of the same type can be relaxed by
|
454
|
+
declaring a column to be "tolerant." You can do so when you create the table
|
455
|
+
by adding a tolerant_columns keyword parameter. If a Column is tolerant,
|
456
|
+
~FatTable~ tries to convert new items into a type other than a ~String~ and,
|
457
|
+
if it can do so, sets /that/ as the Column's type. Any later items that
|
458
|
+
cannot be converted into the Column's type are converted to strings. These
|
459
|
+
interloper strings are treated like nils for purposes of sorting and
|
460
|
+
evaluation, but are displayed according to any string formatting on output.
|
461
|
+
See [[*Designating "Tolerant" Columns][Designating "Tolerant" Columns]] below.
|
462
|
+
|
463
|
+
It is also possible to force ~FatTable~ to treat a column as a String type,
|
464
|
+
even its items look like one of the other types. See [[*Forcing String Type][Forcing String Type]]
|
465
|
+
below.
|
450
466
|
|
451
467
|
Items of input must be either one of the permissible ruby objects or strings. If
|
452
468
|
they are strings, ~FatTable~ attempts to parse them as one of the permissible
|
@@ -519,6 +535,7 @@ You can create an empty table with ~FatTable::Table.new~ or, the shorter form,
|
|
519
535
|
in the added rows determine the names of the headers:
|
520
536
|
|
521
537
|
#+BEGIN_SRC ruby :results silent
|
538
|
+
require 'fat_table'
|
522
539
|
tab = FatTable.new
|
523
540
|
tab << { a: 1, b: 2, c: "<2017-01-21>", d: 'f', e: '' }
|
524
541
|
tab << { a: 3.14, b: 2.17, c: '[2016-01-21 Thu]', d: 'Y', e: nil }
|
@@ -603,10 +620,18 @@ columns to be created:
|
|
603
620
|
**** Forcing String Type
|
604
621
|
Occasionally, ~FatTable~'s automatic type detection can get in the way and you
|
605
622
|
just want it to treat one or more columns as Strings regardless of their
|
606
|
-
appearance. Think, for example, of zip codes.
|
607
|
-
table, you can
|
608
|
-
|
609
|
-
|
623
|
+
appearance. Think, for example, of zip codes. If headers are given when a
|
624
|
+
table is contructed, you can designate a forced-string column by appending a
|
625
|
+
~!~ to the end of the header. It will not become part of the header, it will
|
626
|
+
just mark it as a forced-string Column.
|
627
|
+
|
628
|
+
#+begin_SRC emacs-lisp :wrap EXAMPLE
|
629
|
+
tab = FatTable.new(:a, 'b', 'C!', :d, :zip!)
|
630
|
+
#+end_SRC
|
631
|
+
|
632
|
+
In addition, at any time after creating a table, you can force the String type
|
633
|
+
on any number of columns with the ~force_string!~ method. When you do so, all
|
634
|
+
exisiting items in the column are converted to strings with the #to_s method.
|
610
635
|
|
611
636
|
#+begin_src ruby :wrap EXAMPLE
|
612
637
|
tab = FatTable.new(:a, 'b', 'C', :d, :zip)
|
@@ -1731,8 +1756,6 @@ available in ~ft_console~ as ~@tab_a~ and ~@tab_b~:
|
|
1731
1756
|
| 2 | Engineering | 2 |
|
1732
1757
|
| 3 | Finance | 7 |
|
1733
1758
|
EOS
|
1734
|
-
|
1735
|
-
tab_b = FatTable.from_org_string(tab_b_str)
|
1736
1759
|
#+END_SRC
|
1737
1760
|
|
1738
1761
|
Here is ~tab_a~:
|
@@ -2442,7 +2465,6 @@ Finance&
|
|
2442
2465
|
{:id=>"3", :dept=>"Finance", :emp_id=>"7"}]
|
2443
2466
|
#+end_EXAMPLE
|
2444
2467
|
|
2445
|
-
|
2446
2468
|
*** Formatting Directives
|
2447
2469
|
The formatting methods explained in the next section all take formatting
|
2448
2470
|
directives as strings in which letters and other characters signify what
|
@@ -2683,12 +2705,27 @@ but not for other nils, such as in the last row of the ~:join_date~ column.
|
|
2683
2705
|
|
2684
2706
|
*** Footers
|
2685
2707
|
**** Adding Footers
|
2686
|
-
You can call the ~
|
2687
|
-
objects to add footers and group footers. Note that all of these
|
2688
|
-
return a ~Footer~ object that can be accessed to extract the computed
|
2689
|
-
All of these methods return the ~FatTable::Footer~ object so
|
2690
|
-
can be used to access the values and other attributes of the
|
2691
|
-
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.
|
2692
2729
|
|
2693
2730
|
- ~footer(label, *sum_cols, **agg_cols)~ :: where ~label~ is a label to be
|
2694
2731
|
placed in the first cell of the footer (unless that column is named as one
|
@@ -2701,26 +2738,11 @@ computed. Their signatures are:
|
|
2701
2738
|
number of footers attached, and they will appear at the bottom of the output
|
2702
2739
|
table in the order they are given.
|
2703
2740
|
|
2704
|
-
- ~foot(label, label_col, **agg_cols)~ :: where ~label~ is a label to be
|
2705
|
-
placed in the column with header ~label_col~, or, if ommitted, in the first
|
2706
|
-
cell of the footer (unless that column is named as one of the ~agg_cols~, in
|
2707
|
-
which case the label is ignored), and ~**agg_cols~ is zero or more hash-like
|
2708
|
-
parameters with a column symbol as a key and a valid aggregate as the
|
2709
|
-
value. This causes a table-wide header to be added at the bottom of the
|
2710
|
-
table applying ~agg~, to the ~agg_cols~. A table can have any number of
|
2711
|
-
footers attached, and they will appear at the bottom of the output table in
|
2712
|
-
the order they are given.
|
2713
|
-
|
2714
2741
|
- ~gfooter(label, *sum_cols, **agg_cols)~ :: where the parameters have the
|
2715
2742
|
same meaning as for the ~footer~ method, but results in a footer for each
|
2716
2743
|
group in the table rather than the table as a whole. These will appear in
|
2717
2744
|
the output table just below each group.
|
2718
2745
|
|
2719
|
-
- ~gfoot(label, label_col, **agg_cols)~ :: where the parameters have the same
|
2720
|
-
meaning as for the ~foot~ method, but results in a footer for each group in
|
2721
|
-
the table rather than the table as a whole. These will appear in the output
|
2722
|
-
table just below each group.
|
2723
|
-
|
2724
2746
|
There are also a number of convenience methods for adding common footers:
|
2725
2747
|
- ~sum_footer(*cols)~ :: Add a footer summing the given columns with the label
|
2726
2748
|
'Total'.
|
@@ -2739,6 +2761,39 @@ There are also a number of convenience methods for adding common footers:
|
|
2739
2761
|
- ~max_gfooter(*cols)~ :: Add a group footer showing the maximum for the given
|
2740
2762
|
columns with the label 'Group Maximum'.
|
2741
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
|
+
|
2742
2797
|
**** Aggregators
|
2743
2798
|
When adding a footer with the above methods, you can specify an aggregator for
|
2744
2799
|
each column named in the ~agg_cols~ parameter. There are several candidates
|
@@ -2755,6 +2810,9 @@ for what you can use for an aggregator:
|
|
2755
2810
|
In the case of datetime columns, these aggrgators convert the dates to
|
2756
2811
|
julian date numbers, perform the calculation, then convert the result back
|
2757
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.
|
2758
2816
|
- String :: using a string as an aggrgegator can result in:
|
2759
2817
|
+ the string being converted to an object matching the type of the column
|
2760
2818
|
(for example, using '$1,888' in a numeric column puts the constant number
|
@@ -2769,11 +2827,16 @@ for what you can use for an aggregator:
|
|
2769
2827
|
- A Lambda :: finally, you can provide a lambda for performing arbitrary
|
2770
2828
|
calculations and placing the result in the footer field. The number of
|
2771
2829
|
arguments the lambda takes can vary:
|
2772
|
-
* If the lambda is used in
|
2773
|
-
|
2774
|
-
|
2775
|
-
|
2776
|
-
|
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.
|
2777
2840
|
|
2778
2841
|
**** Footer objects
|
2779
2842
|
Each of the methods for adding a footer to a ~Formatter~ returns a ~Footer~ object
|
@@ -2797,7 +2860,6 @@ their computed values. Here are the accessors available on a
|
|
2797
2860
|
to the footer value for that column, nil for unused columns. Use the index
|
2798
2861
|
~k~ to specify which group to access in the case of a group footer.
|
2799
2862
|
|
2800
|
-
|
2801
2863
|
**** Footer Examples
|
2802
2864
|
As a reminder, here is the table, ~tab_a~ defined earlier:
|
2803
2865
|
|
@@ -2958,18 +3020,20 @@ But it can be any type. Here we pick a lottery winner from the employee ids.
|
|
2958
3020
|
#+end_EXAMPLE
|
2959
3021
|
|
2960
3022
|
***** Lambdas
|
2961
|
-
Perhaps the most flexible form of aggregator is a lambda form. They
|
2962
|
-
or 3 parameters in non-group and group footers, respectively:
|
2963
|
-
|
2964
|
-
- ~->(
|
2965
|
-
paramters: the first, ~
|
2966
|
-
|
2967
|
-
lambda
|
2968
|
-
|
2969
|
-
|
2970
|
-
|
2971
|
-
|
2972
|
-
|
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.
|
2973
3037
|
|
2974
3038
|
With the first argument, the footer itself becomes available and with it all
|
2975
3039
|
the things accessible with the footers, including the items in the current
|
@@ -2980,7 +3044,7 @@ Compute the summ of the squares if the items in the ~:age~ column:
|
|
2980
3044
|
tab_a.to_text do |f|
|
2981
3045
|
f.format(numeric: '0.0R,', datetime: 'd[%v]D[%v]')
|
2982
3046
|
f.footer('Average', age: :avg, salary: :avg, join_date: :avg)
|
2983
|
-
f.footer('SSQ', age: ->(
|
3047
|
+
f.footer('SSQ', age: ->(c) { sa = c.items.map {|x| x * x}.sum; Math.sqrt(sa) })
|
2984
3048
|
end
|
2985
3049
|
#+END_SRC
|
2986
3050
|
|
@@ -3009,8 +3073,8 @@ summ of the squares if the ages in each group:
|
|
3009
3073
|
tab_a.order_with('join_date.year').to_text do |f|
|
3010
3074
|
f.format(numeric: '0.0R,', datetime: 'd[%v]D[%v]', sort_key: '0.0~,')
|
3011
3075
|
f.footer('Average', age: :avg, salary: :avg, join_date: :avg)
|
3012
|
-
f.gfooter('Group SSQ', age: ->(
|
3013
|
-
f.footer('Total SSQ', age: ->(
|
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) })
|
3014
3078
|
end
|
3015
3079
|
#+END_SRC
|
3016
3080
|
|
@@ -3022,19 +3086,19 @@ summ of the squares if the ages in each group:
|
|
3022
3086
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
3023
3087
|
| Group SSQ | | 45 | | | | |
|
3024
3088
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
3025
|
-
| 1 | Paul | 32 | California | 20,000 | 13-JUL-2001 |
|
3089
|
+
| 1 | Paul | 32 | California | 20,000 | 13-JUL-2001 | 2001 |
|
3026
3090
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
3027
3091
|
| Group SSQ | | 32 | | | | |
|
3028
3092
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
3029
|
-
| 2 | Allen | 25 | Texas | | 13-JUL-2005 |
|
3030
|
-
| 8 | Paul | 24 | Houston | 20,000 | 13-JUL-2005 |
|
3031
|
-
| 9 | James | 44 | Norway | 5,000 | 13-JUL-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 |
|
3032
3096
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
3033
3097
|
| Group SSQ | | 56 | | | | |
|
3034
3098
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
3035
|
-
| 3 | Teddy | 23 | Norway | 20,000 | 13-DEC-2007 |
|
3036
|
-
| 4 | Mark | 25 | Rich-Mond | 65,000 | 13-DEC-2007 |
|
3037
|
-
| 5 | David | 27 | Texas | 85,000 | 13-DEC-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 |
|
3038
3102
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
3039
3103
|
| Group SSQ | | 43 | | | | |
|
3040
3104
|
+-----------+-------+-----+------------+--------+-------------+----------+
|
data/TODO.org
CHANGED
@@ -1,10 +1,37 @@
|
|
1
|
+
|
2
|
+
* TODO Specify Column Widths
|
3
|
+
Allow a formatter to specify column widths. This could be a number of
|
4
|
+
characters, which would be interpreted as a number of "ems" for LaTeX.
|
5
|
+
Cell content larger than the width would be truncated. Any column without a
|
6
|
+
width specified would be set at the width of the longest value in that cell,
|
7
|
+
after initial formatting.
|
8
|
+
|
9
|
+
#+begin_SRC ruby
|
10
|
+
tab.to_text do |f|
|
11
|
+
f.widths(a: 13, b: 30)
|
12
|
+
end
|
13
|
+
#+end_SRC
|
14
|
+
|
15
|
+
Possible enhancements:
|
16
|
+
- specify an overall width and column widths as decimal or fractions, so that
|
17
|
+
a column's width would be that fraction of the overall width.
|
18
|
+
- specify a Range for a width, so that the column would at least min and at
|
19
|
+
most max, otherwise the width of its largest cell.
|
20
|
+
|
1
21
|
* TODO Conversion to Spreadsheets
|
2
22
|
- State "TODO" from [2017-04-21 Fri 10:36]
|
3
23
|
This is a [[https://github.com/westonganger/spreadsheet_architect][gem]] that I can include into the Table model to convert a table into
|
4
24
|
a spread-sheet, or even a sheet in a multi-sheet spreadsheet file.
|
5
25
|
|
6
|
-
* TODO Add
|
26
|
+
* TODO Add Quandl or EODDATA Queries
|
27
|
+
Possible replacements for YQL.
|
28
|
+
|
29
|
+
* CNCL Add from_yql for fetching from Yahoo
|
30
|
+
CLOSED: [2022-01-30 Sun 06:03]
|
7
31
|
- State "TODO" from [2017-04-21 Fri 10:35]
|
32
|
+
|
33
|
+
Cancelled because Yahoo shut down the YQL api service.
|
34
|
+
|
8
35
|
Add a constructor to allow fetching stock data from yql. Perhaps grab all
|
9
36
|
available fields, then allow a select of those of interest.
|
10
37
|
|
data/lib/fat_table/column.rb
CHANGED
@@ -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
|
-
|
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
|
##########################################################################
|
@@ -191,8 +196,11 @@ module FatTable
|
|
191
196
|
|
192
197
|
# :category: Aggregates
|
193
198
|
|
194
|
-
# Return the first non-nil item in the Column
|
199
|
+
# Return the first non-nil item in the Column, or nil if all items are
|
200
|
+
# nil. Works with any Column type.
|
195
201
|
def first
|
202
|
+
return nil if items.all?(&:nil?)
|
203
|
+
|
196
204
|
if type == 'String'
|
197
205
|
items.reject(&:blank?).first
|
198
206
|
else
|
@@ -204,6 +212,8 @@ module FatTable
|
|
204
212
|
|
205
213
|
# Return the last non-nil item in the Column. Works with any Column type.
|
206
214
|
def last
|
215
|
+
return nil if items.all?(&:nil?)
|
216
|
+
|
207
217
|
if type == 'String'
|
208
218
|
items.reject(&:blank?).last
|
209
219
|
else
|
@@ -213,9 +223,11 @@ module FatTable
|
|
213
223
|
|
214
224
|
# :category: Aggregates
|
215
225
|
|
216
|
-
# Return a count of the non-nil items in the Column
|
217
|
-
# type.
|
226
|
+
# Return a count of the non-nil items in the Column, or the size of the
|
227
|
+
# column if all items are nil. Works with any Column type.
|
218
228
|
def count
|
229
|
+
return items.size if items.all?(&:nil?)
|
230
|
+
|
219
231
|
if type == 'String'
|
220
232
|
items.reject(&:blank?).count.to_d
|
221
233
|
else
|
@@ -225,8 +237,8 @@ module FatTable
|
|
225
237
|
|
226
238
|
# :category: Aggregates
|
227
239
|
|
228
|
-
# Return the smallest non-nil, non-blank item in the Column
|
229
|
-
# numeric, string, and datetime Columns.
|
240
|
+
# Return the smallest non-nil, non-blank item in the Column, or nil if all
|
241
|
+
# items are nil. Works with numeric, string, and datetime Columns.
|
230
242
|
def min
|
231
243
|
only_with('min', 'NilClass', 'Numeric', 'String', 'DateTime')
|
232
244
|
if type == 'String'
|
@@ -238,8 +250,8 @@ module FatTable
|
|
238
250
|
|
239
251
|
# :category: Aggregates
|
240
252
|
|
241
|
-
# Return the largest non-nil, non-blank item in the Column
|
242
|
-
# numeric, string, and datetime Columns.
|
253
|
+
# Return the largest non-nil, non-blank item in the Column, or nil if all
|
254
|
+
# items are nil. Works with numeric, string, and datetime Columns.
|
243
255
|
def max
|
244
256
|
only_with('max', 'NilClass', 'Numeric', 'String', 'DateTime')
|
245
257
|
if type == 'String'
|
@@ -251,19 +263,24 @@ module FatTable
|
|
251
263
|
|
252
264
|
# :category: Aggregates
|
253
265
|
|
254
|
-
# Return a Range object for the smallest to largest value in the column
|
255
|
-
# Works with numeric, string, and datetime
|
266
|
+
# Return a Range object for the smallest to largest value in the column,
|
267
|
+
# or nil if all items are nil. Works with numeric, string, and datetime
|
268
|
+
# Columns.
|
256
269
|
def range
|
257
270
|
only_with('range', 'NilClass', 'Numeric', 'String', 'DateTime')
|
271
|
+
return nil if items.all?(&:nil?)
|
272
|
+
|
258
273
|
Range.new(min, max)
|
259
274
|
end
|
260
275
|
|
261
276
|
# :category: Aggregates
|
262
277
|
|
263
|
-
# Return the sum of the non-nil items in the Column
|
264
|
-
# string Columns. For a string Column, it
|
265
|
-
# the non-nil items.
|
278
|
+
# Return the sum of the non-nil items in the Column, or 0 if all items are
|
279
|
+
# nil. Works with numeric and string Columns. For a string Column, it
|
280
|
+
# will return the concatenation of the non-nil items.
|
266
281
|
def sum
|
282
|
+
return 0 if type == 'NilClass' || items.all?(&:nil?)
|
283
|
+
|
267
284
|
only_with('sum', 'Numeric', 'String')
|
268
285
|
if type == 'String'
|
269
286
|
items.reject(&:blank?).join(' ')
|
@@ -274,11 +291,13 @@ module FatTable
|
|
274
291
|
|
275
292
|
# :category: Aggregates
|
276
293
|
|
277
|
-
# Return the average value of the non-nil items in the Column
|
278
|
-
# numeric and datetime Columns. For datetime
|
279
|
-
# to its Julian day number, computes the
|
280
|
-
# average back to a DateTime.
|
294
|
+
# Return the average value of the non-nil items in the Column, or 0 if all
|
295
|
+
# items are nil. Works with numeric and datetime Columns. For datetime
|
296
|
+
# Columns, it converts each date to its Julian day number, computes the
|
297
|
+
# average, and then converts the average back to a DateTime.
|
281
298
|
def avg
|
299
|
+
return 0 if type == 'NilClass' || items.all?(&:nil?)
|
300
|
+
|
282
301
|
only_with('avg', 'DateTime', 'Numeric')
|
283
302
|
itms = items.filter_to_type(type)
|
284
303
|
size = itms.size.to_d
|
@@ -293,11 +312,14 @@ module FatTable
|
|
293
312
|
# :category: Aggregates
|
294
313
|
|
295
314
|
# Return the sample variance (the unbiased estimator of the population
|
296
|
-
# variance using a divisor of N-1) as the average squared deviation from
|
297
|
-
# mean, of the non-nil items in the Column
|
298
|
-
# Columns. For datetime Columns, it
|
299
|
-
# number and computes the variance of
|
315
|
+
# variance using a divisor of N-1) as the average squared deviation from
|
316
|
+
# the mean, of the non-nil items in the Column, or 0 if all items are
|
317
|
+
# nil. Works with numeric and datetime Columns. For datetime Columns, it
|
318
|
+
# converts each date to its Julian day number and computes the variance of
|
319
|
+
# those numbers.
|
300
320
|
def var
|
321
|
+
return 0 if type == 'NilClass' || items.all?(&:nil?)
|
322
|
+
|
301
323
|
only_with('var', 'DateTime', 'Numeric')
|
302
324
|
all_items =
|
303
325
|
if type == 'DateTime'
|
@@ -319,10 +341,13 @@ module FatTable
|
|
319
341
|
|
320
342
|
# Return the population variance (the biased estimator of the population
|
321
343
|
# variance using a divisor of N) as the average squared deviation from the
|
322
|
-
# mean, of the non-nil items in the Column
|
323
|
-
# Columns. For datetime Columns, it
|
324
|
-
# number and computes the variance of
|
344
|
+
# mean, of the non-nil items in the Column, or 0 if all items are
|
345
|
+
# nil. Works with numeric and datetime Columns. For datetime Columns, it
|
346
|
+
# converts each date to its Julian day number and computes the variance of
|
347
|
+
# those numbers.
|
325
348
|
def pvar
|
349
|
+
return 0 if type == 'NilClass' || items.all?(&:nil?)
|
350
|
+
|
326
351
|
only_with('var', 'DateTime', 'Numeric')
|
327
352
|
n = items.filter_to_type(type).size.to_d
|
328
353
|
return BigDecimal('0.0') if n <= 1
|
@@ -333,11 +358,13 @@ module FatTable
|
|
333
358
|
|
334
359
|
# Return the sample standard deviation (the unbiased estimator of the
|
335
360
|
# population standard deviation using a divisor of N-1) as the square root
|
336
|
-
# of the sample variance, of the non-nil items in the Column
|
337
|
-
# numeric and datetime Columns. For datetime
|
338
|
-
# to its Julian day number and computes the
|
339
|
-
# numbers.
|
361
|
+
# of the sample variance, of the non-nil items in the Column, or 0 if all
|
362
|
+
# items are nil. Works with numeric and datetime Columns. For datetime
|
363
|
+
# Columns, it converts each date to its Julian day number and computes the
|
364
|
+
# standard deviation of those numbers.
|
340
365
|
def dev
|
366
|
+
return 0 if type == 'NilClass' || items.all?(&:nil?)
|
367
|
+
|
341
368
|
only_with('dev', 'DateTime', 'Numeric')
|
342
369
|
var.sqrt(20)
|
343
370
|
end
|
@@ -345,12 +372,14 @@ module FatTable
|
|
345
372
|
# :category: Aggregates
|
346
373
|
|
347
374
|
# Return the population standard deviation (the biased estimator of the
|
348
|
-
# population standard deviation using a divisor of N) as the square root
|
349
|
-
# the population variance, of the non-nil items in the Column
|
350
|
-
# numeric and datetime Columns. For datetime
|
351
|
-
# to its Julian day number and computes the
|
352
|
-
# numbers.
|
375
|
+
# population standard deviation using a divisor of N) as the square root
|
376
|
+
# of the population variance, of the non-nil items in the Column, or 0 if
|
377
|
+
# all items are nil. Works with numeric and datetime Columns. For datetime
|
378
|
+
# Columns, it converts each date to its Julian day number and computes the
|
379
|
+
# standard deviation of those numbers.
|
353
380
|
def pdev
|
381
|
+
return 0 if type == 'NilClass' || items.all?(&:nil?)
|
382
|
+
|
354
383
|
only_with('dev', 'DateTime', 'Numeric')
|
355
384
|
Math.sqrt(pvar)
|
356
385
|
end
|
@@ -358,8 +387,10 @@ module FatTable
|
|
358
387
|
# :category: Aggregates
|
359
388
|
|
360
389
|
# Return true if any of the items in the Column are true; otherwise return
|
361
|
-
# false. Works only with boolean Columns.
|
390
|
+
# false, or false if all items are nil. Works only with boolean Columns.
|
362
391
|
def any?
|
392
|
+
return false if type == 'NilClass' || items.all?(&:nil?)
|
393
|
+
|
363
394
|
only_with('any?', 'Boolean')
|
364
395
|
items.filter_to_type(type).any?
|
365
396
|
end
|
@@ -367,17 +398,22 @@ module FatTable
|
|
367
398
|
# :category: Aggregates
|
368
399
|
|
369
400
|
# Return true if all of the items in the Column are true; otherwise return
|
370
|
-
# false. Works only with boolean Columns.
|
401
|
+
# false, or false if all items are nil. Works only with boolean Columns.
|
371
402
|
def all?
|
403
|
+
return false if type == 'NilClass' || items.all?(&:nil?)
|
404
|
+
|
372
405
|
only_with('all?', 'Boolean')
|
373
406
|
items.filter_to_type(type).all?
|
374
407
|
end
|
375
408
|
|
376
409
|
# :category: Aggregates
|
377
410
|
|
378
|
-
# Return true if none of the items in the Column are true; otherwise
|
379
|
-
# false. Works only with boolean
|
411
|
+
# Return true if none of the items in the Column are true; otherwise
|
412
|
+
# return false, or true if all items are nil. Works only with boolean
|
413
|
+
# Columns.
|
380
414
|
def none?
|
415
|
+
return true if type == 'NilClass' || items.all?(&:nil?)
|
416
|
+
|
381
417
|
only_with('none?', 'Boolean')
|
382
418
|
items.filter_to_type(type).none?
|
383
419
|
end
|
@@ -387,6 +423,8 @@ module FatTable
|
|
387
423
|
# Return true if precisely one of the items in the Column is true;
|
388
424
|
# otherwise return false. Works only with boolean Columns.
|
389
425
|
def one?
|
426
|
+
return false if type == 'NilClass' || items.all?(&:nil?)
|
427
|
+
|
390
428
|
only_with('one?', 'Boolean')
|
391
429
|
items.filter_to_type(type).one?
|
392
430
|
end
|
@@ -395,6 +433,7 @@ module FatTable
|
|
395
433
|
|
396
434
|
def only_with(agg, *valid_types)
|
397
435
|
return self if valid_types.include?(type)
|
436
|
+
|
398
437
|
msg = "aggregate '#{agg}' cannot be applied to a #{type} column"
|
399
438
|
raise UserError, msg
|
400
439
|
end
|
@@ -436,7 +475,12 @@ module FatTable
|
|
436
475
|
private
|
437
476
|
|
438
477
|
def convert_and_set_type(val)
|
439
|
-
|
478
|
+
begin
|
479
|
+
new_val = Convert.convert_to_type(val, type, tolerant: tolerant?)
|
480
|
+
rescue IncompatibleTypeError
|
481
|
+
err_msg = "attempt to add '#{val}' to column '#{header}' already typed as #{type}"
|
482
|
+
raise IncompatibleTypeError, err_msg
|
483
|
+
end
|
440
484
|
if new_val && (type == 'NilClass' || type == 'String')
|
441
485
|
@type =
|
442
486
|
if [true, false].include?(new_val)
|
data/lib/fat_table/convert.rb
CHANGED
@@ -36,8 +36,7 @@ module FatTable
|
|
36
36
|
else
|
37
37
|
new_val = convert_to_boolean(val)
|
38
38
|
if new_val.nil?
|
39
|
-
|
40
|
-
raise IncompatibleTypeError, msg
|
39
|
+
raise IncompatibleTypeError
|
41
40
|
end
|
42
41
|
new_val
|
43
42
|
end
|
@@ -47,8 +46,7 @@ module FatTable
|
|
47
46
|
else
|
48
47
|
new_val = convert_to_date_time(val)
|
49
48
|
if new_val.nil?
|
50
|
-
|
51
|
-
raise IncompatibleTypeError, msg
|
49
|
+
raise IncompatibleTypeError
|
52
50
|
end
|
53
51
|
new_val
|
54
52
|
end
|
@@ -58,8 +56,7 @@ module FatTable
|
|
58
56
|
else
|
59
57
|
new_val = convert_to_numeric(val)
|
60
58
|
if new_val.nil?
|
61
|
-
|
62
|
-
raise IncompatibleTypeError, msg
|
59
|
+
raise IncompatibleTypeError
|
63
60
|
end
|
64
61
|
new_val
|
65
62
|
end
|
@@ -82,8 +79,7 @@ module FatTable
|
|
82
79
|
else
|
83
80
|
new_val = convert_to_string(val)
|
84
81
|
if new_val.nil?
|
85
|
-
|
86
|
-
raise IncompatibleTypeError, msg
|
82
|
+
raise IncompatibleTypeError
|
87
83
|
end
|
88
84
|
new_val
|
89
85
|
end
|
data/lib/fat_table/footer.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module FatTable
|
4
4
|
class Footer
|
5
|
-
attr_reader :table, :
|
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
|
-
|
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
|
-
|
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,
|
155
|
-
column =
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
@@ -166,7 +166,7 @@ module FatTable
|
|
166
166
|
when String
|
167
167
|
begin
|
168
168
|
converted_val = Convert.convert_to_type(agg, column.type)
|
169
|
-
rescue UserError
|
169
|
+
rescue UserError, IncompatibleTypeError
|
170
170
|
converted_val = false
|
171
171
|
end
|
172
172
|
if converted_val
|
@@ -179,29 +179,77 @@ module FatTable
|
|
179
179
|
when Proc
|
180
180
|
result =
|
181
181
|
if group
|
182
|
-
|
183
|
-
|
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
|
-
|
189
|
-
|
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
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
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
|
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
|
-
|
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
|
259
|
-
#
|
260
|
-
|
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
|
|
@@ -1004,9 +1120,14 @@ module FatTable
|
|
1004
1120
|
# converted to strings formatted according to the Formatter's formatting
|
1005
1121
|
# directives given in Formatter.format_for or Formatter.format.
|
1006
1122
|
def output
|
1007
|
-
#
|
1008
|
-
#
|
1009
|
-
|
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.
|
1010
1131
|
formatted_headers = build_formatted_headers
|
1011
1132
|
|
1012
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
|
data/lib/fat_table/table.rb
CHANGED
@@ -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/, ''),
|
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
|
-
|
123
|
+
dup.__empty!
|
124
124
|
end
|
125
125
|
|
126
126
|
def __empty!
|
@@ -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
|
-
|
534
|
-
|
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
|
|
data/lib/fat_table/version.rb
CHANGED
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.
|
4
|
+
version: 0.6.1
|
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-
|
11
|
+
date: 2022-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|