Skip to content

Commit

Permalink
Column and header mapping supported for diff.
Browse files Browse the repository at this point in the history
  • Loading branch information
richardlawrence committed Oct 26, 2011
1 parent a7135e1 commit abb0025
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 22 deletions.
59 changes: 38 additions & 21 deletions lib/cucumber/ast/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def eof
include Enumerable
include Gherkin::Rubify

NULL_CONVERSIONS = Hash.new(lambda{ |cell_value| cell_value }).freeze
NULL_CONVERSIONS = Hash.new({ :strict => false, :proc => lambda{ |cell_value| cell_value } }).freeze

attr_accessor :file

Expand All @@ -71,25 +71,27 @@ def self.parse(text, uri, offset)
# You don't typically create your own Table objects - Cucumber will do
# it internally and pass them to your Step Definitions.
#
def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup)
def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup, header_mappings = {}, header_conversion_proc = nil)
@cells_class = Cells
@cell_class = Cell
raw = ensure_array_of_array(rubify(raw))
# Verify that it's square
transposed = raw.transpose
create_cell_matrix(raw)
@conversion_procs = conversion_procs
@header_mappings = header_mappings
@header_conversion_proc = header_conversion_proc
end

def to_step_definition_arg
dup
end

# Creates a copy of this table, inheriting any column mappings.
# registered with #map_headers!
# Creates a copy of this table, inheriting any column and header mappings
# registered with #map_column! and #map_headers!.
#
def dup
self.class.new(raw.dup, @conversion_procs.dup)
self.class.new(raw.dup, @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
end

# Returns a new, transposed table. Example:
Expand All @@ -104,9 +106,9 @@ def dup
# | 4 | 2 |
#
def transpose
self.class.new(raw.transpose, @conversion_procs.dup)
self.class.new(raw.transpose, @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
end

# Converts this table into an Array of Hash where the keys of each
# Hash are the headers in the table. For example, a Table built from
# the following plain text:
Expand All @@ -122,9 +124,7 @@ def transpose
# Use #map_column! to specify how values in a column are converted.
#
def hashes
@hashes ||= cells_rows[1..-1].map do |row|
row.to_hash
end
@hashes ||= build_hashes
end

# Converts this table into a Hash where the first column is
Expand Down Expand Up @@ -238,7 +238,8 @@ def to_sexp #:nodoc:
# # => ['phone number', 'ADDRESS']
#
def map_headers!(mappings={}, &block)
convert_headers!(mappings, &block)
@header_mappings = mappings
@header_conversion_proc = block
end

# Returns a new Table where the headers are redefined. See #map_headers!
Expand All @@ -261,8 +262,7 @@ def map_headers(mappings={})
# end
#
def map_column!(column_name, strict=true, &conversion_proc)
verify_column(column_name.to_s) if strict
@conversion_procs[column_name.to_s] = conversion_proc
@conversion_procs[column_name.to_s] = { :strict => strict, :proc => conversion_proc }
self
end

Expand Down Expand Up @@ -304,8 +304,12 @@ def diff!(other_table, options={})
options = {:missing_row => true, :surplus_row => true, :missing_col => true, :surplus_col => false}.merge(options)

other_table = ensure_table(other_table)
other_table.convert_headers!
other_table.convert_columns!
ensure_green!

convert_headers!
convert_columns!

original_width = cell_matrix[0].length
other_table_cell_matrix = pad!(other_table.cell_matrix)
Expand All @@ -316,7 +320,6 @@ def diff!(other_table, options={})

require_diff_lcs
cell_matrix.extend(Diff::LCS)
convert_columns!
changes = cell_matrix.diff(other_table_cell_matrix).flatten

inserted = 0
Expand Down Expand Up @@ -371,7 +374,8 @@ def to_hash(cells) #:nodoc:
hash[key.to_s] if key.is_a?(Symbol)
end
column_names.each_with_index do |column_name, column_index|
value = @conversion_procs[column_name].call(cells.value(column_index))
verify_column(column_name) if @conversion_procs[column_name][:strict]
value = @conversion_procs[column_name][:proc].call(cells.value(column_index))
hash[column_name] = value
end
hash
Expand Down Expand Up @@ -455,6 +459,14 @@ def to_s(options = {}) #:nodoc:

protected

def build_hashes
convert_headers!
convert_columns!
cells_rows[1..-1].map do |row|
row.to_hash
end
end

def inspect_rows(missing_row, inserted_row) #:nodoc:
missing_row.each_with_index do |missing_cell, col|
inserted_cell = inserted_row[col]
Expand All @@ -475,23 +487,28 @@ def create_cell_matrix(raw) #:nodoc:
end

def convert_columns! #:nodoc:
@conversion_procs.each do |column_name, conversion_proc|
verify_column(column_name) if conversion_proc[:strict]
end

cell_matrix.transpose.each do |col|
conversion_proc = @conversion_procs[col[0].value]
column_name = col[0].value
conversion_proc = @conversion_procs[column_name][:proc]
col[1..-1].each do |cell|
cell.value = conversion_proc.call(cell.value)
end
end
end

def convert_headers!(mappings = {}, &block)
def convert_headers! #:nodoc:
header_cells = cell_matrix[0]

if block_given?
header_values = header_cells.map { |cell| cell.value } - mappings.keys
mappings = mappings.merge(Hash[*header_values.zip(header_values.map(&block)).flatten])
if @header_conversion_proc
header_values = header_cells.map { |cell| cell.value } - @header_mappings.keys
@header_mappings = @header_mappings.merge(Hash[*header_values.zip(header_values.map(&@header_conversion_proc)).flatten])
end

mappings.each_pair do |pre, post|
@header_mappings.each_pair do |pre, post|
mapped_cells = header_cells.select { |cell| pre === cell.value }
raise "No headers matched #{pre.inspect}" if mapped_cells.empty?
raise "#{mapped_cells.length} headers matched #{pre.inspect}: #{mapped_cells.map { |c| c.value }.inspect}" if mapped_cells.length > 1
Expand Down
4 changes: 3 additions & 1 deletion spec/cucumber/ast/table_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ def @table.columns; super; end
it "should pass silently if a mapped column does not exist in non-strict mode" do
lambda {
@table.map_column!('two', false) { |v| v.to_i }
@table.hashes
}.should_not raise_error
end

it "should fail if a mapped column does not exist in strict mode" do
lambda {
@table.map_column!('two', true) { |v| v.to_i }
@table.hashes
}.should raise_error('The column named "two" does not exist')
end

Expand Down Expand Up @@ -139,7 +141,7 @@ def @table.columns; super; end
%w{two 22222}
])
table.map_headers!({ 'two' => 'Two' }) { |header| header.upcase }
table.map_column!('two') { |val| val.to_i }
table.map_column!('two', false) { |val| val.to_i }
table.rows_hash.should == { 'ONE' => '1111', 'Two' => 22222 }
end
end
Expand Down

0 comments on commit abb0025

Please sign in to comment.