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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 772c4f7d95d5af94968f650b4bc98ebad4756626
4
- data.tar.gz: c15462e9a444de246690482da1a8acd3a46e0c60
3
+ metadata.gz: fc10b9aadaada3eb303831288c6cc65d9362a5c6
4
+ data.tar.gz: 1a4c9e66382b2879ca6306f955d7aba5c4b9eef1
5
5
  SHA512:
6
- metadata.gz: 4f1177b1568ece2027849b102f2e7db84db99846e7291882f943fba5fd400b75032847df303c528fada0683afb2fb5b0bc97a2a91f2e52ed74cbd6960da07ac2
7
- data.tar.gz: 813ddb4b57d893ce9bfb0ca17bb6d03d8807fa361b166396d0635fc5d23e70f4c1641fb69b0f3973678faa2e1bb87667de6bca4b8ff3c0033ceba38db0168658
6
+ metadata.gz: 2f263f677bdd964642a6700c5c139d689184059ee994e551941dba6fac2d1d3bc3830ac63c7b28e526356b0166ee63c255ffac00769a38ee6e71fba090216b9f
7
+ data.tar.gz: 5d02b955317232d48a78f8ddb73632a4aaea5c49da31f72a808303a362c295203b7f6d508b55f03b14687953b72c7870a840844ff2779bb0cbc1ae16a966bd22
data/.gitignore CHANGED
@@ -20,3 +20,6 @@
20
20
  /lib/GPATH
21
21
  /lib/GRTAGS
22
22
  /lib/GTAGS
23
+ /GPATH
24
+ /GRTAGS
25
+ /GTAGS
@@ -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.3.0
5
- before_install: gem install bundler -v 1.14.3
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 can output as an
32
- array of arrays with its ~.to_aoa~ output function and will be rendered in an
33
- org-mode buffer as an org-table, ready for processing by other code blocks.
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' will
248
- be interpreted as a ~DateTime~ or a ~Date~ (if there are no sub-day time
249
- components present). The number of digits in the month and day can be one
250
- or two, but the year component must be four digits. Any time components are
251
- valid if they can be properly interpreted by ~DateTime.parse~. Org mode
252
- timestamps, active or inactive, are valid input strings for DateTime
253
- columns. Empty strings in a column already having the DateTime type are
254
- converted to nil.
255
- - Numeric :: all commas ',', underscores, '_', and '$' dollar signs are removed
256
- from the string and if the remaining string can be interpreted as a
257
- ~Numeric~, it will be. It is interpreted as an ~Integer~ if there are no
258
- decimal places in the remaining string, as a ~Rational~ if the string has
259
- the form '<number>:<number>' or '<number>/<number>', or as a ~BigDecimal~
260
- if there is a decimal point in the remaining string. Empty strings in a
261
- column already having the Numeric type are converted to nil.
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 ~dbi~ gem to query databases. You must first set 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.
@@ -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 'dbi'
78
- spec.add_runtime_dependency 'dbd-pg'
79
- spec.add_runtime_dependency 'dbd-mysql'
80
- spec.add_runtime_dependency 'dbd-sqlite3'
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
@@ -10,12 +10,13 @@ module FatTable
10
10
  require 'fat_core/hash'
11
11
  require 'fat_core/numeric'
12
12
  require 'csv'
13
- require 'dbi'
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'
@@ -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
- return nil unless val =~ %r{\b\d\d\d\d[-/]\d\d?[-/]\d\d?\b}
489
- val = DateTime.parse(val.to_s.clean)
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 /\A(\d+\.\d*)|(\d*\.\d+)\z/
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(\d+)\s*[:/]\s*(\d+)\z}
521
+ when %r{\A([-+]?\d+)\s*[:/]\s*([-+]?\d+)\z}
514
522
  Rational($1, $2)
515
523
  end
516
524
  end
@@ -1,18 +1,25 @@
1
1
  module FatTable
2
2
  class << self;
3
- # The +DBI+ database handle returned by the last successful call to
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. All of
10
- # the keyword parameters have a default except +database:+, which must contain
11
- # the name of the database to query.
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 'Pg' (for Postgresql), 'Mysql' (for Mysql), or 'SQLite3' (for
15
- # SQLite3) to specify the +DBI+ driver to use. You may have to install 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 DBI driver being used.
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 DBI driver being used.
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 DBI is return.
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(driver: 'Pg',
52
+ def self.set_db(db: nil,
53
+ driver: 'postgres',
46
54
  database:,
47
- user: nil,
55
+ user: ENV['LOGNAME'],
48
56
  password: nil,
49
57
  host: 'localhost',
50
58
  port: '5432',
51
59
  socket: '/tmp/.s.PGSQL.5432')
52
- raise UserError, 'must supply database name to set_db' unless database
60
+ if db
61
+ self.handle = db
62
+ else
63
+ raise UserError, 'must supply database name to set_db' unless database
53
64
 
54
- valid_drivers = ['Pg', 'Mysql', 'SQLite3']
55
- unless valid_drivers.include?(driver)
56
- raise UserError, "set_db driver must be one of #{valid_drivers.join(' or ')}"
57
- end
58
- # In case port is given as an integer
59
- port = port.to_s if port
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
- # Set the dsn for DBI
62
- dsn =
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
- "DBI:Pg:database=#{database};host=#{host};port=#{port}"
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 +DBI+ database handle as returned by the last call to
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
@@ -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(vars: {}, before: nil, after: nil)
20
+ def initialize(ivars: {}, before: nil, after: nil)
21
21
  @before = before
22
22
  @after = after
23
- set_instance_vars(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. Call any +before+
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(@before, bdg) if @before
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
@@ -60,7 +60,8 @@ module FatTable
60
60
  end
61
61
 
62
62
  def strip_ansi(str)
63
- str&.gsub(/\e\[[0-9;]+m/, '')
63
+ return '' unless str
64
+ str.gsub(/\e\[[0-9;]+m/, '')
64
65
  end
65
66
 
66
67
  # Add ANSI codes to string to implement the given decorations
@@ -0,0 +1,16 @@
1
+ unless {a: 1}.respond_to?(:fetch_values)
2
+ class Hash
3
+ def fetch_values(*keys)
4
+ result = []
5
+ keys.each do |k|
6
+ result <<
7
+ if block_given?
8
+ yield(self[k])
9
+ else
10
+ self[k]
11
+ end
12
+ end
13
+ result
14
+ end
15
+ end
16
+ end
@@ -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
- sth = FatTable.db.prepare(query)
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, 'Hash parameters to select must be a symbol or string'
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(vars: { row: 0 },
679
- before: '@row = __row; @group = __group')
756
+ ev = Evaluator.new(ivars: { row: 0, group: 0 },
757
+ before: '@row += 1')
680
758
  rows.each_with_index do |row, k|
681
- vars = row.dup
682
- vars[:__row] = k + 1
683
- vars[:__group] = row_index_to_group_index(k)
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
@@ -1,4 +1,4 @@
1
1
  module FatTable
2
2
  # The current version of FatTable
3
- VERSION = '0.2.4'.freeze
3
+ VERSION = '0.2.6'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fat_table
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
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-05-23 00:00:00.000000000 Z
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: dbi
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: dbd-pg
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: dbd-mysql
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: dbd-sqlite3
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.5.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.