fat_table 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|