class Cucumber::Ast::Table
This will store [['a', 'b'], ['c', 'd']]
in the data
variable.
end
data = table.raw
Given /I have:/ do |table|
And a matching StepDefinition:
| c | d |
| a | b |
Given I have:
For example:
in different ways.
table parsed from a feature file and lets you access and manipulate the data
will receive it as an instance of Table. A Table object holds the data of a
Step Definitions that match a plain text Step with a multiline argument table
def self.default_arg_name #:nodoc:
def self.default_arg_name #:nodoc: "table" end
def accept(visitor) #:nodoc:
def accept(visitor) #:nodoc: return if $cucumber_interrupted cells_rows.each do |row| visitor.visit_table_row(row) end nil end
def arguments_replaced(arguments) #:nodoc:
def arguments_replaced(arguments) #:nodoc: raw_with_replaced_args = raw.map do |row| row.map do |cell| cell_with_replaced_args = cell arguments.each do |name, value| if cell_with_replaced_args && cell_with_replaced_args.include?(name) cell_with_replaced_args = value ? cell_with_replaced_args.gsub(name, value) : nil end end cell_with_replaced_args end end Table.new(raw_with_replaced_args) end
def cell_matrix #:nodoc:
def cell_matrix #:nodoc: @cell_matrix end
def cells_rows #:nodoc:
def cells_rows #:nodoc: @rows ||= cell_matrix.map do |cell_row| @cells_class.new(self, cell_row) end end
def clear_cache! #:nodoc:
def clear_cache! #:nodoc: @hashes = @rows_hash = @rows = @columns = nil end
def col_width(col) #:nodoc:
def col_width(col) #:nodoc: columns[col].__send__(:width) end
def columns #:nodoc:
def columns #:nodoc: @columns ||= cell_matrix.transpose.map do |cell_row| @cells_class.new(self, cell_row) end end
def convert_columns! #:nodoc:
def convert_columns! #:nodoc: cell_matrix.transpose.each do |col| conversion_proc = @conversion_procs[col[0].value] col[1..-1].each do |cell| cell.value = conversion_proc.call(cell.value) end end end
def create_cell_matrix(raw) #:nodoc:
def create_cell_matrix(raw) #:nodoc: @cell_matrix = raw.map do |raw_row| line = raw_row.line rescue -1 raw_row.map do |raw_cell| new_cell(raw_cell, line) end end end
def diff!(other_table, options={})
a Table argument, if you want to compare that table to some actual values.
Calling this method is particularly useful in Then steps that take
an Array of Hash (similar to the structure returned by #hashes).
The +other_table+ argument can be another Table, an Array of Array or
* surplus_col : Raise on surplus columns (defaults to false)
* missing_col : Raise on missing columns (defaults to true)
* surplus_row : Raise on surplus rows (defaults to true)
* missing_row : Raise on missing rows (defaults to true)
+options+ to true or false:
Whether to raise or not raise can be changed by setting values in
surplus rows. An error is not raised for surplus columns.
A Different error is raised if there are missing rows or columns, or
#diff!. You can use #map_column! on either of the tables.
objects in their cells, you may want to use #map_column! before calling
Since all tables that are passed to StepDefinitions always have String
where the difference actually is.
representation and preceded with (i) - to make it easier to identify
boolean true and the string "true") are converted to their Object#inspect
Cells that are different, but look identical (for example the
and displayed so that it's easy to read the differences.
surplus and missing cells are recognised by formatters
rows that are present in self, these are marked as missing.
surplus. Likewise, if +other_table+ lacks columns and/or
relevant positions, marking the cells in those rows/columns as
and/or rows that are not in self, new columns/rows are added at the
Compares +other_table+ to self. If +other_table+ contains columns
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_columns! ensure_green! original_width = cell_matrix[0].length other_table_cell_matrix = pad!(other_table.cell_matrix) padded_width = cell_matrix[0].length missing_col = cell_matrix[0].detect{|cell| cell.status == :undefined} surplus_col = padded_width > original_width require_diff_lcs cell_matrix.extend(Diff::LCS) convert_columns! changes = cell_matrix.diff(other_table_cell_matrix).flatten inserted = 0 missing = 0 row_indices = Array.new(other_table_cell_matrix.length) {|n| n} last_change = nil missing_row_pos = nil insert_row_pos = nil changes.each do |change| if(change.action == '-') missing_row_pos = change.position + inserted cell_matrix[missing_row_pos].each{|cell| cell.status = :undefined} row_indices.insert(missing_row_pos, nil) missing += 1 else # '+' insert_row_pos = change.position + missing inserted_row = change.element inserted_row.each{|cell| cell.status = :comment} cell_matrix.insert(insert_row_pos, inserted_row) row_indices[insert_row_pos] = nil inspect_rows(cell_matrix[missing_row_pos], inserted_row) if last_change && last_change.action == '-' inserted += 1 end last_change = change end other_table_cell_matrix.each_with_index do |other_row, i| row_index = row_indices.index(i) row = cell_matrix[row_index] if row_index if row (original_width..padded_width).each do |col_index| surplus_cell = other_row[col_index] row[col_index].value = surplus_cell.value if row[col_index] end end end clear_cache! should_raise = missing_row_pos && options[:missing_row] || insert_row_pos && options[:surplus_row] || missing_col && options[:missing_col] || surplus_col && options[:surplus_col] raise Different.new(self) if should_raise end
def dup
registered with #map_headers!
Creates a copy of this table, inheriting any column mappings.
def dup self.class.new(raw.dup, @conversion_procs.dup) end
def each_cell(&proc) #:nodoc:
def each_cell(&proc) #:nodoc: cell_matrix.each{|row| row.each(&proc)} end
def each_cells_row(&proc) #:nodoc:
def each_cells_row(&proc) #:nodoc: cells_rows.each(&proc) end
def ensure_array_of_array(array)
def ensure_array_of_array(array) Hash === array[0] ? hashes_to_array(array) : array end
def ensure_green! #:nodoc:
def ensure_green! #:nodoc: each_cell{|cell| cell.status = :passed} end
def ensure_table(table_or_array) #:nodoc:
def ensure_table(table_or_array) #:nodoc: return table_or_array if Table === table_or_array Table.new(table_or_array) end
def has_text?(text) #:nodoc:
def has_text?(text) #:nodoc: raw.flatten.compact.detect{|cell_value| cell_value.index(text)} end
def hashes
Use #map_column! to specify how values in a column are converted.
[{'a' => '2', 'b' => '3', 'sum' => '5'}, {'a' => '7', 'b' => '9', 'sum' => '16'}]
Gets converted into the following:
| 7 | 9 | 16 |
| 2 | 3 | 5 |
| a | b | sum |
the following plain text:
Hash are the headers in the table. For example, a Table built from
Converts this table into an Array of Hash where the keys of each
def hashes @hashes ||= cells_rows[1..-1].map do |row| row.to_hash end end
def hashes_to_array(hashes) #:nodoc:
def hashes_to_array(hashes) #:nodoc: header = hashes[0].keys [header] + hashes.map{|hash| header.map{|key| hash[key]}} end
def header_cell(col) #:nodoc:
def header_cell(col) #:nodoc: cells_rows[0][col] end
def headers #:nodoc:
def headers #:nodoc: raw.first end
def index(cells) #:nodoc:
def index(cells) #:nodoc: cells_rows.index(cells) end
def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup)
it internally and pass them to your Step Definitions.
You don't typically create your own Table objects - Cucumber will do
or an Array of Hash (similar to what #hashes returns).
Creates a new instance. +raw+ should be an Array of Array of String
def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup) @cells_class = Cells @cell_class = Cell raw = ensure_array_of_array(raw) # Verify that it's square transposed = raw.transpose create_cell_matrix(raw) @conversion_procs = conversion_procs end
def inspect_rows(missing_row, inserted_row) #:nodoc:
def inspect_rows(missing_row, inserted_row) #:nodoc: missing_row.each_with_index do |missing_cell, col| inserted_cell = inserted_row[col] if(missing_cell.value != inserted_cell.value && (missing_cell.value.to_s == inserted_cell.value.to_s)) missing_cell.inspect! inserted_cell.inspect! end end end
def map_column!(column_name, strict=true, &conversion_proc)
end
end
# post['amount'] is a Fixnum, rather than a String
posts_table.hashes.each do |post|
posts_table.map_column!('amount') { |a| a.to_i }
Given /^an expense report for (.*) with the following posts:$/ do |table|
is false, no error will be raised. Example:
true, an error will be raised if the column named +column_name+ is not found. If +strict+
and +conversion_proc+ performs the conversion for each cell in that column. If +strict+ is
Change how #hashes converts column values. The +column_name+ argument identifies the column
def map_column!(column_name, strict=true, &conversion_proc) verify_column(column_name) if strict @conversion_procs[column_name] = conversion_proc end
def map_headers(mappings={})
def map_headers(mappings={}) table = self.dup table.map_headers!(mappings) table end
def map_headers!(mappings={}, &block)
# => ['phone number', 'ADDRESS']
table.hashes.keys
table.map_headers!('Address' => 'ADDRESS') { |header| header.downcase }
When a block is passed in along with a hash then the mappings in the hash take precendence:
# => ['phone number', 'address']
table.hashes.keys
table.map_headers! { |header| header.downcase }
You may also pass in a block if you wish to convert all of the headers:
# => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
table.hashes
table.map_headers!(/phone( number)?/i => :phone, 'Address' => :address)
with both Regexp and String:
A StepDefinition receiving this table can then map the columns
| 345678 | abc |
| 123456 | xyz |
| Phone Number | Address |
Example:
desired names for the columns.
column headings in the table. The values of +mappings+ are
(anything that responds to #=== will work) that may match
keys of +mappings+ are Strings or regular expressions
prettier and more flexible header names in the features. The
Redefines the table headers. This makes it possible to use
def map_headers!(mappings={}, &block) 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]) end 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 mapped_cells[0].value = post if @conversion_procs.has_key?(pre) @conversion_procs[post] = @conversion_procs.delete(pre) end end end
def mark_as_missing(col) #:nodoc:
def mark_as_missing(col) #:nodoc: col.each do |cell| cell.status = :undefined end end
def match(pattern)
table.match(/table:column_1_name,column_2_name/) #=> non-nil
| x | y |
| column_1_name | column_2_name |
Example:
This is used especially for argument transforms.
Matches +pattern+ against the header row of the table.
def match(pattern) header_to_match = "table:#{headers.join(',')}" pattern.match(header_to_match) end
def new_cell(raw_cell, line) #:nodoc:
def new_cell(raw_cell, line) #:nodoc: @cell_class.new(raw_cell, self, line) end
def pad!(other_cell_matrix) #:nodoc:
column width that can be used for diffing
Pads our own cell_matrix and returns a cell matrix of same
def pad!(other_cell_matrix) #:nodoc: clear_cache! cols = cell_matrix.transpose unmapped_cols = other_cell_matrix.transpose mapped_cols = [] cols.each_with_index do |col, col_index| header = col[0] candidate_cols, unmapped_cols = unmapped_cols.partition do |other_col| other_col[0] == header end raise "More than one column has the header #{header}" if candidate_cols.size > 2 other_padded_col = if candidate_cols.size == 1 # Found a matching column candidate_cols[0] else mark_as_missing(cols[col_index]) (0...other_cell_matrix.length).map do |row| val = row == 0 ? header.value : nil SurplusCell.new(val, self, -1) end end mapped_cols.insert(col_index, other_padded_col) end unmapped_cols.each_with_index do |col, col_index| empty_col = (0...cell_matrix.length).map do |row| SurplusCell.new(nil, self, -1) end cols << empty_col end @cell_matrix = cols.transpose (mapped_cols + unmapped_cols).transpose end
def raw
[['a', 'b], ['c', 'd']]
gets converted into the following:
| c | d |
| a | b |
the following plain text:
Gets the raw data of this table. For example, a Table built from
def raw cell_matrix.map do |row| row.map do |cell| cell.value end end end
def require_diff_lcs #:nodoc:
def require_diff_lcs #:nodoc: begin require 'diff/lcs' rescue LoadError => e e.message << "\n Please gem install diff-lcs\n" raise e end end
def rows
def rows raw[1..-1] end
def rows_hash
The table must be exactly two columns wide
{'a' => '2', 'b' => '3'}
Gets converted into the following:
| b | 3 |
| a | 2 |
used as keys and the second column is used as values
Converts this table into a Hash where the first column is
def rows_hash return @rows_hash if @rows_hash verify_table_width(2) @rows_hash = self.transpose.hashes[0] end
def to_hash(cells) #:nodoc:
def to_hash(cells) #:nodoc: hash = Hash.new do |hash, key| hash[key.to_s] if key.is_a?(Symbol) end raw[0].each_with_index do |column_name, column_index| value = @conversion_procs[column_name].call(cells.value(column_index)) hash[column_name] = value end hash end
def to_json
def to_json raw.to_json end
def to_s(options = {}) #:nodoc:
def to_s(options = {}) #:nodoc: options = {:color => true, :indent => 2, :prefixes => TO_S_PREFIXES}.merge(options) io = StringIO.new c = Term::ANSIColor.coloring? Term::ANSIColor.coloring = options[:color] formatter = Formatter::Pretty.new(nil, io, options) formatter.instance_variable_set('@indent', options[:indent]) TreeWalker.new(nil, [formatter]).visit_multiline_arg(self) Term::ANSIColor.coloring = c io.rewind s = "\n" + io.read + (" " * (options[:indent] - 2)) s end
def to_sexp #:nodoc:
For testing only
def to_sexp #:nodoc: [:table, *cells_rows.map{|row| row.to_sexp}] end
def transpose
| 4 | 2 |
| 7 | 9 |
| a | b |
Gets converted into the following:
| b | 9 | 2 |
| a | 7 | 4 |
Returns a new, transposed table. Example:
def transpose self.class.new(raw.transpose, @conversion_procs.dup) end
def verify_column(column_name) #:nodoc:
def verify_column(column_name) #:nodoc: raise %{The column named "#{column_name}" does not exist} unless raw[0].include?(column_name) end
def verify_table_width(width) #:nodoc:
def verify_table_width(width) #:nodoc: raise %{The table must have exactly #{width} columns} unless raw[0].size == width end