fat_table 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +2 -0
- data/README.md +2168 -0
- data/README.org +230 -212
- data/TODO.org +7 -0
- data/bin/ft_console +82 -79
- data/fat_table.gemspec +45 -43
- data/lib/fat_table.rb +1 -1
- data/lib/fat_table/column.rb +31 -22
- data/lib/fat_table/db_handle.rb +61 -50
- data/lib/fat_table/evaluator.rb +20 -22
- data/lib/fat_table/formatters/aoa_formatter.rb +1 -2
- data/lib/fat_table/formatters/aoh_formatter.rb +1 -2
- data/lib/fat_table/formatters/formatter.rb +38 -33
- data/lib/fat_table/formatters/latex_formatter.rb +2 -2
- data/lib/fat_table/formatters/org_formatter.rb +5 -6
- data/lib/fat_table/formatters/term_formatter.rb +3 -5
- data/lib/fat_table/formatters/text_formatter.rb +3 -4
- data/lib/fat_table/patches.rb +29 -1
- data/lib/fat_table/table.rb +81 -77
- data/lib/fat_table/version.rb +1 -1
- metadata +80 -82
data/lib/fat_table/db_handle.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
+
# Set and access a database by module-level methods.
|
1
2
|
module FatTable
|
2
|
-
class << self
|
3
|
+
class << self
|
3
4
|
# The +Sequel+ database handle to use in calls to +FatTable.from_sql+.
|
4
5
|
attr_accessor :handle
|
5
6
|
end
|
@@ -18,20 +19,21 @@ module FatTable
|
|
18
19
|
# http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html
|
19
20
|
#
|
20
21
|
# +driver+::
|
21
|
-
# One of 'pg' (for Postgresql), 'mysql' or 'mysql2' (for Mysql), or
|
22
|
-
# SQLite3) to specify the +Sequel+ driver to use.
|
23
|
-
# driver to make this work.
|
22
|
+
# One of 'pg' (for Postgresql), 'mysql' or 'mysql2' (for Mysql), or
|
23
|
+
# 'sqlite' (for SQLite3) to specify the +Sequel+ driver to use. You may
|
24
|
+
# have to install the driver to make this work. By default use 'Pg'.
|
24
25
|
#
|
25
26
|
# +database+::
|
26
27
|
# The name of the database to access. There is no default for this.
|
27
28
|
#
|
28
29
|
# +user+::
|
29
|
-
# The user name to use for accessing the database.
|
30
|
-
# which may be interpreted as a default user by the Sequel driver being
|
30
|
+
# The user name to use for accessing the database. It defaults to nil,
|
31
|
+
# which may be interpreted as a default user by the Sequel driver being
|
32
|
+
# used.
|
31
33
|
#
|
32
34
|
# +password+::
|
33
|
-
# The password to use for accessing the database.
|
34
|
-
#
|
35
|
+
# The password to use for accessing the database. It defaults to nil, which
|
36
|
+
# may be interpreted as a default password by the Sequel driver being used.
|
35
37
|
#
|
36
38
|
# +host+::
|
37
39
|
# The name of the host on which to look for the database connection,
|
@@ -39,57 +41,66 @@ module FatTable
|
|
39
41
|
#
|
40
42
|
# +port+::
|
41
43
|
# The port number as a string or integer on which to access the database on
|
42
|
-
# the given host.
|
44
|
+
# the given host. Defaults to '5432'. Only used if host is not 'localhost'.
|
43
45
|
#
|
44
46
|
# +socket+::
|
45
47
|
# The socket to use to access the database if the host is 'localhost'.
|
46
48
|
# Defaults to the standard socket for the Pg driver, '/tmp/.s.PGSQL.5432'.
|
47
49
|
#
|
48
|
-
# If successful the database handle for Sequel is return.
|
49
|
-
#
|
50
|
-
#
|
51
|
-
#
|
52
|
-
def self.set_db(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
port: '5432',
|
59
|
-
socket: '/tmp/.s.PGSQL.5432')
|
60
|
-
if db
|
61
|
-
self.handle = db
|
62
|
-
else
|
63
|
-
raise UserError, 'must supply database name to set_db' unless database
|
64
|
-
|
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
|
72
|
-
|
73
|
-
if driver == 'sqlite'
|
74
|
-
dsn = "sqlite://#{database}"
|
75
|
-
else
|
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}"
|
88
|
-
end
|
50
|
+
# If successful the database handle for Sequel is return. Once called
|
51
|
+
# successfully, this establishes the database handle to use for all subsequent
|
52
|
+
# calls to FatTable.from_sql or FatTable::Table.from_sql. You can then access
|
53
|
+
# the handle if needed with FatTable.db.
|
54
|
+
def self.set_db(args)
|
55
|
+
# Set the dsn for Sequel
|
56
|
+
begin
|
57
|
+
self.handle = Sequel.connect(args)
|
58
|
+
rescue Sequel::Error => ex
|
59
|
+
raise TransientError, "#{args}: #{ex}"
|
89
60
|
end
|
90
61
|
handle
|
91
62
|
end
|
92
63
|
|
64
|
+
# def self.set_db(adapter: 'postgres',
|
65
|
+
# database:,
|
66
|
+
# user: ENV['LOGNAME'],
|
67
|
+
# password: nil,
|
68
|
+
# host: 'localhost',
|
69
|
+
# port: nil,
|
70
|
+
# socket: '/tmp/.s.PGSQL.5432')
|
71
|
+
# if db
|
72
|
+
# self.handle = db
|
73
|
+
# else
|
74
|
+
# raise UserError, 'must supply database name to set_db' unless database
|
75
|
+
|
76
|
+
# valid_drivers = %w[postgres mysql mysql2 sqlite]
|
77
|
+
# unless valid_drivers.include?(driver)
|
78
|
+
# msg = "'#{driver}' driver must be one of #{valid_drivers.join(' or ')}"
|
79
|
+
# raise UserError, msg
|
80
|
+
# end
|
81
|
+
# if database.blank?
|
82
|
+
# raise UserError, 'must supply database parameter to set_db'
|
83
|
+
# end
|
84
|
+
|
85
|
+
# if driver == 'sqlite'
|
86
|
+
# dsn = "sqlite://#{database}"
|
87
|
+
# else
|
88
|
+
# pw_part = password ? ":#{password}" : ''
|
89
|
+
# hst_part = host ? "@#{host}" : ''
|
90
|
+
# prt_part = port ? ":#{port}" : ''
|
91
|
+
# dsn = "#{driver}:://#{user}#{pw_part}#{hst_part}#{prt_part}/#{database}"
|
92
|
+
# end
|
93
|
+
|
94
|
+
# # Set the dsn for Sequel
|
95
|
+
# begin
|
96
|
+
# self.handle = Sequel.connect(dsn)
|
97
|
+
# rescue Sequel::Error => ex
|
98
|
+
# raise TransientError, "#{dsn}: #{ex}"
|
99
|
+
# end
|
100
|
+
# end
|
101
|
+
# handle
|
102
|
+
# end
|
103
|
+
|
93
104
|
# Return the +Sequel+ database handle.
|
94
105
|
def self.db
|
95
106
|
handle
|
data/lib/fat_table/evaluator.rb
CHANGED
@@ -12,10 +12,10 @@ module FatTable
|
|
12
12
|
# update values of instance variables for use in subsequent calls to
|
13
13
|
# #evaluate.
|
14
14
|
class Evaluator
|
15
|
-
# Return a new Evaluator object in which the Hash +
|
15
|
+
# Return a new Evaluator object in which the Hash +ivars+ defines the
|
16
16
|
# bindings for instance variables to be available and maintained across all
|
17
17
|
# subsequent calls to Evaluator.evaluate. The strings +before+ and +after+
|
18
|
-
# are string expressions that will be evaluated before and after each
|
18
|
+
# are string Ruby expressions that will be evaluated before and after each
|
19
19
|
# subsequent call to Evaluator.evaluate.
|
20
20
|
def initialize(ivars: {}, before: nil, after: nil)
|
21
21
|
@before = before
|
@@ -23,31 +23,28 @@ module FatTable
|
|
23
23
|
set_instance_vars(ivars)
|
24
24
|
end
|
25
25
|
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
26
|
+
# Set the @group instance variable to the given value.
|
27
|
+
def update_ivars(ivars)
|
28
|
+
set_instance_vars(ivars)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Run any before hook in the context of the given local variables.
|
32
|
+
def eval_before_hook(locals: {})
|
33
|
+
return if @before.blank?
|
34
|
+
evaluate(@before, locals: locals)
|
34
35
|
end
|
35
36
|
|
36
|
-
# Run
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
set_local_vars(vars, bdg)
|
41
|
-
eval(@after, bdg) if @after
|
37
|
+
# Run any after hook in the context of the given local variables.
|
38
|
+
def eval_after_hook(locals: {})
|
39
|
+
return if @after.blank?
|
40
|
+
evaluate(@after, locals: locals)
|
42
41
|
end
|
43
42
|
|
44
43
|
# Return the result of evaluating +expr+ as a Ruby expression in which the
|
45
44
|
# instance variables set in Evaluator.new and any local variables set in the
|
46
|
-
# Hash parameter +
|
47
|
-
def evaluate(expr = '',
|
48
|
-
|
49
|
-
set_local_vars(vars, bdg)
|
50
|
-
eval(expr, bdg)
|
45
|
+
# Hash parameter +locals+ are available to the expression.
|
46
|
+
def evaluate(expr = '', locals: {})
|
47
|
+
eval(expr, set_local_vars(binding, locals))
|
51
48
|
end
|
52
49
|
|
53
50
|
private
|
@@ -59,10 +56,11 @@ module FatTable
|
|
59
56
|
end
|
60
57
|
end
|
61
58
|
|
62
|
-
def set_local_vars(vars = {}
|
59
|
+
def set_local_vars(bnd, vars = {})
|
63
60
|
vars.each_pair do |name, val|
|
64
61
|
bnd.local_variable_set(name, val)
|
65
62
|
end
|
63
|
+
bnd
|
66
64
|
end
|
67
65
|
end
|
68
66
|
end
|
@@ -4,7 +4,6 @@ module FatTable
|
|
4
4
|
# directives. All footers are included as extra Arrays of the output.
|
5
5
|
# AoaFormatter supports no +options+
|
6
6
|
class AoaFormatter < Formatter
|
7
|
-
|
8
7
|
private
|
9
8
|
|
10
9
|
def evaluate?
|
@@ -39,7 +38,7 @@ module FatTable
|
|
39
38
|
# eval'ed, we need to escape any single-quotes (') that appear in the
|
40
39
|
# string.
|
41
40
|
def quote_cell(v)
|
42
|
-
if v
|
41
|
+
if v.match?(/'/)
|
43
42
|
# Use a negative look-behind to only quote single-quotes that are not
|
44
43
|
# already preceded by a backslash
|
45
44
|
v.gsub(/(?<!\\)'/, "'" => "\\'")
|
@@ -6,7 +6,6 @@ module FatTable
|
|
6
6
|
# footers are included as extra Hashes of the output. AoaFormatter supports no
|
7
7
|
# +options+
|
8
8
|
class AohFormatter < Formatter
|
9
|
-
|
10
9
|
private
|
11
10
|
|
12
11
|
def evaluate?
|
@@ -39,7 +38,7 @@ module FatTable
|
|
39
38
|
# eval'ed, we need to escape any single-quotes (') that appear in the
|
40
39
|
# string.
|
41
40
|
def quote_cell(v)
|
42
|
-
if v
|
41
|
+
if v.match?(/'/)
|
43
42
|
# Use a negative look-behind to only quote single-quotes that are not
|
44
43
|
# already preceded by a backslash
|
45
44
|
v.gsub(/(?<!\\)'/, "'" => "\\'")
|
@@ -12,7 +12,7 @@ module FatTable
|
|
12
12
|
# provided by subclasses will override these for different output targets.
|
13
13
|
class Formatter
|
14
14
|
# Valid locations in a Table as an array of symbols.
|
15
|
-
LOCATIONS = [
|
15
|
+
LOCATIONS = %i[header body bfirst gfirst gfooter footer].freeze
|
16
16
|
|
17
17
|
# The table that is the subject of the Formatter.
|
18
18
|
attr_reader :table
|
@@ -45,8 +45,9 @@ module FatTable
|
|
45
45
|
# footer content. The value is Hash in which the keys are column symbols and
|
46
46
|
# the values are symbols for the aggregate method to be applied to the
|
47
47
|
# group's column to provide a value in the group footer for that column.
|
48
|
-
# Thus, +gfooters['Average'][:shares]+ might be set to +:avg+ to indicate
|
49
|
-
# the +:shares+ column is to be averaged in the group footer labeled
|
48
|
+
# Thus, +gfooters['Average'][:shares]+ might be set to +:avg+ to indicate
|
49
|
+
# that the +:shares+ column is to be averaged in the group footer labeled
|
50
|
+
# 'Average'.
|
50
51
|
attr_reader :gfooters
|
51
52
|
|
52
53
|
class_attribute :default_format
|
@@ -98,13 +99,13 @@ module FatTable
|
|
98
99
|
@options = options
|
99
100
|
@footers = {}
|
100
101
|
@gfooters = {}
|
101
|
-
# Formatting instructions for various "locations" within the Table, as
|
102
|
-
#
|
103
|
-
#
|
104
|
-
# :datetime, :boolean, or :nil.
|
102
|
+
# Formatting instructions for various "locations" within the Table, as a
|
103
|
+
# hash of hashes. The outer hash is keyed on the location, and each inner
|
104
|
+
# hash is keyed on either a column sym or a type sym, :string, :numeric,
|
105
|
+
# :datetime, :boolean, or :nil. The value of the inner hashes are
|
105
106
|
# OpenStruct structs.
|
106
107
|
@format_at = {}
|
107
|
-
[
|
108
|
+
%i[header bfirst gfirst body footer gfooter].each do |loc|
|
108
109
|
@format_at[loc] = {}
|
109
110
|
table.headers.each do |h|
|
110
111
|
fmt_hash = self.class.default_format
|
@@ -205,13 +206,13 @@ module FatTable
|
|
205
206
|
foot = {}
|
206
207
|
sum_cols.each do |h|
|
207
208
|
unless table.headers.include?(h)
|
208
|
-
raise UserError, "No '#{h}' column in table
|
209
|
+
raise UserError, "No '#{h}' column in table for group sum footer"
|
209
210
|
end
|
210
211
|
foot[h] = :sum
|
211
212
|
end
|
212
213
|
agg_cols.each do |h, agg|
|
213
214
|
unless table.headers.include?(h)
|
214
|
-
raise UserError, "No '#{h}' column in table
|
215
|
+
raise UserError, "No '#{h}' column in table for #{agg} group footer"
|
215
216
|
end
|
216
217
|
foot[h] = agg
|
217
218
|
end
|
@@ -411,7 +412,7 @@ module FatTable
|
|
411
412
|
#
|
412
413
|
# \n\[niltext\]:: render a nil item with the given text.
|
413
414
|
def format(**fmts)
|
414
|
-
[
|
415
|
+
%i[header bfirst gfirst body footer gfooter].each do |loc|
|
415
416
|
format_for(loc, fmts)
|
416
417
|
end
|
417
418
|
self
|
@@ -465,7 +466,7 @@ module FatTable
|
|
465
466
|
unless LOCATIONS.include?(location)
|
466
467
|
raise UserError, "unknown format location '#{location}'"
|
467
468
|
end
|
468
|
-
valid_keys = table.headers + [
|
469
|
+
valid_keys = table.headers + %i[string numeric datetime boolean nil]
|
469
470
|
invalid_keys = (fmts.keys - valid_keys).uniq
|
470
471
|
unless invalid_keys.empty?
|
471
472
|
msg = "invalid #{location} column or type: #{invalid_keys.join(',')}"
|
@@ -504,14 +505,15 @@ module FatTable
|
|
504
505
|
end
|
505
506
|
if fmts[h]
|
506
507
|
# Merge in column formatting
|
507
|
-
col_fmt = send(parse_typ_method_name, fmts[h],
|
508
|
+
col_fmt = send(parse_typ_method_name, fmts[h],
|
509
|
+
strict: location != :header)
|
508
510
|
format_h = format_h.merge(col_fmt)
|
509
511
|
end
|
510
512
|
|
511
513
|
if location == :body
|
512
514
|
# Copy :body formatting for column h to :bfirst and :gfirst if they
|
513
|
-
# still have the default formatting. Can be overridden with a
|
514
|
-
# call with those locations.
|
515
|
+
# still have the default formatting. Can be overridden with a
|
516
|
+
# format_for call with those locations.
|
515
517
|
format_h.each_pair do |k, v|
|
516
518
|
if format_at[:bfirst][h].send(k) == default_format[k]
|
517
519
|
format_at[:bfirst][h].send("#{k}=", v)
|
@@ -538,9 +540,9 @@ module FatTable
|
|
538
540
|
self
|
539
541
|
end
|
540
542
|
|
541
|
-
|
543
|
+
############################################################################
|
542
544
|
# Parsing and validation routines
|
543
|
-
|
545
|
+
############################################################################
|
544
546
|
|
545
547
|
private
|
546
548
|
|
@@ -591,11 +593,11 @@ module FatTable
|
|
591
593
|
fmt = fmt.sub($&, '')
|
592
594
|
end
|
593
595
|
if fmt =~ /(~\s*)?B/
|
594
|
-
fmt_hash[:bold] =
|
596
|
+
fmt_hash[:bold] = !$1
|
595
597
|
fmt = fmt.sub($&, '')
|
596
598
|
end
|
597
599
|
if fmt =~ /(~\s*)?I/
|
598
|
-
fmt_hash[:italic] =
|
600
|
+
fmt_hash[:italic] = !$1
|
599
601
|
fmt = fmt.sub($&, '')
|
600
602
|
end
|
601
603
|
if fmt =~ /R/
|
@@ -611,11 +613,11 @@ module FatTable
|
|
611
613
|
fmt = fmt.sub($&, '')
|
612
614
|
end
|
613
615
|
if fmt =~ /(~\s*)?_/
|
614
|
-
fmt_hash[:underline] =
|
616
|
+
fmt_hash[:underline] = !$1
|
615
617
|
fmt = fmt.sub($&, '')
|
616
618
|
end
|
617
619
|
if fmt =~ /(~\s*)?\*/
|
618
|
-
fmt_hash[:blink] =
|
620
|
+
fmt_hash[:blink] = !$1
|
619
621
|
fmt = fmt.sub($&, '')
|
620
622
|
end
|
621
623
|
[fmt_hash, fmt]
|
@@ -652,15 +654,15 @@ module FatTable
|
|
652
654
|
fmt = fmt.sub($&, '')
|
653
655
|
end
|
654
656
|
if fmt =~ /(~\s*)?,/
|
655
|
-
fmt_hash[:commas] =
|
657
|
+
fmt_hash[:commas] = !$1
|
656
658
|
fmt = fmt.sub($&, '')
|
657
659
|
end
|
658
660
|
if fmt =~ /(~\s*)?\$/
|
659
|
-
fmt_hash[:currency] =
|
661
|
+
fmt_hash[:currency] = !$1
|
660
662
|
fmt = fmt.sub($&, '')
|
661
663
|
end
|
662
664
|
if fmt =~ /(~\s*)?H/
|
663
|
-
fmt_hash[:hms] =
|
665
|
+
fmt_hash[:hms] = !$1
|
664
666
|
fmt = fmt.sub($&, '')
|
665
667
|
end
|
666
668
|
unless fmt.blank? || !strict
|
@@ -687,7 +689,8 @@ module FatTable
|
|
687
689
|
fmt = fmt.sub($&, '')
|
688
690
|
end
|
689
691
|
unless fmt.blank? || !strict
|
690
|
-
|
692
|
+
msg = "unrecognized datetime formatting instructions '#{fmt}'"
|
693
|
+
raise UserError, msg
|
691
694
|
end
|
692
695
|
fmt_hash
|
693
696
|
end
|
@@ -708,7 +711,8 @@ module FatTable
|
|
708
711
|
end
|
709
712
|
# Since true_text, false_text and nil_text may want to have internal
|
710
713
|
# spaces, defer removing extraneous spaces until after they are parsed.
|
711
|
-
if fmt =~ /c\[(#{CLR_RE})(\.(#{CLR_RE}))
|
714
|
+
if fmt =~ /c\[(#{CLR_RE})(\.(#{CLR_RE}))?,
|
715
|
+
\s*(#{CLR_RE})(\.(#{CLR_RE}))?\]/x
|
712
716
|
fmt_hash[:true_color] = $1 unless $1.blank?
|
713
717
|
fmt_hash[:true_bgcolor] = $3 unless $3.blank?
|
714
718
|
fmt_hash[:false_color] = $4 unless $4.blank?
|
@@ -738,9 +742,9 @@ module FatTable
|
|
738
742
|
fmt_hash
|
739
743
|
end
|
740
744
|
|
741
|
-
|
745
|
+
############################################################################
|
742
746
|
# Applying formatting
|
743
|
-
|
747
|
+
############################################################################
|
744
748
|
|
745
749
|
public
|
746
750
|
|
@@ -836,7 +840,7 @@ module FatTable
|
|
836
840
|
result = val.secs_to_hms
|
837
841
|
istruct.commas = false
|
838
842
|
elsif istruct.currency
|
839
|
-
prec = istruct.post_digits
|
843
|
+
prec = istruct.post_digits.zero? ? 2 : istruct.post_digits
|
840
844
|
delim = istruct.commas ? ',' : ''
|
841
845
|
result = val.to_s(:currency, precision: prec, delimiter: delim,
|
842
846
|
unit: FatTable.currency_symbol)
|
@@ -903,9 +907,9 @@ module FatTable
|
|
903
907
|
val
|
904
908
|
end
|
905
909
|
|
906
|
-
|
910
|
+
############################################################################
|
907
911
|
# Output routines
|
908
|
-
|
912
|
+
############################################################################
|
909
913
|
|
910
914
|
public
|
911
915
|
|
@@ -1016,7 +1020,7 @@ module FatTable
|
|
1016
1020
|
# Return a hash mapping the table's headers to their formatted versions. If
|
1017
1021
|
# a hash of column widths is given, perform alignment within the given field
|
1018
1022
|
# widths.
|
1019
|
-
def build_formatted_headers(
|
1023
|
+
def build_formatted_headers(_widths = {})
|
1020
1024
|
# Don't decorate if this Formatter calls for alignment. It will be done
|
1021
1025
|
# in the second pass.
|
1022
1026
|
decorate = !aligned?
|
@@ -1058,7 +1062,8 @@ module FatTable
|
|
1058
1062
|
grp_col[h] ||= Column.new(header: h)
|
1059
1063
|
grp_col[h] << row[h]
|
1060
1064
|
istruct = format_at[location][h]
|
1061
|
-
new_row[h] = [row[h], format_cell(row[h], istruct,
|
1065
|
+
new_row[h] = [row[h], format_cell(row[h], istruct,
|
1066
|
+
decorate: decorate)]
|
1062
1067
|
end
|
1063
1068
|
new_rows << [location, new_row]
|
1064
1069
|
tbl_row_k += 1
|