fat_table 0.2.6 → 0.3.0

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
  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 && istruct.color != 'none'
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(_h)
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(v)
183
- v
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
- self.default_format[:date_fmt] = '[%F]'
10
- self.default_format[:datetime_fmt] = '[%F %a %H:%M:%S]'
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.values.each do |w|
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(_h)
33
+ def pre_cell(_head)
35
34
  ''
36
35
  end
37
36
 
38
- def quote_cell(v)
39
- v
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.values.each do |w|
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.values.each do |w|
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, fg, bg)
79
- fg = nil if fg == 'none'
80
- bg = nil if bg == 'none'
81
- return str unless fg || bg
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 fg
84
- fg = fg.tr(' ', '').downcase.as_sym
85
- result = result.color(fg) if fg
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 bg
88
- bg = bg.tr(' ', '').downcase.as_sym
89
- result = result.bg(bg) if 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.values.each do |w|
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(_h)
235
+ def pre_cell(_head)
235
236
  ''
236
237
  end
237
238
 
238
- def quote_cell(v)
239
- v
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.values.each do |w|
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.values.each do |w|
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.values.each do |w|
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(_h)
29
+ def pre_cell(_head)
31
30
  ''
32
31
  end
33
32
 
34
- def quote_cell(v)
35
- v
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.values.each do |w|
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.values.each do |w|
84
+ widths.each_value do |w|
86
85
  result += '=' * (w + 2) + '+'
87
86
  end
88
87
  result[-1] = '+'
@@ -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
@@ -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 treat
110
- # the first row as headers. With +hlines+ set true, expect +nil+ separators
111
- # to mark the header row and any boundaries. If the second element of the
112
- # array is a +nil+, interpret the first element of the array as a row of
113
- # headers. Otherwise, synthesize headers of the form +:col_1+, +:col_2+, ...
114
- # and so forth. The remaining elements are taken as the body of the table,
115
- # except that if an element of the outer array is a +nil+, mark the
116
- # preceding row as a group boundary. Note for Emacs users: In org mode code
117
- # blocks when an org-mode table is passed in as a variable it is passed in
118
- # as an Array of Arrays. By default (+ HEADER: :hlines no +) org-mode strips
119
- # all from the table; otherwise (+ HEADER: :hlines yes +) they are indicated
120
- # with nil elements in the outer array.
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 any
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.set_db, with the rows of the query result as rows.
153
+ # with FatTable.connect, with the rows of the query result as rows.
153
154
  def self.from_sql(query)
154
- raise UserError, 'FatTable.db must be set with FatTable.set_db' if FatTable.db.nil?
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 can
170
- # respond to #to_h. If an array element is a nil, mark it as a group
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
- raise UserError, 'found an hline in input with hlines false; try setting hlines true'
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
- raise UserError, 'found an hline in input with hlines false; try setting hlines true'
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 =~ table_re
257
- unless line =~ hrule_re
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 =~ table_re
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 !~ table_re
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
- raise UserError, "index '#{key}' out of range" unless (0..size-1).cover?(key.abs)
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
- raise UserError, "header '#{key}' not in table" unless headers.include?(key)
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
- raise UserError, "header ':#{key}' not in table" unless headers.include?(key)
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 +k+, and if +k+ is +nil+, mark the last 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(k = nil) # :nodoc:
497
- if k
498
- boundaries.push(k)
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 k belongs. Groups, from the user's
535
- # point of view are indexed starting at 1.
536
- def row_index_to_group_index(k)
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 k <= b_last
541
+ return (g_num + 1) if row <= b_last
539
542
  end
540
543
  1
541
544
  end
542
545
 
543
- def group_rows(k) # :nodoc:
546
+ def group_rows(row) # :nodoc:
544
547
  normalize_boundaries
545
- return [] unless k < boundaries.size
546
- first = k.zero? ? 0 : boundaries[k - 1] + 1
547
- last = boundaries[k]
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 += "; #{new_cols[: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
- vars = old_row.merge(__group: grp)
706
- ev.eval_before_hook(vars)
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
- raise UserError, "Column '#{h}' in select does not exist" unless column?(h)
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, val|
719
+ new_cols.each_pair do |key, expr|
715
720
  key = key.as_sym
716
721
  vars = old_row.merge(new_row)
717
- case val
722
+ case expr
718
723
  when Symbol
719
- raise UserError, "Column '#{val}' in select does not exist" unless vars.keys.include?(val)
720
- new_row[key] = vars[val]
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(val, vars: vars)
729
+ new_row[key] = ev.evaluate(expr, locals: vars)
723
730
  else
724
- raise UserError, "Hash parameter '#{key}' to select must be a symbol or string"
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(vars)
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
- vars = row.merge(__group: grp)
761
- ev.eval_before_hook(vars)
762
- result << row if ev.evaluate(expr, vars: vars)
763
- ev.eval_after_hook(vars)
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 op between this table and the other table
874
- # given in the first argument. If distinct is true, eliminate duplicates
875
- # from the result.
876
- def set_operation(other, op = :+,
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
- raise UserError, 'Cannot apply a set operation to tables with a different number of columns.'
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
- raise UserError, 'Cannot apply a set operation to tables with different column types.'
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(op, other_rows)
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 = [:inner, :left, :right, :full, :cross].freeze
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
- join_expression, other_common_heads = build_join_expression(exps, other, join_type)
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(join_expression, vars: locals)
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
- if join_type == :left || join_type == :full
1005
- unless self_row_matched
1006
- out_row = build_out_row(row_a: self_row, row_b: other_row_nils, type: join_type)
1007
- result << out_row
1008
- end
1009
- end
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 join_type == :right || join_type == :full
1023
+ if %i[right full].include?(join_type)
1012
1024
  other_rows.each_with_index do |other_row, k|
1013
- unless other_row_matches[k]
1014
- out_row = build_out_row(row_a: self_row_nils, row_b: other_row, type: join_type)
1015
- result << out_row
1016
- end
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
- raise UserError,
1104
- 'A non-cross join with no common column names requires join expressions'
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, other_h: last_sym, other: other)
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, other_h: b_head, other: other)
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
- "must follow '#{last_sym}' by qualified exp from the other table"
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
- raise UserError, "unqualified column '#{exp}' must occur in both tables"
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
- raise UserError, "invalid join expression '#{exp}' of class #{exp.class}"
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
- raise UserError,
1189
- "type of column '#{self_h}' does not match type of column '#{other_h}"
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
- raise "Table already has a column with header '#{col.header}'" if column?(col.header)
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
- raise UserError, "unknown format '#{fmt}'" unless FatTable::FORMATS.include?(fmt)
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