fat_table 0.2.4 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/ddoherty03/fat_table.svg?branch=master)](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.
|