cucumber 0.3.11 → 0.3.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. data/History.txt +101 -0
  2. data/Manifest.txt +14 -5
  3. data/config/hoe.rb +1 -2
  4. data/cucumber.yml +1 -0
  5. data/examples/i18n/ar/Rakefile +1 -1
  6. data/examples/i18n/ar/features/addition.feature +1 -0
  7. data/examples/i18n/bg/Rakefile +1 -1
  8. data/examples/i18n/bg/features/addition.feature +1 -0
  9. data/examples/i18n/bg/features/consecutive_calculations.feature +1 -0
  10. data/examples/i18n/bg/features/division.feature +1 -0
  11. data/examples/i18n/cat/Rakefile +1 -1
  12. data/examples/i18n/cat/features/suma.feature +1 -0
  13. data/examples/i18n/da/Rakefile +1 -1
  14. data/examples/i18n/da/features/summering.feature +1 -0
  15. data/examples/i18n/de/Rakefile +1 -1
  16. data/examples/i18n/de/features/addition.feature +1 -0
  17. data/examples/i18n/de/features/division.feature +1 -0
  18. data/examples/i18n/en-lol/features/stuffing.feature +1 -1
  19. data/examples/i18n/en/Rakefile +1 -1
  20. data/examples/i18n/en/features/addition.feature +1 -0
  21. data/examples/i18n/en/features/division.feature +1 -0
  22. data/examples/i18n/es/Rakefile +1 -1
  23. data/examples/i18n/es/features/adicion.feature +1 -0
  24. data/examples/i18n/et/Rakefile +1 -1
  25. data/examples/i18n/et/features/jagamine.feature +1 -0
  26. data/examples/i18n/et/features/liitmine.feature +1 -0
  27. data/examples/i18n/fi/Rakefile +1 -1
  28. data/examples/i18n/fi/features/jakolasku.feature +1 -0
  29. data/examples/i18n/fr/Rakefile +1 -1
  30. data/examples/i18n/fr/features/addition.feature +2 -1
  31. data/examples/i18n/he/Rakefile +1 -1
  32. data/examples/i18n/he/features/addition.feature +1 -0
  33. data/examples/i18n/he/features/division.feature +1 -0
  34. data/examples/i18n/hu/Rakefile +1 -1
  35. data/examples/i18n/hu/features/addition.feature +1 -0
  36. data/examples/i18n/hu/features/division.feature +1 -0
  37. data/examples/i18n/id/Rakefile +1 -1
  38. data/examples/i18n/id/features/addition.feature +1 -0
  39. data/examples/i18n/id/features/division.feature +1 -0
  40. data/examples/i18n/it/Rakefile +1 -1
  41. data/examples/i18n/it/features/somma.feature +1 -0
  42. data/examples/i18n/ja/Rakefile +1 -1
  43. data/examples/i18n/ja/features/addition.feature +1 -0
  44. data/examples/i18n/ja/features/division.feature +1 -0
  45. data/examples/i18n/ko/Rakefile +1 -1
  46. data/examples/i18n/ko/features/addition.feature +6 -5
  47. data/examples/i18n/ko/features/division.feature +1 -0
  48. data/examples/i18n/ko/features/step_definitons/calculator_steps.rb +1 -1
  49. data/examples/i18n/lt/Rakefile +1 -1
  50. data/examples/i18n/lt/features/addition.feature +1 -0
  51. data/examples/i18n/lt/features/division.feature +1 -0
  52. data/examples/i18n/lv/Rakefile +1 -1
  53. data/examples/i18n/lv/features/addition.feature +1 -0
  54. data/examples/i18n/lv/features/division.feature +1 -0
  55. data/examples/i18n/no/Rakefile +1 -1
  56. data/examples/i18n/no/features/step_definitons/kalkulator_steps.rb +1 -1
  57. data/examples/i18n/no/features/summering.feature +1 -0
  58. data/examples/i18n/pl/Rakefile +1 -1
  59. data/examples/i18n/pl/features/addition.feature +1 -0
  60. data/examples/i18n/pl/features/division.feature +1 -0
  61. data/examples/i18n/pt/Rakefile +1 -1
  62. data/examples/i18n/pt/features/adicao.feature +1 -0
  63. data/examples/i18n/ro/Rakefile +1 -1
  64. data/examples/i18n/ro/features/suma.feature +1 -0
  65. data/examples/i18n/ru/Rakefile +1 -1
  66. data/examples/i18n/ru/features/addition.feature +1 -0
  67. data/examples/i18n/ru/features/consecutive_calculations.feature +1 -0
  68. data/examples/i18n/ru/features/division.feature +1 -0
  69. data/examples/i18n/se/Rakefile +1 -1
  70. data/examples/i18n/se/features/summering.feature +1 -0
  71. data/examples/i18n/sk/Rakefile +1 -1
  72. data/examples/i18n/sk/features/addition.feature +1 -0
  73. data/examples/i18n/sk/features/division.feature +1 -0
  74. data/examples/i18n/zh-CN/features/addition.feature +1 -0
  75. data/examples/i18n/zh-TW/features/addition.feature +1 -0
  76. data/examples/i18n/zh-TW/features/division.feature +1 -0
  77. data/examples/self_test/features/support/env.rb +2 -1
  78. data/examples/sinatra/features/step_definitions/add_steps.rb +1 -1
  79. data/examples/sinatra/features/support/env.rb +13 -5
  80. data/examples/steps_library/features/step_definitions/steps_lib1.rb +8 -0
  81. data/examples/steps_library/features/step_definitions/steps_lib2.rb +8 -0
  82. data/examples/tickets/features/step_definitons/tickets_steps.rb +15 -0
  83. data/examples/tickets/features/table_diffing.feature +13 -0
  84. data/features/after_block_exceptions.feature +4 -1
  85. data/features/after_step_block_exceptions.feature +4 -1
  86. data/features/background.feature +6 -0
  87. data/features/bug_371.feature +32 -0
  88. data/features/cucumber_cli.feature +11 -1
  89. data/features/cucumber_cli_diff_disabled.feature +9 -2
  90. data/features/drb_server_integration.feature +28 -5
  91. data/features/expand.feature +2 -1
  92. data/features/html_formatter/a.html +129 -1614
  93. data/features/junit_formatter.feature +4 -4
  94. data/features/language_from_header.feature +30 -0
  95. data/features/rake_task.feature +28 -0
  96. data/features/step_definitions/cucumber_steps.rb +2 -2
  97. data/features/steps_formatter.feature +25 -0
  98. data/features/support/env.rb +13 -5
  99. data/features/table_diffing.feature +45 -0
  100. data/features/unicode_table.feature +35 -0
  101. data/features/work_in_progress.feature +3 -0
  102. data/gem_tasks/sass.rake +4 -0
  103. data/lib/cucumber.rb +0 -57
  104. data/lib/cucumber/ast/comment.rb +1 -1
  105. data/lib/cucumber/ast/feature.rb +2 -2
  106. data/lib/cucumber/ast/feature_element.rb +4 -0
  107. data/lib/cucumber/ast/outline_table.rb +2 -1
  108. data/lib/cucumber/ast/py_string.rb +0 -1
  109. data/lib/cucumber/ast/scenario.rb +5 -3
  110. data/lib/cucumber/ast/scenario_outline.rb +6 -1
  111. data/lib/cucumber/ast/step.rb +8 -1
  112. data/lib/cucumber/ast/step_invocation.rb +6 -1
  113. data/lib/cucumber/ast/table.rb +287 -49
  114. data/lib/cucumber/ast/visitor.rb +2 -1
  115. data/lib/cucumber/cli/configuration.rb +45 -28
  116. data/lib/cucumber/cli/drb_client.rb +8 -9
  117. data/lib/cucumber/cli/language_help_formatter.rb +9 -7
  118. data/lib/cucumber/cli/main.rb +8 -9
  119. data/lib/cucumber/feature_file.rb +53 -0
  120. data/lib/cucumber/filter.rb +50 -0
  121. data/lib/cucumber/formatter/console.rb +12 -0
  122. data/lib/cucumber/formatter/cucumber.css +106 -48
  123. data/lib/cucumber/formatter/cucumber.sass +121 -31
  124. data/lib/cucumber/formatter/html.rb +6 -5
  125. data/lib/cucumber/formatter/junit.rb +3 -6
  126. data/lib/cucumber/formatter/pretty.rb +22 -9
  127. data/lib/cucumber/formatter/profile.rb +1 -1
  128. data/lib/cucumber/formatter/progress.rb +1 -1
  129. data/lib/cucumber/formatter/steps.rb +49 -0
  130. data/lib/cucumber/languages.yml +3 -3
  131. data/lib/cucumber/parser.rb +1 -33
  132. data/lib/cucumber/parser/feature.rb +39 -16
  133. data/lib/cucumber/parser/feature.tt +1 -3
  134. data/lib/cucumber/parser/i18n.tt +30 -23
  135. data/lib/cucumber/parser/i18n/language.rb +83 -0
  136. data/lib/cucumber/parser/treetop_ext.rb +12 -83
  137. data/lib/cucumber/platform.rb +6 -0
  138. data/lib/cucumber/rake/task.rb +6 -0
  139. data/lib/cucumber/step_definition.rb +6 -0
  140. data/lib/cucumber/step_match.rb +1 -1
  141. data/lib/cucumber/step_mother.rb +3 -3
  142. data/lib/cucumber/version.rb +1 -1
  143. data/lib/cucumber/webrat/table_locator.rb +66 -0
  144. data/rails_generators/cucumber/templates/cucumber.rake +4 -0
  145. data/rails_generators/cucumber/templates/env.rb +1 -0
  146. data/rails_generators/cucumber/templates/spork_env.rb +1 -1
  147. data/rails_generators/cucumber/templates/webrat_steps.rb +17 -0
  148. data/rails_generators/feature/templates/feature.erb +1 -1
  149. data/rails_generators/feature/templates/steps.erb +2 -8
  150. data/spec/cucumber/ast/step_collection_spec.rb +5 -4
  151. data/spec/cucumber/ast/table_spec.rb +145 -0
  152. data/spec/cucumber/cli/configuration_spec.rb +55 -9
  153. data/spec/cucumber/cli/drb_client_spec.rb +5 -4
  154. data/spec/cucumber/cli/main_spec.rb +9 -12
  155. data/spec/cucumber/formatter/progress_spec.rb +2 -2
  156. data/spec/cucumber/parser/feature_parser_spec.rb +11 -9
  157. data/spec/cucumber/parser/table_parser_spec.rb +1 -1
  158. data/spec/spec_helper.rb +2 -1
  159. metadata +21 -20
  160. data/spec/cucumber/formatter/html/cucumber.css +0 -37
  161. data/spec/cucumber/formatter/html/cucumber.js +0 -13
  162. data/spec/cucumber/formatter/html/index.html +0 -45
  163. data/spec/cucumber/formatter/html/jquery-1.3.min.js +0 -19
  164. data/spec/cucumber/formatter/html/jquery.uitableedit.js +0 -100
