fat_table 0.2.7 → 0.3.1

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This module provides objects for treating tables as a data type on which you
2
4
  # can (1) perform operations, such as select, where, join, and others and (2)
3
5
  # output the tables in several formats, including text, ANSI terminal, LaTeX,
@@ -24,6 +26,15 @@ module FatTable
24
26
  require 'fat_table/db_handle'
25
27
  require 'fat_table/errors'
26
28
 
29
+ # Add paths for common db gems to the load paths
30
+ %w[pg mysql2 sqlite].each do |gem_name|
31
+ path = Dir.glob("#{ENV['GEM_HOME']}/gems/#{gem_name}*").sort.last
32
+ if path
33
+ path = File.join(path, 'lib')
34
+ $LOAD_PATH << path unless $LOAD_PATH.include?(path)
35
+ end
36
+ end
37
+
27
38
  # Valid output formats as symbols.
28
39
  FORMATS = %i[psv aoa aoh latex org term text].freeze
29
40
 
@@ -114,7 +125,7 @@ module FatTable
114
125
  end
115
126
 
116
127
  # Construct a Table by running a SQL query against the database set up with
117
- # FatTable.set_db. Return the Table with the query results as rows and the
128
+ # FatTable.connect. Return the Table with the query results as rows and the
118
129
  # headers from the query, converted to symbols, as headers.
119
130
  def self.from_sql(query)
120
131
  Table.from_sql(query)
@@ -149,9 +160,9 @@ module FatTable
149
160
  raise UserError, "unknown format '#{fmt}'" unless FORMATS.include?(fmt)
150
161
  method = "to_#{fmt}"
151
162
  if block_given?
152
- send method, table, options, &Proc.new
163
+ send(method, table, options, &Proc.new)
153
164
  else
154
- send method, table, options
165
+ send(method, table, options)
155
166
  end
156
167
  end
157
168
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # Column objects are a thin wrapper around an Array to allow columns to be
3
5
  # summed and have other aggregate operations performed on them, but compacting
@@ -21,7 +23,7 @@ module FatTable
21
23
  attr_reader :type
22
24
 
23
25
  # An Array of the items of this Column, all of which must be values of the
24
- # Columns type or a nil. This Array contains the value of the item after
26
+ # Column's type or a nil. This Array contains the value of the item after
25
27
  # conversion to a native Ruby type, such as TrueClass, Date, DateTime,
26
28
  # Integer, String, etc. Thus, you can perform operations on the items,
27
29
  # perhaps after removing nils with +.items.compact+.
@@ -92,6 +94,7 @@ module FatTable
92
94
  @type = 'NilClass'
93
95
  msg = "unknown column type '#{type}"
94
96
  raise UserError, msg unless TYPES.include?(@type.to_s)
97
+
95
98
  @items = []
96
99
  items.each { |i| self << i }
97
100
  end
@@ -103,8 +106,8 @@ module FatTable
103
106
  # :category: Attributes
104
107
 
105
108
  # Return the item of the Column at the given index.
106
- def [](k)
107
- items[k]
109
+ def [](idx)
110
+ items[idx]
108
111
  end
109
112
 
110
113
  # :category: Attributes
@@ -229,11 +232,13 @@ module FatTable
229
232
  # average back to a DateTime.
230
233
  def avg
231
234
  only_with('avg', 'DateTime', 'Numeric')
235
+ itms = items.compact
236
+ size = itms.size.to_d
232
237
  if type == 'DateTime'
233
- avg_jd = items.compact.map(&:jd).sum / items.compact.size.to_d
238
+ avg_jd = itms.map(&:jd).sum / size
234
239
  DateTime.jd(avg_jd)
235
240
  else
236
- sum / items.compact.size.to_d
241
+ itms.sum / size
237
242
  end
238
243
  end
239
244
 
@@ -480,34 +485,41 @@ module FatTable
480
485
  end
481
486
  end
482
487
 
