kosmas58-cucumber 0.3.11.6 → 0.3.90

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/History.txt +62 -4
  2. data/Manifest.txt +4 -0
  3. data/examples/i18n/ko/features/addition.feature +5 -5
  4. data/examples/i18n/ko/features/step_definitons/calculator_steps.rb +1 -1
  5. data/examples/i18n/no/features/step_definitons/kalkulator_steps.rb +1 -1
  6. data/examples/self_test/features/support/env.rb +2 -1
  7. data/examples/steps_library/features/step_definitions/steps_lib1.rb +8 -0
  8. data/examples/steps_library/features/step_definitions/steps_lib2.rb +8 -0
  9. data/examples/tickets/features/step_definitons/tickets_steps.rb +15 -0
  10. data/examples/tickets/features/table_diffing.feature +13 -0
  11. data/examples/watir/features/step_definitons/search_steps.rb +5 -1
  12. data/features/bug_371.feature +32 -0
  13. data/features/cucumber_cli_diff_disabled.feature +2 -1
  14. data/features/html_formatter/a.html +6 -5
  15. data/features/language_from_header.feature +30 -0
  16. data/features/rake_task.feature +28 -0
  17. data/features/steps_formatter.feature +25 -0
  18. data/features/support/env.rb +5 -0
  19. data/features/table_diffing.feature +45 -0
  20. data/features/unicode_table.feature +35 -0
  21. data/gem_tasks/sass.rake +4 -0
  22. data/lib/cucumber/ast/outline_table.rb +2 -1
  23. data/lib/cucumber/ast/py_string.rb +0 -1
  24. data/lib/cucumber/ast/step.rb +4 -1
  25. data/lib/cucumber/ast/table.rb +286 -48
  26. data/lib/cucumber/ast/visitor.rb +2 -1
  27. data/lib/cucumber/cli/configuration.rb +8 -2
  28. data/lib/cucumber/cli/language_help_formatter.rb +9 -7
  29. data/lib/cucumber/feature_file.rb +53 -0
  30. data/lib/cucumber/filter.rb +50 -0
  31. data/lib/cucumber/formatter/html.rb +1 -1
  32. data/lib/cucumber/formatter/pretty.rb +20 -5
  33. data/lib/cucumber/formatter/progress.rb +1 -1
  34. data/lib/cucumber/formatter/steps.rb +49 -0
  35. data/lib/cucumber/parser/feature.rb +27 -0
  36. data/lib/cucumber/parser/i18n/language.rb +83 -0
  37. data/lib/cucumber/rake/task.rb +6 -0
  38. data/lib/cucumber/step_match.rb +1 -1
  39. data/lib/cucumber/version.rb +2 -2
  40. data/lib/cucumber/webrat/table_locator.rb +66 -0
  41. data/rails_generators/cucumber/templates/en/webrat_steps.rb +17 -0
  42. data/rails_generators/cucumber/templates/env.rb +1 -0
  43. data/rails_generators/feature/templates/feature.erb +1 -1
  44. data/rails_generators/feature/templates/steps.erb +2 -8
  45. data/spec/cucumber/ast/table_spec.rb +145 -0
  46. data/spec/cucumber/cli/configuration_spec.rb +13 -0
  47. data/spec/cucumber/cli/main_spec.rb +0 -1
  48. data/spec/cucumber/formatter/progress_spec.rb +2 -2
  49. metadata +18 -4
@@ -7,7 +7,9 @@ module Cucumber
7
7
  #
8
8
  # This gets parsed into a Table holding the values <tt>[['a', 'b'], ['c', 'd']]</tt>
9
9
  #
10
- class Table
10
+ class Table
11
+ include Enumerable
12
+
11
13
  NULL_CONVERSIONS = Hash.new(lambda{ |cell_value| cell_value }).freeze
12
14
 
13
15
  attr_accessor :file
@@ -16,18 +18,19 @@ module Cucumber
16
18
  "table"
17
19
  end
18
20
 
19
- def initialize(raw, conversions = NULL_CONVERSIONS.dup)
20
- # Verify that it's square
21
- raw.transpose
22
- @raw = raw
21
+ def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup)
23
22
  @cells_class = Cells
24
23
  @cell_class = Cell
