lib/fat_table/table.rb in fat_table-0.2.8 vs lib/fat_table/table.rb in fat_table-0.2.9
- old
+ new
@@ -104,22 +104,23 @@
end
# :category: Constructors
# Construct a new table from an Array of Arrays +aoa+. By default, with
- # +hlines+ set to false, do not look for separators, i.e. +nils+, just treat
- # the first row as headers. With +hlines+ set true, expect +nil+ separators
- # to mark the header row and any boundaries. If the second element of the
- # array is a +nil+, interpret the first element of the array as a row of
- # headers. Otherwise, synthesize headers of the form +:col_1+, +:col_2+, ...
- # and so forth. The remaining elements are taken as the body of the table,
- # except that if an element of the outer array is a +nil+, mark the
- # preceding row as a group boundary. Note for Emacs users: In org mode code
- # blocks when an org-mode table is passed in as a variable it is passed in
- # as an Array of Arrays. By default (+ HEADER: :hlines no +) org-mode strips
- # all from the table; otherwise (+ HEADER: :hlines yes +) they are indicated
- # with nil elements in the outer array.
+ # +hlines+ set to false, do not look for separators, i.e. +nils+, just
+ # treat the first row as headers. With +hlines+ set true, expect +nil+
+ # separators to mark the header row and any boundaries. If the second
+ # element of the array is a +nil+, interpret the first element of the
+ # array as a row of headers. Otherwise, synthesize headers of the form
+ # +:col_1+, +:col_2+, ... and so forth. The remaining elements are taken
+ # as the body of the table, except that if an element of the outer array
+ # is a +nil+, mark the preceding row as a group boundary. Note for Emacs
+ # users: In org mode code blocks when an org-mode table is passed in as a
+ # variable it is passed in as an Array of Arrays. By default (+ HEADER:
+ # :hlines no +) org-mode strips all hrules from the table; otherwise (+
+ # HEADER: :hlines yes +) they are indicated with nil elements in the outer
+ # array.
def self.from_aoa(aoa, hlines: false)
from_array_of_arrays(aoa, hlines: hlines)
end
# :category: Constructors
@@ -147,14 +148,15 @@
end
# :category: Constructors
# Construct a Table by running a SQL +query+ against the database set up
- # with FatTable.set_db, with the rows of the query result as rows.
+ # with FatTable.connect, with the rows of the query result as rows.
def self.from_sql(query)
- msg = 'FatTable.db must be set with FatTable.set_db'
+ msg = 'FatTable.db must be set with FatTable.connect'
raise UserError, msg if FatTable.db.nil?
+
result = Table.new
FatTable.db[query].each do |h|
result << h
end
result
@@ -255,26 +257,28 @@
header_found = false
io.each do |line|
unless table_found
# Skip through the file until a table is found
next unless line.match?(table_re)
+
unless line.match?(hrule_re)
line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
rows << line.split('|').map(&:clean)
end
table_found = true
next
end
break unless line.match?(table_re)
+
if !header_found && line =~ hrule_re
rows << nil
header_found = true
next
elsif header_found && line =~ hrule_re
# Mark the boundary with a nil
rows << nil
- elsif line !~ table_re
+ elsif !line.match?(table_re)
# Stop reading at the second hline
break
else
line = line.sub(/\A\s*\|/, '').sub(/\|\s*\z/, '')
rows << line.split('|').map(&:clean)
@@ -314,18 +318,21 @@
def [](key)
case key
when Integer
msg = "index '#{key}' out of range"
raise UserError, msg unless (0..size - 1).cover?(key.abs)
+
rows[key]
when String
msg = "header '#{key}' not in table"
raise UserError, msg unless headers.include?(key)
+
column(key).items
when Symbol
msg = "header ':#{key}' not in table"
raise UserError, msg unless headers.include?(key)
+
column(key).items
else
raise UserError, "cannot index table with a #{key.class}"
end
end
@@ -358,18 +365,20 @@
# :category: Attributes
# Return the number of rows in the Table.
def size
return 0 if columns.empty?
+
columns.first.size
end
# :category: Attributes
# Return the number of Columns in the Table.
def width
return 0 if columns.empty?
+
columns.size
end
# :category: Attributes
@@ -404,10 +413,11 @@
# of any size.
def rows_range(first = 0, last = nil) # :nodoc:
last ||= size - 1
last = [last, 0].max
raise UserError, 'first must be <= last' unless first <= last
+
rows = []
unless columns.empty?
first.upto(last) do |rnum|
row = {}
columns.each do |col|
@@ -491,16 +501,16 @@
def degroup!
@boundaries = []
self
end
- # Mark a group boundary at row +k+, and if +k+ is +nil+, mark the last row
- # in the table as a group boundary. This is mainly used for internal
+ # Mark a group boundary at row +row+, and if +row+ is +nil+, mark the last
+ # row in the table as a group boundary. This is mainly used for internal
# purposes.
- def mark_boundary(k = nil) # :nodoc:
- if k
- boundaries.push(k)
+ def mark_boundary(row = nil) # :nodoc:
+ if row
+ boundaries.push(row)
else
boundaries.push(size - 1)
end
end
@@ -522,24 +532,25 @@
# #union_all method.
def append_boundaries(bounds, shift: 0)
@boundaries += bounds.map { |k| k + shift }
end
- # Return the group number to which row k belongs. Groups, from the user's
- # point of view are indexed starting at 1.
- def row_index_to_group_index(k)
+ # Return the group number to which row ~row~ belongs. Groups, from the
+ # user's point of view are indexed starting at 1.
+ def row_index_to_group_index(row)
boundaries.each_with_index do |b_last, g_num|
- return (g_num + 1) if k <= b_last
+ return (g_num + 1) if row <= b_last
end
1
end
- def group_rows(k) # :nodoc:
+ def group_rows(row) # :nodoc:
normalize_boundaries
- return [] unless k < boundaries.size
- first = k.zero? ? 0 : boundaries[k - 1] + 1
- last = boundaries[k]
+ return [] unless row < boundaries.size
+
+ first = row.zero? ? 0 : boundaries[row - 1] + 1
+ last = boundaries[row]
rows_range(first, last)
end
# :startdoc:
@@ -700,19 +711,21 @@
new_row = {}
cols.each do |k|
h = k.as_sym
msg = "Column '#{h}' in select does not exist"
raise UserError, msg unless column?(h)
+
new_row[h] = old_row[h]
end
new_cols.each_pair do |key, expr|
key = key.as_sym
vars = old_row.merge(new_row)
case expr
when Symbol
msg = "Column '#{expr}' in select does not exist"
- raise UserError, msg unless vars.keys.include?(expr)
+ raise UserError, msg unless vars.key?(expr)
+
new_row[key] = vars[expr]
when String
new_row[key] = ev.evaluate(expr, locals: vars)
else
msg = "Hash parameter '#{key}' to select must be a symbol or string"
@@ -862,14 +875,14 @@
set_operation(other, :difference, distinct: false)
end
private
- # Apply the set operation given by op between this table and the other table
- # given in the first argument. If distinct is true, eliminate duplicates
- # from the result.
- def set_operation(other, op = :+,
+ # Apply the set operation given by ~oper~ between this table and the other
+ # table given in the first argument. If distinct is true, eliminate
+ # duplicates from the result.
+ def set_operation(other, oper = :+,
distinct: true,
add_boundaries: true,
inherit_boundaries: false)
unless columns.size == other.columns.size
msg = "can't apply set ops to tables with a different number of columns"
@@ -879,11 +892,11 @@
msg = "can't apply a set ops to tables with different column types."
raise UserError, msg
end
other_rows = other.rows.map { |r| r.replace_keys(headers) }
result = Table.new
- new_rows = rows.send(op, other_rows)
+ new_rows = rows.send(oper, other_rows)
new_rows.each_with_index do |row, k|
result << row
result.mark_boundary if k == size - 1 && add_boundaries
end
if inherit_boundaries
@@ -973,10 +986,11 @@
raise UserError, 'need other table as first argument to join'
end
unless JOIN_TYPES.include?(join_type)
raise UserError, "join_type may only be: #{JOIN_TYPES.join(', ')}"
end
+
# These may be needed for outer joins.
self_row_nils = headers.map { |h| [h, nil] }.to_h
other_row_nils = other.headers.map { |h| [h, nil] }.to_h
join_exp, other_common_heads =
build_join_expression(exps, other, join_type)
@@ -990,25 +1004,28 @@
# Same as other_row, but with keys that are common with self and equal
# in value, removed, so the output table need not repeat them.
locals = build_locals_hash(row_a: self_row, row_b: other_row)
matches = ev.evaluate(join_exp, locals: locals)
next unless matches
+
self_row_matched = other_row_matches[k] = true
out_row = build_out_row(row_a: self_row, row_b: other_row,
common_heads: other_common_heads,
type: join_type)
result << out_row
end
next unless %i[left full].include?(join_type)
next if self_row_matched
+
result << build_out_row(row_a: self_row,
row_b: other_row_nils,
type: join_type)
end
if %i[right full].include?(join_type)
other_rows.each_with_index do |other_row, k|
next if other_row_matches[k]
+
result << build_out_row(row_a: self_row_nils,
row_b: other_row,
type: join_type)
end
end
@@ -1088,10 +1105,11 @@
# the expression will be evaluated in the context of a binding in which the
# local variables are all the headers in the self table with '_a' appended
# and all the headers in the other table with '_b' appended.
def build_join_expression(exps, other, type)
return ['true', []] if type == :cross
+
a_heads = headers
b_heads = other.headers
common_heads = a_heads & b_heads
b_common_heads = []
if exps.empty?
@@ -1118,10 +1136,11 @@
when /\A(.*)_a\z/
a_head = $1.to_sym
unless a_heads.include?(a_head)
raise UserError, "no column '#{a_head}' in table"
end
+
if partial_result
# Second of a pair
ensure_common_types!(self_h: a_head,
other_h: last_sym,
other: other)
@@ -1136,10 +1155,11 @@
when /\A(.*)_b\z/
b_head = $1.to_sym
unless b_heads.include?(b_head)
raise UserError, "no column '#{b_head}' in second table"
end
+
if partial_result
# Second of a pair
ensure_common_types!(self_h: last_sym,
other_h: b_head,
other: other)
@@ -1279,10 +1299,11 @@
# Add a FatTable::Column object +col+ to the table.
def add_column(col)
msg = "Table already has a column with header '#{col.header}'"
raise msg if column?(col.header)
+
columns << col
self
end
############################################################################
@@ -1327,9 +1348,10 @@
#
def to_any(fmt_type, options = {})
fmt = fmt_type.as_sym
msg = "unknown format '#{fmt}'"
raise UserError, msg unless FatTable::FORMATS.include?(fmt)
+
method = "to_#{fmt}"
if block_given?
send method, options, &Proc.new
else
send method, options