lucid 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. data/.gitignore +30 -10
  2. data/.rspec +1 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +15 -0
  6. data/Gemfile +4 -2
  7. data/HISTORY.md +22 -0
  8. data/{LICENSE.txt → LICENSE} +6 -3
  9. data/README.md +22 -8
  10. data/Rakefile +2 -1
  11. data/bin/lucid +10 -10
  12. data/bin/lucid-gen +4 -0
  13. data/lib/autotest/discover.rb +11 -0
  14. data/lib/autotest/lucid.rb +6 -0
  15. data/lib/autotest/lucid_mixin.rb +135 -0
  16. data/lib/autotest/lucid_rails.rb +6 -0
  17. data/lib/autotest/lucid_rails_rspec.rb +6 -0
  18. data/lib/autotest/lucid_rails_rspec2.rb +6 -0
  19. data/lib/autotest/lucid_rspec.rb +6 -0
  20. data/lib/autotest/lucid_rspec2.rb +6 -0
  21. data/lib/lucid.rb +32 -1
  22. data/lib/lucid/ast.rb +20 -0
  23. data/lib/lucid/ast/background.rb +116 -0
  24. data/lib/lucid/ast/comment.rb +24 -0
  25. data/lib/lucid/ast/doc_string.rb +44 -0
  26. data/lib/lucid/ast/empty_background.rb +33 -0
  27. data/lib/lucid/ast/examples.rb +49 -0
  28. data/lib/lucid/ast/feature.rb +99 -0
  29. data/lib/lucid/ast/has_steps.rb +74 -0
  30. data/lib/lucid/ast/location.rb +41 -0
  31. data/lib/lucid/ast/multiline_argument.rb +31 -0
  32. data/lib/lucid/ast/names.rb +13 -0
  33. data/lib/lucid/ast/outline_table.rb +194 -0
  34. data/lib/lucid/ast/scenario.rb +103 -0
  35. data/lib/lucid/ast/scenario_outline.rb +144 -0
  36. data/lib/lucid/ast/specs.rb +38 -0
  37. data/lib/lucid/ast/step.rb +122 -0
  38. data/lib/lucid/ast/step_collection.rb +92 -0
  39. data/lib/lucid/ast/step_invocation.rb +196 -0
  40. data/lib/lucid/ast/table.rb +730 -0
  41. data/lib/lucid/ast/tags.rb +28 -0
  42. data/lib/lucid/ast/tdl_walker.rb +195 -0
  43. data/lib/lucid/cli/app.rb +78 -0
  44. data/lib/lucid/cli/configuration.rb +261 -0
  45. data/lib/lucid/cli/options.rb +463 -0
  46. data/lib/lucid/cli/profile.rb +101 -0
  47. data/lib/lucid/configuration.rb +53 -0
  48. data/lib/lucid/core_ext/disable_autorunners.rb +15 -0
  49. data/lib/lucid/core_ext/instance_exec.rb +70 -0
  50. data/lib/lucid/core_ext/proc.rb +36 -0
  51. data/lib/lucid/core_ext/string.rb +9 -0
  52. data/lib/lucid/errors.rb +40 -0
  53. data/lib/lucid/factory.rb +43 -0
  54. data/lib/lucid/formatter/ansicolor.rb +168 -0
  55. data/lib/lucid/formatter/console.rb +218 -0
  56. data/lib/lucid/formatter/debug.rb +33 -0
  57. data/lib/lucid/formatter/duration.rb +11 -0
  58. data/lib/lucid/formatter/gherkin_formatter_adapter.rb +94 -0
  59. data/lib/lucid/formatter/gpretty.rb +24 -0
  60. data/lib/lucid/formatter/html.rb +610 -0
  61. data/lib/lucid/formatter/interceptor.rb +66 -0
  62. data/lib/lucid/formatter/io.rb +31 -0
  63. data/lib/lucid/formatter/jquery-min.js +154 -0
  64. data/lib/lucid/formatter/json.rb +19 -0
  65. data/lib/lucid/formatter/json_pretty.rb +10 -0
  66. data/lib/lucid/formatter/junit.rb +177 -0
  67. data/lib/lucid/formatter/lucid.css +283 -0
  68. data/lib/lucid/formatter/lucid.sass +244 -0
  69. data/lib/lucid/formatter/ordered_xml_markup.rb +24 -0
  70. data/lib/lucid/formatter/progress.rb +95 -0
  71. data/lib/lucid/formatter/rerun.rb +91 -0
  72. data/lib/lucid/formatter/standard.rb +235 -0
  73. data/lib/lucid/formatter/stepdefs.rb +14 -0
  74. data/lib/lucid/formatter/steps.rb +49 -0
  75. data/lib/lucid/formatter/summary.rb +35 -0
  76. data/lib/lucid/formatter/unicode.rb +53 -0
  77. data/lib/lucid/formatter/usage.rb +132 -0
  78. data/lib/lucid/generator.rb +21 -0
  79. data/lib/lucid/generators/project.rb +70 -0
  80. data/lib/lucid/generators/project/Gemfile.tt +6 -0
  81. data/lib/lucid/generators/project/browser-symbiont.rb +24 -0
  82. data/lib/lucid/generators/project/driver-symbiont.rb +4 -0
  83. data/lib/lucid/generators/project/errors.rb +26 -0
  84. data/lib/lucid/generators/project/events-symbiont.rb +36 -0
  85. data/lib/lucid/generators/project/lucid-symbiont.yml +6 -0
  86. data/lib/lucid/interface.rb +8 -0
  87. data/lib/lucid/interface_methods.rb +125 -0
  88. data/lib/lucid/interface_rb/matcher.rb +108 -0
  89. data/lib/lucid/interface_rb/rb_hook.rb +18 -0
  90. data/lib/lucid/interface_rb/rb_language.rb +190 -0
  91. data/lib/lucid/interface_rb/rb_lucid.rb +119 -0
  92. data/lib/lucid/interface_rb/rb_step_definition.rb +122 -0
  93. data/lib/lucid/interface_rb/rb_transform.rb +57 -0
  94. data/lib/lucid/interface_rb/rb_world.rb +136 -0
  95. data/lib/lucid/interface_rb/regexp_argument_matcher.rb +21 -0
  96. data/lib/lucid/load_path.rb +13 -0
  97. data/lib/lucid/parser.rb +2 -126
  98. data/lib/lucid/platform.rb +27 -0
  99. data/lib/lucid/rspec/allow_doubles.rb +20 -0
  100. data/lib/lucid/rspec/disallow_options.rb +27 -0
  101. data/lib/lucid/runtime.rb +200 -0
  102. data/lib/lucid/runtime/facade.rb +60 -0
  103. data/lib/lucid/runtime/interface_io.rb +60 -0
  104. data/lib/lucid/runtime/orchestrator.rb +218 -0
  105. data/lib/lucid/runtime/results.rb +64 -0
  106. data/lib/lucid/runtime/specs_loader.rb +79 -0
  107. data/lib/lucid/spec_file.rb +112 -0
  108. data/lib/lucid/step_definition_light.rb +20 -0
  109. data/lib/lucid/step_definitions.rb +13 -0
  110. data/lib/lucid/step_match.rb +99 -0
  111. data/lib/lucid/tdl_builder.rb +282 -0
  112. data/lib/lucid/term/ansicolor.rb +118 -0
  113. data/lib/lucid/unit.rb +11 -0
  114. data/lib/lucid/wire_support/configuration.rb +38 -0
  115. data/lib/lucid/wire_support/connection.rb +61 -0
  116. data/lib/lucid/wire_support/request_handler.rb +32 -0
  117. data/lib/lucid/wire_support/wire_exception.rb +32 -0
  118. data/lib/lucid/wire_support/wire_language.rb +54 -0
  119. data/lib/lucid/wire_support/wire_packet.rb +34 -0
  120. data/lib/lucid/wire_support/wire_protocol.rb +43 -0
  121. data/lib/lucid/wire_support/wire_protocol/requests.rb +125 -0
  122. data/lib/lucid/wire_support/wire_step_definition.rb +26 -0
  123. data/lucid.gemspec +25 -14
  124. metadata +220 -12
  125. data/lib/lucid/app.rb +0 -103
  126. data/lib/lucid/options.rb +0 -168
  127. data/lib/lucid/version.rb +0 -3
  128. data/lucid.yml +0 -8
