diff --git a/lib/cucumber/core/ast.rb b/lib/cucumber/core/ast.rb index 5a317323..642727fc 100644 --- a/lib/cucumber/core/ast.rb +++ b/lib/cucumber/core/ast.rb @@ -6,7 +6,6 @@ require 'cucumber/core/ast/background' require 'cucumber/core/ast/scenario' require 'cucumber/core/ast/step' -require 'cucumber/core/ast/multiline_argument' require 'cucumber/core/ast/doc_string' require 'cucumber/core/ast/data_table' require 'cucumber/core/ast/scenario_outline' diff --git a/lib/cucumber/core/ast/data_table.rb b/lib/cucumber/core/ast/data_table.rb index 22187360..8e6625d7 100644 --- a/lib/cucumber/core/ast/data_table.rb +++ b/lib/cucumber/core/ast/data_table.rb @@ -30,48 +30,20 @@ class DataTable include DescribesItself include HasLocation - class Builder - attr_reader :rows - - def initialize - @rows = [] - end - - def row(row, line_number) - @rows << row - end - - def eof - end - end - include ::Gherkin::Rubify - NULL_CONVERSIONS = Hash.new({ :strict => false, :proc => lambda{ |cell_value| cell_value } }).freeze - - attr_accessor :file - - def self.parse(text, uri, location) - builder = Builder.new - lexer = ::Gherkin::Lexer::I18nLexer.new(builder) - lexer.scan(text) - new(builder.rows, location) - end - # Creates a new instance. +raw+ should be an Array of Array of String # or an Array of Hash # You don't typically create your own DataTable objects - Cucumber will do # it internally and pass them to your Step Definitions. # def initialize(raw, location) - @cells_class = Cells - @cell_class = Cell raw = ensure_array_of_array(rubify(raw)) - # Verify that it's square - raw.transpose - create_cell_matrix(raw) + verify_rows_are_same_length(raw) + @raw = raw.freeze @location = location end + attr_reader :raw def to_step_definition_arg dup @@ -106,108 +78,6 @@ def map(&block) self.class.new(new_raw, location) end - # Converts this table into an Array of Hash where the keys of each - # Hash are the headers in the table. For example, a DataTable built from - # the following plain text: - # - # | a | b | sum | - # | 2 | 3 | 5 | - # | 7 | 9 | 16 | - # - # Gets converted into the following: - # - # [{'a' => '2', 'b' => '3', 'sum' => '5'}, {'a' => '7', 'b' => '9', 'sum' => '16'}] - # - def hashes - build_hashes - end - - # Converts this table into a Hash where the first column is - # used as keys and the second column is used as values - # - # | a | 2 | - # | b | 3 | - # - # Gets converted into the following: - # - # {'a' => '2', 'b' => '3'} - # - # The table must be exactly two columns wide - # - def rows_hash - verify_table_width(2) - self.transpose.hashes[0] - end - - # Gets the raw data of this table. For example, a DataTable built from - # the following plain text: - # - # | a | b | - # | c | d | - # - # gets converted into the following: - # - # [['a', 'b'], ['c', 'd']] - # - def raw - cell_matrix.map do |row| - row.map do |cell| - cell.value - end - end - end - - def column_names #:nodoc: - cell_matrix[0].map { |cell| cell.value } - end - - def rows - hashes.map do |hash| - hash.values_at(*headers) - end - end - - # For testing only - def to_sexp #:nodoc: - [:table, *cells_rows.map{|row| row.to_sexp}] - end - - def to_hash(cells) #:nodoc: - hash = Hash.new do |the_hash, key| - the_hash[key.to_s] if key.is_a?(Symbol) - end - column_names.each_with_index do |column_name, column_index| - hash[column_name] = cells.value(column_index) - end - hash - end - - def verify_table_width(width) #:nodoc: - raise %{The table must have exactly #{width} columns} unless raw[0].size == width - end - - def cells_rows #:nodoc: - cell_matrix.map do |cell_row| - @cells_class.new(self, cell_row) - end - end - - def headers #:nodoc: - raw.first - end - - def cell_matrix #:nodoc: - @cell_matrix - end - - def col_width(col) #:nodoc: - columns[col].__send__(:width) - end - - def each_cell(&proc) - cell_matrix.each{ |row| row.each(&proc) } - end - def ==(other) other.class == self.class && raw == other.raw end @@ -218,35 +88,14 @@ def inspect private - TO_S_PREFIXES = Hash.new(' ') - TO_S_PREFIXES[:comment] = '(+) ' - TO_S_PREFIXES[:undefined] = '(-) ' - - def build_hashes - cells_rows[1..-1].map do |row| - row.to_hash - end - end - - 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 columns #:nodoc: - cell_matrix.transpose.map do |cell_row| - @cells_class.new(self, cell_row) + def verify_rows_are_same_length(raw) + begin + raw.transpose + rescue IndexError + raise ArgumentError, "Rows must all be the same length" end end - def new_cell(raw_cell, line) #:nodoc: - @cell_class.new(raw_cell, self, line) - end - def ensure_array_of_array(array) Hash === array[0] ? hashes_to_array(array) : array end @@ -260,70 +109,6 @@ def description_for_visitors :data_table end - # Represents a row of cells or columns of cells - class Cells #:nodoc: - include Enumerable - include Gherkin::Formatter::Escaping - - attr_reader :exception - - def initialize(table, cells) - @table, @cells = table, cells - end - - # For testing only - def to_sexp #:nodoc: - [:row, line, *@cells.map{|cell| cell.to_sexp}] - end - - def to_hash #:nodoc: - @to_hash ||= @table.to_hash(self) - end - - def value(n) #:nodoc: - self[n].value - end - - def [](n) - @cells[n] - end - - def line - @cells[0].line - end - - private - - def width - map{|cell| cell.value ? escape_cell(cell.value.to_s).unpack('U*').length : 0}.max - end - - def each(&proc) - @cells.each(&proc) - end - end - - class Cell #:nodoc: - attr_reader :line, :table - attr_accessor :status, :value - - def initialize(value, table, line) - @value, @table, @line = value, table, line - end - - def inspect! - @value = "(i) #{value.inspect}" - end - - def ==(o) - value == o.value - end - - # For testing only - def to_sexp #:nodoc: - [:cell, @value] - end - end end end end diff --git a/lib/cucumber/core/ast/location.rb b/lib/cucumber/core/ast/location.rb index dad24a3a..f49f9cab 100644 --- a/lib/cucumber/core/ast/location.rb +++ b/lib/cucumber/core/ast/location.rb @@ -13,10 +13,14 @@ class Location < Struct.new(:filepath, :lines) def_delegator :filepath, :same_as? def_delegator :filepath, :filename, :file - def initialize(filepath, lines=WILDCARD) + def self.of_caller + file, raw_line = *caller[1].split(':')[0..1] + new(file, raw_line.to_i) + end + + def initialize(filepath, raw_lines=WILDCARD) filepath || raise(ArgumentError, "file is mandatory") - lines || raise(ArgumentError, "line is mandatory") - super(FilePath.new(filepath), Lines.new(lines)) + super(FilePath.new(filepath), Lines.new(raw_lines)) end def match?(other) @@ -58,12 +62,12 @@ class Lines < Struct.new(:data) protected :data attr_reader :line - def initialize(line) - if Cucumber::JRUBY && line.is_a?(::Java::GherkinFormatterModel::Range) - line = Range.new(line.first, line.last) + def initialize(raw_data) + if Cucumber::JRUBY && raw_data.is_a?(::Java::GherkinFormatterModel::Range) + raw_data = Range.new(raw_data.first, raw_data.last) end - @line = line - super Array(line).to_set + super Array(raw_data).to_set + @line = data.first end def include?(other) diff --git a/lib/cucumber/core/ast/multiline_argument.rb b/lib/cucumber/core/ast/multiline_argument.rb deleted file mode 100644 index 97ff26ba..00000000 --- a/lib/cucumber/core/ast/multiline_argument.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'gherkin/rubify' - -module Cucumber - module Core - module Ast - module MultilineArgument - - class << self - include Gherkin::Rubify - - # TODO: move this up to the front-end - def from(argument, parent_location) - return EmptyMultilineArgument.new unless argument - return argument if argument.respond_to?(:to_step_definition_arg) - - argument = rubify(argument) - case argument - when String - Ast::DocString.new(argument, 'text/plain', parent_location) - when ::Gherkin::Formatter::Model::DocString - Ast::DocString.new(argument.value, argument.content_type, parent_location.on_line(argument.line_range)) - when Array - location = parent_location.on_line(argument.first.line..argument.last.line) - Ast::DataTable.new(argument.map{|row| row.cells}, location) - else - raise ArgumentError, "Don't know how to convert #{argument.inspect} into a MultilineArgument" - end - end - - end - end - end - end -end diff --git a/lib/cucumber/core/gherkin/ast_builder.rb b/lib/cucumber/core/gherkin/ast_builder.rb index 1de2afe3..e1b842fb 100644 --- a/lib/cucumber/core/gherkin/ast_builder.rb +++ b/lib/cucumber/core/gherkin/ast_builder.rb @@ -1,6 +1,7 @@ require 'cucumber/initializer' require 'cucumber/core/ast' require 'cucumber/core/platform' +require 'gherkin/rubify' module Cucumber module Core @@ -214,7 +215,8 @@ def result(language) location, node.keyword, node.name, - Ast::MultilineArgument.from(node.doc_string || node.rows, location) + + MultilineArgument.from(node.doc_string || node.rows, location) ) end end @@ -301,12 +303,32 @@ def result(language) location, node.keyword, node.name, - Ast::MultilineArgument.from(node.doc_string || node.rows, location) + MultilineArgument.from(node.doc_string || node.rows, location) ) end end end + module MultilineArgument + class << self + include ::Gherkin::Rubify + + def from(argument, parent_location) + return Ast::EmptyMultilineArgument.new unless argument + argument = rubify(argument) + case argument + when ::Gherkin::Formatter::Model::DocString + Ast::DocString.new(argument.value, argument.content_type, parent_location.on_line(argument.line_range)) + when Array + location = parent_location.on_line(argument.first.line..argument.last.line) + Ast::DataTable.new(argument.map{|row| row.cells}, location) + else + raise ArgumentError, "Don't know how to convert #{argument.inspect} into a MultilineArgument" + end + end + end + end + end end end diff --git a/spec/cucumber/core/ast/data_table_spec.rb b/spec/cucumber/core/ast/data_table_spec.rb index d22c0ec8..327141fb 100644 --- a/spec/cucumber/core/ast/data_table_spec.rb +++ b/spec/cucumber/core/ast/data_table_spec.rb @@ -12,43 +12,6 @@ module Ast %w{one four seven}, %w{4444 55555 666666} ], location) - def @table.cells_rows; super; end - def @table.columns; super; end - end - - it "should have rows" do - expect( @table.cells_rows[0].map{|cell| cell.value} ).to eq %w{one four seven} - end - - it "should have columns" do - expect( @table.columns[1].map{|cell| cell.value} ).to eq %w{four 55555} - end - - it "should have headers" do - expect( @table.headers ).to eq %w{one four seven} - end - - it "should have same cell objects in rows and columns" do - # 666666 - expect( @table.cells_rows[1].__send__(:[], 2) ).to eq @table.columns[2].__send__(:[], 1) - end - - it "should know about max width of a row" do - expect( @table.columns[1].__send__(:width) ).to eq 5 - end - - it "should be convertible to an array of hashes" do - expect( @table.hashes ).to eq [ - {'one' => '4444', 'four' => '55555', 'seven' => '666666'} - ] - end - - it "should accept symbols as keys for the hashes" do - expect( @table.hashes.first[:one] ).to eq '4444' - end - - it "should return the row values in order" do - expect( @table.rows.first ).to eq %w{4444 55555 666666} end describe "equality" do @@ -86,62 +49,12 @@ def @table.columns; super; end ], location) end - it "should be convertible in to an array where each row is a hash" do - expect( @table.transpose.hashes[0] ).to eq({'one' => '1111', 'two' => '22222'}) - end - end - - describe "#rows_hash" do - - it "should return a hash of the rows" do - table = DataTable.new([ - %w{one 1111}, - %w{two 22222} - ], location) - expect( table.rows_hash ).to eq({'one' => '1111', 'two' => '22222'}) - end - - it "should fail if the table doesn't have two columns" do - faulty_table = DataTable.new([ - %w{one 1111 abc}, - %w{two 22222 def} + it "should transpose the table" do + transposed = DataTable.new([ + %w{one two}, + %w{1111 22222} ], location) - expect { faulty_table.rows_hash }.to raise_error('The table must have exactly 2 columns') - end - end - - describe "#new" do - it "should allow Array of Hash" do - t1 = DataTable.new([{'name' => 'aslak', 'male' => 'true'}], location) - expect( t1.hashes ).to eq [{'name' => 'aslak', 'male' => 'true'}] - end - end - - it "should convert to sexp" do - sexp_value = - [:table, - [:row, -1, - [:cell, "one"], - [:cell, "four"], - [:cell, "seven"] - ], - [:row, -1, - [:cell, "4444"], - [:cell, "55555"], - [:cell, "666666"] - ] - ] - expect( @table.to_sexp ).to eq sexp_value - end - - describe "#each_cell" do - it "runs the given block on each cell in the table" do - table = DataTable.new([[1,2],[3,4]], location) - values = [] - table.each_cell do |cell| - values << cell.value - end - expect( values ).to eq [1,2,3,4] + expect( @table.transpose ).to eq( transposed ) end end diff --git a/spec/cucumber/core/ast/location_spec.rb b/spec/cucumber/core/ast/location_spec.rb index db11c0c0..ae93415f 100644 --- a/spec/cucumber/core/ast/location_spec.rb +++ b/spec/cucumber/core/ast/location_spec.rb @@ -24,6 +24,14 @@ module Cucumber::Core::Ast end end + describe "line" do + it "is an integer" do + expect(Location.new(file, line).line).to be_kind_of(Integer) + expect(Location.new(file, 1..2).line).to be_kind_of(Integer) + expect(Location.of_caller.line).to be_kind_of(Integer) + end + end + describe "to_s" do it "is file:line for a precise location" do expect( Location.new("foo.feature", 12).to_s ).to eq "foo.feature:12"