483
- IS0_DATE_RE = %r{\b(\d\d\d\d)[-/](\d\d?)[-/](\d\d?)\s*
484
- (T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?\b}x
485
- AMR_DATE_RE = %r{\b(\d\d?)[-/](\d\d?)[-/](\d\d\d\d)\s*
486
- (T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?\b}x
488
+ ISO_DATE_RE = %r{(?<yr>\d\d\d\d)[-\/]
489
+ (?<mo>\d\d?)[-\/]
490
+ (?<dy>\d\d?)\s*
491
+ (T?\s*\d\d:\d\d(:\d\d)?
492
+ ([-+](\d\d?)(:\d\d?))?)?}x
493
+
494
+ AMR_DATE_RE = %r{(?<dy>\d\d?)[-/](?<mo>\d\d?)[-/](?<yr>\d\d\d\d)\s*
495
+ (?<tm>T\d\d:\d\d:\d\d(\+\d\d:\d\d)?)?}x
487
496
 
488
- # Convert the val to a DateTime if it is either a DateTime, a Date, or a
497
+ # Convert the val to a DateTime if it is either a DateTime, a Date, a Time, or a
489
498
  # String that can be parsed as a DateTime, otherwise return nil. It only
490
- # recognizes strings that contain a something like '2016-01-14' or
491
- # '2/12/1985' within them, otherwise DateTime.parse would treat many bare
492
- # numbers as dates, such as '2841381', which it would recognize as a valid
493
- # date, but the user probably does not intend it to be so treated.
499
+ # recognizes strings that contain a something like '2016-01-14' or '2/12/1985'
500
+ # within them, otherwise DateTime.parse would treat many bare numbers as dates,
501
+ # such as '2841381', which it would recognize as a valid date, but the user
502
+ # probably does not intend it to be so treated.
494
503
  def convert_to_date_time(val)
495
504
  return val if val.is_a?(DateTime)
496
505
  return val if val.is_a?(Date)
497
506
  begin
498
- val = val.to_s.clean
499
- return nil if val.blank?
500
- if val.match?(IS0_DATE_RE)
501
- val = DateTime.parse(val)
502
- elsif val =~ AMR_DATE_RE
503
- val = DateTime.new($3.to_i, $1.to_i, $2.to_i)
507
+ str = val.to_s.clean
508
+ return nil if str.blank?
509
+
510
+ if str.match(ISO_DATE_RE)
511
+ date = DateTime.parse(val)
512
+ elsif str =~ AMR_DATE_RE
513
+ date = DateTime.new(Regexp.last_match[:yr].to_i,
514
+ Regexp.last_match[:mo].to_i,
515
+ Regexp.last_match[:dy].to_i)
504
516
  else
505
517
  return nil
506
518
  end
507
- val = val.to_date if val.seconds_since_midnight.zero?
508
- val
519
+ # val = val.to_date if
520
+ date.seconds_since_midnight.zero? ? date.to_date : date
509
521
  rescue ArgumentError
510
- return nil
522
+ nil
511
523
  end
512
524
  end
513
525
 
@@ -515,7 +527,7 @@ module FatTable
515
527
  # looks like one. Any Float is promoted to a BigDecimal. Otherwise return
516
528
  # nil.
517
529
  def convert_to_numeric(val)
518
- return BigDecimal.new(val, Float::DIG) if val.is_a?(Float)
530
+ return BigDecimal(val, Float::DIG) if val.is_a?(Float)
519
531
  return val if val.is_a?(Numeric)
520
532
  # Eliminate any commas, $'s (or other currency symbol), or _'s.
521
533
  cursym = Regexp.quote(FatTable.currency_symbol)
@@ -524,11 +536,11 @@ module FatTable
524
536
  return nil if val.blank?
525
537
  case val
526
538
  when /(\A[-+]?\d+\.\d*\z)|(\A[-+]?\d*\.\d+\z)/
527
- BigDecimal.new(val.to_s.clean)
539
+ BigDecimal(val.to_s.clean)
528
540
  when /\A[-+]?[\d]+\z/
529
541
  val.to_i
530
- when %r{\A([-+]?\d+)\s*[:/]\s*([-+]?\d+)\z}
531
- Rational($1, $2)
542
+ when %r{\A(?<nm>[-+]?\d+)\s*[:/]\s*(?<dn>[-+]?\d+)\z}
543
+ Rational(Regexp.last_match[:nm], Regexp.last_match[:dn])
532
544
  end
533
545
  end
534
546
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Set and access a database by module-level methods.
2
4
  module FatTable
3
5
  class << self
@@ -18,10 +20,11 @@ module FatTable
18
20
  # Sequel's adapter-specific connection methods.
19
21
  # http://sequel.jeremyevans.net/rdoc/files/doc/opening_databases_rdoc.html
20
22
  #
21
- # +driver+::
23
+ # +adapter+::
22
24
  # 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'.
25
+ # 'sqlite' (for SQLite3) (or any other adapter supported by the +Sequel+
26
+ # gem) to specify the driver to use. You may have to install the
27
+ # appropriate driver to make this work.
25
28
  #
26
29
  # +database+::
27
30
  # The name of the database to access. There is no default for this.
@@ -51,63 +54,32 @@ module FatTable
51
54
  # successfully, this establishes the database handle to use for all subsequent
52
55
  # calls to FatTable.from_sql or FatTable::Table.from_sql. You can then access
53
56
  # the handle if needed with FatTable.db.
54
- def self.set_db(args)
57
+ def self.connect(args)
55
58
  # Set the dsn for Sequel
56
59
  begin
57
60
  self.handle = Sequel.connect(args)
58
- rescue Sequel::Error => ex
59
- raise TransientError, "#{args}: #{ex}"
61
+ rescue Sequel::AdapterNotFound => ex
62
+ case ex.to_s
63
+ when /pg/
64
+ raise TransientError, 'You need to install the postgres adapter pg'
65
+ when /mysql/
66
+ raise TransientError, 'You need to install the mysql adapter'
67
+ when /sqlite/
68
+ raise TransientError, 'You need to install the sqlite adapter'
69
+ else
70
+ raise ex
71
+ end
60
72
  end
61
73
  handle
62
74
  end
63
75
 
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
-
104
76
  # Return the +Sequel+ database handle.
105
77
  def self.db
106
78
  handle
107
79
  end
108
80
 
109
81
  # Directly set the db handle to a Sequel connection formed without
110
- # FatTable.set_db.
82
+ # FatTable.connect.
111
83
  def self.db=(db)
112
84
  self.handle = db
113
85
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # Raised when the caller of the code made an error that the caller can
3
5
  # correct.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # The Evaluator class provides a class for evaluating Ruby expressions based
3
5
  # on variable settings provided at runtime. If the same Evaluator object is
@@ -20,23 +22,25 @@ module FatTable
20
22
  def initialize(ivars: {}, before: nil, after: nil)
21
23
  @before = before
22
24
  @after = after
23
- set_instance_vars(ivars)
25
+ instance_vars(ivars)
24
26
  end
25
27
 
26
28
  # Set the @group instance variable to the given value.
27
29
  def update_ivars(ivars)
28
- set_instance_vars(ivars)
30
+ instance_vars(ivars)
29
31
  end
30
32
 
31
33
  # Run any before hook in the context of the given local variables.
32
34
  def eval_before_hook(locals: {})
33
35
  return if @before.blank?
36
+
34
37
  evaluate(@before, locals: locals)
35
38
  end
36
39
 
37
40
  # Run any after hook in the context of the given local variables.
38
41
  def eval_after_hook(locals: {})
39
42
  return if @after.blank?
43
+
40
44
  evaluate(@after, locals: locals)
41
45
  end
42
46
 
@@ -44,19 +48,21 @@ module FatTable
44
48
  # instance variables set in Evaluator.new and any local variables set in the
45
49
  # Hash parameter +locals+ are available to the expression.
46
50
  def evaluate(expr = '', locals: {})
47
- eval(expr, set_local_vars(binding, locals))
51
+ eval(expr, local_vars(binding, locals))
48
52
  end
49
53
 
50
54
  private
51
55
 
52
- def set_instance_vars(vars = {})
56
+ # Set the instance variables according to Hash vars.
57
+ def instance_vars(vars = {})
53
58
  vars.each_pair do |name, val|
54
59
  name = "@#{name}" unless name.to_s.start_with?('@')
55
60
  instance_variable_set(name, val)
56
61
  end
57
62
  end
58
63
 
59
- def set_local_vars(bnd, vars = {})
64
+ # Set the local variables within the binding bnd according to Hash vars.
65
+ def local_vars(bnd, vars = {})
60
66
  vars.each_pair do |name, val|
61
67
  bnd.local_variable_set(name, val)
62
68
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'fat_table/formatters/formatter'
2
4
  require 'fat_table/formatters/aoa_formatter'
3
5
  require 'fat_table/formatters/aoh_formatter'
@@ -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 Ruby Array of Arrays.
3
5
  # Each cell is formatted as a string in accordance with the formatting
@@ -30,20 +32,20 @@ module FatTable
30
32
  '['
31
33
  end
32
34
 
33
- def pre_cell(_h)
35
+ def pre_cell(_val)
34
36
  "'"
35
37
  end
36
38
 
37
39
  # Because the cell, after conversion to a single-quoted string will be
38
40
  # eval'ed, we need to escape any single-quotes (') that appear in the
39
41
  # string.
40
- def quote_cell(v)
41
- if v.match?(/'/)
42
+ def quote_cell(val)
43
+ if val.match?(/'/)
42
44
  # Use a negative look-behind to only quote single-quotes that are not
43
45
  # already preceded by a backslash
44
- v.gsub(/(?<!\\)'/, "'" => "\\'")
46
+ val.gsub(/(?<!\\)'/, "'" => "\\'")
45
47
  else
46
- v
48
+ val
47
49
  end
48
50
  end
49
51
 
@@ -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 Ruby Array of Hashes.
3
5
  # Each row of the Array is a Hash representing one row of the table with the
@@ -30,20 +32,20 @@ module FatTable
30
32
  '{'
31
33
  end
32
34
 
33
- def pre_cell(h)
34
- ":#{h.as_sym} => '"
35
+ def pre_cell(head)
36
+ ":#{head.as_sym} => '"
35
37
  end
36
38
 
37
39
  # Because the cell, after conversion to a single-quoted string will be
38
40
  # eval'ed, we need to escape any single-quotes (') that appear in the
39
41
  # string.
40
- def quote_cell(v)
41
- if v.match?(/'/)
42
+ def quote_cell(val)
43
+ if val.match?(/'/)
42
44
  # Use a negative look-behind to only quote single-quotes that are not
43
45
  # already preceded by a backslash
44
- v.gsub(/(?<!\\)'/, "'" => "\\'")
46
+ val.gsub(/(?<!\\)'/, "'" => "\\'")
45
47
  else
46
- v
48
+ val
47
49
  end
48
50
  end
49
51
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module FatTable
2
4
  # A Formatter is for use in Table output routines, and provides methods for
3
5
  # adding group and table footers to the output and instructions for how the
@@ -73,7 +75,7 @@ module FatTable
73
75
  false_color: 'none',
74
76
  false_bgcolor: 'none',
75
77
  underline: false,
76
- blink: false
78
+ blink: false,
77
79
  }
78
80
 
79
81
  class_attribute :valid_colors
@@ -92,9 +94,10 @@ module FatTable
92
94
  # Formatter is yielded to the block so that methods for formatting and
93
95
  # adding footers can be called on it.
94
96
  def initialize(table = Table.new, **options)
95
- unless table && table.is_a?(Table)
97
+ unless table&.is_a?(Table)
96
98
  raise UserError, 'must initialize Formatter with a Table'
97
99
  end
100
+
98
101
  @table = table
99
102
  @options = options
100
103
  @footers = {}
@@ -170,12 +173,14 @@ module FatTable
170
173
  unless table.headers.include?(h)
171
174
  raise UserError, "No '#{h}' column in table to sum in the footer"
172
175
  end
176
+
173
177
  foot[h] = :sum
174
178
  end
175
179
  agg_cols.each do |h, agg|
176
180
  unless table.headers.include?(h)
177
181
  raise UserError, "No '#{h}' column in table to #{agg} in the footer"
178
182
  end
183
+
179
184
  foot[h] = agg
180
185
  end
181
186
  @footers[label] = foot
@@ -208,12 +213,14 @@ module FatTable
208
213
  unless table.headers.include?(h)
209
214
  raise UserError, "No '#{h}' column in table for group sum footer"
210
215
  end
216
+
211
217
  foot[h] = :sum
212
218
  end
213
219
  agg_cols.each do |h, agg|
214
220
  unless table.headers.include?(h)
215
221
  raise UserError, "No '#{h}' column in table for #{agg} group footer"
216
222
  end
223
+
217
224
  foot[h] = agg
218
225
  end
219
226
  @gfooters[label] = foot
@@ -242,7 +249,7 @@ module FatTable
242
249
  cols.each do |c|
243
250
  hsh[c] = :avg
244
251
  end
245
- footer('Average', hsh)
252
+ footer('Average', **hsh)
246
253
  end
247
254
 
248
255
  # :category: Footers
@@ -253,7 +260,7 @@ module FatTable
253
260
  cols.each do |c|
254
261
  hsh[c] = :avg
255
262
  end
256
- gfooter('Group Average', hsh)
263
+ gfooter('Group Average', **hsh)
257
264
  end
258
265
 
259
266
  # :category: Footers
@@ -265,7 +272,7 @@ module FatTable
265
272
  cols.each do |c|
266
273
  hsh[c] = :min
267
274
  end
268
- footer('Minimum', hsh)
275
+ footer('Minimum', **hsh)
269
276
  end
270
277
 
271
278
  # :category: Footers
@@ -277,7 +284,7 @@ module FatTable
277
284
  cols.each do |c|
278
285
  hsh[c] = :min
279
286
  end
280
- gfooter('Group Minimum', hsh)
287
+ gfooter('Group Minimum', **hsh)
281
288
  end
282
289
 
283
290
  # :category: Footers
@@ -289,7 +296,7 @@ module FatTable
289
296
  cols.each do |c|
290
297
  hsh[c] = :max
291
298
  end
292
- footer('Maximum', hsh)
299
+ footer('Maximum', **hsh)
293
300
  end
294
301
 
295
302
  # :category: Footers
@@ -301,7 +308,7 @@ module FatTable
301
308
  cols.each do |c|
302
309
  hsh[c] = :max
303
310
  end
304
- gfooter('Group Maximum', hsh)
311
+ gfooter('Group Maximum', **hsh)
305
312
  end
306
313
 
307
314
  # :category: Formatting
@@ -413,7 +420,7 @@ module FatTable
413
420
  # \n\[niltext\]:: render a nil item with the given text.
414
421
  def format(**fmts)
415
422
  %i[header bfirst gfirst body footer gfooter].each do |loc|
416
- format_for(loc, fmts)
423
+ format_for(loc, **fmts)
417
424
  end
418
425
  self
419
426
  end
@@ -466,6 +473,7 @@ module FatTable
466
473
  unless LOCATIONS.include?(location)
467
474
  raise UserError, "unknown format location '#{location}'"
468
475
  end
476
+
469
477
  valid_keys = table.headers + %i[string numeric datetime boolean nil]
470
478
  invalid_keys = (fmts.keys - valid_keys).uniq
471
479
  unless invalid_keys.empty?
@@ -487,18 +495,18 @@ module FatTable
487
495
  # Merge in string and nil formatting, but not in header. Header is
488
496
  # always typed a string, so it will get formatted in type-based
489
497
  # formatting below. And headers are never nil.
490
- if fmts.keys.include?(:string)
498
+ if fmts.key?(:string)
491
499
  typ_fmt = parse_string_fmt(fmts[:string])
492
500
  format_h = format_h.merge(typ_fmt)
493
501
  end
494
- if fmts.keys.include?(:nil)
502
+ if fmts.key?(:nil)
495
503
  typ_fmt = parse_nil_fmt(fmts[:nil]).first
496
504
  format_h = format_h.merge(typ_fmt)
497
505
  end
498
506
  end
499
507
  typ = location == :header ? :string : table.type(h).as_sym
500
508
  parse_typ_method_name = 'parse_' + typ.to_s + '_fmt'
501
- if fmts.keys.include?(typ)
509
+ if fmts.key?(typ)
502
510
  # Merge in type-based formatting
503
511
  typ_fmt = send(parse_typ_method_name, fmts[typ])
504
512
  format_h = format_h.merge(typ_fmt)
@@ -547,7 +555,7 @@ module FatTable
547
555
  private
548
556
 
549
557
  # Re to match a color name
550
- CLR_RE = /(?:[-_a-zA-Z0-9 ]*)/
558
+ CLR_RE = /(?:[-_a-zA-Z0-9 ]*)/.freeze
551
559
 
552
560
  # Return a hash that reflects the formatting instructions given in the
553
561
  # string fmt. Raise an error if it contains invalid formatting instructions.
@@ -558,6 +566,7 @@ module FatTable
558
566
  unless fmt.blank? || !strict
559
567
  raise UserError, "unrecognized string formatting instructions '#{fmt}'"
560
568
  end
569
+
561
570
  format
562
571
  end
563
572
 
@@ -570,9 +579,9 @@ module FatTable
570
579
  # parse, we remove the matched construct from fmt. At the end, any
571
580
  # remaining characters in fmt should be invalid.
572
581
  fmt_hash = {}
573
- if fmt =~ /c\[(#{CLR_RE})(\.(#{CLR_RE}))?\]/
574
- fmt_hash[:color] = $1 unless $1.blank?
575
- fmt_hash[:bgcolor] = $3 unless $3.blank?
582
+ if fmt =~ /c\[(?<co>#{CLR_RE})(\.(?<bg>#{CLR_RE}))?\]/
583
+ fmt_hash[:color] = Regexp.last_match[:co] unless Regexp.last_match[:co].blank?
584
+ fmt_hash[:bgcolor] = Regexp.last_match[:bg] unless Regexp.last_match[:bg].blank?
576
585
  validate_color(fmt_hash[:color])
577
586
  validate_color(fmt_hash[:bgcolor])
578
587
  fmt = fmt.sub($&, '')
@@ -592,12 +601,12 @@ module FatTable
592
601
  fmt_hash[:case] = :title
593
602
  fmt = fmt.sub($&, '')
594
603
  end
595
- if fmt =~ /(~\s*)?B/
596
- fmt_hash[:bold] = !$1
604
+ if fmt =~ /(?<neg>~\s*)?B/
605
+ fmt_hash[:bold] = !Regexp.last_match[:neg]
597
606
  fmt = fmt.sub($&, '')
598
607
  end
599
- if fmt =~ /(~\s*)?I/
600
- fmt_hash[:italic] = !$1
608
+ if fmt =~ /(?<neg>~\s*)?I/
609
+ fmt_hash[:italic] = !Regexp.last_match[:neg]
601
610
  fmt = fmt.sub($&, '')
602
611
  end
603
612
  if fmt =~ /R/
@@ -612,12 +621,12 @@ module FatTable
612
621
  fmt_hash[:alignment] = :left
613
622
  fmt = fmt.sub($&, '')
614
623
  end
615
- if fmt =~ /(~\s*)?_/
616
- fmt_hash[:underline] = !$1
624
+ if fmt =~ /(?<neg>~\s*)?_/
625
+ fmt_hash[:underline] = !Regexp.last_match[:neg]
617
626
  fmt = fmt.sub($&, '')
618
627
  end
619
- if fmt =~ /(~\s*)?\*/
620
- fmt_hash[:blink] = !$1
628
+ if fmt =~ /(?<neg>~\s*)?\*/
629
+ fmt_hash[:blink] = !Regexp.last_match[:neg]
621
630
  fmt = fmt.sub($&, '')
622
631
  end
623
632
  [fmt_hash, fmt]
@@ -632,9 +641,9 @@ module FatTable
632
641
  # parse, we remove the matched construct from fmt. At the end, any
633
642
  # remaining characters in fmt should be invalid.
634
643
  fmt_hash = {}
635
- if fmt =~ /n\[\s*([^\]]*)\s*\]/
636
- fmt_hash[:nil_text] = $1.clean
637
- fmt = fmt.sub($&, '')
644
+ if fmt =~ /n\[\s*(?<bdy>[^\]]*)\s*\]/
645
+ fmt_hash[:nil_text] = Regexp.last_match[:bdy].clean
646
+ fmt = fmt.sub(Regexp.last_match[0], '')
638
647
  end
639
648
  [fmt_hash, fmt]
640
649
  end
@@ -648,26 +657,27 @@ module FatTable
648
657
  # parse, we remove the matched construct from fmt. At the end, any
649
658
  # remaining characters in fmt should be invalid.
650
659
  fmt_hash, fmt = parse_str_fmt(fmt)
651
- if fmt =~ /(\d+).(\d+)/
652
- fmt_hash[:pre_digits] = $1.to_i
653
- fmt_hash[:post_digits] = $2.to_i
654
- fmt = fmt.sub($&, '')
660
+ if fmt =~ /(?<pre>\d+).(?<post>\d+)/
661
+ fmt_hash[:pre_digits] = Regexp.last_match[:pre].to_i
662
+ fmt_hash[:post_digits] = Regexp.last_match[:post].to_i
663
+ fmt = fmt.sub(Regexp.last_match[0], '')
655
664
  end
656
- if fmt =~ /(~\s*)?,/
657
- fmt_hash[:commas] = !$1
658
- fmt = fmt.sub($&, '')
665
+ if fmt =~ /(?<neg>~\s*)?,/
666
+ fmt_hash[:commas] = !Regexp.last_match[:neg]
667
+ fmt = fmt.sub(Regexp.last_match[0], '')
659
668
  end
660
- if fmt =~ /(~\s*)?\$/
661
- fmt_hash[:currency] = !$1
662
- fmt = fmt.sub($&, '')
669
+ if fmt =~ /(?<neg>~\s*)?\$/
670
+ fmt_hash[:currency] = !Regexp.last_match[:neg]
671
+ fmt = fmt.sub(Regexp.last_match[0], '')
663
672
  end
664
- if fmt =~ /(~\s*)?H/
665
- fmt_hash[:hms] = !$1
666
- fmt = fmt.sub($&, '')
673
+ if fmt =~ /(?<neg>~\s*)?H/
674
+ fmt_hash[:hms] = !Regexp.last_match[:neg]
675
+ fmt = fmt.sub(Regexp.last_match[0], '')
667
676
  end
668
677
  unless fmt.blank? || !strict
669
678
  raise UserError, "unrecognized numeric formatting instructions '#{fmt}'"
670
679
  end
680
+
671
681
  fmt_hash
672
682
  end
673
683
 
@@ -680,13 +690,13 @@ module FatTable
680
690
  # parse, we remove the matched construct from fmt. At the end, any
681
691
  # remaining characters in fmt should be invalid.
682
692
  fmt_hash, fmt = parse_str_fmt(fmt)
683
- if fmt =~ /d\[([^\]]*)\]/
684
- fmt_hash[:date_fmt] = $1
685
- fmt = fmt.sub($&, '')
693
+ if fmt =~ /d\[(?<bdy>[^\]]*)\]/
694
+ fmt_hash[:date_fmt] = Regexp.last_match[:bdy]
695
+ fmt = fmt.sub(Regexp.last_match[0], '')
686
696
  end
687
- if fmt =~ /D\[([^\]]*)\]/
688
- fmt_hash[:date_fmt] = $1
689
- fmt = fmt.sub($&, '')
697
+ if fmt =~ /D\[(?<bdy>[^\]]*)\]/
698
+ fmt_hash[:date_fmt] = Regexp.last_match[:bdy]
699
+ fmt = fmt.sub(Regexp.last_match[0], '')
690
700
  end
691
701
  unless fmt.blank? || !strict
692
702
  msg = "unrecognized datetime formatting instructions '#{fmt}'"
@@ -704,41 +714,43 @@ module FatTable
704
714
  # parse, we remove the matched construct from fmt. At the end, any
705
715
  # remaining characters in fmt should be invalid.
706
716
  fmt_hash = {}
707
- if fmt =~ /b\[\s*([^\],]*),([^\]]*)\s*\]/
708
- fmt_hash[:true_text] = $1.clean
709
- fmt_hash[:false_text] = $2.clean
710
- fmt = fmt.sub($&, '')
717
+ if fmt =~ /b\[\s*(?<t>[^\],]*),(?<f>[^\]]*)\s*\]/
718
+ fmt_hash[:true_text] = Regexp.last_match[:t].clean
719
+ fmt_hash[:false_text] = Regexp.last_match[:f].clean
720
+ fmt = fmt.sub(Regexp.last_match[0], '')
711
721
  end
712
722
  # Since true_text, false_text and nil_text may want to have internal
713
723
  # spaces, defer removing extraneous spaces until after they are parsed.
714
724
  if fmt =~ /c\[(#{CLR_RE})(\.(#{CLR_RE}))?,
715
725
  \s*(#{CLR_RE})(\.(#{CLR_RE}))?\]/x
716
- fmt_hash[:true_color] = $1 unless $1.blank?
717
- fmt_hash[:true_bgcolor] = $3 unless $3.blank?
718
- fmt_hash[:false_color] = $4 unless $4.blank?
719
- fmt_hash[:false_bgcolor] = $6 unless $6.blank?
720
- fmt = fmt.sub($&, '')
726
+ tco, _, tbg, fco, _, fbg = Regexp.last_match.captures
727
+ fmt_hash[:true_color] = tco unless tco.blank?
728
+ fmt_hash[:true_bgcolor] = tbg unless tbg.blank?
729
+ fmt_hash[:false_color] = fco unless fco.blank?
730
+ fmt_hash[:false_bgcolor] = fbg unless fbg.blank?
731
+ fmt = fmt.sub(Regexp.last_match[0], '')
721
732
  end
722
733
  str_fmt_hash, fmt = parse_str_fmt(fmt)
723
734
  fmt_hash = fmt_hash.merge(str_fmt_hash)
724
735
  if fmt =~ /Y/
725
736
  fmt_hash[:true_text] = 'Y'
726
737
  fmt_hash[:false_text] = 'N'
727
- fmt = fmt.sub($&, '')
738
+ fmt = fmt.sub(Regexp.last_match[0], '')
728
739
  end
729
740
  if fmt =~ /T/
730
741
  fmt_hash[:true_text] = 'T'
731
742
  fmt_hash[:false_text] = 'F'
732
- fmt = fmt.sub($&, '')
743
+ fmt = fmt.sub(Regexp.last_match[0], '')
733
744
  end
734
745
  if fmt =~ /X/
735
746
  fmt_hash[:true_text] = 'X'
736
747
  fmt_hash[:false_text] = ''
737
- fmt = fmt.sub($&, '')
748
+ fmt = fmt.sub(Regexp.last_match[0], '')
738
749
  end
739
750
  unless fmt.blank? || !strict
740
751
  raise UserError, "unrecognized boolean formatting instructions '#{fmt}'"
741
752
  end
753
+
742
754
  fmt_hash
743
755
  end
744
756
 
@@ -810,6 +822,7 @@ module FatTable
810
822
  # specializing this method.
811
823
  def format_boolean(val, istruct)
812
824
  return istruct.nil_text if val.nil?
825
+
813
826
  val ? istruct.true_text : istruct.false_text
814
827
  end
815
828
 
@@ -820,6 +833,7 @@ module FatTable
820
833
  # specializing this method.
821
834
  def format_datetime(val, istruct)
822
835
  return istruct.nil_text if val.nil?
836
+
823
837
  if val.to_date == val
824
838
  # It is a Date, with no time component.
825
839
  val.strftime(istruct.date_fmt)
@@ -835,6 +849,7 @@ module FatTable
835
849
  # specializing this method.
836
850
  def format_numeric(val, istruct)
837
851
  return istruct.nil_text if val.nil?
852
+
838
853
  val = val.round(istruct.post_digits) if istruct.post_digits >= 0
839
854
  if istruct.hms
840
855
  result = val.secs_to_hms
@@ -998,6 +1013,7 @@ module FatTable
998
1013
  new_rows.each do |loc_row|
999
1014
  result += hline(widths) if loc_row.nil?
1000
1015
  next if loc_row.nil?
1016
+
1001
1017
  _loc, row = *loc_row
1002
1018
  result += pre_row
1003
1019
  cells = []
@@ -1140,6 +1156,7 @@ module FatTable
1140
1156
  end
1141
1157
  rows.each do |loc_row|
1142
1158
  next if loc_row.nil?
1159
+
1143
1160
  _loc, row = *loc_row
1144
1161
  row.each_pair do |h, (_v, fmt_v)|
1145
1162
  widths[h] ||= 0
@@ -1222,12 +1239,12 @@ module FatTable
1222
1239
  ''
1223
1240
  end
1224
1241
 
1225
- def pre_cell(_h)
1242
+ def pre_cell(_head)
1226
1243
  ''
1227
1244
  end
1228
1245
 
1229
- def quote_cell(v)
1230
- v
1246
+ def quote_cell(val)
1247
+ val
1231
1248
  end
1232
1249
 
1233
1250
  def post_cell