@@ -0,0 +1,730 @@
1
+ require 'gherkin/rubify'
2
+ require 'gherkin/lexer/i18n_lexer'
3
+ require 'gherkin/formatter/escaping'
4
+
5
+ module Lucid
6
+ module AST
7
+ # Step Definitions that match a plain text Step with a multiline argument table
8
+ # will receive it as an instance of Table. A Table object holds the data of a
9
+ # table parsed from a feature file and lets you access and manipulate the data
10
+ # in different ways.
11
+ #
12
+ # For example:
13
+ #
14
+ # Given I have:
15
+ # | a | b |
16
+ # | c | d |
17
+ #
18
+ # And a matching StepDefinition:
19
+ #
20
+ # Given /I have:/ do |table|
21
+ # data = table.raw
22
+ # end
23
+ #
24
+ # This will store <tt>[['a', 'b'], ['c', 'd']]</tt> in the <tt>data</tt> variable.
25
+ #
26
+ class Table
27
+ class Different < StandardError
28
+ attr_reader :table
29
+
30
+ def initialize(table)
31
+ super('Tables were not identical')
32
+ @table = table
33
+ end
34
+ end
35
+
36
+ class Builder
37
+ attr_reader :rows
38
+
39
+ def initialize
40
+ @rows = []
41
+ end
42
+
43
+ def row(row, line_number)
44
+ @rows << row
45
+ end
46
+
47
+ def eof
48
+ end
49
+ end
50
+
51
+ include Enumerable
52
+ include Gherkin::Rubify
53
+
54
+ NULL_CONVERSIONS = Hash.new({ :strict => false, :proc => lambda{ |cell_value| cell_value } }).freeze
55
+
56
+ attr_accessor :file
57
+
58
+ def self.default_arg_name #:nodoc:
59
+ "table"
60
+ end
61
+
62
+ def self.parse(text, uri, offset)
63
+ builder = Builder.new
64
+ lexer = Gherkin::Lexer::I18nLexer.new(builder)
65
+ lexer.scan(text)
66
+ new(builder.rows)
67
+ end
68
+
69
+ # Creates a new instance. +raw+ should be an Array of Array of String
70
+ # or an Array of Hash (similar to what #hashes returns).
71
+ # You don't typically create your own Table objects - Lucid will do
72
+ # it internally and pass them to your test definitions.
73
+ #
74
+ def initialize(raw, conversion_procs = NULL_CONVERSIONS.dup, header_mappings = {}, header_conversion_proc = nil)
75
+ @cells_class = Cells
76
+ @cell_class = Cell
77
+ raw = ensure_array_of_array(rubify(raw))
78
+ # Verify that it's square
79
+ raw.transpose
80
+ create_cell_matrix(raw)
81
+ @conversion_procs = conversion_procs
82
+ @header_mappings = header_mappings
83
+ @header_conversion_proc = header_conversion_proc
84
+ end
85
+
86
+ def to_step_definition_arg
87
+ dup
88
+ end
89
+
90
+ # Creates a copy of this table, inheriting any column and header mappings
91
+ # registered with #map_column! and #map_headers!.
92
+ def dup
93
+ self.class.new(raw.dup, @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
94
+ end
95
+
96
+ # Returns a new, transposed table. Example:
97
+ #
98
+ # | a | 7 | 4 |
99
+ # | b | 9 | 2 |
100
+ #
101
+ # Gets converted into the following:
102
+ #
103
+ # | a | b |
104
+ # | 7 | 9 |
105
+ # | 4 | 2 |
106
+ #
107
+ def transpose
108
+ self.class.new(raw.transpose, @conversion_procs.dup, @header_mappings.dup, @header_conversion_proc)
109
+ end
110
+
111
+ # Converts this table into an Array of Hash where the keys of each
112
+ # Hash are the headers in the table. For example, a Table built from
113
+ # the following plain text:
114
+ #
115
+ # | a | b | sum |
116
+ # | 2 | 3 | 5 |
117
+ # | 7 | 9 | 16 |
118
+ #
119
+ # Gets converted into the following:
120
+ #
121
+ # [{'a' => '2', 'b' => '3', 'sum' => '5'}, {'a' => '7', 'b' => '9', 'sum' => '16'}]
122
+ #
123
+ # Use #map_column! to specify how values in a column are converted.
124
+ #
125
+ def hashes
126
+ @hashes ||= build_hashes
127
+ end
128
+
129
+ # Converts this table into a Hash where the first column is
130
+ # used as keys and the second column is used as values
131
+ #
132
+ # | a | 2 |
133
+ # | b | 3 |
134
+ #
135
+ # Gets converted into the following:
136
+ #
137
+ # {'a' => '2', 'b' => '3'}
138
+ #
139
+ # The table must be exactly two columns wide
140
+ #
141
+ def rows_hash
142
+ return @rows_hash if @rows_hash
143
+ verify_table_width(2)
144
+ @rows_hash = self.transpose.hashes[0]
145
+ end
146
+
147
+ # Gets the raw data of this table. For example, a Table built from
148
+ # the following plain text:
149
+ #
150
+ # | a | b |
151
+ # | c | d |
152
+ #
153
+ # gets converted into the following:
154
+ #
155
+ # [['a', 'b'], ['c', 'd']]
156
+ #
157
+ def raw
158
+ cell_matrix.map do |row|
159
+ row.map do |cell|
160
+ cell.value
161
+ end
162
+ end
163
+ end
164
+
165
+ def column_names #:nodoc:
166
+ @col_names ||= cell_matrix[0].map { |cell| cell.value }
167
+ end
168
+
169
+ def rows
170
+ hashes.map do |hash|
171
+ hash.values_at *headers
172
+ end
173
+ end
174
+
175
+ def each_cells_row(&proc) #:nodoc:
176
+ cells_rows.each(&proc)
177
+ end
178
+
179
+ def accept(visitor) #:nodoc:
180
+ return if Lucid.wants_to_quit
181
+ cells_rows.each do |row|
182
+ visitor.visit_table_row(row)
183
+ end
184
+ nil
185
+ end
186
+
187
+ # Matches +pattern+ against the header row of the table.
188
+ # This is used especially for argument transforms.
189
+ #
190
+ # Example:
191
+ # | column_1_name | column_2_name |
192
+ # | x | y |
193
+ #
194
+ # table.match(/table:column_1_name,column_2_name/) #=> non-nil
195
+ #
196
+ # Note: must use 'table:' prefix on match
197
+ def match(pattern)
198
+ header_to_match = "table:#{headers.join(',')}"
199
+ pattern.match(header_to_match)
200
+ end
201
+
202
+ # For testing only
203
+ def to_sexp #:nodoc:
204
+ [:table, *cells_rows.map{|row| row.to_sexp}]
205
+ end
206
+
207
+ # Redefines the table headers. This makes it possible to use
208
+ # prettier and more flexible header names in the features. The
209
+ # keys of +mappings+ are Strings or regular expressions
210
+ # (anything that responds to #=== will work) that may match
211
+ # column headings in the table. The values of +mappings+ are
212
+ # desired names for the columns.
213
+ #
214
+ # Example:
215
+ #
216
+ # | Phone Number | Address |
217
+ # | 123456 | xyz |
218
+ # | 345678 | abc |
219
+ #
220
+ # A StepDefinition receiving this table can then map the columns
221
+ # with both Regexp and String:
222
+ #
223
+ # table.map_headers!(/phone( number)?/i => :phone, 'Address' => :address)
224
+ # table.hashes
225
+ # # => [{:phone => '123456', :address => 'xyz'}, {:phone => '345678', :address => 'abc'}]
226
+ #
227
+ # You may also pass in a block if you wish to convert all of the headers:
228
+ #
229
+ # table.map_headers! { |header| header.downcase }
230
+ # table.hashes.keys
231
+ # # => ['phone number', 'address']
232
+ #
233
+ # When a block is passed in along with a hash then the mappings in the hash take precendence:
234
+ #
235
+ # table.map_headers!('Address' => 'ADDRESS') { |header| header.downcase }
236
+ # table.hashes.keys
237
+ # # => ['phone number', 'ADDRESS']
238
+ #
239
+ def map_headers!(mappings={}, &block)
240
+ clear_cache!
241
+ @header_mappings = mappings
242
+ @header_conversion_proc = block
243
+ end
244
+
245
+ # Returns a new Table where the headers are redefined. See #map_headers!
246
+ def map_headers(mappings={})
247
+ table = self.dup
248
+ table.map_headers!(mappings)
249
+ table
250
+ end
251
+
252
+ # Change how #hashes converts column values. The +column_name+ argument identifies the column
253
+ # and +conversion_proc+ performs the conversion for each cell in that column. If +strict+ is
254
+ # true, an error will be raised if the column named +column_name+ is not found. If +strict+
255
+ # is false, no error will be raised. Example:
256
+ #
257
+ # Given /^an expense report for (.*) with the following posts:$/ do |table|
258
+ # posts_table.map_column!('amount') { |a| a.to_i }
259
+ # posts_table.hashes.each do |post|
260
+ # # post['amount'] is a Fixnum, rather than a String
261
+ # end
262
+ # end
263
+ #
264
+ def map_column!(column_name, strict=true, &conversion_proc)
265
+ @conversion_procs[column_name.to_s] = { :strict => strict, :proc => conversion_proc }
266
+ self
267
+ end
268
+
269
+ # Compares +other_table+ to self. If +other_table+ contains columns
270
+ # and/or rows that are not in self, new columns/rows are added at the
271
+ # relevant positions, marking the cells in those rows/columns as
272
+ # <tt>surplus</tt>. Likewise, if +other_table+ lacks columns and/or
273
+ # rows that are present in self, these are marked as <tt>missing</tt>.
274
+ #
275
+ # <tt>surplus</tt> and <tt>missing</tt> cells are recognised by formatters
276
+ # and displayed so that it's easy to read the differences.
277
+ #
278
+ # Cells that are different, but <em>look</em> identical (for example the
279
+ # boolean true and the string "true") are converted to their Object#inspect
280
+ # representation and preceded with (i) - to make it easier to identify
281
+ # where the difference actually is.
282
+ #
283
+ # Since all tables that are passed to StepDefinitions always have String
284
+ # objects in their cells, you may want to use #map_column! before calling
285
+ # #diff!. You can use #map_column! on either of the tables.
286
+ #
287
+ # A Different error is raised if there are missing rows or columns, or
288
+ # surplus rows. An error is <em>not</em> raised for surplus columns. An
289
+ # error is <em>not</em> raised for misplaced (out of sequence) columns.
290
+ # Whether to raise or not raise can be changed by setting values in
291
+ # +options+ to true or false:
292
+ #
293
+ # * <tt>missing_row</tt> : Raise on missing rows (defaults to true)
294
+ # * <tt>surplus_row</tt> : Raise on surplus rows (defaults to true)
295
+ # * <tt>missing_col</tt> : Raise on missing columns (defaults to true)
296
+ # * <tt>surplus_col</tt> : Raise on surplus columns (defaults to false)
297
+ # * <tt>misplaced_col</tt> : Raise on misplaced columns (defaults to false)
298
+ #
299
+ # The +other_table+ argument can be another Table, an Array of Array or
300
+ # an Array of Hash (similar to the structure returned by #hashes).
301
+ #
302
+ # Calling this method is particularly useful in <tt>Then</tt> steps that take
303
+ # a Table argument, if you want to compare that table to some actual values.
304
+ #
305
+ def diff!(other_table, options={})
306
+ options = {
307
+ :missing_row => true,
308
+ :surplus_row => true,
309
+ :missing_col => true,
310
+ :surplus_col => false,
311
+ :misplaced_col => false
312
+ }.merge(options)
313
+
314
+ other_table = ensure_table(other_table)
315
+ other_table.convert_headers!
316
+ other_table.convert_columns!
317
+ ensure_green!
318
+
319
+ convert_headers!
320
+ convert_columns!
321
+
322
+ original_width = cell_matrix[0].length
323
+ other_table_cell_matrix = pad!(other_table.cell_matrix)
324
+ padded_width = cell_matrix[0].length
325
+
326
+ missing_col = cell_matrix[0].detect{|cell| cell.status == :undefined}
327
+ surplus_col = padded_width > original_width
328
+ misplaced_col = cell_matrix[0] != other_table.cell_matrix[0]
329
+
330
+ require_diff_lcs
331
+ cell_matrix.extend(Diff::LCS)
332
+ changes = cell_matrix.diff(other_table_cell_matrix).flatten
333
+
334
+ inserted = 0
335
+ missing = 0
336
+
337
+ row_indices = Array.new(other_table_cell_matrix.length) {|n| n}
338
+
339
+ last_change = nil
340
+ missing_row_pos = nil
341
+ insert_row_pos = nil
342
+
343
+ changes.each do |change|
344
+ if(change.action == '-')
345
+ missing_row_pos = change.position + inserted
346
+ cell_matrix[missing_row_pos].each{|cell| cell.status = :undefined}
347
+ row_indices.insert(missing_row_pos, nil)
348
+ missing += 1
349
+ else # '+'
350
+ insert_row_pos = change.position + missing
351
+ inserted_row = change.element
352
+ inserted_row.each{|cell| cell.status = :comment}
353
+ cell_matrix.insert(insert_row_pos, inserted_row)
354
+ row_indices[insert_row_pos] = nil
355
+ inspect_rows(cell_matrix[missing_row_pos], inserted_row) if last_change && last_change.action == '-'
356
+ inserted += 1
357
+ end
358
+ last_change = change
359
+ end
360
+
361
+ other_table_cell_matrix.each_with_index do |other_row, i|
362
+ row_index = row_indices.index(i)
363
+ row = cell_matrix[row_index] if row_index
364
+ if row
365
+ (original_width..padded_width).each do |col_index|
366
+ surplus_cell = other_row[col_index]
367
+ row[col_index].value = surplus_cell.value if row[col_index]
368
+ end
369
+ end
370
+ end
371
+
372
+ clear_cache!
373
+ should_raise =
374
+ missing_row_pos && options[:missing_row] ||
375
+ insert_row_pos && options[:surplus_row] ||
376
+ missing_col && options[:missing_col] ||
377
+ surplus_col && options[:surplus_col] ||
378
+ misplaced_col && options[:misplaced_col]
379
+ raise Different.new(self) if should_raise
380
+ end
381
+
382
+ def to_hash(cells) #:nodoc:
383
+ hash = Hash.new do |hash, key|
384
+ hash[key.to_s] if key.is_a?(Symbol)
385
+ end
386
+ column_names.each_with_index do |column_name, column_index|
387
+ hash[column_name] = cells.value(column_index)
388
+ end
389
+ hash
390
+ end
391
+
392
+ def index(cells) #:nodoc:
393
+ cells_rows.index(cells)
394
+ end
395
+
396
+ def verify_column(column_name) #:nodoc:
397
+ raise %{The column named "#{column_name}" does not exist} unless raw[0].include?(column_name)
398
+ end
399
+
400
+ def verify_table_width(width) #:nodoc:
401
+ raise %{The table must have exactly #{width} columns} unless raw[0].size == width
402
+ end
403
+
404
+ def arguments_replaced(arguments) #:nodoc:
405
+ raw_with_replaced_args = raw.map do |row|
406
+ row.map do |cell|
407
+ cell_with_replaced_args = cell
408
+ arguments.each do |name, value|
409
+ if cell_with_replaced_args && cell_with_replaced_args.include?(name)
410
+ cell_with_replaced_args = value ? cell_with_replaced_args.gsub(name, value) : nil
411
+ end
412
+ end
413
+ cell_with_replaced_args
414
+ end
415
+ end
416
+ Table.new(raw_with_replaced_args)
417
+ end
418
+
419
+ def has_text?(text) #:nodoc:
420
+ raw.flatten.compact.detect{|cell_value| cell_value.index(text)}
421
+ end
422
+
423
+ def cells_rows #:nodoc:
424
+ @rows ||= cell_matrix.map do |cell_row|
425
+ @cells_class.new(self, cell_row)
426
+ end
427
+ end
428
+
429
+ def headers #:nodoc:
430
+ raw.first
431
+ end
432
+
433
+ def header_cell(col) #:nodoc:
434
+ cells_rows[0][col]
435
+ end
436
+
437
+ def cell_matrix #:nodoc:
438
+ @cell_matrix
439
+ end
440
+
441
+ def col_width(col) #:nodoc:
442
+ columns[col].__send__(:width)
443
+ end
444
+
445
+ def to_s(options = {}) #:nodoc:
446
+ require 'lucid/formatter/standard'
447
+ options = {:color => true, :indent => 2, :prefixes => TO_S_PREFIXES}.merge(options)
448
+ io = StringIO.new
449
+
450
+ c = Lucid::Term::ANSIColor.coloring?
451
+ Lucid::Term::ANSIColor.coloring = options[:color]
452
+ formatter = Formatter::Standard.new(nil, io, options)
453
+ formatter.instance_variable_set('@indent', options[:indent])
454
+ TDLWalker.new(nil, [formatter]).visit_multiline_arg(self)
455
+
456
+ Lucid::Term::ANSIColor.coloring = c
457
+ io.rewind
458
+ s = "\n" + io.read + (" " * (options[:indent] - 2))
459
+ s
460
+ end
461
+
462
+ private
463
+
464
+ TO_S_PREFIXES = Hash.new(' ')
465
+ TO_S_PREFIXES[:comment] = '(+) '
466
+ TO_S_PREFIXES[:undefined] = '(-) '
467
+
468
+ protected
469
+
470
+ def build_hashes
471
+ convert_headers!
472
+ convert_columns!
473
+ cells_rows[1..-1].map do |row|
474
+ row.to_hash
475
+ end
476
+ end
477
+
478
+ def inspect_rows(missing_row, inserted_row) #:nodoc:
479
+ missing_row.each_with_index do |missing_cell, col|
480
+ inserted_cell = inserted_row[col]
481
+ if(missing_cell.value != inserted_cell.value && (missing_cell.value.to_s == inserted_cell.value.to_s))
482
+ missing_cell.inspect!
483
+ inserted_cell.inspect!
484
+ end
485
+ end
486
+ end
487
+
488
+ def create_cell_matrix(raw) #:nodoc:
489
+ @cell_matrix = raw.map do |raw_row|
490
+ line = raw_row.line rescue -1
491
+ raw_row.map do |raw_cell|
492
+ new_cell(raw_cell, line)
493
+ end
494
+ end
495
+ end
496
+
497
+ def convert_columns! #:nodoc:
498
+ @conversion_procs.each do |column_name, conversion_proc|
499
+ verify_column(column_name) if conversion_proc[:strict]
500
+ end
501
+
502
+ cell_matrix.transpose.each do |col|
503
+ column_name = col[0].value
504
+ conversion_proc = @conversion_procs[column_name][:proc]
505
+ col[1..-1].each do |cell|
506
+ cell.value = conversion_proc.call(cell.value)
507
+ end
508
+ end
509
+ end
510
+
511
+ def convert_headers! #:nodoc:
512
+ header_cells = cell_matrix[0]
513
+
514
+ if @header_conversion_proc
515
+ header_values = header_cells.map { |cell| cell.value } - @header_mappings.keys
516
+ @header_mappings = @header_mappings.merge(Hash[*header_values.zip(header_values.map(&@header_conversion_proc)).flatten])
517
+ end
518
+
519
+ @header_mappings.each_pair do |pre, post|
520
+ mapped_cells = header_cells.select { |cell| pre === cell.value }
521
+ raise "No headers matched #{pre.inspect}" if mapped_cells.empty?
522
+ raise "#{mapped_cells.length} headers matched #{pre.inspect}: #{mapped_cells.map { |c| c.value }.inspect}" if mapped_cells.length > 1
523
+ mapped_cells[0].value = post
524
+ if @conversion_procs.has_key?(pre)
525
+ @conversion_procs[post] = @conversion_procs.delete(pre)
526
+ end
527
+ end
528
+ end
529
+
530
+ def require_diff_lcs #:nodoc:
531
+ begin
532
+ require 'diff/lcs'
533
+ rescue LoadError => e
534
+ e.message << "\n Please gem install diff-lcs\n"
535
+ raise e
536
+ end
537
+ end
538
+
539
+ def clear_cache! #:nodoc:
540
+ @hashes = @rows_hash = @col_names = @rows = @columns = nil
541
+ end
542
+
543
+ def columns #:nodoc:
544
+ @columns ||= cell_matrix.transpose.map do |cell_row|
545
+ @cells_class.new(self, cell_row)
546
+ end
547
+ end
548
+
549
+ def new_cell(raw_cell, line) #:nodoc:
550
+ @cell_class.new(raw_cell, self, line)
551
+ end
552
+
553
+ # Pads our own cell_matrix and returns a cell matrix of same
554
+ # column width that can be used for diffing
555
+ def pad!(other_cell_matrix) #:nodoc:
556
+ clear_cache!
557
+ cols = cell_matrix.transpose
558
+ unmapped_cols = other_cell_matrix.transpose
559
+
560
+ mapped_cols = []
561
+
562
+ cols.each_with_index do |col, col_index|
563
+ header = col[0]
564
+ candidate_cols, unmapped_cols = unmapped_cols.partition do |other_col|
565
+ other_col[0] == header
566
+ end
567
+ raise "More than one column has the header #{header}" if candidate_cols.size > 2
568
+
569
+ other_padded_col = if candidate_cols.size == 1
570
+ # Found a matching column
571
+ candidate_cols[0]
572
+ else
573
+ mark_as_missing(cols[col_index])
574
+ (0...other_cell_matrix.length).map do |row|
575
+ val = row == 0 ? header.value : nil
576
+ SurplusCell.new(val, self, -1)
577
+ end
578
+ end
579
+ mapped_cols.insert(col_index, other_padded_col)
580
+ end
581
+
582
+ unmapped_cols.each_with_index do |col, col_index|
583
+ empty_col = (0...cell_matrix.length).map do |row|
584
+ SurplusCell.new(nil, self, -1)
585
+ end
586
+ cols << empty_col
587
+ end
588
+
589
+ @cell_matrix = cols.transpose
590
+ (mapped_cols + unmapped_cols).transpose
591
+ end
592
+
593
+ def ensure_table(table_or_array) #:nodoc:
594
+ return table_or_array if Table === table_or_array
595
+ Table.new(table_or_array)
596
+ end
597
+
598
+ def ensure_array_of_array(array)
599
+ Hash === array[0] ? hashes_to_array(array) : array
600
+ end
601
+
602
+ def hashes_to_array(hashes) #:nodoc:
603
+ header = hashes[0].keys.sort
604
+ [header] + hashes.map{|hash| header.map{|key| hash[key]}}
605
+ end
606
+
607
+ def ensure_green! #:nodoc:
608
+ each_cell{|cell| cell.status = :passed}
609
+ end
610
+
611
+ def each_cell(&proc) #:nodoc:
612
+ cell_matrix.each{|row| row.each(&proc)}
613
+ end
614
+
615
+ def mark_as_missing(col) #:nodoc:
616
+ col.each do |cell|
617
+ cell.status = :undefined
618
+ end
619
+ end
620
+
621
+ # Represents a row of cells or columns of cells
622
+ class Cells #:nodoc:
623
+ include Enumerable
624
+ include Gherkin::Formatter::Escaping
625
+
626
+ attr_reader :exception
627
+
628
+ def initialize(table, cells)
629
+ @table, @cells = table, cells
630
+ end
631
+
632
+ def accept(visitor)
633
+ return if Lucid.wants_to_quit
634
+ each do |cell|
635
+ visitor.visit_table_cell(cell)
636
+ end
637
+ nil
638
+ end
639
+
640
+ # For testing only
641
+ def to_sexp #:nodoc:
642
+ [:row, line, *@cells.map{|cell| cell.to_sexp}]
643
+ end
644
+
645
+ def to_hash #:nodoc:
646
+ @to_hash ||= @table.to_hash(self)
647
+ end
648
+
649
+ def value(n) #:nodoc:
650
+ self[n].value
651
+ end
652
+
653
+ def [](n)
654
+ @cells[n]
655
+ end
656
+
657
+ def line
658
+ @cells[0].line
659
+ end
660
+
661
+ def dom_id
662
+ "row_#{line}"
663
+ end
664
+
665
+ private
666
+
667
+ def index
668
+ @table.index(self)
669
+ end
670
+
671
+ def width
672
+ map{|cell| cell.value ? escape_cell(cell.value.to_s).unpack('U*').length : 0}.max
673
+ end
674
+
675
+ def each(&proc)
676
+ @cells.each(&proc)
677
+ end
678
+ end
679
+
680
+ class Cell #:nodoc:
681
+ attr_reader :line, :table
682
+ attr_accessor :status, :value
683
+
684
+ def initialize(value, table, line)
685
+ @value, @table, @line = value, table, line
686
+ end
687
+
688
+ def accept(visitor)
689
+ return if Lucid.wants_to_quit
690
+ visitor.visit_table_cell_value(value, status)
691
+ end
692
+
693
+ def inspect!
694
+ @value = "(i) #{value.inspect}"
695
+ end
696
+
697
+ def ==(o)
698
+ SurplusCell === o || value == o.value
699
+ end
700
+
701
+ def eql?(o)
702
+ self == o
703
+ end
704
+
705
+ def hash
706
+ 0
707
+ end
708
+
709
+ # For testing only
710
+ def to_sexp #:nodoc:
711
+ [:cell, @value]
712
+ end
713
+ end
714
+
715
+ class SurplusCell < Cell #:nodoc:
716
+ def status
717
+ :comment
718
+ end
719
+
720
+ def ==(o)
721
+ true
722
+ end
723
+
724
+ def hash
725
+ 0
726
+ end
727
+ end
728
+ end
729
+ end
730
+ end