fat_table 0.2.6 → 0.2.7
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/.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
|