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.
@@ -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