fat_table 0.2.6 → 0.3.0
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 +3 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +7 -2
- data/.yardopts +5 -1
- data/README.org +271 -251
- data/README.rdoc +4 -4
- data/TODO.org +7 -0
- data/bin/ft_console +82 -79
- data/fat_table.gemspec +47 -46
- data/lib/fat_table.rb +11 -2
- data/lib/fat_table/column.rb +41 -29
- data/lib/fat_table/db_handle.rb +31 -50
- data/lib/fat_table/evaluator.rb +26 -24
- data/lib/fat_table/formatters/aoa_formatter.rb +5 -6
- data/lib/fat_table/formatters/aoh_formatter.rb +6 -7
- data/lib/fat_table/formatters/formatter.rb +67 -48
- data/lib/fat_table/formatters/latex_formatter.rb +9 -7
- data/lib/fat_table/formatters/org_formatter.rb +8 -9
- data/lib/fat_table/formatters/term_formatter.rb +19 -18
- data/lib/fat_table/formatters/text_formatter.rb +6 -7
- data/lib/fat_table/patches.rb +30 -1
- data/lib/fat_table/table.rb +143 -117
- data/lib/fat_table/version.rb +1 -1
- data/md/README.md +2167 -0
- metadata +78 -84
@@ -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)
|
@@ -25,7 +26,7 @@ module FatTable
|
|
25
26
|
end
|
26
27
|
|
27
28
|
# Taken from the Rainbow gem's list of valid colors.
|
28
|
-
self.valid_colors = %w
|
29
|
+
self.valid_colors = %w[
|
29
30
|
none black blue brown cyan darkgray gray green lightgray lime magenta
|
30
31
|
olive orange pink purple red teal violet white yellow AntiqueWhite1
|
31
32
|
AntiqueWhite2 AntiqueWhite3 AntiqueWhite4 Aquamarine1 Aquamarine2
|
@@ -76,7 +77,7 @@ module FatTable
|
|
76
77
|
Turquoise1 Turquoise2 Turquoise3 Turquoise4 VioletRed1 VioletRed2
|
77
78
|
VioletRed3 VioletRed4 Wheat1 Wheat2 Wheat3 Wheat4 Yellow1 Yellow2 Yellow3
|
78
79
|
Yellow4
|
79
|
-
|
80
|
+
]
|
80
81
|
|
81
82
|
# LaTeX commands to load the needed packages based on the :environement
|
82
83
|
# option. For now, just handles the default 'longtable' :environment. The
|
@@ -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
|
@@ -4,10 +4,9 @@ module FatTable
|
|
4
4
|
# timestamps and the connector at the beginning of hlines is a '|' rather than
|
5
5
|
# a '+' as for text tables.
|
6
6
|
class OrgFormatter < Formatter
|
7
|
-
|
8
7
|
self.default_format = default_format.dup
|
9
|
-
|
10
|
-
|
8
|
+
default_format[:date_fmt] = '[%F]'
|
9
|
+
default_format[:datetime_fmt] = '[%F %a %H:%M:%S]'
|
11
10
|
|
12
11
|
private
|
13
12
|
|
@@ -20,7 +19,7 @@ module FatTable
|
|
20
19
|
|
21
20
|
def pre_header(widths)
|
22
21
|
result = '|'
|
23
|
-
widths.
|
22
|
+
widths.each_value do |w|
|
24
23
|
result += '-' * (w + 2) + '+'
|
25
24
|
end
|
26
25
|
result[-1] = '|'
|
@@ -31,12 +30,12 @@ module FatTable
|
|
31
30
|
'|'
|
32
31
|
end
|
33
32
|
|
34
|
-
def pre_cell(
|
33
|
+
def pre_cell(_head)
|
35
34
|
''
|
36
35
|
end
|
37
36
|
|
38
|
-
def quote_cell(
|
39
|
-
|
37
|
+
def quote_cell(val)
|
38
|
+
val
|
40
39
|
end
|
41
40
|
|
42
41
|
def post_cell
|
@@ -53,7 +52,7 @@ module FatTable
|
|
53
52
|
|
54
53
|
def hline(widths)
|
55
54
|
result = '|'
|
56
|
-
widths.
|
55
|
+
widths.each_value do |w|
|
57
56
|
result += '-' * (w + 2) + '+'
|
58
57
|
end
|
59
58
|
result[-1] = '|'
|
@@ -62,7 +61,7 @@ module FatTable
|
|
62
61
|
|
63
62
|
def post_footers(widths)
|
64
63
|
result = '|'
|
65
|
-
widths.
|
64
|
+
widths.each_value do |w|
|
66
65
|
result += '-' * (w + 2) + '+'
|
67
66
|
end
|
68
67
|
result[-1] = '|'
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
1
|
require 'rainbow'
|
4
2
|
|
5
3
|
module FatTable
|
@@ -29,6 +27,7 @@ module FatTable
|
|
29
27
|
@options[:unicode] = options.fetch(:unicode, true)
|
30
28
|
@options[:framecolor] = options.fetch(:framecolor, 'none.none')
|
31
29
|
return unless @options[:framecolor] =~ /([-_a-zA-Z]*)(\.([-_a-zA-Z]*))/
|
30
|
+
|
32
31
|
@options[:frame_fg] = $1.downcase unless $1.blank?
|
33
32
|
@options[:frame_bg] = $3.downcase unless $3.blank?
|
34
33
|
end
|
@@ -61,6 +60,7 @@ module FatTable
|
|
61
60
|
|
62
61
|
def strip_ansi(str)
|
63
62
|
return '' unless str
|
63
|
+
|
64
64
|
str.gsub(/\e\[[0-9;]+m/, '')
|
65
65
|
end
|
66
66
|
|
@@ -75,18 +75,19 @@ module FatTable
|
|
75
75
|
result
|
76
76
|
end
|
77
77
|
|
78
|
-
def colorize(str,
|
79
|
-
|
80
|
-
|
81
|
-
return str unless
|
78
|
+
def colorize(str, fg_color, bg_color)
|
79
|
+
fg_color = nil if fg_color == 'none'
|
80
|
+
bg_color = nil if bg_color == 'none'
|
81
|
+
return str unless fg_color || bg_color
|
82
|
+
|
82
83
|
result = Rainbow(str)
|
83
|
-
if
|
84
|
-
|
85
|
-
result = result.color(
|
84
|
+
if fg_color
|
85
|
+
fg_color = fg_color.tr(' ', '').downcase.as_sym
|
86
|
+
result = result.color(fg_color) if fg_color
|
86
87
|
end
|
87
|
-
if
|
88
|
-
|
89
|
-
result = result.bg(
|
88
|
+
if bg_color
|
89
|
+
bg_color = bg_color.tr(' ', '').downcase.as_sym
|
90
|
+
result = result.bg(bg_color) if bg_color
|
90
91
|
end
|
91
92
|
result
|
92
93
|
end
|
@@ -219,7 +220,7 @@ module FatTable
|
|
219
220
|
|
220
221
|
def pre_header(widths)
|
221
222
|
result = upper_left
|
222
|
-
widths.
|
223
|
+
widths.each_value do |w|
|
223
224
|
result += double_rule * (w + 2) + upper_tee
|
224
225
|
end
|
225
226
|
result[-1] = upper_right
|
@@ -231,12 +232,12 @@ module FatTable
|
|
231
232
|
frame_colorize(vertical_rule)
|
232
233
|
end
|
233
234
|
|
234
|
-
def pre_cell(
|
235
|
+
def pre_cell(_head)
|
235
236
|
''
|
236
237
|
end
|
237
238
|
|
238
|
-
def quote_cell(
|
239
|
-
|
239
|
+
def quote_cell(val)
|
240
|
+
val
|
240
241
|
end
|
241
242
|
|
242
243
|
def post_cell
|
@@ -253,7 +254,7 @@ module FatTable
|
|
253
254
|
|
254
255
|
def hline(widths)
|
255
256
|
result = left_tee
|
256
|
-
widths.
|
257
|
+
widths.each_value do |w|
|
257
258
|
result += horizontal_rule * (w + 2) + single_cross
|
258
259
|
end
|
259
260
|
result[-1] = right_tee
|
@@ -287,7 +288,7 @@ module FatTable
|
|
287
288
|
|
288
289
|
def post_footers(widths)
|
289
290
|
result = lower_left
|
290
|
-
widths.
|
291
|
+
widths.each_value do |w|
|
291
292
|
result += double_rule * (w + 2) + lower_tee
|
292
293
|
end
|
293
294
|
result[-1] = lower_right
|
@@ -4,7 +4,6 @@ module FatTable
|
|
4
4
|
# connector at the beginning of hlines is a '+' rather than a '|' as for org
|
5
5
|
# tables.
|
6
6
|
class TextFormatter < Formatter
|
7
|
-
|
8
7
|
private
|
9
8
|
|
10
9
|
# Does this Formatter require a second pass over the cells to align the
|
@@ -16,7 +15,7 @@ module FatTable
|
|
16
15
|
|
17
16
|
def pre_header(widths)
|
18
17
|
result = '+'
|
19
|
-
widths.
|
18
|
+
widths.each_value do |w|
|
20
19
|
result += '=' * (w + 2) + '+'
|
21
20
|
end
|
22
21
|
result[-1] = '+'
|
@@ -27,12 +26,12 @@ module FatTable
|
|
27
26
|
'|'
|
28
27
|
end
|
29
28
|
|
30
|
-
def pre_cell(
|
29
|
+
def pre_cell(_head)
|
31
30
|
''
|
32
31
|
end
|
33
32
|
|
34
|
-
def quote_cell(
|
35
|
-
|
33
|
+
def quote_cell(val)
|
34
|
+
val
|
36
35
|
end
|
37
36
|
|
38
37
|
def post_cell
|
@@ -49,7 +48,7 @@ module FatTable
|
|
49
48
|
|
50
49
|
def hline(widths)
|
51
50
|
result = '+'
|
52
|
-
widths.
|
51
|
+
widths.each_value do |w|
|
53
52
|
result += '-' * (w + 2) + '+'
|
54
53
|
end
|
55
54
|
result[-1] = '+'
|
@@ -82,7 +81,7 @@ module FatTable
|
|
82
81
|
|
83
82
|
def post_footers(widths)
|
84
83
|
result = '+'
|
85
|
-
widths.
|
84
|
+
widths.each_value do |w|
|
86
85
|
result += '=' * (w + 2) + '+'
|
87
86
|
end
|
88
87
|
result[-1] = '+'
|
data/lib/fat_table/patches.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
unless {a: 1}.respond_to?(:fetch_values)
|
1
|
+
unless { a: 1 }.respond_to?(:fetch_values)
|
2
|
+
# Add fetch_values if this version of ruby does not define it.
|
2
3
|
class Hash
|
3
4
|
def fetch_values(*keys)
|
4
5
|
result = []
|
@@ -14,3 +15,31 @@ unless {a: 1}.respond_to?(:fetch_values)
|
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
18
|
+
|
19
|
+
unless ''.respond_to?(:match?)
|
20
|
+
# Add String#match? to pre-2.4 ruby
|
21
|
+
class String
|
22
|
+
def match?(regexp)
|
23
|
+
self =~ regexp
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
unless //.respond_to?(:match?)
|
29
|
+
# Add Regexp#match? to pre-2.4 ruby
|
30
|
+
class Regexp
|
31
|
+
def match?(str)
|
32
|
+
self =~ str
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
unless ''.respond_to?(:strip_heredoc)
|
38
|
+
# Patch String to provide heredocs with whitespace stripped
|
39
|
+
class String
|
40
|
+
def strip_heredoc
|
41
|
+
indent = chomp.scan(/^\s*/).min.size
|
42
|
+
gsub(/^\s{#{indent}}/, '')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/fat_table/table.rb
CHANGED
@@ -49,9 +49,9 @@ module FatTable
|
|
49
49
|
# spaces converted to underscore and everything down-cased. So, the heading,
|
50
50
|
# 'Two Words' becomes the header +:two_words+.
|
51
51
|
class Table
|
52
|
-
|
53
52
|
# An Array of FatTable::Columns that constitute the table.
|
54
53
|
attr_reader :columns
|
54
|
+
attr_accessor :boundaries
|
55
55
|
|
56
56
|
###########################################################################
|
57
57
|
# Constructors
|
@@ -106,18 +106,19 @@ module FatTable
|
|
106
106
|
# :category: Constructors
|
107
107
|
|
108
108
|
# 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
|
109
|
+
# +hlines+ set to false, do not look for separators, i.e. +nils+, just
|
110
|
+
# treat the first row as headers. With +hlines+ set true, expect +nil+
|
111
|
+
# separators to mark the header row and any boundaries. If the second
|
112
|
+
# element of the array is a +nil+, interpret the first element of the
|
113
|
+
# array as a row of headers. Otherwise, synthesize headers of the form
|
114
|
+
# +:col_1+, +:col_2+, ... and so forth. The remaining elements are taken
|
115
|
+
# as the body of the table, except that if an element of the outer array
|
116
|
+
# is a +nil+, mark the preceding row as a group boundary. Note for Emacs
|
117
|
+
# users: In org mode code blocks when an org-mode table is passed in as a
|
118
|
+
# variable it is passed in as an Array of Arrays. By default (+ HEADER:
|
119
|
+
# :hlines no +) org-mode strips all hrules from the table; otherwise (+
|
120
|
+
# HEADER: :hlines yes +) they are indicated with nil elements in the outer
|
121
|
+
# array.
|
121
122
|
def self.from_aoa(aoa, hlines: false)
|
122
123
|
from_array_of_arrays(aoa, hlines: hlines)
|
123
124
|
end
|
@@ -140,8 +141,8 @@ module FatTable
|
|
140
141
|
|
141
142
|
# :category: Constructors
|
142
143
|
|
143
|
-
# Construct a new table from another FatTable::Table object +table+. Inherit
|
144
|
-
# group boundaries from the input table.
|
144
|
+
# Construct a new table from another FatTable::Table object +table+. Inherit
|
145
|
+
# any group boundaries from the input table.
|
145
146
|
def self.from_table(table)
|
146
147
|
table.deep_dup
|
147
148
|
end
|
@@ -149,9 +150,11 @@ module FatTable
|
|
149
150
|
# :category: Constructors
|
150
151
|
|
151
152
|
# Construct a Table by running a SQL +query+ against the database set up
|
152
|
-
# with FatTable.
|
153
|
+
# with FatTable.connect, with the rows of the query result as rows.
|
153
154
|
def self.from_sql(query)
|
154
|
-
|
155
|
+
msg = 'FatTable.db must be set with FatTable.connect'
|
156
|
+
raise UserError, msg if FatTable.db.nil?
|
157
|
+
|
155
158
|
result = Table.new
|
156
159
|
FatTable.db[query].each do |h|
|
157
160
|
result << h
|
@@ -166,15 +169,16 @@ module FatTable
|
|
166
169
|
class << self
|
167
170
|
private
|
168
171
|
|
169
|
-
# Construct table from an array of hashes or an array of any object that
|
170
|
-
# respond to #to_h.
|
172
|
+
# Construct table from an array of hashes or an array of any object that
|
173
|
+
# can respond to #to_h. If an array element is a nil, mark it as a group
|
171
174
|
# boundary in the Table.
|
172
175
|
def from_array_of_hashes(hashes, hlines: false)
|
173
176
|
result = new
|
174
177
|
hashes.each do |hsh|
|
175
178
|
if hsh.nil?
|
176
179
|
unless hlines
|
177
|
-
|
180
|
+
msg = 'found an hline in input: try setting hlines true'
|
181
|
+
raise UserError, msg
|
178
182
|
end
|
179
183
|
result.mark_boundary
|
180
184
|
next
|
@@ -219,7 +223,8 @@ module FatTable
|
|
219
223
|
rows[first_data_row..-1].each do |row|
|
220
224
|
if row.nil?
|
221
225
|
unless hlines
|
222
|
-
|
226
|
+
msg = 'found an hline in input: try setting hlines true'
|
227
|
+
raise UserError, msg
|
223
228
|
end
|
224
229
|
result.mark_boundary
|
225
230
|
next
|
@@ -253,15 +258,17 @@ module FatTable
|
|
253
258
|
io.each do |line|
|
254
259
|
unless table_found
|
255
260
|
# Skip through the file until a table is found
|
256
|
-
next unless line
|
257
|
-
|
261
|
+
next unless line.match?(table_re)
|
262
|
+
|
263
|
+
unless line.match?(hrule_re)
|
258
264
|
line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
|
259
265
|
rows << line.split('|').map(&:clean)
|
260
266
|
end
|
261
267
|
table_found = true
|
262
268
|
next
|
263
269
|
end
|
264
|
-
break unless line
|
270
|
+
break unless line.match?(table_re)
|
271
|
+
|
265
272
|
if !header_found && line =~ hrule_re
|
266
273
|
rows << nil
|
267
274
|
header_found = true
|
@@ -269,7 +276,7 @@ module FatTable
|
|
269
276
|
elsif header_found && line =~ hrule_re
|
270
277
|
# Mark the boundary with a nil
|
271
278
|
rows << nil
|
272
|
-
elsif line
|
279
|
+
elsif !line.match?(table_re)
|
273
280
|
# Stop reading at the second hline
|
274
281
|
break
|
275
282
|
else
|
@@ -311,13 +318,19 @@ module FatTable
|
|
311
318
|
def [](key)
|
312
319
|
case key
|
313
320
|
when Integer
|
314
|
-
|
321
|
+
msg = "index '#{key}' out of range"
|
322
|
+
raise UserError, msg unless (0..size - 1).cover?(key.abs)
|
323
|
+
|
315
324
|
rows[key]
|
316
325
|
when String
|
317
|
-
|
326
|
+
msg = "header '#{key}' not in table"
|
327
|
+
raise UserError, msg unless headers.include?(key)
|
328
|
+
|
318
329
|
column(key).items
|
319
330
|
when Symbol
|
320
|
-
|
331
|
+
msg = "header ':#{key}' not in table"
|
332
|
+
raise UserError, msg unless headers.include?(key)
|
333
|
+
|
321
334
|
column(key).items
|
322
335
|
else
|
323
336
|
raise UserError, "cannot index table with a #{key.class}"
|
@@ -354,6 +367,7 @@ module FatTable
|
|
354
367
|
# Return the number of rows in the Table.
|
355
368
|
def size
|
356
369
|
return 0 if columns.empty?
|
370
|
+
|
357
371
|
columns.first.size
|
358
372
|
end
|
359
373
|
|
@@ -362,6 +376,7 @@ module FatTable
|
|
362
376
|
# Return the number of Columns in the Table.
|
363
377
|
def width
|
364
378
|
return 0 if columns.empty?
|
379
|
+
|
365
380
|
columns.size
|
366
381
|
end
|
367
382
|
|
@@ -400,6 +415,7 @@ module FatTable
|
|
400
415
|
last ||= size - 1
|
401
416
|
last = [last, 0].max
|
402
417
|
raise UserError, 'first must be <= last' unless first <= last
|
418
|
+
|
403
419
|
rows = []
|
404
420
|
unless columns.empty?
|
405
421
|
first.upto(last) do |rnum|
|
@@ -413,9 +429,9 @@ module FatTable
|
|
413
429
|
rows
|
414
430
|
end
|
415
431
|
|
416
|
-
|
432
|
+
############################################################################
|
417
433
|
# Enumerable
|
418
|
-
|
434
|
+
############################################################################
|
419
435
|
|
420
436
|
public
|
421
437
|
|
@@ -430,9 +446,6 @@ module FatTable
|
|
430
446
|
end
|
431
447
|
end
|
432
448
|
|
433
|
-
|
434
|
-
public
|
435
|
-
|
436
449
|
# :category: Attributes
|
437
450
|
|
438
451
|
# Boundaries mark the last row in each "group" within the table. The last
|
@@ -490,12 +503,12 @@ module FatTable
|
|
490
503
|
self
|
491
504
|
end
|
492
505
|
|
493
|
-
# Mark a group boundary at row +
|
494
|
-
# in the table as a group boundary. This is mainly used for internal
|
506
|
+
# Mark a group boundary at row +row+, and if +row+ is +nil+, mark the last
|
507
|
+
# row in the table as a group boundary. This is mainly used for internal
|
495
508
|
# purposes.
|
496
|
-
def mark_boundary(
|
497
|
-
if
|
498
|
-
boundaries.push(
|
509
|
+
def mark_boundary(row = nil) # :nodoc:
|
510
|
+
if row
|
511
|
+
boundaries.push(row)
|
499
512
|
else
|
500
513
|
boundaries.push(size - 1)
|
501
514
|
end
|
@@ -505,16 +518,6 @@ module FatTable
|
|
505
518
|
|
506
519
|
# :stopdoc:
|
507
520
|
|
508
|
-
# Reader for boundaries, but not public.
|
509
|
-
def boundaries
|
510
|
-
@boundaries
|
511
|
-
end
|
512
|
-
|
513
|
-
# Writer for boundaries, but not public.
|
514
|
-
def boundaries=(bounds)
|
515
|
-
@boundaries = bounds
|
516
|
-
end
|
517
|
-
|
518
521
|
# Make sure size - 1 is last boundary and that they are unique and sorted.
|
519
522
|
def normalize_boundaries
|
520
523
|
unless empty?
|
@@ -531,20 +534,21 @@ module FatTable
|
|
531
534
|
@boundaries += bounds.map { |k| k + shift }
|
532
535
|
end
|
533
536
|
|
534
|
-
# Return the group number to which row
|
535
|
-
# point of view are indexed starting at 1.
|
536
|
-
def row_index_to_group_index(
|
537
|
+
# Return the group number to which row ~row~ belongs. Groups, from the
|
538
|
+
# user's point of view are indexed starting at 1.
|
539
|
+
def row_index_to_group_index(row)
|
537
540
|
boundaries.each_with_index do |b_last, g_num|
|
538
|
-
return (g_num + 1) if
|
541
|
+
return (g_num + 1) if row <= b_last
|
539
542
|
end
|
540
543
|
1
|
541
544
|
end
|
542
545
|
|
543
|
-
def group_rows(
|
546
|
+
def group_rows(row) # :nodoc:
|
544
547
|
normalize_boundaries
|
545
|
-
return [] unless
|
546
|
-
|
547
|
-
|
548
|
+
return [] unless row < boundaries.size
|
549
|
+
|
550
|
+
first = row.zero? ? 0 : boundaries[row - 1] + 1
|
551
|
+
last = boundaries[row]
|
548
552
|
rows_range(first, last)
|
549
553
|
end
|
550
554
|
|
@@ -682,9 +686,8 @@ module FatTable
|
|
682
686
|
ivars = ivars.merge(new_cols[:ivars])
|
683
687
|
new_cols.delete(:ivars)
|
684
688
|
end
|
685
|
-
before_hook = '@row += 1'
|
686
689
|
if new_cols.key?(:before_hook)
|
687
|
-
before_hook
|
690
|
+
before_hook = new_cols[:before_hook].to_s
|
688
691
|
new_cols.delete(:before_hook)
|
689
692
|
end
|
690
693
|
after_hook = nil
|
@@ -702,32 +705,37 @@ module FatTable
|
|
702
705
|
# Set the group number in the before hook and run the hook with the
|
703
706
|
# local variables set to the row before the new row is evaluated.
|
704
707
|
grp = row_index_to_group_index(old_k)
|
705
|
-
|
706
|
-
ev.eval_before_hook(
|
708
|
+
ev.update_ivars(row: old_k + 1, group: grp)
|
709
|
+
ev.eval_before_hook(locals: old_row)
|
707
710
|
# Compute the new row.
|
708
711
|
new_row = {}
|
709
712
|
cols.each do |k|
|
710
713
|
h = k.as_sym
|
711
|
-
|
714
|
+
msg = "Column '#{h}' in select does not exist"
|
715
|
+
raise UserError, msg unless column?(h)
|
716
|
+
|
712
717
|
new_row[h] = old_row[h]
|
713
718
|
end
|
714
|
-
new_cols.each_pair do |key,
|
719
|
+
new_cols.each_pair do |key, expr|
|
715
720
|
key = key.as_sym
|
716
721
|
vars = old_row.merge(new_row)
|
717
|
-
case
|
722
|
+
case expr
|
718
723
|
when Symbol
|
719
|
-
|
720
|
-
|
724
|
+
msg = "Column '#{expr}' in select does not exist"
|
725
|
+
raise UserError, msg unless vars.key?(expr)
|
726
|
+
|
727
|
+
new_row[key] = vars[expr]
|
721
728
|
when String
|
722
|
-
new_row[key] = ev.evaluate(
|
729
|
+
new_row[key] = ev.evaluate(expr, locals: vars)
|
723
730
|
else
|
724
|
-
|
731
|
+
msg = "Hash parameter '#{key}' to select must be a symbol or string"
|
732
|
+
raise UserError, msg
|
725
733
|
end
|
726
734
|
end
|
727
735
|
# Set the group number and run the hook with the local variables set to
|
728
736
|
# the row after the new row is evaluated.
|
729
|
-
vars = new_row.merge(__group: grp)
|
730
|
-
ev.eval_after_hook(
|
737
|
+
# vars = new_row.merge(__group: grp)
|
738
|
+
ev.eval_after_hook(locals: new_row)
|
731
739
|
result << new_row
|
732
740
|
end
|
733
741
|
result.boundaries = boundaries
|
@@ -753,14 +761,13 @@ module FatTable
|
|
753
761
|
col = Column.new(header: h)
|
754
762
|
result.add_column(col)
|
755
763
|
end
|
756
|
-
ev = Evaluator.new(ivars: { row: 0, group: 0 }
|
757
|
-
before: '@row += 1')
|
764
|
+
ev = Evaluator.new(ivars: { row: 0, group: 0 })
|
758
765
|
rows.each_with_index do |row, k|
|
759
766
|
grp = row_index_to_group_index(k)
|
760
|
-
|
761
|
-
ev.eval_before_hook(
|
762
|
-
result << row if ev.evaluate(expr,
|
763
|
-
ev.eval_after_hook(
|
767
|
+
ev.update_ivars(row: k + 1, group: grp)
|
768
|
+
ev.eval_before_hook(locals: row)
|
769
|
+
result << row if ev.evaluate(expr, locals: row)
|
770
|
+
ev.eval_after_hook(locals: row)
|
764
771
|
end
|
765
772
|
result.normalize_boundaries
|
766
773
|
result
|
@@ -870,22 +877,24 @@ module FatTable
|
|
870
877
|
|
871
878
|
private
|
872
879
|
|
873
|
-
# Apply the set operation given by
|
874
|
-
# given in the first argument. If distinct is true, eliminate
|
875
|
-
# from the result.
|
876
|
-
def set_operation(other,
|
880
|
+
# Apply the set operation given by ~oper~ between this table and the other
|
881
|
+
# table given in the first argument. If distinct is true, eliminate
|
882
|
+
# duplicates from the result.
|
883
|
+
def set_operation(other, oper = :+,
|
877
884
|
distinct: true,
|
878
885
|
add_boundaries: true,
|
879
886
|
inherit_boundaries: false)
|
880
887
|
unless columns.size == other.columns.size
|
881
|
-
|
888
|
+
msg = "can't apply set ops to tables with a different number of columns"
|
889
|
+
raise UserError, msg
|
882
890
|
end
|
883
891
|
unless columns.map(&:type) == other.columns.map(&:type)
|
884
|
-
|
892
|
+
msg = "can't apply a set ops to tables with different column types."
|
893
|
+
raise UserError, msg
|
885
894
|
end
|
886
895
|
other_rows = other.rows.map { |r| r.replace_keys(headers) }
|
887
896
|
result = Table.new
|
888
|
-
new_rows = rows.send(
|
897
|
+
new_rows = rows.send(oper, other_rows)
|
889
898
|
new_rows.each_with_index do |row, k|
|
890
899
|
result << row
|
891
900
|
result.mark_boundary if k == size - 1 && add_boundaries
|
@@ -902,7 +911,7 @@ module FatTable
|
|
902
911
|
public
|
903
912
|
|
904
913
|
# An Array of symbols for the valid join types.
|
905
|
-
JOIN_TYPES = [
|
914
|
+
JOIN_TYPES = %i[inner left right full cross].freeze
|
906
915
|
|
907
916
|
# :category: Operators
|
908
917
|
#
|
@@ -979,10 +988,12 @@ module FatTable
|
|
979
988
|
unless JOIN_TYPES.include?(join_type)
|
980
989
|
raise UserError, "join_type may only be: #{JOIN_TYPES.join(', ')}"
|
981
990
|
end
|
991
|
+
|
982
992
|
# These may be needed for outer joins.
|
983
993
|
self_row_nils = headers.map { |h| [h, nil] }.to_h
|
984
994
|
other_row_nils = other.headers.map { |h| [h, nil] }.to_h
|
985
|
-
|
995
|
+
join_exp, other_common_heads =
|
996
|
+
build_join_expression(exps, other, join_type)
|
986
997
|
ev = Evaluator.new
|
987
998
|
result = Table.new
|
988
999
|
other_rows = other.rows
|
@@ -993,27 +1004,29 @@ module FatTable
|
|
993
1004
|
# Same as other_row, but with keys that are common with self and equal
|
994
1005
|
# in value, removed, so the output table need not repeat them.
|
995
1006
|
locals = build_locals_hash(row_a: self_row, row_b: other_row)
|
996
|
-
matches = ev.evaluate(
|
1007
|
+
matches = ev.evaluate(join_exp, locals: locals)
|
997
1008
|
next unless matches
|
1009
|
+
|
998
1010
|
self_row_matched = other_row_matches[k] = true
|
999
1011
|
out_row = build_out_row(row_a: self_row, row_b: other_row,
|
1000
1012
|
common_heads: other_common_heads,
|
1001
1013
|
type: join_type)
|
1002
1014
|
result << out_row
|
1003
1015
|
end
|
1004
|
-
|
1005
|
-
|
1006
|
-
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1016
|
+
next unless %i[left full].include?(join_type)
|
1017
|
+
next if self_row_matched
|
1018
|
+
|
1019
|
+
result << build_out_row(row_a: self_row,
|
1020
|
+
row_b: other_row_nils,
|
1021
|
+
type: join_type)
|
1010
1022
|
end
|
1011
|
-
if
|
1023
|
+
if %i[right full].include?(join_type)
|
1012
1024
|
other_rows.each_with_index do |other_row, k|
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1025
|
+
next if other_row_matches[k]
|
1026
|
+
|
1027
|
+
result << build_out_row(row_a: self_row_nils,
|
1028
|
+
row_b: other_row,
|
1029
|
+
type: join_type)
|
1017
1030
|
end
|
1018
1031
|
end
|
1019
1032
|
result.normalize_boundaries
|
@@ -1094,14 +1107,15 @@ module FatTable
|
|
1094
1107
|
# and all the headers in the other table with '_b' appended.
|
1095
1108
|
def build_join_expression(exps, other, type)
|
1096
1109
|
return ['true', []] if type == :cross
|
1110
|
+
|
1097
1111
|
a_heads = headers
|
1098
1112
|
b_heads = other.headers
|
1099
1113
|
common_heads = a_heads & b_heads
|
1100
1114
|
b_common_heads = []
|
1101
1115
|
if exps.empty?
|
1102
1116
|
if common_heads.empty?
|
1103
|
-
|
1104
|
-
|
1117
|
+
msg = "#{type}-join with no common column names needs join expression"
|
1118
|
+
raise UserError, msg
|
1105
1119
|
else
|
1106
1120
|
# A Natural join on all common heads
|
1107
1121
|
common_heads.each do |h|
|
@@ -1124,9 +1138,12 @@ module FatTable
|
|
1124
1138
|
unless a_heads.include?(a_head)
|
1125
1139
|
raise UserError, "no column '#{a_head}' in table"
|
1126
1140
|
end
|
1141
|
+
|
1127
1142
|
if partial_result
|
1128
1143
|
# Second of a pair
|
1129
|
-
ensure_common_types!(self_h: a_head,
|
1144
|
+
ensure_common_types!(self_h: a_head,
|
1145
|
+
other_h: last_sym,
|
1146
|
+
other: other)
|
1130
1147
|
partial_result << "#{a_head}_a)"
|
1131
1148
|
and_conds << partial_result
|
1132
1149
|
partial_result = nil
|
@@ -1140,9 +1157,12 @@ module FatTable
|
|
1140
1157
|
unless b_heads.include?(b_head)
|
1141
1158
|
raise UserError, "no column '#{b_head}' in second table"
|
1142
1159
|
end
|
1160
|
+
|
1143
1161
|
if partial_result
|
1144
1162
|
# Second of a pair
|
1145
|
-
ensure_common_types!(self_h: last_sym,
|
1163
|
+
ensure_common_types!(self_h: last_sym,
|
1164
|
+
other_h: b_head,
|
1165
|
+
other: other)
|
1146
1166
|
partial_result << "#{b_head}_b)"
|
1147
1167
|
and_conds << partial_result
|
1148
1168
|
partial_result = nil
|
@@ -1158,12 +1178,13 @@ module FatTable
|
|
1158
1178
|
# We were expecting the second of a modified pair, but got an
|
1159
1179
|
# unmodified symbol instead.
|
1160
1180
|
msg =
|
1161
|
-
"
|
1181
|
+
"follow '#{last_sym}' by qualified exp from the other table"
|
1162
1182
|
raise UserError, msg
|
1163
1183
|
end
|
1164
1184
|
# We have an unqualified symbol that must appear in both tables
|
1165
1185
|
unless common_heads.include?(exp)
|
1166
|
-
|
1186
|
+
msg = "unqualified column '#{exp}' must occur in both tables"
|
1187
|
+
raise UserError, msg
|
1167
1188
|
end
|
1168
1189
|
ensure_common_types!(self_h: exp, other_h: exp, other: other)
|
1169
1190
|
and_conds << "(#{exp}_a == #{exp}_b)"
|
@@ -1174,7 +1195,8 @@ module FatTable
|
|
1174
1195
|
# qualified.
|
1175
1196
|
and_conds << "(#{exp})"
|
1176
1197
|
else
|
1177
|
-
|
1198
|
+
msg = "invalid join expression '#{exp}' of class #{exp.class}"
|
1199
|
+
raise UserError, msg
|
1178
1200
|
end
|
1179
1201
|
end
|
1180
1202
|
[and_conds.join(' && '), b_common_heads]
|
@@ -1185,15 +1207,15 @@ module FatTable
|
|
1185
1207
|
# have the same types.
|
1186
1208
|
def ensure_common_types!(self_h:, other_h:, other:)
|
1187
1209
|
unless column(self_h).type == other.column(other_h).type
|
1188
|
-
|
1189
|
-
|
1210
|
+
msg = "column '#{self_h}' type does not match column '#{other_h}"
|
1211
|
+
raise UserError, msg
|
1190
1212
|
end
|
1191
1213
|
self
|
1192
1214
|
end
|
1193
1215
|
|
1194
|
-
|
1216
|
+
############################################################################
|
1195
1217
|
# Group By
|
1196
|
-
|
1218
|
+
############################################################################
|
1197
1219
|
|
1198
1220
|
public
|
1199
1221
|
|
@@ -1268,7 +1290,7 @@ module FatTable
|
|
1268
1290
|
|
1269
1291
|
# :category: Constructors
|
1270
1292
|
|
1271
|
-
# Add a +row+ without marking it as a group boundary.
|
1293
|
+
# Add a +row+ to this Table without marking it as a group boundary.
|
1272
1294
|
def <<(row)
|
1273
1295
|
add_row(row)
|
1274
1296
|
end
|
@@ -1277,7 +1299,9 @@ module FatTable
|
|
1277
1299
|
|
1278
1300
|
# Add a FatTable::Column object +col+ to the table.
|
1279
1301
|
def add_column(col)
|
1280
|
-
|
1302
|
+
msg = "Table already has a column with header '#{col.header}'"
|
1303
|
+
raise msg if column?(col.header)
|
1304
|
+
|
1281
1305
|
columns << col
|
1282
1306
|
self
|
1283
1307
|
end
|
@@ -1324,7 +1348,9 @@ module FatTable
|
|
1324
1348
|
#
|
1325
1349
|
def to_any(fmt_type, options = {})
|
1326
1350
|
fmt = fmt_type.as_sym
|
1327
|
-
|
1351
|
+
msg = "unknown format '#{fmt}'"
|
1352
|
+
raise UserError, msg unless FatTable::FORMATS.include?(fmt)
|
1353
|
+
|
1328
1354
|
method = "to_#{fmt}"
|
1329
1355
|
if block_given?
|
1330
1356
|
send method, options, &Proc.new
|
@@ -1343,7 +1369,7 @@ module FatTable
|
|
1343
1369
|
# default format for Formatter, there is no class PsvFormatter as you might
|
1344
1370
|
# expect.
|
1345
1371
|
def to_psv(options = {})
|
1346
|
-
fmt = Formatter.new(self, options)
|
1372
|
+
fmt = Formatter.new(self, **options)
|
1347
1373
|
yield fmt if block_given?
|
1348
1374
|
fmt.output
|
1349
1375
|
end
|
@@ -1356,7 +1382,7 @@ module FatTable
|
|
1356
1382
|
# the block to which formatting instructions and footers can be added by
|
1357
1383
|
# calling methods on it.
|
1358
1384
|
def to_aoa(options = {})
|
1359
|
-
fmt = FatTable::AoaFormatter.new(self, options)
|
1385
|
+
fmt = FatTable::AoaFormatter.new(self, **options)
|
1360
1386
|
yield fmt if block_given?
|
1361
1387
|
fmt.output
|
1362
1388
|
end
|
@@ -1370,7 +1396,7 @@ module FatTable
|
|
1370
1396
|
# given, it yields an AohFormatter to the block to which formatting
|
1371
1397
|
# instructions and footers can be added by calling methods on it.
|
1372
1398
|
def to_aoh(options = {})
|
1373
|
-
fmt = AohFormatter.new(self, options)
|
1399
|
+
fmt = AohFormatter.new(self, **options)
|
1374
1400
|
yield fmt if block_given?
|
1375
1401
|
fmt.output
|
1376
1402
|
end
|
@@ -1383,7 +1409,7 @@ module FatTable
|
|
1383
1409
|
# LaTeXFormatter to the block to which formatting instructions and footers
|
1384
1410
|
# can be added by calling methods on it.
|
1385
1411
|
def to_latex(options = {})
|
1386
|
-
fmt = LaTeXFormatter.new(self, options)
|
1412
|
+
fmt = LaTeXFormatter.new(self, **options)
|
1387
1413
|
yield fmt if block_given?
|
1388
1414
|
fmt.output
|
1389
1415
|
end
|
@@ -1396,7 +1422,7 @@ module FatTable
|
|
1396
1422
|
# OrgFormatter to the block to which formatting instructions and footers can
|
1397
1423
|
# be added by calling methods on it.
|
1398
1424
|
def to_org(options = {})
|
1399
|
-
fmt = OrgFormatter.new(self, options)
|
1425
|
+
fmt = OrgFormatter.new(self, **options)
|
1400
1426
|
yield fmt if block_given?
|
1401
1427
|
fmt.output
|
1402
1428
|
end
|
@@ -1409,7 +1435,7 @@ module FatTable
|
|
1409
1435
|
# given, it yields a TermFormatter to the block to which formatting
|
1410
1436
|
# instructions and footers can be added by calling methods on it.
|
1411
1437
|
def to_term(options = {})
|
1412
|
-
fmt = TermFormatter.new(self, options)
|
1438
|
+
fmt = TermFormatter.new(self, **options)
|
1413
1439
|
yield fmt if block_given?
|
1414
1440
|
fmt.output
|
1415
1441
|
end
|
@@ -1423,7 +1449,7 @@ module FatTable
|
|
1423
1449
|
# footers can be added by calling methods on it.
|
1424
1450
|
# @return [String]
|
1425
1451
|
def to_text(options = {})
|
1426
|
-
fmt = TextFormatter.new(self, options)
|
1452
|
+
fmt = TextFormatter.new(self, **options)
|
1427
1453
|
yield fmt if block_given?
|
1428
1454
|
fmt.output
|
1429
1455
|
end
|