fat_table 0.2.4 → 0.2.6
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/.gitignore +3 -0
- data/.travis.yml +15 -3
- data/README.org +109 -20
- data/fat_table.gemspec +5 -4
- data/lib/fat_table.rb +2 -1
- data/lib/fat_table/column.rb +13 -5
- data/lib/fat_table/db_handle.rb +53 -31
- data/lib/fat_table/evaluator.rb +22 -9
- data/lib/fat_table/formatters/term_formatter.rb +2 -1
- data/lib/fat_table/patches.rb +16 -0
- data/lib/fat_table/table.rb +93 -14
- data/lib/fat_table/version.rb +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc10b9aadaada3eb303831288c6cc65d9362a5c6
|
4
|
+
data.tar.gz: 1a4c9e66382b2879ca6306f955d7aba5c4b9eef1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f263f677bdd964642a6700c5c139d689184059ee994e551941dba6fac2d1d3bc3830ac63c7b28e526356b0166ee63c255ffac00769a38ee6e71fba090216b9f
|
7
|
+
data.tar.gz: 5d02b955317232d48a78f8ddb73632a4aaea5c49da31f72a808303a362c295203b7f6d508b55f03b14687953b72c7870a840844ff2779bb0cbc1ae16a966bd22
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
-
sudo: false
|
2
1
|
language: ruby
|
2
|
+
before_install:
|
3
|
+
- sudo apt-get -qq update
|
4
|
+
- sudo apt-get install -y texlive-latex-base texlive-latex-recommended
|
5
|
+
after_failure:
|
6
|
+
- "pwd"
|
7
|
+
- "cat ./spec/tmp/latex.err"
|
8
|
+
- "cat ./spec/tmp/example1.log"
|
9
|
+
- "cat ./spec/tmp/psql.out"
|
10
|
+
bundler_args: --without debug
|
11
|
+
services:
|
12
|
+
- postgresql
|
3
13
|
rvm:
|
4
|
-
- 2.
|
5
|
-
|
14
|
+
- 2.2.2
|
15
|
+
- 2.3
|
16
|
+
- 2.4
|
17
|
+
- ruby-head
|
data/README.org
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
#+OPTIONS: :toc
|
2
2
|
#+LATEX_HEADER: \usepackage[margin=0.75in]{geometry}
|
3
3
|
|
4
|
+
#+BEGIN_COMMENT
|
5
|
+
[](https://travis-ci.org/ddoherty03/fat_table)
|
6
|
+
#+END_COMMENT
|
7
|
+
|
8
|
+
[[https://travis-ci.org/ddoherty03/fat_table.svg?branch=master]]
|
9
|
+
|
4
10
|
* Introduction
|
5
11
|
|
6
12
|
~FatTable~ is a gem that treats tables as a data type. It provides methods for
|
@@ -28,9 +34,9 @@ as a foundation for providing reporting functions where flexibility about the
|
|
28
34
|
output medium can be quite useful. Finally ~FatTable~ can be used within Emacs
|
29
35
|
~org-mode~ files in code blocks targeting the Ruby language. Org mode tables are
|
30
36
|
presented to a ruby code block as an array of arrays, so ~FatTable~ can read
|
31
|
-
them in with its ~.from_aoa~ constructor. A ~FatTable~ table
|
32
|
-
|
33
|
-
|
37
|
+
them in with its ~.from_aoa~ constructor. A ~FatTable~ table output as an array
|
38
|
+
of arrays with its ~.to_aoa~ output function will be rendered in an org-mode
|
39
|
+
buffer as an org-table, ready for processing by other code blocks.
|
34
40
|
|
35
41
|
* Installation
|
36
42
|
|
@@ -244,21 +250,24 @@ types as follows:
|
|
244
250
|
regardless of case, are interpreted as ~FalseClass~, in either case
|
245
251
|
resulting in a Boolean column. Empty strings in a column already having a
|
246
252
|
Boolean type are converted to nil.
|
247
|
-
- DateTime :: strings that contain patterns of 'yyyy-mm-dd' or 'yyyy/mm/dd'
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
the
|
260
|
-
|
261
|
-
|
253
|
+
- DateTime :: strings that contain patterns of 'yyyy-mm-dd' or 'yyyy/mm/dd' or
|
254
|
+
'mm-dd-yyy' or 'mm/dd/yyyy' or any of the foregoing with an added
|
255
|
+
'Thh:mm:ss' or 'Thh:mm' will be interpreted as a ~DateTime~ or a ~Date~ (if
|
256
|
+
there are no sub-day time components present). The number of digits in the
|
257
|
+
month and day can be one or two, but the year component must be four
|
258
|
+
digits. Any time components are valid if they can be properly interpreted
|
259
|
+
by ~DateTime.parse~. Org mode timestamps (any of the foregoing surrounded
|
260
|
+
by square '[]' or pointy '<>' brackets), active or inactive, are valid
|
261
|
+
input strings for DateTime columns. Empty strings in a column already
|
262
|
+
having the DateTime type are converted to nil.
|
263
|
+
- Numeric :: all commas ',', underscores, '_', and '$' dollar signs (or other
|
264
|
+
currency symbol as set by ~FatTable.currency_symbol~ are removed from the
|
265
|
+
string and if the remaining string can be interpreted as a ~Numeric~, it
|
266
|
+
will be. It is interpreted as an ~Integer~ if there are no decimal places
|
267
|
+
in the remaining string, as a ~Rational~ if the string has the form
|
268
|
+
'<number>:<number>' or '<number>/<number>', or as a ~BigDecimal~ if there
|
269
|
+
is a decimal point in the remaining string. Empty strings in a column
|
270
|
+
already having the Numeric type are converted to nil.
|
262
271
|
- String :: if all else fails, ~FatTable~ applies ~#to_s~ to the input value
|
263
272
|
and, treats it as an item of type ~String~. Empty strings in a column
|
264
273
|
already having the String type are kept as empty strings.
|
@@ -497,10 +506,11 @@ or strings that can parsed into one of the permissible column types.
|
|
497
506
|
*** From SQL queries
|
498
507
|
|
499
508
|
Another way to initialize a ~FatTable~ table is with the results of a SQL query.
|
500
|
-
~FatTable~ uses the ~
|
509
|
+
~FatTable~ uses the ~sequel~ gem to query databases. You must first set the
|
501
510
|
database parameters to be used for the queries.
|
502
511
|
|
503
512
|
#+BEGIN_SRC ruby
|
513
|
+
# This automatically requires sequel.
|
504
514
|
require 'fat_table'
|
505
515
|
FatTable.set_db(driver: 'Pg',
|
506
516
|
database: 'XXX_development',
|
@@ -522,6 +532,19 @@ The ~.set_db~ function need only be called once, and the database handle it
|
|
522
532
|
creates will be used for all subsequent ~.from_sql~ calls until ~.set_db~ is
|
523
533
|
called again.
|
524
534
|
|
535
|
+
Alternatively, you can build the ~Sequel~ connection with ~Sequel.connect~ or
|
536
|
+
with adapter-specific ~Sequel~ connection methods and let ~FatTable~ know to use
|
537
|
+
that connection:
|
538
|
+
|
539
|
+
#+BEGIN_SRC ruby
|
540
|
+
require 'fat_table'
|
541
|
+
FatTable.db = Sequel.connect('postgres://user:password@localhost/dbname')
|
542
|
+
FatTable.db = Sequel.ado(conn_string: 'Provider=Microsoft.ACE.OLEDB.12.0;Data Source=drive:\path\filename.accdb')
|
543
|
+
#+END_SRC
|
544
|
+
|
545
|
+
Consult ~Sequel's~ documentation for details on its connection methods.
|
546
|
+
[[http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html]]
|
547
|
+
|
525
548
|
*** Marking Groups in Input
|
526
549
|
|
527
550
|
The ~.from_aoa~ and ~.from_aoh~ functions take an optional keyword parameter
|
@@ -679,6 +702,8 @@ With the ~select~ method, you can select which existing columns should appear in
|
|
679
702
|
the output table and create new columns in the output table that are a function
|
680
703
|
of existing and new columns.
|
681
704
|
|
705
|
+
**** Selecting Existing Columns
|
706
|
+
|
682
707
|
Here we select three existing columns by simply passing header symbols in the
|
683
708
|
order we want them to appear in the output. Thus, one use of =select= is to
|
684
709
|
filter and permute the order of existing columns. The =select= method preserves
|
@@ -716,6 +741,8 @@ any group boundaries present in the input table.
|
|
716
741
|
| 8.25 | T016 | 100 |
|
717
742
|
#+END_EXAMPLE
|
718
743
|
|
744
|
+
**** Adding New Columns
|
745
|
+
|
719
746
|
More interesting is that ~select~ can take hash-like keyword arguments following
|
720
747
|
all of the symbol arguments to create new columns in the output as functions of
|
721
748
|
other columns. For each hash-like parameter, the keyword given must be a symbol,
|
@@ -810,9 +837,71 @@ second, chained call to ~select~:
|
|
810
837
|
| T016 | 2016-11-02 | 8.25 | 100 | 825.0 |
|
811
838
|
#+END_EXAMPLE
|
812
839
|
|
840
|
+
**** Custom Instance Variables and Hooks
|
841
|
+
|
842
|
+
As the above examples demonstrate, the instance variables ~@row~ and ~@group~
|
843
|
+
are available when evaluating expressions that add new columns. You can set up
|
844
|
+
your own instance variables as well for keeping track of things that cross row
|
845
|
+
boundaries, such as running sums.
|
846
|
+
|
847
|
+
To declare instance variables, you can use the ~ivars:~ hash parameter to
|
848
|
+
~select~. Each key of the hash becomes an instance variable and each value
|
849
|
+
becomes its initial value before any rows are evaluated.
|
850
|
+
|
851
|
+
In addition, you can provide ~before_hook:~ and ~after_hook:~ parameters as
|
852
|
+
strings that are evaluated as ruby expressions before and after each row is
|
853
|
+
processed. You can use these to update instance variables. The values set in
|
854
|
+
the ~before_hook:~ can be used in expressions for adding new columns by
|
855
|
+
referencing them with the '@' prefix.
|
856
|
+
|
857
|
+
For example, suppose we wanted to not only add a cost column, but a column that
|
858
|
+
shows the cumulative cost after each transaction in our example table. The
|
859
|
+
following example uses the ~ivars:~ and ~before_hook:~ parameters to keep track
|
860
|
+
of the running cost of shares, then formats the table.
|
861
|
+
|
862
|
+
#+HEADER: :colnames no :session readme :hlines yes :wrap EXAMPLE :exports both
|
863
|
+
#+BEGIN_SRC ruby
|
864
|
+
tab = tab1.select(:ref, :price, :shares, traded_on: :date, \
|
865
|
+
cost: 'price * shares', cumulative: '@total_cost', \
|
866
|
+
ivars: { total_cost: 0 }, \
|
867
|
+
before_hook: '@total_cost += price * shares')
|
868
|
+
FatTable.to_aoa(tab) do |f|
|
869
|
+
f.format(price: '0.4', shares: '0.0,', cost: '0.2,', cumulative: '0.2,')
|
870
|
+
end
|
871
|
+
#+END_SRC
|
872
|
+
|
873
|
+
#+BEGIN_EXAMPLE
|
874
|
+
| Ref | Price | Shares | Traded On | Cost | Cumulative |
|
875
|
+
|------+--------+--------+------------+------------+------------|
|
876
|
+
| T001 | 7.7000 | 100 | 2016-11-01 | 770.00 | 770.00 |
|
877
|
+
| T002 | 7.7500 | 200 | 2016-11-01 | 1,550.00 | 2,320.00 |
|
878
|
+
| T003 | 7.5000 | 800 | 2016-11-01 | 6,000.00 | 8,320.00 |
|
879
|
+
| T003 | 7.5000 | 800 | 2016-11-01 | 6,000.00 | 14,320.00 |
|
880
|
+
|------+--------+--------+------------+------------+------------|
|
881
|
+
| T004 | 7.5500 | 6,811 | 2016-11-01 | 51,423.05 | 65,743.05 |
|
882
|
+
| T005 | 7.5000 | 4,000 | 2016-11-01 | 30,000.00 | 95,743.05 |
|
883
|
+
| T006 | 7.6000 | 1,000 | 2016-11-01 | 7,600.00 | 103,343.05 |
|
884
|
+
| T006 | 7.6000 | 1,000 | 2016-11-01 | 7,600.00 | 110,943.05 |
|
885
|
+
| T007 | 7.6500 | 200 | 2016-11-01 | 1,530.00 | 112,473.05 |
|
886
|
+
| T008 | 7.6500 | 2,771 | 2016-11-01 | 21,198.15 | 133,671.20 |
|
887
|
+
| T009 | 7.6000 | 9,550 | 2016-11-01 | 72,580.00 | 206,251.20 |
|
888
|
+
|------+--------+--------+------------+------------+------------|
|
889
|
+
| T010 | 7.5500 | 3,175 | 2016-11-01 | 23,971.25 | 230,222.45 |
|
890
|
+
| T011 | 7.4250 | 100 | 2016-11-02 | 742.50 | 230,964.95 |
|
891
|
+
| T012 | 7.5500 | 4,700 | 2016-11-02 | 35,485.00 | 266,449.95 |
|
892
|
+
| T012 | 7.5500 | 4,700 | 2016-11-02 | 35,485.00 | 301,934.95 |
|
893
|
+
| T013 | 7.3500 | 53,100 | 2016-11-02 | 390,285.00 | 692,219.95 |
|
894
|
+
|------+--------+--------+------------+------------+------------|
|
895
|
+
| T014 | 7.4500 | 5,847 | 2016-11-02 | 43,560.15 | 735,780.10 |
|
896
|
+
| T015 | 7.7500 | 500 | 2016-11-02 | 3,875.00 | 739,655.10 |
|
897
|
+
| T016 | 8.2500 | 100 | 2016-11-02 | 825.00 | 740,480.10 |
|
898
|
+
#+END_EXAMPLE
|
899
|
+
|
900
|
+
**** Argument Order and Boundaries
|
813
901
|
|
814
902
|
Notice that ~select~ can take any number of arguments but all the symbol
|
815
|
-
arguments must come first followed by all the hash-like keyword arguments
|
903
|
+
arguments must come first followed by all the hash-like keyword arguments,
|
904
|
+
including the special arguments for instance variables and hooks.
|
816
905
|
|
817
906
|
As the example illustrates, ~.select~ transmits any group boundaries in its
|
818
907
|
input table to the result table.
|
data/fat_table.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'fat_table/version'
|
@@ -74,8 +75,8 @@ org-mode buffer as an org-table, ready for processing by other code blocks.
|
|
74
75
|
spec.add_runtime_dependency 'fat_core', '~> 4.0', '>= 4.1'
|
75
76
|
spec.add_runtime_dependency 'activesupport'
|
76
77
|
spec.add_runtime_dependency 'rainbow'
|
77
|
-
spec.add_runtime_dependency '
|
78
|
-
spec.add_runtime_dependency '
|
79
|
-
spec.add_runtime_dependency '
|
80
|
-
spec.add_runtime_dependency '
|
78
|
+
spec.add_runtime_dependency 'sequel'
|
79
|
+
spec.add_runtime_dependency 'pg'
|
80
|
+
spec.add_runtime_dependency 'sqlite3'
|
81
|
+
spec.add_runtime_dependency 'mysql2'
|
81
82
|
end
|
data/lib/fat_table.rb
CHANGED
@@ -10,12 +10,13 @@ module FatTable
|
|
10
10
|
require 'fat_core/hash'
|
11
11
|
require 'fat_core/numeric'
|
12
12
|
require 'csv'
|
13
|
-
require '
|
13
|
+
require 'sequel'
|
14
14
|
require 'active_support'
|
15
15
|
require 'active_support/core_ext'
|
16
16
|
require 'active_support/number_helper'
|
17
17
|
|
18
18
|
require 'fat_table/version'
|
19
|
+
require 'fat_table/patches'
|
19
20
|
require 'fat_table/evaluator'
|
20
21
|
require 'fat_table/column'
|
21
22
|
require 'fat_table/table'
|
data/lib/fat_table/column.rb
CHANGED
@@ -473,6 +473,9 @@ module FatTable
|
|
473
473
|
end
|
474
474
|
end
|
475
475
|
|
476
|
+
IS0_DATE_RE = %r{\b(\d\d\d\d)[-/](\d\d?)[-/](\d\d?)\s*(T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?\b}
|
477
|
+
AMR_DATE_RE = %r{\b(\d\d?)[-/](\d\d?)[-/](\d\d\d\d)\s*(T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?\b}
|
478
|
+
|
476
479
|
# Convert the val to a DateTime if it is either a DateTime, a Date, or a
|
477
480
|
# String that can be parsed as a DateTime, otherwise return nil. It only
|
478
481
|
# recognizes strings that contain a something like '2016-01-14' or
|
@@ -485,8 +488,13 @@ module FatTable
|
|
485
488
|
begin
|
486
489
|
val = val.to_s.clean
|
487
490
|
return nil if val.blank?
|
488
|
-
|
489
|
-
|
491
|
+
if val =~ IS0_DATE_RE
|
492
|
+
val = DateTime.parse(val)
|
493
|
+
elsif val =~ AMR_DATE_RE
|
494
|
+
val = DateTime.new($3.to_i, $1.to_i, $2.to_i)
|
495
|
+
else
|
496
|
+
return nil
|
497
|
+
end
|
490
498
|
val = val.to_date if val.seconds_since_midnight.zero?
|
491
499
|
val
|
492
500
|
rescue ArgumentError
|
@@ -506,11 +514,11 @@ module FatTable
|
|
506
514
|
val = val.to_s.clean.gsub(clean_re, '')
|
507
515
|
return nil if val.blank?
|
508
516
|
case val
|
509
|
-
when
|
517
|
+
when /(\A[-+]?\d+\.\d*\z)|(\A[-+]?\d*\.\d+\z)/
|
510
518
|
BigDecimal.new(val.to_s.clean)
|
511
|
-
when /\A[\d]+\z/
|
519
|
+
when /\A[-+]?[\d]+\z/
|
512
520
|
val.to_i
|
513
|
-
when %r{\A(
|
521
|
+
when %r{\A([-+]?\d+)\s*[:/]\s*([-+]?\d+)\z}
|
514
522
|
Rational($1, $2)
|
515
523
|
end
|
516
524
|
end
|
data/lib/fat_table/db_handle.rb
CHANGED
@@ -1,18 +1,25 @@
|
|
1
1
|
module FatTable
|
2
2
|
class << self;
|
3
|
-
# The +
|
4
|
-
# FatTable.set_db.
|
3
|
+
# The +Sequel+ database handle to use in calls to +FatTable.from_sql+.
|
5
4
|
attr_accessor :handle
|
6
5
|
end
|
7
6
|
|
8
|
-
# This method must be called before calling FatTable.from_sql or
|
9
|
-
# FatTable::Table.from_sql in order to specify the database to use.
|
10
|
-
#
|
11
|
-
#
|
7
|
+
# This method must be called before calling +FatTable.from_sql+ or
|
8
|
+
# +FatTable::Table.from_sql+ in order to specify the database to use.
|
9
|
+
#
|
10
|
+
# You can pass in a +Sequel+ connection with +db+, or have fat_table construct
|
11
|
+
# a uri from given components. In the latter case, all of the keyword
|
12
|
+
# parameters have a default except +database:+, which must contain the name of
|
13
|
+
# the database to query.
|
14
|
+
#
|
15
|
+
# +db+::
|
16
|
+
# Inject a Sequel connection constructed +Sequel.connect+ or one of
|
17
|
+
# Sequel's adapter-specific connection methods.
|
18
|
+
# http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html
|
12
19
|
#
|
13
20
|
# +driver+::
|
14
|
-
# One of '
|
15
|
-
# SQLite3) to specify the +
|
21
|
+
# One of 'pg' (for Postgresql), 'mysql' or 'mysql2' (for Mysql), or 'sqlite' (for
|
22
|
+
# SQLite3) to specify the +Sequel+ driver to use. You may have to install the
|
16
23
|
# driver to make this work. By default use 'Pg'.
|
17
24
|
#
|
18
25
|
# +database+::
|
@@ -20,11 +27,11 @@ module FatTable
|
|
20
27
|
#
|
21
28
|
# +user+::
|
22
29
|
# The user name to use for accessing the database. It defaults to nil,
|
23
|
-
# which may be interpreted as a default user by the
|
30
|
+
# which may be interpreted as a default user by the Sequel driver being used.
|
24
31
|
#
|
25
32
|
# +password+::
|
26
33
|
# The password to use for accessing the database. It defaults to nil,
|
27
|
-
# which may be interpreted as a default password by the
|
34
|
+
# which may be interpreted as a default password by the Sequel driver being used.
|
28
35
|
#
|
29
36
|
# +host+::
|
30
37
|
# The name of the host on which to look for the database connection,
|
@@ -38,44 +45,59 @@ module FatTable
|
|
38
45
|
# The socket to use to access the database if the host is 'localhost'.
|
39
46
|
# Defaults to the standard socket for the Pg driver, '/tmp/.s.PGSQL.5432'.
|
40
47
|
#
|
41
|
-
# If successful the database handle for
|
48
|
+
# If successful the database handle for Sequel is return.
|
42
49
|
# Once called successfully, this establishes the database handle to use for
|
43
50
|
# all subsequent calls to FatTable.from_sql or FatTable::Table.from_sql. You
|
44
51
|
# can then access the handle if needed with FatTable.db.
|
45
|
-
def self.set_db(
|
52
|
+
def self.set_db(db: nil,
|
53
|
+
driver: 'postgres',
|
46
54
|
database:,
|
47
|
-
user:
|
55
|
+
user: ENV['LOGNAME'],
|
48
56
|
password: nil,
|
49
57
|
host: 'localhost',
|
50
58
|
port: '5432',
|
51
59
|
socket: '/tmp/.s.PGSQL.5432')
|
52
|
-
|
60
|
+
if db
|
61
|
+
self.handle = db
|
62
|
+
else
|
63
|
+
raise UserError, 'must supply database name to set_db' unless database
|
53
64
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
65
|
+
valid_drivers = ['postgres', 'mysql', 'mysql2', 'sqlite']
|
66
|
+
unless valid_drivers.include?(driver)
|
67
|
+
raise UserError, "'#{driver}' driver must be one of #{valid_drivers.join(' or ')}"
|
68
|
+
end
|
69
|
+
if database.blank?
|
70
|
+
raise UserError, "must supply database parameter to set_db"
|
71
|
+
end
|
60
72
|
|
61
|
-
|
62
|
-
|
63
|
-
if host == 'localhost'
|
64
|
-
"DBI:Pg:database=#{database};host=#{host};socket=#{socket}"
|
73
|
+
if driver == 'sqlite'
|
74
|
+
dsn = "sqlite://#{database}"
|
65
75
|
else
|
66
|
-
"
|
76
|
+
pw_part = password ? ":#{password}" : ''
|
77
|
+
hst_part = host ? "@#{host}" : ''
|
78
|
+
prt_part = port ? ":#{port}" : ''
|
79
|
+
dsn = "#{driver}:://#{user}#{pw_part}#{hst_part}#{prt_part}/#{database}"
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set the dsn for Sequel
|
83
|
+
begin
|
84
|
+
# DB = Sequel.connect(dsn)
|
85
|
+
self.handle = Sequel.connect(dsn)
|
86
|
+
rescue => ex
|
87
|
+
raise TransientError, "#{dsn}: #{ex}"
|
67
88
|
end
|
68
|
-
begin
|
69
|
-
self.handle = ::DBI.connect(dsn, user, password)
|
70
|
-
rescue DBI::OperationalError => ex
|
71
|
-
raise TransientError, "#{dsn}: #{ex}"
|
72
89
|
end
|
73
90
|
handle
|
74
91
|
end
|
75
92
|
|
76
|
-
# Return the +
|
77
|
-
# FatTable.set_db.
|
93
|
+
# Return the +Sequel+ database handle.
|
78
94
|
def self.db
|
79
95
|
handle
|
80
96
|
end
|
97
|
+
|
98
|
+
# Directly set the db handle to a Sequel connection formed without
|
99
|
+
# FatTable.set_db.
|
100
|
+
def self.db=(db)
|
101
|
+
self.handle = db
|
102
|
+
end
|
81
103
|
end
|
data/lib/fat_table/evaluator.rb
CHANGED
@@ -17,24 +17,37 @@ module FatTable
|
|
17
17
|
# subsequent calls to Evaluator.evaluate. The strings +before+ and +after+
|
18
18
|
# are string expressions that will be evaluated before and after each
|
19
19
|
# subsequent call to Evaluator.evaluate.
|
20
|
-
def initialize(
|
20
|
+
def initialize(ivars: {}, before: nil, after: nil)
|
21
21
|
@before = before
|
22
22
|
@after = after
|
23
|
-
set_instance_vars(
|
23
|
+
set_instance_vars(ivars)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Run the before hook after setting the @group to grp, passed in as a
|
27
|
+
# parameter. This is run before any column has been evaluated with the
|
28
|
+
# evaluate method.
|
29
|
+
def eval_before_hook(vars)
|
30
|
+
bdg = binding
|
31
|
+
set_local_vars(vars, bdg)
|
32
|
+
@group = vars[:__group]
|
33
|
+
eval(@before, bdg) if @before
|
34
|
+
end
|
35
|
+
|
36
|
+
# Run the after hook. This is run after each column in the row has been
|
37
|
+
# evaluated with the evaluate method.
|
38
|
+
def eval_after_hook(vars)
|
39
|
+
bdg = binding
|
40
|
+
set_local_vars(vars, bdg)
|
41
|
+
eval(@after, bdg) if @after
|
24
42
|
end
|
25
43
|
|
26
44
|
# Return the result of evaluating +expr+ as a Ruby expression in which the
|
27
45
|
# instance variables set in Evaluator.new and any local variables set in the
|
28
|
-
# Hash parameter +vars+ are available to the expression.
|
29
|
-
# hook defined in Evaluator.new before evaluating +expr+ and any +after+
|
30
|
-
# hook defined in Evaluator.new after evaluating +expr+.
|
46
|
+
# Hash parameter +vars+ are available to the expression.
|
31
47
|
def evaluate(expr = '', vars: {})
|
32
48
|
bdg = binding
|
33
49
|
set_local_vars(vars, bdg)
|
34
|
-
eval(
|
35
|
-
result = eval(expr, bdg)
|
36
|
-
eval(@after, bdg) if @after
|
37
|
-
result
|
50
|
+
eval(expr, bdg)
|
38
51
|
end
|
39
52
|
|
40
53
|
private
|
data/lib/fat_table/table.rb
CHANGED
@@ -153,9 +153,7 @@ module FatTable
|
|
153
153
|
def self.from_sql(query)
|
154
154
|
raise UserError, 'FatTable.db must be set with FatTable.set_db' if FatTable.db.nil?
|
155
155
|
result = Table.new
|
156
|
-
|
157
|
-
sth.execute
|
158
|
-
sth.fetch_hash do |h|
|
156
|
+
FatTable.db[query].each do |h|
|
159
157
|
result << h
|
160
158
|
end
|
161
159
|
result
|
@@ -595,7 +593,7 @@ module FatTable
|
|
595
593
|
end
|
596
594
|
|
597
595
|
# :category: Operators
|
598
|
-
|
596
|
+
#
|
599
597
|
# Return a Table having the selected column expressions. Each expression can
|
600
598
|
# be either a
|
601
599
|
#
|
@@ -613,6 +611,12 @@ module FatTable
|
|
613
611
|
# access the instance variable @row, as the row number of the row being
|
614
612
|
# evaluated, and @group, as the group number of the row being evaluated.
|
615
613
|
#
|
614
|
+
# 4. a hash in +new_cols+ with one of the special keys, +ivars: {literal
|
615
|
+
# hash}+, +before_hook: 'ruby-code'+, or +after_hook: 'ruby-code'+ for
|
616
|
+
# defining custom instance variables to be used during evaluation of
|
617
|
+
# parameters described in point 3 and hooks of ruby code snippets to be
|
618
|
+
# evaluated before and after processing each row.
|
619
|
+
#
|
616
620
|
# The bare symbol arguments +cols+ (1) must precede any hash arguments
|
617
621
|
# +new_cols+ (2 or 3). Each expression results in a column in the resulting
|
618
622
|
# Table in the order given in the argument list. The expressions are
|
@@ -623,12 +627,84 @@ module FatTable
|
|
623
627
|
# tab.select(:ref, :date, shares: :quantity) => rename :shares->:quantity
|
624
628
|
# tab.select(:ref, :date, :shares, cost: 'price * shares') => new column
|
625
629
|
# tab.select(:ref, :date, :shares, seq: '@row') => add sequential nums
|
630
|
+
#
|
631
|
+
# The instance variables and hooks mentioned in point 4 above allow you to
|
632
|
+
# keep track of things that cross row boundaries, such as running sums or
|
633
|
+
# the values of columns before or after construction of the new row. You can
|
634
|
+
# define instance variables other than the default @row and @group variables
|
635
|
+
# to be available when evaluating normal string expressions for constructing
|
636
|
+
# a new row.
|
637
|
+
#
|
638
|
+
# You define custom instance variables by passing a Hash to the ivars
|
639
|
+
# parameter. The names of the instance variables will be the keys and their
|
640
|
+
# initial values will be the values. For example, you can keep track of a
|
641
|
+
# running sum of the cost of shares and the number of shares in the prior
|
642
|
+
# row by adding two custom instance variables and the appropriate hooks:
|
643
|
+
#
|
644
|
+
# tab.select(:ref, :date, :shares, :price,
|
645
|
+
# cost: 'shares * price', cumulative_cost: '@total_cost'
|
646
|
+
# ivars: { total_cost: 0, prior_shares: 0},
|
647
|
+
# before_hook: '@total_cost += shares * price,
|
648
|
+
# after_hook: '@prior_shares = shares')
|
649
|
+
#
|
650
|
+
# Notice that in the +ivars:+ parameter, the '@' is not prefixed to the name
|
651
|
+
# since it is a symbol, but must be prefixed when the instance variable is
|
652
|
+
# referenced in an expression, otherwise it would be interpreted as a column
|
653
|
+
# name. You could include the '@' if you use a string as a key, e.g., +{
|
654
|
+
# '@total_cost' => 0 }+ The ivars values are evaluated once, before the
|
655
|
+
# first row is processed with the select statement.
|
656
|
+
#
|
657
|
+
# For each row, the +before_hook+ is evaluated, then the +new_cols+
|
658
|
+
# expressions for setting the new value of columns, then the +after_hook+ is
|
659
|
+
# evaluated.
|
660
|
+
#
|
661
|
+
# In the before_hook, the values of all columns are available as local
|
662
|
+
# variables as they were before processing the row. The values of all
|
663
|
+
# instance variables are available as well with the values they had after
|
664
|
+
# processing the prior row of the table.
|
665
|
+
#
|
666
|
+
# In the string expressions for new columns, all the instance variables are
|
667
|
+
# available with the values they have after the before_hook is evaluated.
|
668
|
+
# You could also modify instance variables in the new_cols expression, but
|
669
|
+
# remember, they are evaluated once for each new column expression. Also,
|
670
|
+
# the new column is assigned the value of the entire expression, so you must
|
671
|
+
# ensure that the last expression is the one you want assigned to the new
|
672
|
+
# column. You might want to use a semicolon: +cost: '@total_cost += shares *
|
673
|
+
# price; shares * price'
|
674
|
+
#
|
675
|
+
# In the after_hook, the new, updated values of all columns, old and new are
|
676
|
+
# available as local variables, and the instance variables are available
|
677
|
+
# with the values they had after executing the before_hook.
|
626
678
|
def select(*cols, **new_cols)
|
679
|
+
# Set up the Evaluator
|
680
|
+
ivars = { row: 0, group: 0 }
|
681
|
+
if new_cols.key?(:ivars)
|
682
|
+
ivars = ivars.merge(new_cols[:ivars])
|
683
|
+
new_cols.delete(:ivars)
|
684
|
+
end
|
685
|
+
before_hook = '@row += 1'
|
686
|
+
if new_cols.key?(:before_hook)
|
687
|
+
before_hook += "; #{new_cols[:before_hook]}"
|
688
|
+
new_cols.delete(:before_hook)
|
689
|
+
end
|
690
|
+
after_hook = nil
|
691
|
+
if new_cols.key?(:after_hook)
|
692
|
+
after_hook = new_cols[:after_hook].to_s
|
693
|
+
new_cols.delete(:after_hook)
|
694
|
+
end
|
695
|
+
ev = Evaluator.new(ivars: ivars,
|
696
|
+
before: before_hook,
|
697
|
+
after: after_hook)
|
698
|
+
# Compute the new Table from this Table
|
627
699
|
result = Table.new
|
628
700
|
normalize_boundaries
|
629
|
-
ev = Evaluator.new(vars: { row: 0, group: 1 },
|
630
|
-
before: '@row = __row; @group = __group')
|
631
701
|
rows.each_with_index do |old_row, old_k|
|
702
|
+
# Set the group number in the before hook and run the hook with the
|
703
|
+
# local variables set to the row before the new row is evaluated.
|
704
|
+
grp = row_index_to_group_index(old_k)
|
705
|
+
vars = old_row.merge(__group: grp)
|
706
|
+
ev.eval_before_hook(vars)
|
707
|
+
# Compute the new row.
|
632
708
|
new_row = {}
|
633
709
|
cols.each do |k|
|
634
710
|
h = k.as_sym
|
@@ -638,8 +714,6 @@ module FatTable
|
|
638
714
|
new_cols.each_pair do |key, val|
|
639
715
|
key = key.as_sym
|
640
716
|
vars = old_row.merge(new_row)
|
641
|
-
vars[:__row] = old_k + 1
|
642
|
-
vars[:__group] = row_index_to_group_index(old_k)
|
643
717
|
case val
|
644
718
|
when Symbol
|
645
719
|
raise UserError, "Column '#{val}' in select does not exist" unless vars.keys.include?(val)
|
@@ -647,9 +721,13 @@ module FatTable
|
|
647
721
|
when String
|
648
722
|
new_row[key] = ev.evaluate(val, vars: vars)
|
649
723
|
else
|
650
|
-
raise UserError,
|
724
|
+
raise UserError, "Hash parameter '#{key}' to select must be a symbol or string"
|
651
725
|
end
|
652
726
|
end
|
727
|
+
# Set the group number and run the hook with the local variables set to
|
728
|
+
# the row after the new row is evaluated.
|
729
|
+
vars = new_row.merge(__group: grp)
|
730
|
+
ev.eval_after_hook(vars)
|
653
731
|
result << new_row
|
654
732
|
end
|
655
733
|
result.boundaries = boundaries
|
@@ -675,13 +753,14 @@ module FatTable
|
|
675
753
|
col = Column.new(header: h)
|
676
754
|
result.add_column(col)
|
677
755
|
end
|
678
|
-
ev = Evaluator.new(
|
679
|
-
before: '@row
|
756
|
+
ev = Evaluator.new(ivars: { row: 0, group: 0 },
|
757
|
+
before: '@row += 1')
|
680
758
|
rows.each_with_index do |row, k|
|
681
|
-
|
682
|
-
vars
|
683
|
-
vars
|
759
|
+
grp = row_index_to_group_index(k)
|
760
|
+
vars = row.merge(__group: grp)
|
761
|
+
ev.eval_before_hook(vars)
|
684
762
|
result << row if ev.evaluate(expr, vars: vars)
|
763
|
+
ev.eval_after_hook(vars)
|
685
764
|
end
|
686
765
|
result.normalize_boundaries
|
687
766
|
result
|
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.2.
|
4
|
+
version: 0.2.6
|
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: 2017-
|
11
|
+
date: 2017-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simplecov
|
@@ -185,7 +185,7 @@ dependencies:
|
|
185
185
|
- !ruby/object:Gem::Version
|
186
186
|
version: '0'
|
187
187
|
- !ruby/object:Gem::Dependency
|
188
|
-
name:
|
188
|
+
name: sequel
|
189
189
|
requirement: !ruby/object:Gem::Requirement
|
190
190
|
requirements:
|
191
191
|
- - ">="
|
@@ -199,7 +199,7 @@ dependencies:
|
|
199
199
|
- !ruby/object:Gem::Version
|
200
200
|
version: '0'
|
201
201
|
- !ruby/object:Gem::Dependency
|
202
|
-
name:
|
202
|
+
name: pg
|
203
203
|
requirement: !ruby/object:Gem::Requirement
|
204
204
|
requirements:
|
205
205
|
- - ">="
|
@@ -213,7 +213,7 @@ dependencies:
|
|
213
213
|
- !ruby/object:Gem::Version
|
214
214
|
version: '0'
|
215
215
|
- !ruby/object:Gem::Dependency
|
216
|
-
name:
|
216
|
+
name: sqlite3
|
217
217
|
requirement: !ruby/object:Gem::Requirement
|
218
218
|
requirements:
|
219
219
|
- - ">="
|
@@ -227,7 +227,7 @@ dependencies:
|
|
227
227
|
- !ruby/object:Gem::Version
|
228
228
|
version: '0'
|
229
229
|
- !ruby/object:Gem::Dependency
|
230
|
-
name:
|
230
|
+
name: mysql2
|
231
231
|
requirement: !ruby/object:Gem::Requirement
|
232
232
|
requirements:
|
233
233
|
- - ">="
|
@@ -303,6 +303,7 @@ files:
|
|
303
303
|
- lib/fat_table/formatters/org_formatter.rb
|
304
304
|
- lib/fat_table/formatters/term_formatter.rb
|
305
305
|
- lib/fat_table/formatters/text_formatter.rb
|
306
|
+
- lib/fat_table/patches.rb
|
306
307
|
- lib/fat_table/table.rb
|
307
308
|
- lib/fat_table/version.rb
|
308
309
|
homepage: https://github.com/ddoherty03/fat_table
|
@@ -326,7 +327,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
326
327
|
version: '0'
|
327
328
|
requirements: []
|
328
329
|
rubyforge_project:
|
329
|
-
rubygems_version: 2.
|
330
|
+
rubygems_version: 2.6.13
|
330
331
|
signing_key:
|
331
332
|
specification_version: 4
|
332
333
|
summary: Provides tools for working with tables as a data type.
|