25
- @conversion_procs = conversions
24
+
25
+ # Verify that it's square
26
+ transposed = raw.transpose
27
+ create_cell_matrix(raw)
28
+ @conversion_procs = conversion_procs
26
29
  end
27
30
 
28
31
  # Creates a copy of this table, inheriting the column mappings.
29
32
  def dup
30
- self.class.new(@raw.dup, @conversion_procs.dup)
33
+ self.class.new(raw.dup, @conversion_procs.dup)
31
34
  end
32
35
 
33
36
  # Returns a new, transposed table. Example:
@@ -42,7 +45,7 @@ module Cucumber
42
45
  # | 4 | 2 |
43
46
  #
44
47
  def transpose
45
- self.class.new(@raw.transpose, @conversion_procs.dup)
48
+ self.class.new(raw.transpose, @conversion_procs.dup)
46
49
  end
47
50
 
48
51
  # Converts this table into an Array of Hash where the keys of each
@@ -78,8 +81,9 @@ module Cucumber
78
81
  # The table must be exactly two columns wide
79
82
  #
80
83
  def rows_hash
84
+ return @rows_hash if @rows_hash
81
85
  verify_table_width(2)
82
- @rows_hash = self.transpose.hashes[0]
86
+ @rows_hash = self.transpose.hashes[0].freeze
83
87
  end
84
88
 
85
89
  # Gets the raw data of this table. For example, a Table built from
@@ -88,17 +92,21 @@ module Cucumber
88
92
  # | a | b |
89
93
  # | c | d |
90
94
  #
91
- # Get converted into the following:
95
+ # gets converted into the following:
92
96
  #
93
97
  # [['a', 'b], ['c', 'd']]
94
98
  #
95
99
  def raw
96
- @raw
100
+ cell_matrix.map do |row|
101
+ row.map do |cell|
102
+ cell.value
103
+ end
104
+ end
97
105
  end
98
106
 
99
107
  # Same as #raw, but skips the first (header) row
100
108
  def rows
101
- @raw[1..-1]
109
+ raw[1..-1]
102
110
  end
103
111
 
104
112
  def each_cells_row(&proc)
@@ -117,7 +125,7 @@ module Cucumber
117
125
  [:table, *cells_rows.map{|row| row.to_sexp}]
118
126
  end
119
127
 
120
- # Returns a new Table where the headers are redefined. This makes it
128
+ # Redefines the table headers. This makes it
121
129
  # possible to use prettier header names in the features. Example:
122
130
  #
123
131
  # | Phone Number | Address |
@@ -130,6 +138,18 @@ module Cucumber
130
138
  # hashes = mapped_table.hashes
131
139
  # # => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
132
140
  #
141
+ def map_headers!(mappings)
142
+ header_cells = cell_matrix[0]
143
+ mappings.each_pair do |pre, post|
144
+ header_cell = header_cells.detect{|cell| cell.value == pre}
145
+ header_cell.value = post
146
+ if @conversion_procs.has_key?(pre)
147
+ @conversion_procs[post] = @conversion_procs.delete(pre)
148
+ end
149
+ end
150
+ end
151
+
152
+ # Returns a new Table where the headers are redefined. See #map_headers!
133
153
  def map_headers(mappings)
134
154
  table = self.dup
135
155
  table.map_headers!(mappings)
@@ -153,11 +173,110 @@ module Cucumber
153
173
  @conversion_procs[column_name] = conversion_proc
154
174
  end
155
175
 
