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