fat_table 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.org +5 -5
- data/README.rdoc +4 -4
- data/lib/fat_table.rb +1 -1
- data/lib/fat_table/column.rb +10 -7
- data/lib/fat_table/db_handle.rb +2 -2
- data/lib/fat_table/evaluator.rb +9 -5
- data/lib/fat_table/formatters/aoa_formatter.rb +5 -5
- data/lib/fat_table/formatters/aoh_formatter.rb +6 -6
- data/lib/fat_table/formatters/formatter.rb +22 -8
- data/lib/fat_table/formatters/latex_formatter.rb +7 -5
- data/lib/fat_table/formatters/org_formatter.rb +3 -3
- data/lib/fat_table/formatters/term_formatter.rb +16 -13
- data/lib/fat_table/formatters/text_formatter.rb +3 -3
- data/lib/fat_table/patches.rb +3 -2
- data/lib/fat_table/table.rb +56 -34
- data/lib/fat_table/version.rb +1 -1
- data/md/README.md +4 -4
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af65227dda201238ba06b2296e548bcfbab9cc209ad66fc7e5efafe6bbcde8d5
|
4
|
+
data.tar.gz: f59e53d1ec7aa54a36e96d475f32d50e0d7b668a838476fe3e73f9cd091396f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e5d1f93e4c0fb505afd5f8f5e1c2cb918d31653a4c64ce2db63cd20733fdccd75649f756cb90597c761a61a26d4a76ddaf8a34f7f9f4db8029cd93db62dc70d
|
7
|
+
data.tar.gz: a6f8b68c21b13382cb6cff7b65f05ed8de120d6e1296ccf2376152da92ba30efbee5196d9b792642e8f3097ed57f11147cc5eac65a330acb4efe4719bae51a79
|
data/.travis.yml
CHANGED
data/README.org
CHANGED
@@ -533,7 +533,7 @@ database parameters to be used for the queries.
|
|
533
533
|
#+BEGIN_SRC ruby
|
534
534
|
# This automatically requires sequel.
|
535
535
|
require 'fat_table'
|
536
|
-
FatTable.
|
536
|
+
FatTable.connect(adapter: 'postgres',
|
537
537
|
database: 'XXX_development',
|
538
538
|
user: 'ken',
|
539
539
|
password: 'imsecret',
|
@@ -541,8 +541,8 @@ database parameters to be used for the queries.
|
|
541
541
|
tab = FatTable.from_sql('select * from trades;')
|
542
542
|
#+END_SRC
|
543
543
|
|
544
|
-
The arguments to ~
|
545
|
-
any set of arguments that work for it should work for ~
|
544
|
+
The arguments to ~connect~ are simply passed on to ~sequel~'s connect method, so
|
545
|
+
any set of arguments that work for it should work for ~connect~. Alternatively,
|
546
546
|
you can build the ~Sequel~ connection directly with ~Sequel.connect~ or with
|
547
547
|
adapter-specific ~Sequel~ connection methods and let ~FatTable~ know to use that
|
548
548
|
connection:
|
@@ -556,8 +556,8 @@ connection:
|
|
556
556
|
Consult ~Sequel's~ documentation for details on its connection methods.
|
557
557
|
[[http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html]]
|
558
558
|
|
559
|
-
The ~.
|
560
|
-
creates will be used for all subsequent ~.from_sql~ calls until ~.
|
559
|
+
The ~.connect~ function need only be called once, and the database handle it
|
560
|
+
creates will be used for all subsequent ~.from_sql~ calls until ~.connect~ is
|
561
561
|
called again.
|
562
562
|
|
563
563
|
*** Marking Groups in Input
|
data/README.rdoc
CHANGED
@@ -485,7 +485,7 @@ Another way to initialize a +FatTable+ table is with the results of a SQL query.
|
|
485
485
|
database parameters to be used for the queries.
|
486
486
|
|
487
487
|
require 'fat_table'
|
488
|
-
FatTable.
|
488
|
+
FatTable.connect(driver: 'Pg',
|
489
489
|
database: 'XXX_development',
|
490
490
|
user: 'dtd',
|
491
491
|
password: 'slflpowert',
|
@@ -493,15 +493,15 @@ database parameters to be used for the queries.
|
|
493
493
|
socket: '/tmp/.s.PGSQL.5432')
|
494
494
|
tab = FatTable.from_sql('select * from trades;')
|
495
495
|
|
496
|
-
Some of the parameters to the +.
|
496
|
+
Some of the parameters to the +.connect+ function have defaults. The driver
|
497
497
|
defaults to 'Pg' for postgresql and the socket defaults to +/tmp/.s.PGSQL.5432+
|
498
498
|
if the host is 'localhost', which it is by default. If the host is not
|
499
499
|
'localhost', the dsn uses a port rather than a socket and defaults to port
|
500
500
|
'5432'. While user and password default to nil, the database parameter is
|
501
501
|
required.
|
502
502
|
|
503
|
-
The +.
|
504
|
-
creates will be used for all subsequent +.from_sql+ calls until +.
|
503
|
+
The +.connect+ function need only be called once, and the database handle it
|
504
|
+
creates will be used for all subsequent +.from_sql+ calls until +.connect+ is
|
505
505
|
called again.
|
506
506
|
|
507
507
|
=== Marking Groups in Input
|
data/lib/fat_table.rb
CHANGED
@@ -114,7 +114,7 @@ module FatTable
|
|
114
114
|
end
|
115
115
|
|
116
116
|
# Construct a Table by running a SQL query against the database set up with
|
117
|
-
# FatTable.
|
117
|
+
# FatTable.connect. Return the Table with the query results as rows and the
|
118
118
|
# headers from the query, converted to symbols, as headers.
|
119
119
|
def self.from_sql(query)
|
120
120
|
Table.from_sql(query)
|
data/lib/fat_table/column.rb
CHANGED
@@ -21,7 +21,7 @@ module FatTable
|
|
21
21
|
attr_reader :type
|
22
22
|
|
23
23
|
# An Array of the items of this Column, all of which must be values of the
|
24
|
-
#
|
24
|
+
# Column's type or a nil. This Array contains the value of the item after
|
25
25
|
# conversion to a native Ruby type, such as TrueClass, Date, DateTime,
|
26
26
|
# Integer, String, etc. Thus, you can perform operations on the items,
|
27
27
|
# perhaps after removing nils with +.items.compact+.
|
@@ -92,6 +92,7 @@ module FatTable
|
|
92
92
|
@type = 'NilClass'
|
93
93
|
msg = "unknown column type '#{type}"
|
94
94
|
raise UserError, msg unless TYPES.include?(@type.to_s)
|
95
|
+
|
95
96
|
@items = []
|
96
97
|
items.each { |i| self << i }
|
97
98
|
end
|
@@ -103,8 +104,8 @@ module FatTable
|
|
103
104
|
# :category: Attributes
|
104
105
|
|
105
106
|
# Return the item of the Column at the given index.
|
106
|
-
def [](
|
107
|
-
items[
|
107
|
+
def [](idx)
|
108
|
+
items[idx]
|
108
109
|
end
|
109
110
|
|
110
111
|
# :category: Attributes
|
@@ -229,11 +230,13 @@ module FatTable
|
|
229
230
|
# average back to a DateTime.
|
230
231
|
def avg
|
231
232
|
only_with('avg', 'DateTime', 'Numeric')
|
233
|
+
itms = items.compact
|
234
|
+
size = itms.size.to_d
|
232
235
|
if type == 'DateTime'
|
233
|
-
avg_jd =
|
236
|
+
avg_jd = itms.map(&:jd).sum / size
|
234
237
|
DateTime.jd(avg_jd)
|
235
238
|
else
|
236
|
-
sum /
|
239
|
+
itms.sum / size
|
237
240
|
end
|
238
241
|
end
|
239
242
|
|
@@ -515,7 +518,7 @@ module FatTable
|
|
515
518
|
# looks like one. Any Float is promoted to a BigDecimal. Otherwise return
|
516
519
|
# nil.
|
517
520
|
def convert_to_numeric(val)
|
518
|
-
return BigDecimal
|
521
|
+
return BigDecimal(val, Float::DIG) if val.is_a?(Float)
|
519
522
|
return val if val.is_a?(Numeric)
|
520
523
|
# Eliminate any commas, $'s (or other currency symbol), or _'s.
|
521
524
|
cursym = Regexp.quote(FatTable.currency_symbol)
|
@@ -524,7 +527,7 @@ module FatTable
|
|
524
527
|
return nil if val.blank?
|
525
528
|
case val
|
526
529
|
when /(\A[-+]?\d+\.\d*\z)|(\A[-+]?\d*\.\d+\z)/
|
527
|
-
BigDecimal
|
530
|
+
BigDecimal(val.to_s.clean)
|
528
531
|
when /\A[-+]?[\d]+\z/
|
529
532
|
val.to_i
|
530
533
|
when %r{\A([-+]?\d+)\s*[:/]\s*([-+]?\d+)\z}
|
data/lib/fat_table/db_handle.rb
CHANGED
@@ -51,7 +51,7 @@ module FatTable
|
|
51
51
|
# successfully, this establishes the database handle to use for all subsequent
|
52
52
|
# calls to FatTable.from_sql or FatTable::Table.from_sql. You can then access
|
53
53
|
# the handle if needed with FatTable.db.
|
54
|
-
def self.
|
54
|
+
def self.connect(args)
|
55
55
|
# Set the dsn for Sequel
|
56
56
|
begin
|
57
57
|
self.handle = Sequel.connect(args)
|
@@ -67,7 +67,7 @@ module FatTable
|
|
67
67
|
end
|
68
68
|
|
69
69
|
# Directly set the db handle to a Sequel connection formed without
|
70
|
-
# FatTable.
|
70
|
+
# FatTable.connect.
|
71
71
|
def self.db=(db)
|
72
72
|
self.handle = db
|
73
73
|
end
|
data/lib/fat_table/evaluator.rb
CHANGED
@@ -20,23 +20,25 @@ module FatTable
|
|
20
20
|
def initialize(ivars: {}, before: nil, after: nil)
|
21
21
|
@before = before
|
22
22
|
@after = after
|
23
|
-
|
23
|
+
instance_vars(ivars)
|
24
24
|
end
|
25
25
|
|
26
26
|
# Set the @group instance variable to the given value.
|
27
27
|
def update_ivars(ivars)
|
28
|
-
|
28
|
+
instance_vars(ivars)
|
29
29
|
end
|
30
30
|
|
31
31
|
# Run any before hook in the context of the given local variables.
|
32
32
|
def eval_before_hook(locals: {})
|
33
33
|
return if @before.blank?
|
34
|
+
|
34
35
|
evaluate(@before, locals: locals)
|
35
36
|
end
|
36
37
|
|
37
38
|
# Run any after hook in the context of the given local variables.
|
38
39
|
def eval_after_hook(locals: {})
|
39
40
|
return if @after.blank?
|
41
|
+
|
40
42
|
evaluate(@after, locals: locals)
|
41
43
|
end
|
42
44
|
|
@@ -44,19 +46,21 @@ module FatTable
|
|
44
46
|
# instance variables set in Evaluator.new and any local variables set in the
|
45
47
|
# Hash parameter +locals+ are available to the expression.
|
46
48
|
def evaluate(expr = '', locals: {})
|
47
|
-
eval(expr,
|
49
|
+
eval(expr, local_vars(binding, locals))
|
48
50
|
end
|
49
51
|
|
50
52
|
private
|
51
53
|
|
52
|
-
|
54
|
+
# Set the instance variables according to Hash vars.
|
55
|
+
def instance_vars(vars = {})
|
53
56
|
vars.each_pair do |name, val|
|
54
57
|
name = "@#{name}" unless name.to_s.start_with?('@')
|
55
58
|
instance_variable_set(name, val)
|
56
59
|
end
|
57
60
|
end
|
58
61
|
|
59
|
-
|
62
|
+
# Set the local variables within the binding bnd according to Hash vars.
|
63
|
+
def local_vars(bnd, vars = {})
|
60
64
|
vars.each_pair do |name, val|
|
61
65
|
bnd.local_variable_set(name, val)
|
62
66
|
end
|
@@ -30,20 +30,20 @@ module FatTable
|
|
30
30
|
'['
|
31
31
|
end
|
32
32
|
|
33
|
-
def pre_cell(
|
33
|
+
def pre_cell(_val)
|
34
34
|
"'"
|
35
35
|
end
|
36
36
|
|
37
37
|
# Because the cell, after conversion to a single-quoted string will be
|
38
38
|
# eval'ed, we need to escape any single-quotes (') that appear in the
|
39
39
|
# string.
|
40
|
-
def quote_cell(
|
41
|
-
if
|
40
|
+
def quote_cell(val)
|
41
|
+
if val.match?(/'/)
|
42
42
|
# Use a negative look-behind to only quote single-quotes that are not
|
43
43
|
# already preceded by a backslash
|
44
|
-
|
44
|
+
val.gsub(/(?<!\\)'/, "'" => "\\'")
|
45
45
|
else
|
46
|
-
|
46
|
+
val
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
@@ -30,20 +30,20 @@ module FatTable
|
|
30
30
|
'{'
|
31
31
|
end
|
32
32
|
|
33
|
-
def pre_cell(
|
34
|
-
":#{
|
33
|
+
def pre_cell(head)
|
34
|
+
":#{head.as_sym} => '"
|
35
35
|
end
|
36
36
|
|
37
37
|
# Because the cell, after conversion to a single-quoted string will be
|
38
38
|
# eval'ed, we need to escape any single-quotes (') that appear in the
|
39
39
|
# string.
|
40
|
-
def quote_cell(
|
41
|
-
if
|
40
|
+
def quote_cell(val)
|
41
|
+
if val.match?(/'/)
|
42
42
|
# Use a negative look-behind to only quote single-quotes that are not
|
43
43
|
# already preceded by a backslash
|
44
|
-
|
44
|
+
val.gsub(/(?<!\\)'/, "'" => "\\'")
|
45
45
|
else
|
46
|
-
|
46
|
+
val
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
@@ -92,9 +92,10 @@ module FatTable
|
|
92
92
|
# Formatter is yielded to the block so that methods for formatting and
|
93
93
|
# adding footers can be called on it.
|
94
94
|
def initialize(table = Table.new, **options)
|
95
|
-
unless table
|
95
|
+
unless table&.is_a?(Table)
|
96
96
|
raise UserError, 'must initialize Formatter with a Table'
|
97
97
|
end
|
98
|
+
|
98
99
|
@table = table
|
99
100
|
@options = options
|
100
101
|
@footers = {}
|
@@ -170,12 +171,14 @@ module FatTable
|
|
170
171
|
unless table.headers.include?(h)
|
171
172
|
raise UserError, "No '#{h}' column in table to sum in the footer"
|
172
173
|
end
|
174
|
+
|
173
175
|
foot[h] = :sum
|
174
176
|
end
|
175
177
|
agg_cols.each do |h, agg|
|
176
178
|
unless table.headers.include?(h)
|
177
179
|
raise UserError, "No '#{h}' column in table to #{agg} in the footer"
|
178
180
|
end
|
181
|
+
|
179
182
|
foot[h] = agg
|
180
183
|
end
|
181
184
|
@footers[label] = foot
|
@@ -208,12 +211,14 @@ module FatTable
|
|
208
211
|
unless table.headers.include?(h)
|
209
212
|
raise UserError, "No '#{h}' column in table for group sum footer"
|
210
213
|
end
|
214
|
+
|
211
215
|
foot[h] = :sum
|
212
216
|
end
|
213
217
|
agg_cols.each do |h, agg|
|
214
218
|
unless table.headers.include?(h)
|
215
219
|
raise UserError, "No '#{h}' column in table for #{agg} group footer"
|
216
220
|
end
|
221
|
+
|
217
222
|
foot[h] = agg
|
218
223
|
end
|
219
224
|
@gfooters[label] = foot
|
@@ -466,6 +471,7 @@ module FatTable
|
|
466
471
|
unless LOCATIONS.include?(location)
|
467
472
|
raise UserError, "unknown format location '#{location}'"
|
468
473
|
end
|
474
|
+
|
469
475
|
valid_keys = table.headers + %i[string numeric datetime boolean nil]
|
470
476
|
invalid_keys = (fmts.keys - valid_keys).uniq
|
471
477
|
unless invalid_keys.empty?
|
@@ -487,18 +493,18 @@ module FatTable
|
|
487
493
|
# Merge in string and nil formatting, but not in header. Header is
|
488
494
|
# always typed a string, so it will get formatted in type-based
|
489
495
|
# formatting below. And headers are never nil.
|
490
|
-
if fmts.
|
496
|
+
if fmts.key?(:string)
|
491
497
|
typ_fmt = parse_string_fmt(fmts[:string])
|
492
498
|
format_h = format_h.merge(typ_fmt)
|
493
499
|
end
|
494
|
-
if fmts.
|
500
|
+
if fmts.key?(:nil)
|
495
501
|
typ_fmt = parse_nil_fmt(fmts[:nil]).first
|
496
502
|
format_h = format_h.merge(typ_fmt)
|
497
503
|
end
|
498
504
|
end
|
499
505
|
typ = location == :header ? :string : table.type(h).as_sym
|
500
506
|
parse_typ_method_name = 'parse_' + typ.to_s + '_fmt'
|
501
|
-
if fmts.
|
507
|
+
if fmts.key?(typ)
|
502
508
|
# Merge in type-based formatting
|
503
509
|
typ_fmt = send(parse_typ_method_name, fmts[typ])
|
504
510
|
format_h = format_h.merge(typ_fmt)
|
@@ -547,7 +553,7 @@ module FatTable
|
|
547
553
|
private
|
548
554
|
|
549
555
|
# Re to match a color name
|
550
|
-
CLR_RE = /(?:[-_a-zA-Z0-9 ]*)
|
556
|
+
CLR_RE = /(?:[-_a-zA-Z0-9 ]*)/.freeze
|
551
557
|
|
552
558
|
# Return a hash that reflects the formatting instructions given in the
|
553
559
|
# string fmt. Raise an error if it contains invalid formatting instructions.
|
@@ -558,6 +564,7 @@ module FatTable
|
|
558
564
|
unless fmt.blank? || !strict
|
559
565
|
raise UserError, "unrecognized string formatting instructions '#{fmt}'"
|
560
566
|
end
|
567
|
+
|
561
568
|
format
|
562
569
|
end
|
563
570
|
|
@@ -668,6 +675,7 @@ module FatTable
|
|
668
675
|
unless fmt.blank? || !strict
|
669
676
|
raise UserError, "unrecognized numeric formatting instructions '#{fmt}'"
|
670
677
|
end
|
678
|
+
|
671
679
|
fmt_hash
|
672
680
|
end
|
673
681
|
|
@@ -739,6 +747,7 @@ module FatTable
|
|
739
747
|
unless fmt.blank? || !strict
|
740
748
|
raise UserError, "unrecognized boolean formatting instructions '#{fmt}'"
|
741
749
|
end
|
750
|
+
|
742
751
|
fmt_hash
|
743
752
|
end
|
744
753
|
|
@@ -810,6 +819,7 @@ module FatTable
|
|
810
819
|
# specializing this method.
|
811
820
|
def format_boolean(val, istruct)
|
812
821
|
return istruct.nil_text if val.nil?
|
822
|
+
|
813
823
|
val ? istruct.true_text : istruct.false_text
|
814
824
|
end
|
815
825
|
|
@@ -820,6 +830,7 @@ module FatTable
|
|
820
830
|
# specializing this method.
|
821
831
|
def format_datetime(val, istruct)
|
822
832
|
return istruct.nil_text if val.nil?
|
833
|
+
|
823
834
|
if val.to_date == val
|
824
835
|
# It is a Date, with no time component.
|
825
836
|
val.strftime(istruct.date_fmt)
|
@@ -835,6 +846,7 @@ module FatTable
|
|
835
846
|
# specializing this method.
|
836
847
|
def format_numeric(val, istruct)
|
837
848
|
return istruct.nil_text if val.nil?
|
849
|
+
|
838
850
|
val = val.round(istruct.post_digits) if istruct.post_digits >= 0
|
839
851
|
if istruct.hms
|
840
852
|
result = val.secs_to_hms
|
@@ -998,6 +1010,7 @@ module FatTable
|
|
998
1010
|
new_rows.each do |loc_row|
|
999
1011
|
result += hline(widths) if loc_row.nil?
|
1000
1012
|
next if loc_row.nil?
|
1013
|
+
|
1001
1014
|
_loc, row = *loc_row
|
1002
1015
|
result += pre_row
|
1003
1016
|
cells = []
|
@@ -1140,6 +1153,7 @@ module FatTable
|
|
1140
1153
|
end
|
1141
1154
|
rows.each do |loc_row|
|
1142
1155
|
next if loc_row.nil?
|
1156
|
+
|
1143
1157
|
_loc, row = *loc_row
|
1144
1158
|
row.each_pair do |h, (_v, fmt_v)|
|
1145
1159
|
widths[h] ||= 0
|
@@ -1222,12 +1236,12 @@ module FatTable
|
|
1222
1236
|
''
|
1223
1237
|
end
|
1224
1238
|
|
1225
|
-
def pre_cell(
|
1239
|
+
def pre_cell(_head)
|
1226
1240
|
''
|
1227
1241
|
end
|
1228
1242
|
|
1229
|
-
def quote_cell(
|
1230
|
-
|
1243
|
+
def quote_cell(val)
|
1244
|
+
val
|
1231
1245
|
end
|
1232
1246
|
|
1233
1247
|
def post_cell
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FatTable
|
2
4
|
# A subclass of Formatter for rendering the table as a LaTeX table. It allows
|
3
5
|
# foreground colors through LaTeX's xcolor package but ignores background
|
@@ -17,7 +19,6 @@ module FatTable
|
|
17
19
|
# LaTeX tabular-like environment to use for the table. The default is good
|
18
20
|
# for tables that might continue over multiple pages since it repeats the
|
19
21
|
# header at the top of each continuation page.
|
20
|
-
|
21
22
|
def initialize(table = Table.new, **options)
|
22
23
|
super
|
23
24
|
@options[:document] = options.fetch(:document, false)
|
@@ -115,7 +116,8 @@ module FatTable
|
|
115
116
|
result = ''
|
116
117
|
result += '\\bfseries{}' if istruct.bold
|
117
118
|
result += '\\itshape{}' if istruct.italic
|
118
|
-
result += "\\color{#{istruct.color}}" if istruct.color &&
|
119
|
+
result += "\\color{#{istruct.color}}" if istruct.color &&
|
120
|
+
istruct.color != 'none'
|
119
121
|
result = "#{result}#{str}"
|
120
122
|
unless istruct.alignment == format_at[:body][istruct._h].alignment
|
121
123
|
ac = alignment_code(istruct.alignment)
|
@@ -173,14 +175,14 @@ module FatTable
|
|
173
175
|
''
|
174
176
|
end
|
175
177
|
|
176
|
-
def pre_cell(
|
178
|
+
def pre_cell(_head)
|
177
179
|
''
|
178
180
|
end
|
179
181
|
|
180
182
|
# We do quoting before applying decoration, so do not re-quote here. We
|
181
183
|
# will have LaTeX commands in v.
|
182
|
-
def quote_cell(
|
183
|
-
|
184
|
+
def quote_cell(val)
|
185
|
+
val
|
184
186
|
end
|
185
187
|
|
186
188
|
def post_cell
|
@@ -27,6 +27,7 @@ module FatTable
|
|
27
27
|
@options[:unicode] = options.fetch(:unicode, true)
|
28
28
|
@options[:framecolor] = options.fetch(:framecolor, 'none.none')
|
29
29
|
return unless @options[:framecolor] =~ /([-_a-zA-Z]*)(\.([-_a-zA-Z]*))/
|
30
|
+
|
30
31
|
@options[:frame_fg] = $1.downcase unless $1.blank?
|
31
32
|
@options[:frame_bg] = $3.downcase unless $3.blank?
|
32
33
|
end
|
@@ -59,6 +60,7 @@ module FatTable
|
|
59
60
|
|
60
61
|
def strip_ansi(str)
|
61
62
|
return '' unless str
|
63
|
+
|
62
64
|
str.gsub(/\e\[[0-9;]+m/, '')
|
63
65
|
end
|
64
66
|
|
@@ -73,18 +75,19 @@ module FatTable
|
|
73
75
|
result
|
74
76
|
end
|
75
77
|
|
76
|
-
def colorize(str,
|
77
|
-
|
78
|
-
|
79
|
-
return str unless
|
78
|
+
def colorize(str, fg_color, bg_color)
|
79
|
+
fg_color = nil if fg_color == 'none'
|
80
|
+
bg_color = nil if bg_color == 'none'
|
81
|
+
return str unless fg_color || bg_color
|
82
|
+
|
80
83
|
result = Rainbow(str)
|
81
|
-
if
|
82
|
-
|
83
|
-
result = result.color(
|
84
|
+
if fg_color
|
85
|
+
fg_color = fg_color.tr(' ', '').downcase.as_sym
|
86
|
+
result = result.color(fg_color) if fg_color
|
84
87
|
end
|
85
|
-
if
|
86
|
-
|
87
|
-
result = result.bg(
|
88
|
+
if bg_color
|
89
|
+
bg_color = bg_color.tr(' ', '').downcase.as_sym
|
90
|
+
result = result.bg(bg_color) if bg_color
|
88
91
|
end
|
89
92
|
result
|
90
93
|
end
|
@@ -229,12 +232,12 @@ module FatTable
|
|
229
232
|
frame_colorize(vertical_rule)
|
230
233
|
end
|
231
234
|
|
232
|
-
def pre_cell(
|
235
|
+
def pre_cell(_head)
|
233
236
|
''
|
234
237
|
end
|
235
238
|
|
236
|
-
def quote_cell(
|
237
|
-
|
239
|
+
def quote_cell(val)
|
240
|
+
val
|
238
241
|
end
|
239
242
|
|
240
243
|
def post_cell
|
data/lib/fat_table/patches.rb
CHANGED
@@ -19,8 +19,8 @@ end
|
|
19
19
|
unless ''.respond_to?(:match?)
|
20
20
|
# Add String#match? to pre-2.4 ruby
|
21
21
|
class String
|
22
|
-
def match?(
|
23
|
-
self =~
|
22
|
+
def match?(regexp)
|
23
|
+
self =~ regexp
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -35,6 +35,7 @@ unless //.respond_to?(:match?)
|
|
35
35
|
end
|
36
36
|
|
37
37
|
unless ''.respond_to?(:strip_heredoc)
|
38
|
+
# Patch String to provide heredocs with whitespace stripped
|
38
39
|
class String
|
39
40
|
def strip_heredoc
|
40
41
|
indent = chomp.scan(/^\s*/).min.size
|
data/lib/fat_table/table.rb
CHANGED
@@ -106,18 +106,19 @@ module FatTable
|
|
106
106
|
# :category: Constructors
|
107
107
|
|
108
108
|
# Construct a new table from an Array of Arrays +aoa+. By default, with
|
109
|
-
# +hlines+ set to false, do not look for separators, i.e. +nils+, just
|
110
|
-
# the first row as headers. With +hlines+ set true, expect +nil+
|
111
|
-
# to mark the header row and any boundaries. If the second
|
112
|
-
# array is a +nil+, interpret the first element of the
|
113
|
-
# headers. Otherwise, synthesize headers of the form
|
114
|
-
# and so forth. The remaining elements are taken
|
115
|
-
# except that if an element of the outer array
|
116
|
-
# preceding row as a group boundary. Note for Emacs
|
117
|
-
# blocks when an org-mode table is passed in as a
|
118
|
-
# as an Array of Arrays. By default (+ HEADER:
|
119
|
-
# all from the table; otherwise (+
|
120
|
-
# with nil elements in the outer
|
109
|
+
# +hlines+ set to false, do not look for separators, i.e. +nils+, just
|
110
|
+
# treat the first row as headers. With +hlines+ set true, expect +nil+
|
111
|
+
# separators to mark the header row and any boundaries. If the second
|
112
|
+
# element of the array is a +nil+, interpret the first element of the
|
113
|
+
# array as a row of headers. Otherwise, synthesize headers of the form
|
114
|
+
# +:col_1+, +:col_2+, ... and so forth. The remaining elements are taken
|
115
|
+
# as the body of the table, except that if an element of the outer array
|
116
|
+
# is a +nil+, mark the preceding row as a group boundary. Note for Emacs
|
117
|
+
# users: In org mode code blocks when an org-mode table is passed in as a
|
118
|
+
# variable it is passed in as an Array of Arrays. By default (+ HEADER:
|
119
|
+
# :hlines no +) org-mode strips all hrules from the table; otherwise (+
|
120
|
+
# HEADER: :hlines yes +) they are indicated with nil elements in the outer
|
121
|
+
# array.
|
121
122
|
def self.from_aoa(aoa, hlines: false)
|
122
123
|
from_array_of_arrays(aoa, hlines: hlines)
|
123
124
|
end
|
@@ -149,10 +150,11 @@ module FatTable
|
|
149
150
|
# :category: Constructors
|
150
151
|
|
151
152
|
# Construct a Table by running a SQL +query+ against the database set up
|
152
|
-
# with FatTable.
|
153
|
+
# with FatTable.connect, with the rows of the query result as rows.
|
153
154
|
def self.from_sql(query)
|
154
|
-
msg = 'FatTable.db must be set with FatTable.
|
155
|
+
msg = 'FatTable.db must be set with FatTable.connect'
|
155
156
|
raise UserError, msg if FatTable.db.nil?
|
157
|
+
|
156
158
|
result = Table.new
|
157
159
|
FatTable.db[query].each do |h|
|
158
160
|
result << h
|
@@ -257,6 +259,7 @@ module FatTable
|
|
257
259
|
unless table_found
|
258
260
|
# Skip through the file until a table is found
|
259
261
|
next unless line.match?(table_re)
|
262
|
+
|
260
263
|
unless line.match?(hrule_re)
|
261
264
|
line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
|
262
265
|
rows << line.split('|').map(&:clean)
|
@@ -265,6 +268,7 @@ module FatTable
|
|
265
268
|
next
|
266
269
|
end
|
267
270
|
break unless line.match?(table_re)
|
271
|
+
|
268
272
|
if !header_found && line =~ hrule_re
|
269
273
|
rows << nil
|
270
274
|
header_found = true
|
@@ -272,7 +276,7 @@ module FatTable
|
|
272
276
|
elsif header_found && line =~ hrule_re
|
273
277
|
# Mark the boundary with a nil
|
274
278
|
rows << nil
|
275
|
-
elsif line
|
279
|
+
elsif !line.match?(table_re)
|
276
280
|
# Stop reading at the second hline
|
277
281
|
break
|
278
282
|
else
|
@@ -316,14 +320,17 @@ module FatTable
|
|
316
320
|
when Integer
|
317
321
|
msg = "index '#{key}' out of range"
|
318
322
|
raise UserError, msg unless (0..size - 1).cover?(key.abs)
|
323
|
+
|
319
324
|
rows[key]
|
320
325
|
when String
|
321
326
|
msg = "header '#{key}' not in table"
|
322
327
|
raise UserError, msg unless headers.include?(key)
|
328
|
+
|
323
329
|
column(key).items
|
324
330
|
when Symbol
|
325
331
|
msg = "header ':#{key}' not in table"
|
326
332
|
raise UserError, msg unless headers.include?(key)
|
333
|
+
|
327
334
|
column(key).items
|
328
335
|
else
|
329
336
|
raise UserError, "cannot index table with a #{key.class}"
|
@@ -360,6 +367,7 @@ module FatTable
|
|
360
367
|
# Return the number of rows in the Table.
|
361
368
|
def size
|
362
369
|
return 0 if columns.empty?
|
370
|
+
|
363
371
|
columns.first.size
|
364
372
|
end
|
365
373
|
|
@@ -368,6 +376,7 @@ module FatTable
|
|
368
376
|
# Return the number of Columns in the Table.
|
369
377
|
def width
|
370
378
|
return 0 if columns.empty?
|
379
|
+
|
371
380
|
columns.size
|
372
381
|
end
|
373
382
|
|
@@ -406,6 +415,7 @@ module FatTable
|
|
406
415
|
last ||= size - 1
|
407
416
|
last = [last, 0].max
|
408
417
|
raise UserError, 'first must be <= last' unless first <= last
|
418
|
+
|
409
419
|
rows = []
|
410
420
|
unless columns.empty?
|
411
421
|
first.upto(last) do |rnum|
|
@@ -493,12 +503,12 @@ module FatTable
|
|
493
503
|
self
|
494
504
|
end
|
495
505
|
|
496
|
-
# Mark a group boundary at row +
|
497
|
-
# in the table as a group boundary. This is mainly used for internal
|
506
|
+
# Mark a group boundary at row +row+, and if +row+ is +nil+, mark the last
|
507
|
+
# row in the table as a group boundary. This is mainly used for internal
|
498
508
|
# purposes.
|
499
|
-
def mark_boundary(
|
500
|
-
if
|
501
|
-
boundaries.push(
|
509
|
+
def mark_boundary(row = nil) # :nodoc:
|
510
|
+
if row
|
511
|
+
boundaries.push(row)
|
502
512
|
else
|
503
513
|
boundaries.push(size - 1)
|
504
514
|
end
|
@@ -524,20 +534,21 @@ module FatTable
|
|
524
534
|
@boundaries += bounds.map { |k| k + shift }
|
525
535
|
end
|
526
536
|
|
527
|
-
# Return the group number to which row
|
528
|
-
# point of view are indexed starting at 1.
|
529
|
-
def row_index_to_group_index(
|
537
|
+
# Return the group number to which row ~row~ belongs. Groups, from the
|
538
|
+
# user's point of view are indexed starting at 1.
|
539
|
+
def row_index_to_group_index(row)
|
530
540
|
boundaries.each_with_index do |b_last, g_num|
|
531
|
-
return (g_num + 1) if
|
541
|
+
return (g_num + 1) if row <= b_last
|
532
542
|
end
|
533
543
|
1
|
534
544
|
end
|
535
545
|
|
536
|
-
def group_rows(
|
546
|
+
def group_rows(row) # :nodoc:
|
537
547
|
normalize_boundaries
|
538
|
-
return [] unless
|
539
|
-
|
540
|
-
|
548
|
+
return [] unless row < boundaries.size
|
549
|
+
|
550
|
+
first = row.zero? ? 0 : boundaries[row - 1] + 1
|
551
|
+
last = boundaries[row]
|
541
552
|
rows_range(first, last)
|
542
553
|
end
|
543
554
|
|
@@ -702,6 +713,7 @@ module FatTable
|
|
702
713
|
h = k.as_sym
|
703
714
|
msg = "Column '#{h}' in select does not exist"
|
704
715
|
raise UserError, msg unless column?(h)
|
716
|
+
|
705
717
|
new_row[h] = old_row[h]
|
706
718
|
end
|
707
719
|
new_cols.each_pair do |key, expr|
|
@@ -710,7 +722,8 @@ module FatTable
|
|
710
722
|
case expr
|
711
723
|
when Symbol
|
712
724
|
msg = "Column '#{expr}' in select does not exist"
|
713
|
-
raise UserError, msg unless vars.
|
725
|
+
raise UserError, msg unless vars.key?(expr)
|
726
|
+
|
714
727
|
new_row[key] = vars[expr]
|
715
728
|
when String
|
716
729
|
new_row[key] = ev.evaluate(expr, locals: vars)
|
@@ -864,10 +877,10 @@ module FatTable
|
|
864
877
|
|
865
878
|
private
|
866
879
|
|
867
|
-
# Apply the set operation given by
|
868
|
-
# given in the first argument. If distinct is true, eliminate
|
869
|
-
# from the result.
|
870
|
-
def set_operation(other,
|
880
|
+
# Apply the set operation given by ~oper~ between this table and the other
|
881
|
+
# table given in the first argument. If distinct is true, eliminate
|
882
|
+
# duplicates from the result.
|
883
|
+
def set_operation(other, oper = :+,
|
871
884
|
distinct: true,
|
872
885
|
add_boundaries: true,
|
873
886
|
inherit_boundaries: false)
|
@@ -881,7 +894,7 @@ module FatTable
|
|
881
894
|
end
|
882
895
|
other_rows = other.rows.map { |r| r.replace_keys(headers) }
|
883
896
|
result = Table.new
|
884
|
-
new_rows = rows.send(
|
897
|
+
new_rows = rows.send(oper, other_rows)
|
885
898
|
new_rows.each_with_index do |row, k|
|
886
899
|
result << row
|
887
900
|
result.mark_boundary if k == size - 1 && add_boundaries
|
@@ -975,6 +988,7 @@ module FatTable
|
|
975
988
|
unless JOIN_TYPES.include?(join_type)
|
976
989
|
raise UserError, "join_type may only be: #{JOIN_TYPES.join(', ')}"
|
977
990
|
end
|
991
|
+
|
978
992
|
# These may be needed for outer joins.
|
979
993
|
self_row_nils = headers.map { |h| [h, nil] }.to_h
|
980
994
|
other_row_nils = other.headers.map { |h| [h, nil] }.to_h
|
@@ -992,6 +1006,7 @@ module FatTable
|
|
992
1006
|
locals = build_locals_hash(row_a: self_row, row_b: other_row)
|
993
1007
|
matches = ev.evaluate(join_exp, locals: locals)
|
994
1008
|
next unless matches
|
1009
|
+
|
995
1010
|
self_row_matched = other_row_matches[k] = true
|
996
1011
|
out_row = build_out_row(row_a: self_row, row_b: other_row,
|
997
1012
|
common_heads: other_common_heads,
|
@@ -1000,6 +1015,7 @@ module FatTable
|
|
1000
1015
|
end
|
1001
1016
|
next unless %i[left full].include?(join_type)
|
1002
1017
|
next if self_row_matched
|
1018
|
+
|
1003
1019
|
result << build_out_row(row_a: self_row,
|
1004
1020
|
row_b: other_row_nils,
|
1005
1021
|
type: join_type)
|
@@ -1007,6 +1023,7 @@ module FatTable
|
|
1007
1023
|
if %i[right full].include?(join_type)
|
1008
1024
|
other_rows.each_with_index do |other_row, k|
|
1009
1025
|
next if other_row_matches[k]
|
1026
|
+
|
1010
1027
|
result << build_out_row(row_a: self_row_nils,
|
1011
1028
|
row_b: other_row,
|
1012
1029
|
type: join_type)
|
@@ -1090,6 +1107,7 @@ module FatTable
|
|
1090
1107
|
# and all the headers in the other table with '_b' appended.
|
1091
1108
|
def build_join_expression(exps, other, type)
|
1092
1109
|
return ['true', []] if type == :cross
|
1110
|
+
|
1093
1111
|
a_heads = headers
|
1094
1112
|
b_heads = other.headers
|
1095
1113
|
common_heads = a_heads & b_heads
|
@@ -1120,6 +1138,7 @@ module FatTable
|
|
1120
1138
|
unless a_heads.include?(a_head)
|
1121
1139
|
raise UserError, "no column '#{a_head}' in table"
|
1122
1140
|
end
|
1141
|
+
|
1123
1142
|
if partial_result
|
1124
1143
|
# Second of a pair
|
1125
1144
|
ensure_common_types!(self_h: a_head,
|
@@ -1138,6 +1157,7 @@ module FatTable
|
|
1138
1157
|
unless b_heads.include?(b_head)
|
1139
1158
|
raise UserError, "no column '#{b_head}' in second table"
|
1140
1159
|
end
|
1160
|
+
|
1141
1161
|
if partial_result
|
1142
1162
|
# Second of a pair
|
1143
1163
|
ensure_common_types!(self_h: last_sym,
|
@@ -1281,6 +1301,7 @@ module FatTable
|
|
1281
1301
|
def add_column(col)
|
1282
1302
|
msg = "Table already has a column with header '#{col.header}'"
|
1283
1303
|
raise msg if column?(col.header)
|
1304
|
+
|
1284
1305
|
columns << col
|
1285
1306
|
self
|
1286
1307
|
end
|
@@ -1329,6 +1350,7 @@ module FatTable
|
|
1329
1350
|
fmt = fmt_type.as_sym
|
1330
1351
|
msg = "unknown format '#{fmt}'"
|
1331
1352
|
raise UserError, msg unless FatTable::FORMATS.include?(fmt)
|
1353
|
+
|
1332
1354
|
method = "to_#{fmt}"
|
1333
1355
|
if block_given?
|
1334
1356
|
send method, options, &Proc.new
|
data/lib/fat_table/version.rb
CHANGED
data/md/README.md
CHANGED
@@ -597,7 +597,7 @@ database parameters to be used for the queries.
|
|
597
597
|
|
598
598
|
# This automatically requires sequel.
|
599
599
|
require 'fat_table'
|
600
|
-
FatTable.
|
600
|
+
FatTable.connect(driver: 'Pg',
|
601
601
|
database: 'XXX_development',
|
602
602
|
user: 'dtd',
|
603
603
|
password: 'slflpowert',
|
@@ -605,15 +605,15 @@ database parameters to be used for the queries.
|
|
605
605
|
socket: '/tmp/.s.PGSQL.5432')
|
606
606
|
tab = FatTable.from_sql('select * from trades;')
|
607
607
|
|
608
|
-
Some of the parameters to the `.
|
608
|
+
Some of the parameters to the `.connect` function have defaults. The driver
|
609
609
|
defaults to `'Pg'` for postgresql and the socket defaults to
|
610
610
|
`/tmp/.s.PGSQL.5432` if the host is ’localhost’, which it is by default. If the
|
611
611
|
host is not `'localhost'`, the dsn uses a port rather than a socket and defaults
|
612
612
|
to port `'5432'`. While user and password default to nil, the database parameter
|
613
613
|
is required.
|
614
614
|
|
615
|
-
The `.
|
616
|
-
creates will be used for all subsequent `.from_sql` calls until `.
|
615
|
+
The `.connect` function need only be called once, and the database handle it
|
616
|
+
creates will be used for all subsequent `.from_sql` calls until `.connect` is
|
617
617
|
called again.
|
618
618
|
|
619
619
|
Alternatively, you can build the `Sequel` connection with `Sequel.connect` or
|
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.9
|
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:
|
11
|
+
date: 2019-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -338,8 +338,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
338
338
|
- !ruby/object:Gem::Version
|
339
339
|
version: '0'
|
340
340
|
requirements: []
|
341
|
-
|
342
|
-
rubygems_version: 2.7.3
|
341
|
+
rubygems_version: 3.0.3
|
343
342
|
signing_key:
|
344
343
|
specification_version: 4
|
345
344
|
summary: Provides tools for working with tables as a data type.
|