176
+ # Compares +other_table+ to self. If +other_table+ contains columns
177
+ # and/or rows that are not in self, new columns/rows are added at the
178
+ # relevant positions, marking the cells in those rows/columns as
179
+ # <tt>surplus</tt>. Likewise, if +other_table+ lacks columns and/or
180
+ # rows that are present in self, these are marked as <tt>missing</tt>.
181
+ #
182
+ # <tt>surplus</tt> and <tt>missing</tt> cells are recognised by formatters
183
+ # and displayed so that it's easy to read the differences.
184
+ #
185
+ # Cells that are different, but <em>look</em> identical (for example the
186
+ # boolean true and the string "true") are converted to their Object#inspect
187
+ # representation and preceded with (i) - to make it easier to identify
188
+ # where the difference actually is.
189
+ #
190
+ # Since all tables that are passed to StepDefinitions always have String
191
+ # objects in their cells, you may want to use #map_column! before calling
192
+ # #diff!
193
+ #
194
+ # An exception is raised if there are missing rows or columns, or
195
+ # surplus rows. An error is <em>not</em> raised for surplus columns.
196
+ # Whether to raise or not raise can be changed by setting values in
197
+ # +options+ to true or false:
198
+ #
199
+ # * <tt>missing_row</tt>: Raise on missing rows (defaults to true)
200
+ # * <tt>surplus_row</tt>: Raise on surplus rows (defaults to true)
201
+ # * <tt>missing_col</tt>: Raise on missing columns (defaults to true)
202
+ # * <tt>surplus_col</tt>: Raise on surplus columns (defaults to false)
203
+ #
204
+ # The +other_table+ argument can be another Table, an Array of Array or
205
+ # an Array of Hash (similar to the structure returned by #hashes).
206
+ #
207
+ # Calling this method is particularly useful in <tt>Then</tt> steps that take
208
+ # a Table argument, if you want to compare that table to some actual values.
209
+ #
210
+ def diff!(other_table, options={})
211
+ options = {:missing_row => true, :surplus_row => true, :missing_col => true, :surplus_col => false}.merge(options)
212
+
213
+ other_table = ensure_table(other_table)
214
+ ensure_green!
215
+
216
+ original_width = cell_matrix[0].length
217
+ other_table_cell_matrix = pad!(other_table.cell_matrix)
218
+ padded_width = cell_matrix[0].length
219
+
220
+ missing_col = cell_matrix[0].detect{|cell| cell.status == :undefined}
221
+ surplus_col = padded_width > original_width
222
+
223
+ require_diff_lcs
224
+ cell_matrix.extend(Diff::LCS)
225
+ convert_columns!
226
+ changes = cell_matrix.diff(other_table_cell_matrix).flatten
227
+
228
+ inserted = 0
229
+ missing = 0
230
+
231
+ row_indices = Array.new(other_table_cell_matrix.length) {|n| n}
232
+
233
+ last_change = nil
234
+ missing_row_pos = nil
235
+ insert_row_pos = nil
236
+
237
+ changes.each do |change|
238
+ if(change.action == '-')
239
+ missing_row_pos = change.position + inserted
240
+ cell_matrix[missing_row_pos].each{|cell| cell.status = :undefined}
241
+ row_indices.insert(missing_row_pos, nil)
242
+ missing += 1
243
+ else # '+'
244
+ insert_row_pos = change.position + missing
245
+ inserted_row = change.element
246
+ inserted_row.each{|cell| cell.status = :comment}
247
+ cell_matrix.insert(insert_row_pos, inserted_row)
248
+ row_indices[insert_row_pos] = nil
249
+ inspect_rows(cell_matrix[missing_row_pos], inserted_row) if last_change && last_change.action == '-'
250
+ inserted += 1
251
+ end
252
+ last_change = change
253
+ end
254
+
255
+ other_table_cell_matrix.each_with_index do |other_row, i|
256
+ row_index = row_indices.index(i)
257
+ row = cell_matrix[row_index] if row_index
258
+ if row
259
+ (original_width..padded_width).each do |col_index|
260
+ surplus_cell = other_row[col_index]
261
+ row[col_index].value = surplus_cell.value if row[col_index]
262
+ end
263
+ end
264
+ end
265
+
266
+ clear_cache!
267
+ should_raise =
268
+ missing_row_pos && options[:missing_row] ||
269
+ insert_row_pos && options[:surplus_row] ||
270
+ missing_col && options[:missing_col] ||
271
+ surplus_col && options[:surplus_col]
272
+ raise 'Tables were not identical' if should_raise
273
+ end
274
+
156
275
  def to_hash(cells) #:nodoc:
157
276
  hash = Hash.new do |hash, key|
158
277
  hash[key.to_s] if key.is_a?(Symbol)
159
278
  end
160
- @raw[0].each_with_index do |column_name, column_index|
279
+ raw[0].each_with_index do |column_name, column_index|
161
280
  value = @conversion_procs[column_name].call(cells.value(column_index))
162
281
  hash[column_name] = value
163
282
  end
@@ -169,11 +288,11 @@ module Cucumber
169
288
  end
170
289
 
171
290
  def verify_column(column_name)
