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.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/.rubocop.yml +18 -0
- data/.travis.yml +7 -4
- data/.yardopts +5 -1
- data/Gemfile +2 -0
- data/README.org +82 -76
- data/README.rdoc +4 -4
- data/fat_table.gemspec +8 -8
- data/lib/fat_table.rb +14 -3
- data/lib/fat_table/column.rb +39 -27
- data/lib/fat_table/db_handle.rb +19 -47
- data/lib/fat_table/errors.rb +2 -0
- data/lib/fat_table/evaluator.rb +11 -5
- data/lib/fat_table/formatters.rb +2 -0
- data/lib/fat_table/formatters/aoa_formatter.rb +7 -5
- data/lib/fat_table/formatters/aoh_formatter.rb +8 -6
- data/lib/fat_table/formatters/formatter.rb +78 -61
- data/lib/fat_table/formatters/latex_formatter.rb +7 -5
- data/lib/fat_table/formatters/org_formatter.rb +5 -3
- data/lib/fat_table/formatters/term_formatter.rb +33 -28
- data/lib/fat_table/formatters/text_formatter.rb +5 -3
- data/lib/fat_table/patches.rb +5 -2
- data/lib/fat_table/table.rb +78 -57
- data/lib/fat_table/version.rb +3 -1
- data/{README.md → md/README.md} +5 -6
- metadata +49 -39
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FatTable
|
2
4
|
# A subclass of Formatter for rendering the table as a LaTeX table. It allows
|
3
5
|
# foreground colors through LaTeX's xcolor package but ignores background
|
@@ -17,7 +19,6 @@ module FatTable
|
|
17
19
|
# LaTeX tabular-like environment to use for the table. The default is good
|
18
20
|
# for tables that might continue over multiple pages since it repeats the
|
19
21
|
# header at the top of each continuation page.
|
20
|
-
|
21
22
|
def initialize(table = Table.new, **options)
|
22
23
|
super
|
23
24
|
@options[:document] = options.fetch(:document, false)
|
@@ -115,7 +116,8 @@ module FatTable
|
|
115
116
|
result = ''
|
116
117
|
result += '\\bfseries{}' if istruct.bold
|
117
118
|
result += '\\itshape{}' if istruct.italic
|
118
|
-
result += "\\color{#{istruct.color}}" if istruct.color &&
|
119
|
+
result += "\\color{#{istruct.color}}" if istruct.color &&
|
120
|
+
istruct.color != 'none'
|
119
121
|
result = "#{result}#{str}"
|
120
122
|
unless istruct.alignment == format_at[:body][istruct._h].alignment
|
121
123
|
ac = alignment_code(istruct.alignment)
|
@@ -173,14 +175,14 @@ module FatTable
|
|
173
175
|
''
|
174
176
|
end
|
175
177
|
|
176
|
-
def pre_cell(
|
178
|
+
def pre_cell(_head)
|
177
179
|
''
|
178
180
|
end
|
179
181
|
|
180
182
|
# We do quoting before applying decoration, so do not re-quote here. We
|
181
183
|
# will have LaTeX commands in v.
|
182
|
-
def quote_cell(
|
183
|
-
|
184
|
+
def quote_cell(val)
|
185
|
+
val
|
184
186
|
end
|
185
187
|
|
186
188
|
def post_cell
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FatTable
|
2
4
|
# Output the table in the same way as org-mode for emacs does. This is almost
|
3
5
|
# identical to TextFormatter except that dates do get formatted as inactive
|
@@ -30,12 +32,12 @@ module FatTable
|
|
30
32
|
'|'
|
31
33
|
end
|
32
34
|
|
33
|
-
def pre_cell(
|
35
|
+
def pre_cell(_head)
|
34
36
|
''
|
35
37
|
end
|
36
38
|
|
37
|
-
def quote_cell(
|
38
|
-
|
39
|
+
def quote_cell(val)
|
40
|
+
val
|
39
41
|
end
|
40
42
|
|
41
43
|
def post_cell
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rainbow'
|
2
4
|
|
3
5
|
module FatTable
|
@@ -26,9 +28,10 @@ module FatTable
|
|
26
28
|
super
|
27
29
|
@options[:unicode] = options.fetch(:unicode, true)
|
28
30
|
@options[:framecolor] = options.fetch(:framecolor, 'none.none')
|
29
|
-
return unless @options[:framecolor] =~ /([-_a-zA-Z]*)(\.([-_a-zA-Z]*))/
|
30
|
-
|
31
|
-
@options[:
|
31
|
+
return unless @options[:framecolor] =~ /(?<co>[-_a-zA-Z]*)(\.(?<bg>[-_a-zA-Z]*))/
|
32
|
+
|
33
|
+
@options[:frame_fg] = Regexp.last_match[:co].downcase unless Regexp.last_match[:co].blank?
|
34
|
+
@options[:frame_bg] = Regexp.last_match[:bg].downcase unless Regexp.last_match[:bg].blank?
|
32
35
|
end
|
33
36
|
|
34
37
|
# Valid colors for ANSI terminal using the rainbow gem's X11ColorNames.
|
@@ -59,6 +62,7 @@ module FatTable
|
|
59
62
|
|
60
63
|
def strip_ansi(str)
|
61
64
|
return '' unless str
|
65
|
+
|
62
66
|
str.gsub(/\e\[[0-9;]+m/, '')
|
63
67
|
end
|
64
68
|
|
@@ -73,18 +77,19 @@ module FatTable
|
|
73
77
|
result
|
74
78
|
end
|
75
79
|
|
76
|
-
def colorize(str,
|
77
|
-
|
78
|
-
|
79
|
-
return str unless
|
80
|
+
def colorize(str, fg_color, bg_color)
|
81
|
+
fg_color = nil if fg_color == 'none'
|
82
|
+
bg_color = nil if bg_color == 'none'
|
83
|
+
return str unless fg_color || bg_color
|
84
|
+
|
80
85
|
result = Rainbow(str)
|
81
|
-
if
|
82
|
-
|
83
|
-
result = result.color(
|
86
|
+
if fg_color
|
87
|
+
fg_color = fg_color.tr(' ', '').downcase.as_sym
|
88
|
+
result = result.color(fg_color) if fg_color
|
84
89
|
end
|
85
|
-
if
|
86
|
-
|
87
|
-
result = result.bg(
|
90
|
+
if bg_color
|
91
|
+
bg_color = bg_color.tr(' ', '').downcase.as_sym
|
92
|
+
result = result.bg(bg_color) if bg_color
|
88
93
|
end
|
89
94
|
result
|
90
95
|
end
|
@@ -98,18 +103,18 @@ module FatTable
|
|
98
103
|
# Unicode line-drawing characters. We use double lines before and after the
|
99
104
|
# table and single lines for the sides and hlines between groups and
|
100
105
|
# footers.
|
101
|
-
UPPER_LEFT = "\u2552"
|
102
|
-
UPPER_RIGHT = "\u2555"
|
103
|
-
DOUBLE_RULE = "\u2550"
|
104
|
-
UPPER_TEE = "\u2564"
|
105
|
-
VERTICAL_RULE = "\u2502"
|
106
|
-
LEFT_TEE = "\u251C"
|
107
|
-
HORIZONTAL_RULE = "\u2500"
|
108
|
-
SINGLE_CROSS = "\u253C"
|
109
|
-
RIGHT_TEE = "\u2524"
|
110
|
-
LOWER_LEFT = "\u2558"
|
111
|
-
LOWER_RIGHT = "\u255B"
|
112
|
-
LOWER_TEE = "\u2567"
|
106
|
+
UPPER_LEFT = "\u2552"
|
107
|
+
UPPER_RIGHT = "\u2555"
|
108
|
+
DOUBLE_RULE = "\u2550"
|
109
|
+
UPPER_TEE = "\u2564"
|
110
|
+
VERTICAL_RULE = "\u2502"
|
111
|
+
LEFT_TEE = "\u251C"
|
112
|
+
HORIZONTAL_RULE = "\u2500"
|
113
|
+
SINGLE_CROSS = "\u253C"
|
114
|
+
RIGHT_TEE = "\u2524"
|
115
|
+
LOWER_LEFT = "\u2558"
|
116
|
+
LOWER_RIGHT = "\u255B"
|
117
|
+
LOWER_TEE = "\u2567"
|
113
118
|
# :startdoc:
|
114
119
|
|
115
120
|
def upper_left
|
@@ -229,12 +234,12 @@ module FatTable
|
|
229
234
|
frame_colorize(vertical_rule)
|
230
235
|
end
|
231
236
|
|
232
|
-
def pre_cell(
|
237
|
+
def pre_cell(_head)
|
233
238
|
''
|
234
239
|
end
|
235
240
|
|
236
|
-
def quote_cell(
|
237
|
-
|
241
|
+
def quote_cell(val)
|
242
|
+
val
|
238
243
|
end
|
239
244
|
|
240
245
|
def post_cell
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FatTable
|
2
4
|
# Output the table as plain text. This is almost identical to OrgFormatter
|
3
5
|
# except that dates do not get formatted as inactive timestamps and the
|
@@ -26,12 +28,12 @@ module FatTable
|
|
26
28
|
'|'
|
27
29
|
end
|
28
30
|
|
29
|
-
def pre_cell(
|
31
|
+
def pre_cell(_head)
|
30
32
|
''
|
31
33
|
end
|
32
34
|
|
33
|
-
def quote_cell(
|
34
|
-
|
35
|
+
def quote_cell(val)
|
36
|
+
val
|
35
37
|
end
|
36
38
|
|
37
39
|
def post_cell
|
data/lib/fat_table/patches.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
unless { a: 1 }.respond_to?(:fetch_values)
|
2
4
|
# Add fetch_values if this version of ruby does not define it.
|
3
5
|
class Hash
|
@@ -19,8 +21,8 @@ end
|
|
19
21
|
unless ''.respond_to?(:match?)
|
20
22
|
# Add String#match? to pre-2.4 ruby
|
21
23
|
class String
|
22
|
-
def match?(
|
23
|
-
self =~
|
24
|
+
def match?(regexp)
|
25
|
+
self =~ regexp
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -35,6 +37,7 @@ unless //.respond_to?(:match?)
|
|
35
37
|
end
|
36
38
|
|
37
39
|
unless ''.respond_to?(:strip_heredoc)
|
40
|
+
# Patch String to provide heredocs with whitespace stripped
|
38
41
|
class String
|
39
42
|
def strip_heredoc
|
40
43
|
indent = chomp.scan(/^\s*/).min.size
|
data/lib/fat_table/table.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module FatTable
|
2
4
|
# A container for a two-dimensional table. All cells in the table must be a
|
3
5
|
# String, a DateTime (or Date), a Numeric (Bignum, Integer, or BigDecimal), or
|
@@ -106,18 +108,19 @@ module FatTable
|
|
106
108
|
# :category: Constructors
|
107
109
|
|
108
110
|
# Construct a new table from an Array of Arrays +aoa+. By default, with
|
109
|
-
# +hlines+ set to false, do not look for separators, i.e. +nils+, just
|
110
|
-
# the first row as headers. With +hlines+ set true, expect +nil+
|
111
|
-
# to mark the header row and any boundaries. If the second
|
112
|
-
# array is a +nil+, interpret the first element of the
|
113
|
-
# headers. Otherwise, synthesize headers of the form
|
114
|
-
# and so forth. The remaining elements are taken
|
115
|
-
# except that if an element of the outer array
|
116
|
-
# preceding row as a group boundary. Note for Emacs
|
117
|
-
# blocks when an org-mode table is passed in as a
|
118
|
-
# as an Array of Arrays. By default (+ HEADER:
|
119
|
-
# all from the table; otherwise (+
|
120
|
-
# with nil elements in the outer
|
111
|
+
# +hlines+ set to false, do not look for separators, i.e. +nils+, just
|
112
|
+
# treat the first row as headers. With +hlines+ set true, expect +nil+
|
113
|
+
# separators to mark the header row and any boundaries. If the second
|
114
|
+
# element of the array is a +nil+, interpret the first element of the
|
115
|
+
# array as a row of headers. Otherwise, synthesize headers of the form
|
116
|
+
# +:col_1+, +:col_2+, ... and so forth. The remaining elements are taken
|
117
|
+
# as the body of the table, except that if an element of the outer array
|
118
|
+
# is a +nil+, mark the preceding row as a group boundary. Note for Emacs
|
119
|
+
# users: In org mode code blocks when an org-mode table is passed in as a
|
120
|
+
# variable it is passed in as an Array of Arrays. By default (+ HEADER:
|
121
|
+
# :hlines no +) org-mode strips all hrules from the table; otherwise (+
|
122
|
+
# HEADER: :hlines yes +) they are indicated with nil elements in the outer
|
123
|
+
# array.
|
121
124
|
def self.from_aoa(aoa, hlines: false)
|
122
125
|
from_array_of_arrays(aoa, hlines: hlines)
|
123
126
|
end
|
@@ -149,10 +152,11 @@ module FatTable
|
|
149
152
|
# :category: Constructors
|
150
153
|
|
151
154
|
# Construct a Table by running a SQL +query+ against the database set up
|
152
|
-
# with FatTable.
|
155
|
+
# with FatTable.connect, with the rows of the query result as rows.
|
153
156
|
def self.from_sql(query)
|
154
|
-
msg = 'FatTable.db must be set with FatTable.
|
157
|
+
msg = 'FatTable.db must be set with FatTable.connect'
|
155
158
|
raise UserError, msg if FatTable.db.nil?
|
159
|
+
|
156
160
|
result = Table.new
|
157
161
|
FatTable.db[query].each do |h|
|
158
162
|
result << h
|
@@ -257,6 +261,7 @@ module FatTable
|
|
257
261
|
unless table_found
|
258
262
|
# Skip through the file until a table is found
|
259
263
|
next unless line.match?(table_re)
|
264
|
+
|
260
265
|
unless line.match?(hrule_re)
|
261
266
|
line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
|
262
267
|
rows << line.split('|').map(&:clean)
|
@@ -265,6 +270,7 @@ module FatTable
|
|
265
270
|
next
|
266
271
|
end
|
267
272
|
break unless line.match?(table_re)
|
273
|
+
|
268
274
|
if !header_found && line =~ hrule_re
|
269
275
|
rows << nil
|
270
276
|
header_found = true
|
@@ -272,7 +278,7 @@ module FatTable
|
|
272
278
|
elsif header_found && line =~ hrule_re
|
273
279
|
# Mark the boundary with a nil
|
274
280
|
rows << nil
|
275
|
-
elsif line
|
281
|
+
elsif !line.match?(table_re)
|
276
282
|
# Stop reading at the second hline
|
277
283
|
break
|
278
284
|
else
|
@@ -316,14 +322,17 @@ module FatTable
|
|
316
322
|
when Integer
|
317
323
|
msg = "index '#{key}' out of range"
|
318
324
|
raise UserError, msg unless (0..size - 1).cover?(key.abs)
|
325
|
+
|
319
326
|
rows[key]
|
320
327
|
when String
|
321
328
|
msg = "header '#{key}' not in table"
|
322
329
|
raise UserError, msg unless headers.include?(key)
|
330
|
+
|
323
331
|
column(key).items
|
324
332
|
when Symbol
|
325
333
|
msg = "header ':#{key}' not in table"
|
326
334
|
raise UserError, msg unless headers.include?(key)
|
335
|
+
|
327
336
|
column(key).items
|
328
337
|
else
|
329
338
|
raise UserError, "cannot index table with a #{key.class}"
|
@@ -360,6 +369,7 @@ module FatTable
|
|
360
369
|
# Return the number of rows in the Table.
|
361
370
|
def size
|
362
371
|
return 0 if columns.empty?
|
372
|
+
|
363
373
|
columns.first.size
|
364
374
|
end
|
365
375
|
|
@@ -368,6 +378,7 @@ module FatTable
|
|
368
378
|
# Return the number of Columns in the Table.
|
369
379
|
def width
|
370
380
|
return 0 if columns.empty?
|
381
|
+
|
371
382
|
columns.size
|
372
383
|
end
|
373
384
|
|
@@ -406,6 +417,7 @@ module FatTable
|
|
406
417
|
last ||= size - 1
|
407
418
|
last = [last, 0].max
|
408
419
|
raise UserError, 'first must be <= last' unless first <= last
|
420
|
+
|
409
421
|
rows = []
|
410
422
|
unless columns.empty?
|
411
423
|
first.upto(last) do |rnum|
|
@@ -493,12 +505,12 @@ module FatTable
|
|
493
505
|
self
|
494
506
|
end
|
495
507
|
|
496
|
-
# Mark a group boundary at row +
|
497
|
-
# in the table as a group boundary. This is mainly used for internal
|
508
|
+
# Mark a group boundary at row +row+, and if +row+ is +nil+, mark the last
|
509
|
+
# row in the table as a group boundary. This is mainly used for internal
|
498
510
|
# purposes.
|
499
|
-
def mark_boundary(
|
500
|
-
if
|
501
|
-
boundaries.push(
|
511
|
+
def mark_boundary(row = nil) # :nodoc:
|
512
|
+
if row
|
513
|
+
boundaries.push(row)
|
502
514
|
else
|
503
515
|
boundaries.push(size - 1)
|
504
516
|
end
|
@@ -524,20 +536,21 @@ module FatTable
|
|
524
536
|
@boundaries += bounds.map { |k| k + shift }
|
525
537
|
end
|
526
538
|
|
527
|
-
# Return the group number to which row
|
528
|
-
# point of view are indexed starting at 1.
|
529
|
-
def row_index_to_group_index(
|
539
|
+
# Return the group number to which row ~row~ belongs. Groups, from the
|
540
|
+
# user's point of view are indexed starting at 1.
|
541
|
+
def row_index_to_group_index(row)
|
530
542
|
boundaries.each_with_index do |b_last, g_num|
|
531
|
-
return (g_num + 1) if
|
543
|
+
return (g_num + 1) if row <= b_last
|
532
544
|
end
|
533
545
|
1
|
534
546
|
end
|
535
547
|
|
536
|
-
def group_rows(
|
548
|
+
def group_rows(row) # :nodoc:
|
537
549
|
normalize_boundaries
|
538
|
-
return [] unless
|
539
|
-
|
540
|
-
|
550
|
+
return [] unless row < boundaries.size
|
551
|
+
|
552
|
+
first = row.zero? ? 0 : boundaries[row - 1] + 1
|
553
|
+
last = boundaries[row]
|
541
554
|
rows_range(first, last)
|
542
555
|
end
|
543
556
|
|
@@ -702,6 +715,7 @@ module FatTable
|
|
702
715
|
h = k.as_sym
|
703
716
|
msg = "Column '#{h}' in select does not exist"
|
704
717
|
raise UserError, msg unless column?(h)
|
718
|
+
|
705
719
|
new_row[h] = old_row[h]
|
706
720
|
end
|
707
721
|
new_cols.each_pair do |key, expr|
|
@@ -710,7 +724,8 @@ module FatTable
|
|
710
724
|
case expr
|
711
725
|
when Symbol
|
712
726
|
msg = "Column '#{expr}' in select does not exist"
|
713
|
-
raise UserError, msg unless vars.
|
727
|
+
raise UserError, msg unless vars.key?(expr)
|
728
|
+
|
714
729
|
new_row[key] = vars[expr]
|
715
730
|
when String
|
716
731
|
new_row[key] = ev.evaluate(expr, locals: vars)
|
@@ -864,13 +879,10 @@ module FatTable
|
|
864
879
|
|
865
880
|
private
|
866
881
|
|
867
|
-
# Apply the set operation given by
|
868
|
-
# given in the first argument. If distinct is true, eliminate
|
869
|
-
# from the result.
|
870
|
-
def set_operation(other,
|
871
|
-
distinct: true,
|
872
|
-
add_boundaries: true,
|
873
|
-
inherit_boundaries: false)
|
882
|
+
# Apply the set operation given by ~oper~ between this table and the other
|
883
|
+
# table given in the first argument. If distinct is true, eliminate
|
884
|
+
# duplicates from the result.
|
885
|
+
def set_operation(other, oper = :+, distinct: true, add_boundaries: true, inherit_boundaries: false)
|
874
886
|
unless columns.size == other.columns.size
|
875
887
|
msg = "can't apply set ops to tables with a different number of columns"
|
876
888
|
raise UserError, msg
|
@@ -881,7 +893,7 @@ module FatTable
|
|
881
893
|
end
|
882
894
|
other_rows = other.rows.map { |r| r.replace_keys(headers) }
|
883
895
|
result = Table.new
|
884
|
-
new_rows = rows.send(
|
896
|
+
new_rows = rows.send(oper, other_rows)
|
885
897
|
new_rows.each_with_index do |row, k|
|
886
898
|
result << row
|
887
899
|
result.mark_boundary if k == size - 1 && add_boundaries
|
@@ -975,6 +987,7 @@ module FatTable
|
|
975
987
|
unless JOIN_TYPES.include?(join_type)
|
976
988
|
raise UserError, "join_type may only be: #{JOIN_TYPES.join(', ')}"
|
977
989
|
end
|
990
|
+
|
978
991
|
# These may be needed for outer joins.
|
979
992
|
self_row_nils = headers.map { |h| [h, nil] }.to_h
|
980
993
|
other_row_nils = other.headers.map { |h| [h, nil] }.to_h
|
@@ -992,6 +1005,7 @@ module FatTable
|
|
992
1005
|
locals = build_locals_hash(row_a: self_row, row_b: other_row)
|
993
1006
|
matches = ev.evaluate(join_exp, locals: locals)
|
994
1007
|
next unless matches
|
1008
|
+
|
995
1009
|
self_row_matched = other_row_matches[k] = true
|
996
1010
|
out_row = build_out_row(row_a: self_row, row_b: other_row,
|
997
1011
|
common_heads: other_common_heads,
|
@@ -1000,6 +1014,7 @@ module FatTable
|
|
1000
1014
|
end
|
1001
1015
|
next unless %i[left full].include?(join_type)
|
1002
1016
|
next if self_row_matched
|
1017
|
+
|
1003
1018
|
result << build_out_row(row_a: self_row,
|
1004
1019
|
row_b: other_row_nils,
|
1005
1020
|
type: join_type)
|
@@ -1007,6 +1022,7 @@ module FatTable
|
|
1007
1022
|
if %i[right full].include?(join_type)
|
1008
1023
|
other_rows.each_with_index do |other_row, k|
|
1009
1024
|
next if other_row_matches[k]
|
1025
|
+
|
1010
1026
|
result << build_out_row(row_a: self_row_nils,
|
1011
1027
|
row_b: other_row,
|
1012
1028
|
type: join_type)
|
@@ -1066,10 +1082,10 @@ module FatTable
|
|
1066
1082
|
# Translate any remaining row_b heads to append '_b' if they have the
|
1067
1083
|
# same name as a row_a key.
|
1068
1084
|
a_heads = row_a.keys
|
1069
|
-
row_b = row_b.to_a.each.map
|
1085
|
+
row_b = row_b.to_a.each.map do |k, v|
|
1070
1086
|
[a_heads.include?(k) ? "#{k}_b".to_sym : k, v]
|
1071
|
-
|
1072
|
-
row_a.merge(row_b)
|
1087
|
+
end
|
1088
|
+
row_a.merge(row_b.to_h)
|
1073
1089
|
end
|
1074
1090
|
|
1075
1091
|
# Return a hash for the local variables of a join expression in which all
|
@@ -1090,6 +1106,7 @@ module FatTable
|
|
1090
1106
|
# and all the headers in the other table with '_b' appended.
|
1091
1107
|
def build_join_expression(exps, other, type)
|
1092
1108
|
return ['true', []] if type == :cross
|
1109
|
+
|
1093
1110
|
a_heads = headers
|
1094
1111
|
b_heads = other.headers
|
1095
1112
|
common_heads = a_heads & b_heads
|
@@ -1107,7 +1124,7 @@ module FatTable
|
|
1107
1124
|
[nat_exp, common_heads]
|
1108
1125
|
end
|
1109
1126
|
else
|
1110
|
-
# We have expressions to evaluate
|
1127
|
+
# We have join expressions to evaluate
|
1111
1128
|
and_conds = []
|
1112
1129
|
partial_result = nil
|
1113
1130
|
last_sym = nil
|
@@ -1115,11 +1132,12 @@ module FatTable
|
|
1115
1132
|
case exp
|
1116
1133
|
when Symbol
|
1117
1134
|
case exp.to_s.clean
|
1118
|
-
when /\A(
|
1119
|
-
a_head =
|
1135
|
+
when /\A(?<sy>.*)_a\z/
|
1136
|
+
a_head = Regexp.last_match[:sy].to_sym
|
1120
1137
|
unless a_heads.include?(a_head)
|
1121
1138
|
raise UserError, "no column '#{a_head}' in table"
|
1122
1139
|
end
|
1140
|
+
|
1123
1141
|
if partial_result
|
1124
1142
|
# Second of a pair
|
1125
1143
|
ensure_common_types!(self_h: a_head,
|
@@ -1130,14 +1148,15 @@ module FatTable
|
|
1130
1148
|
partial_result = nil
|
1131
1149
|
else
|
1132
1150
|
# First of a pair of _a or _b
|
1133
|
-
partial_result = "(#{a_head}_a == "
|
1151
|
+
partial_result = String.new("(#{a_head}_a == ")
|
1134
1152
|
end
|
1135
1153
|
last_sym = a_head
|
1136
|
-
when /\A(
|
1137
|
-
b_head =
|
1154
|
+
when /\A(?<sy>.*)_b\z/
|
1155
|
+
b_head = Regexp.last_match[:sy].to_sym
|
1138
1156
|
unless b_heads.include?(b_head)
|
1139
1157
|
raise UserError, "no column '#{b_head}' in second table"
|
1140
1158
|
end
|
1159
|
+
|
1141
1160
|
if partial_result
|
1142
1161
|
# Second of a pair
|
1143
1162
|
ensure_common_types!(self_h: last_sym,
|
@@ -1148,7 +1167,7 @@ module FatTable
|
|
1148
1167
|
partial_result = nil
|
1149
1168
|
else
|
1150
1169
|
# First of a pair of _a or _b
|
1151
|
-
partial_result = "(#{b_head}_b == "
|
1170
|
+
partial_result = String.new("(#{b_head}_b == ")
|
1152
1171
|
end
|
1153
1172
|
b_common_heads << b_head
|
1154
1173
|
last_sym = b_head
|
@@ -1270,7 +1289,7 @@ module FatTable
|
|
1270
1289
|
|
1271
1290
|
# :category: Constructors
|
1272
1291
|
|
1273
|
-
# Add a +row+ without marking it as a group boundary.
|
1292
|
+
# Add a +row+ to this Table without marking it as a group boundary.
|
1274
1293
|
def <<(row)
|
1275
1294
|
add_row(row)
|
1276
1295
|
end
|
@@ -1281,6 +1300,7 @@ module FatTable
|
|
1281
1300
|
def add_column(col)
|
1282
1301
|
msg = "Table already has a column with header '#{col.header}'"
|
1283
1302
|
raise msg if column?(col.header)
|
1303
|
+
|
1284
1304
|
columns << col
|
1285
1305
|
self
|
1286
1306
|
end
|
@@ -1329,11 +1349,12 @@ module FatTable
|
|
1329
1349
|
fmt = fmt_type.as_sym
|
1330
1350
|
msg = "unknown format '#{fmt}'"
|
1331
1351
|
raise UserError, msg unless FatTable::FORMATS.include?(fmt)
|
1352
|
+
|
1332
1353
|
method = "to_#{fmt}"
|
1333
1354
|
if block_given?
|
1334
|
-
send
|
1355
|
+
send(method, options, &Proc.new)
|
1335
1356
|
else
|
1336
|
-
send
|
1357
|
+
send(method, options)
|
1337
1358
|
end
|
1338
1359
|
end
|
1339
1360
|
|
@@ -1347,7 +1368,7 @@ module FatTable
|
|
1347
1368
|
# default format for Formatter, there is no class PsvFormatter as you might
|
1348
1369
|
# expect.
|
1349
1370
|
def to_psv(options = {})
|
1350
|
-
fmt = Formatter.new(self, options)
|
1371
|
+
fmt = Formatter.new(self, **options)
|
1351
1372
|
yield fmt if block_given?
|
1352
1373
|
fmt.output
|
1353
1374
|
end
|
@@ -1360,7 +1381,7 @@ module FatTable
|
|
1360
1381
|
# the block to which formatting instructions and footers can be added by
|
1361
1382
|
# calling methods on it.
|
1362
1383
|
def to_aoa(options = {})
|
1363
|
-
fmt = FatTable::AoaFormatter.new(self, options)
|
1384
|
+
fmt = FatTable::AoaFormatter.new(self, **options)
|
1364
1385
|
yield fmt if block_given?
|
1365
1386
|
fmt.output
|
1366
1387
|
end
|
@@ -1374,7 +1395,7 @@ module FatTable
|
|
1374
1395
|
# given, it yields an AohFormatter to the block to which formatting
|
1375
1396
|
# instructions and footers can be added by calling methods on it.
|
1376
1397
|
def to_aoh(options = {})
|
1377
|
-
fmt = AohFormatter.new(self, options)
|
1398
|
+
fmt = AohFormatter.new(self, **options)
|
1378
1399
|
yield fmt if block_given?
|
1379
1400
|
fmt.output
|
1380
1401
|
end
|
@@ -1387,7 +1408,7 @@ module FatTable
|
|
1387
1408
|
# LaTeXFormatter to the block to which formatting instructions and footers
|
1388
1409
|
# can be added by calling methods on it.
|
1389
1410
|
def to_latex(options = {})
|
1390
|
-
fmt = LaTeXFormatter.new(self, options)
|
1411
|
+
fmt = LaTeXFormatter.new(self, **options)
|
1391
1412
|
yield fmt if block_given?
|
1392
1413
|
fmt.output
|
1393
1414
|
end
|
@@ -1400,7 +1421,7 @@ module FatTable
|
|
1400
1421
|
# OrgFormatter to the block to which formatting instructions and footers can
|
1401
1422
|
# be added by calling methods on it.
|
1402
1423
|
def to_org(options = {})
|
1403
|
-
fmt = OrgFormatter.new(self, options)
|
1424
|
+
fmt = OrgFormatter.new(self, **options)
|
1404
1425
|
yield fmt if block_given?
|
1405
1426
|
fmt.output
|
1406
1427
|
end
|
@@ -1413,7 +1434,7 @@ module FatTable
|
|
1413
1434
|
# given, it yields a TermFormatter to the block to which formatting
|
1414
1435
|
# instructions and footers can be added by calling methods on it.
|
1415
1436
|
def to_term(options = {})
|
1416
|
-
fmt = TermFormatter.new(self, options)
|
1437
|
+
fmt = TermFormatter.new(self, **options)
|
1417
1438
|
yield fmt if block_given?
|
1418
1439
|
fmt.output
|
1419
1440
|
end
|
@@ -1427,7 +1448,7 @@ module FatTable
|
|
1427
1448
|
# footers can be added by calling methods on it.
|
1428
1449
|
# @return [String]
|
1429
1450
|
def to_text(options = {})
|
1430
|
-
fmt = TextFormatter.new(self, options)
|
1451
|
+
fmt = TextFormatter.new(self, **options)
|
1431
1452
|
yield fmt if block_given?
|
1432
1453
|
fmt.output
|
1433
1454
|
end
|