fat_table 0.2.7 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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