@@ -88,7 +88,8 @@ module Cucumber
88
88
  end
89
89
 
90
90
  def actual_keyword
91
- if [Cucumber.keyword_hash['and'], Cucumber.keyword_hash['but']].index(@step.keyword) && previous
91
+ repeat_keywords = [language.but_keywords, language.and_keywords].flatten
92
+ if repeat_keywords.index(@step.keyword) && previous
92
93
  previous.actual_keyword
93
94
  else
94
95
  keyword
@@ -123,6 +124,10 @@ module Cucumber
123
124
  @step.backtrace_line
124
125
  end
125
126
 
127
+ def language
128
+ @step.language
129
+ end
130
+
126
131
  def to_sexp
127
132
  [:step_invocation, @step.line, @step.keyword, @name, (@multiline_arg.nil? ? nil : @multiline_arg.to_sexp)].compact
128
133
  end
@@ -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
@@ -62,7 +65,7 @@ module Cucumber
62
65
  def hashes
63
66
  @hashes ||= cells_rows[1..-1].map do |row|
64
67
  row.to_hash
65
- end
68
+ end.freeze
66
69
  end
67
70
 
68
71
  # Converts this table into a Hash where the first column is
@@ -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:
@@ -202,47 +321,158 @@ module Cucumber
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
231
405
  @columns ||= cell_matrix.transpose.map do |cell_row|
232
406
  @cells_class.new(self, cell_row)
233
- end
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
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
246
476
  end
247
477
  end
248
478
 
@@ -268,7 +498,7 @@ module Cucumber
268
498
  end
269
499
 
270
500
  def to_hash #:nodoc:
271
- @to_hash ||= @table.to_hash(self)
501
+ @to_hash ||= @table.to_hash(self).freeze
272
502
  end
273
503
 
274
504
  def value(n) #:nodoc:
@@ -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)
566
+ def ==(o)
567
+ true
330
568
  end
331
569
  end
332
570
  end