172
- raise %{The column named "#{column_name}" does not exist} unless @raw[0].include?(column_name)
291
+ raise %{The column named "#{column_name}" does not exist} unless raw[0].include?(column_name)
173
292
  end
174
293
 
175
294
  def verify_table_width(width)
176
- raise %{The table must have exactly #{width} columns} unless @raw[0].size == width
295
+ raise %{The table must have exactly #{width} columns} unless raw[0].size == width
177
296
  end
178
297
 
179
298
  def arguments_replaced(arguments) #:nodoc:
@@ -198,33 +317,88 @@ module Cucumber
198
317
  def cells_rows
199
318
  @rows ||= cell_matrix.map do |cell_row|
200
319
  @cells_class.new(self, cell_row)
201
- end.freeze
320
+ end
202
321
  end
203
322
 
204
323
  def headers
205
- @raw.first
324
+ raw.first
206
325
  end
207
326
 
208
327
  def header_cell(col)
209
328
  cells_rows[0][col]
210
329
  end
211
330
 
331
+ def cell_matrix
332
+ @cell_matrix
333
+ end
334
+
335
+ def col_width(col)
336
+ columns[col].__send__(:width)
337
+ end
338
+
339
+ def to_s(options = {})
340
+ options = {:color => true, :indent => 2, :prefixes => TO_S_PREFIXES}.merge(options)
341
+ io = StringIO.new
342
+
343
+ c = Term::ANSIColor.coloring?
344
+ Term::ANSIColor.coloring = options[:color]
345
+ f = Formatter::Pretty.new(nil, io, options)
346
+ f.instance_variable_set('@indent', options[:indent])
347
+ f.visit_multiline_arg(self)
348
+ Term::ANSIColor.coloring = c
349
+
350
+ io.rewind
351
+ s = "\n" + io.read + (" " * (options[:indent] - 2))
352
+ s
353
+ end
354
+
355
+ private
356
+
357
+ TO_S_PREFIXES = Hash.new(' ')
358
+ TO_S_PREFIXES[:comment] = ['(+) ']
359
+ TO_S_PREFIXES[:undefined] = ['(-) ']
360
+
212
361
  protected
213
362
 
214
- def map_headers!(mappings)
215
- headers = @raw[0]
216
- mappings.each_pair do |pre, post|
217
- headers[headers.index(pre)] = post
218
- if @conversion_procs.has_key?(pre)
219
- @conversion_procs[post] = @conversion_procs.delete(pre)
363
+ def inspect_rows(missing_row, inserted_row)
364
+ missing_row.each_with_index do |missing_cell, col|
365
+ inserted_cell = inserted_row[col]
366
+ if(missing_cell.value != inserted_cell.value && (missing_cell.value.to_s == inserted_cell.value.to_s))
367
+ missing_cell.inspect!
368
+ inserted_cell.inspect!
220
369
  end
221
370
  end
222
371
  end
223
372
 
224
- private
373
+ def create_cell_matrix(raw)
374
+ @cell_matrix = raw.map do |raw_row|
375
+ line = raw_row.line rescue -1
376
+ raw_row.map do |raw_cell|
377
+ new_cell(raw_cell, line)
378
+ end
379
+ end
380
+ end
225
381
 
226
- def col_width(col)
227
- columns[col].__send__(:width)
382
+ def convert_columns!
383
+ cell_matrix.transpose.each do |col|
384
+ conversion_proc = @conversion_procs[col[0].value]
385
+ col[1..-1].each do |cell|
386
+ cell.value = conversion_proc.call(cell.value)
387
+ end
388
+ end
389
+ end
390
+
391
+ def require_diff_lcs
392
+ begin
393
+ require 'diff/lcs'
394
+ rescue LoadError => e
395
+ e.message << "\n Please gem install diff-lcs\n"
396
+ raise e
397
+ end
398
+ end
399
+
400
+ def clear_cache!
401
+ @hashes = @rows_hash = @rows = @columns = nil
228
402
  end
229
403
 
230
404
  def columns
@@ -233,17 +407,73 @@ module Cucumber
233
407
  end.freeze
234
408
  end
235
409
 
236
- def cell_matrix
237
- row = -1
238
- @cell_matrix ||= @raw.map do |raw_row|
239
- line = raw_row.line rescue -1
240
- row += 1
241
- col = -1
242
- raw_row.map do |raw_cell|
243
- col += 1
244
- @cell_class.new(raw_cell, self, row, col, line)
410
+ def new_cell(raw_cell, line)
411
+ @cell_class.new(raw_cell, self, line)
412
+ end
413
+
414
+ # Pads our own cell_matrix and returns a cell matrix of same
415
+ # column width that can be used for diffing
416
+ def pad!(other_cell_matrix)
417
+ clear_cache!
418
+ cols = cell_matrix.transpose
419
+ unmapped_cols = other_cell_matrix.transpose
420
+
421
+ mapped_cols = []
422
+
423
+ cols.each_with_index do |col, col_index|
424
+ header = col[0]
425
+ candidate_cols, unmapped_cols = unmapped_cols.partition do |other_col|
426
+ other_col[0] == header
245
427
  end
246
- end.freeze
428
+ raise "More than one column has the header #{header}" if candidate_cols.size > 2
429
+
430
+ other_padded_col = if candidate_cols.size == 1
431
+ # Found a matching column
432
+ candidate_cols[0]
433
+ else
434
+ mark_as_missing(cols[col_index])
435
+ (0...other_cell_matrix.length).map do |row|
436
+ val = row == 0 ? header.value : nil
437
+ SurplusCell.new(val, self, -1)
438
+ end
439
+ end
440
+ mapped_cols.insert(col_index, other_padded_col)
441
+ end
442
+
443
+ unmapped_cols.each_with_index do |col, col_index|
444
+ empty_col = (0...cell_matrix.length).map do |row|
445
+ SurplusCell.new(nil, self, -1)
446
+ end
447
+ cols << empty_col
448
+ end
449
+
450
+ @cell_matrix = cols.transpose
451
+ (mapped_cols + unmapped_cols).transpose
452
+ end
453
+
454
+ def ensure_table(table_or_array)
455
+ return table_or_array if Table === table_or_array
456
+ table_or_array = hashes_to_array(table_or_array) if Hash === table_or_array[0]
457
+ Table.new(table_or_array)
458
+ end
459
+
460
+ def hashes_to_array(hashes)
461
+ header = hashes[0].keys
462
+ [header] + hashes.map{|hash| header.map{|key| hash[key]}}
463
+ end
464
+
465
+ def ensure_green!
466
+ each_cell{|cell| cell.status = :passed}
467
+ end
468
+
469
+ def each_cell(&proc)
470
+ cell_matrix.each{|row| row.each(&proc)}
471
+ end
472
+
473
+ def mark_as_missing(col)
474
+ col.each do |cell|
475
+ cell.status = :undefined
476
+ end
247
477
  end
248
478
 
249
479
  # Represents a row of cells or columns of cells
@@ -303,30 +533,38 @@ module Cucumber
303
533
  end
304
534
 
305
535
  class Cell
306
- attr_reader :value, :line
307
- attr_writer :status
536
+ attr_reader :line, :table
537
+ attr_accessor :status, :value
308
538
 
309
- def initialize(value, table, row, col, line)
310
- @value, @table, @row, @col, @line = value, table, row, col, line
539
+ def initialize(value, table, line)
540
+ @value, @table, @line = value, table, line
311
541
  end
312
542
 
313
543
  def accept(visitor)
314
- visitor.visit_table_cell_value(@value, col_width, @status)
544
+ visitor.visit_table_cell_value(value, status)
545
+ end
546
+
547
+ def inspect!
548
+ @value = "(i) #{value.inspect}"
315
549
  end
316
550
 
317
- def header_cell
318
- @table.header_cell(@col)
551
+ def ==(o)
552
+ SurplusCell === o || value == o.value
319
553
  end
320
554
 
321
555
  # For testing only
322
556
  def to_sexp #:nodoc:
323
557
  [:cell, @value]
324
558
  end
559
+ end
560
+
561
+ class SurplusCell < Cell
562
+ def status
563
+ :comment
564
+ end
325
565
 
326
- private
327
-
328
- def col_width
329
- @col_width ||= @table.__send__(:col_width, @col).freeze
566
+ def ==(o)
567
+ true
330
568
  end
331
569
  end
332
570
  end
@@ -64,6 +64,7 @@ module Cucumber
64
64
  end
65
65
 
66
66
  def visit_outline_table(outline_table)
67
+ @table = outline_table
67
68
  outline_table.accept(self)
68
69
  end
69
70
 
@@ -105,7 +106,7 @@ module Cucumber
105
106
  table_cell.accept(self)
106
107
  end
107
108
 
108
- def visit_table_cell_value(value, width, status)
109
+ def visit_table_cell_value(value, status)
109
110
  end
110
111
 
111
112
  def announce(announcement)
@@ -102,7 +102,9 @@ module Cucumber
102
102
  "TAGS must be comma-separated without spaces. Prefix tags with ~ to",
103
103
  "exclude features or scenarios having that tag. Tags can be specified",
104
104
  "with or without the @ prefix.") do |v|
105
- @options[:include_tags], @options[:exclude_tags] = *parse_tags(v)
105
+ include_tags, exclude_tags = *parse_tags(v)
106
+ @options[:include_tags] += include_tags
107
+ @options[:exclude_tags] += exclude_tags
106
108
  end
107
109
  opts.on("-n NAME", "--name NAME",
108
110
  "Only execute the feature elements which match part of the given name.",
@@ -268,7 +270,7 @@ module Cucumber
268
270
  end
269
271
 
270
272
  def files_to_require
271
- requires = @options[:require] || feature_dirs
273
+ requires = @options[:require] || require_dirs
272
274
  files = requires.map do |path|
273
275
  path = path.gsub(/\\/, '/') # In case we're on windows. Globs don't work with backslashes.
274
276
  path = path.gsub(/\/$/, '') # Strip trailing slash.
@@ -310,6 +312,10 @@ module Cucumber
310
312
  @paths.map { |f| File.directory?(f) ? f : File.dirname(f) }.uniq
311
313
  end
312
314
 
315
+ def require_dirs
316
+ feature_dirs+Dir['vendor/{gems,plugins}/*/cucumber']
317
+ end
318
+
313
319
  def constantize(camel_cased_word)
314
320
  begin
315
321
  names = camel_cased_word.split('::')
@@ -1,4 +1,5 @@
1
1
  require 'cucumber/formatter/pretty'
2
+ require 'cucumber/parser/i18n/language'
2
3
 
3
4
  module Cucumber
4
5
  module Cli
@@ -18,15 +19,16 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
18
19
  [lang, Cucumber::LANGUAGES[lang]['name'], Cucumber::LANGUAGES[lang]['native']]
19
20
  end
20
21
  table = Ast::Table.new(raw)
21
- new(nil, io, {:check_lang=>true}, '').visit_multiline_arg(table)
22
+ new(nil, io, {:check_lang=>true}).visit_multiline_arg(table)
22
23
  end
23
24
 
24
25
  def self.list_keywords(io, lang)
25
- raw = Cucumber::KEYWORD_KEYS.map do |key|
26
- [key, Cucumber::LANGUAGES[lang][key]]
26
+ language = Parser::I18n::Language[lang]
27
+ raw = Parser::I18n::Language::KEYWORD_KEYS.map do |key|
28
+ [key, language.keywords(key)]
27
29
  end
28
30
  table = Ast::Table.new(raw)
29
- new(nil, io, {:incomplete => Cucumber.language_incomplete?(lang)}, '').visit_multiline_arg(table)
31
+ new(nil, io, {:incomplete => language.incomplete?}).visit_multiline_arg(table)
30
32
  end
31
33
 
32
34
  def visit_multiline_arg(table)
@@ -41,10 +43,10 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
41
43
  super
42
44
  end
43
45
 
44
- def visit_table_cell_value(value, width, status)
46
+ def visit_table_cell_value(value, status)
45
47
  if @col == 1
46
48
  if(@options[:check_lang])
47
- @incomplete = Cucumber.language_incomplete?(value)
49
+ @incomplete = Parser::I18n::Language[value].incomplete?
48
50
  end
49
51
  status = :comment
50
52
  elsif @incomplete
@@ -52,7 +54,7 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
52
54
  end
53
55
 
54
56
  @col += 1
55
- super(value, width, status)
57
+ super(value, status)
56
58
  end
57
59
  end
58
60
  end
@@ -0,0 +1,53 @@
1
+ require 'cucumber/parser/i18n/language'
2
+ require 'cucumber/filter'
3
+
4
+ module Cucumber
5
+ class FeatureFile
6
+ FILE_COLON_LINE_PATTERN = /^([\w\W]*?):([\d:]+)$/
7
+ LANGUAGE_PATTERN = /language:\s*(.*)/
8
+
9
+ # The +uri+ argument can ba a path or a path:line1:line2 etc.
10
+ def initialize(uri, source=nil)
11
+ @source = source
12
+ _, @path, @lines = *FILE_COLON_LINE_PATTERN.match(uri)
13
+ if @path
14
+ @lines = @lines.split(':').map { |line| line.to_i }
15
+ else
16
+ @path = uri
17
+ end
18
+ end
19
+
20
+ # Parses a file and returns a Cucumber::Ast
21
+ # If +options+ contains tags, the result will
22
+ # be filtered.
23
+ def parse(options={})
24
+ filter = Filter.new(@lines, options)
25
+ language = Parser::I18n::Language[lang || options[:lang] || 'en']
26
+ language.parse(source, @path, filter)
27
+ end
28
+
29
+ def source
30
+ @source ||= if @path =~ /^http/
31
+ require 'open-uri'
32
+ open(@path).read
33
+ else
34
+ begin
35
+ File.open(@path, Cucumber.file_mode('r')).read
36
+ rescue Errno::EACCES => e
37
+ p = File.expand_path(@path)
38
+ e.message << "\nCouldn't open #{p}"
39
+ raise e
40
+ end
41
+ end
42
+ end
43
+
44
+ def lang
45
+ line_one = source.split(/\n/)[0]
46
+ if line_one =~ LANGUAGE_PATTERN
47
+ $1.strip
48
+ else
49
+ nil
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,50 @@
1
+ module Cucumber
2
+ class Filter
3
+ def initialize(lines, options)
4
+ @lines = lines
5
+ @include_tags = options[:include_tags] || []
6
+ @exclude_tags = options[:exclude_tags] || []
7
+ @name_regexps = options[:name_regexps] || []
8
+ end
9
+
10
+ def accept?(syntax_node)
11
+ at_line?(syntax_node) &&
12
+ matches_tags?(syntax_node) &&
13
+ matches_names?(syntax_node)
14
+ end
15
+
16
+ def accept_example?(syntax_node, outline)
17
+ (at_line?(syntax_node) || outline_at_line?(outline)) &&
18
+ (matches_names?(syntax_node) || outline_matches_names?(outline))
19
+ end
20
+
21
+ def at_line?(syntax_node)
22
+ @lines.nil? || @lines.empty? || @lines.detect{|line| syntax_node.at_line?(line)}
23
+ end
24
+
25
+ def outline_at_line?(syntax_node)
26
+ @lines.nil? || @lines.empty? || @lines.detect{|line| syntax_node.outline_at_line?(line)}
27
+ end
28
+
29
+ def matches_tags?(syntax_node)
30
+ !excluded_by_tags?(syntax_node) &&
31
+ included_by_tags?(syntax_node)
32
+ end
33
+
34
+ def included_by_tags?(syntax_node)
35
+ @include_tags.empty? || syntax_node.has_all_tags?(@include_tags)
36
+ end
37
+
38
+ def excluded_by_tags?(syntax_node)
39
+ @exclude_tags.any? && syntax_node.has_tags?(@exclude_tags)
40
+ end
41
+
42
+ def outline_matches_names?(syntax_node)
43
+ @name_regexps.nil? || @name_regexps.empty? || @name_regexps.detect{|name_regexp| syntax_node.outline_matches_name?(name_regexp)}
44
+ end
45
+
46
+ def matches_names?(syntax_node)
47
+ @name_regexps.nil? || @name_regexps.empty? || @name_regexps.detect{|name_regexp| syntax_node.matches_name?(name_regexp)}
48
+ end
49
+ end
50
+ end
@@ -214,7 +214,7 @@ module Cucumber
214
214
  @outline_row += 1 if @outline_row
215
215
  end
216
216
 
217
- def visit_table_cell_value(value, width, status)
217
+ def visit_table_cell_value(value, status)
218
218
  cell_type = @outline_row == 0 ? :th : :td
219
219
  attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'val'}
220
220
  attributes[:class] += " #{status}" if status