kosmas58-cucumber 0.3.102 → 0.3.103

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 (39) hide show
  1. data/History.txt +8 -2
  2. data/Manifest.txt +6 -1
  3. data/examples/java/README.textile +3 -3
  4. data/examples/self_test/features/sample.feature +1 -1
  5. data/examples/self_test/features/search_sample.feature +1 -1
  6. data/features/custom_formatter.feature +4 -4
  7. data/lib/cucumber/ast.rb +1 -0
  8. data/lib/cucumber/ast/table.rb +4 -4
  9. data/lib/cucumber/ast/visitor.rb +2 -106
  10. data/lib/cucumber/cli/configuration.rb +28 -28
  11. data/lib/cucumber/cli/language_help_formatter.rb +5 -7
  12. data/lib/cucumber/cli/main.rb +3 -3
  13. data/lib/cucumber/formatter/html.rb +203 -113
  14. data/lib/cucumber/formatter/junit.rb +29 -23
  15. data/lib/cucumber/formatter/pdf.rb +74 -69
  16. data/lib/cucumber/formatter/pretty.rb +93 -78
  17. data/lib/cucumber/formatter/profile.rb +2 -2
  18. data/lib/cucumber/formatter/progress.rb +16 -10
  19. data/lib/cucumber/formatter/rerun.rb +4 -5
  20. data/lib/cucumber/formatter/steps.rb +2 -3
  21. data/lib/cucumber/formatter/tag_cloud.rb +7 -6
  22. data/lib/cucumber/formatter/usage.rb +4 -7
  23. data/lib/cucumber/language_support/step_definition_methods.rb +2 -2
  24. data/lib/cucumber/rb_support/rb_language.rb +5 -0
  25. data/lib/cucumber/rb_support/rb_step_definition.rb +3 -3
  26. data/lib/cucumber/rb_support/regexp_argument_matcher.rb +21 -0
  27. data/lib/cucumber/step_argument.rb +9 -0
  28. data/lib/cucumber/step_match.rb +12 -14
  29. data/lib/cucumber/version.rb +1 -1
  30. data/spec/cucumber/ast/background_spec.rb +1 -2
  31. data/spec/cucumber/ast/scenario_outline_spec.rb +3 -2
  32. data/spec/cucumber/ast/scenario_spec.rb +1 -1
  33. data/spec/cucumber/formatter/html_spec.rb +221 -2
  34. data/spec/cucumber/formatter/progress_spec.rb +9 -4
  35. data/spec/cucumber/parser/feature_parser_spec.rb +31 -27
  36. data/spec/cucumber/rb_support/regexp_argument_matcher_spec.rb +18 -0
  37. data/spec/cucumber/step_match_spec.rb +40 -0
  38. metadata +6 -3
  39. data/lib/cucumber/rb_support/rb_group.rb +0 -32
data/History.txt CHANGED
@@ -1,4 +1,4 @@
1
- == (In Git)
1
+ == 2009-09-24
2
2
 
3
3
  This release gives you back some of the control over the Rails environment that was accidentally taken away from you in the
4
4
  previous release.
@@ -6,10 +6,16 @@ previous release.
6
6
  Using this release on a Rails project requires a rerun of script/generate cucumber.
7
7
 
8
8
  === New Features
9
- * Added a new @no-txn tag to selectively turn off transactions for a particluar scenario.
9
+ * Added a new @no-txn tag to selectively turn off transactions for a particlular scenario.
10
10
  * Added back a way to globally turn off transactions.
11
11
  * Renamed @allow_rescue tag to @allow-rescue.
12
12
 
13
+ === Bugfixes
14
+ * Gracefully handle cases when optional regexp groups are not matched. Ex: /should( not)? be flashed '([^']*?)'$/ (Aslak Hellesøy)
15
+
16
+ === Changed Features
17
+ * The Formatter API has completely changed. Formatters are no longer a double-dispacth visitor - just a single-dispatch listener (#438 Matt Wynne)
18
+
13
19
  == 2009-09-22
14
20
 
15
21
  This release has some changes in the Rails support, so make sure you run "script/generate cucumber" after you upgrade.
data/Manifest.txt CHANGED
@@ -329,6 +329,7 @@ lib/cucumber/ast/step_collection.rb
329
329
  lib/cucumber/ast/step_invocation.rb
330
330
  lib/cucumber/ast/table.rb
331
331
  lib/cucumber/ast/tags.rb
332
+ lib/cucumber/ast/tree_walker.rb
332
333
  lib/cucumber/ast/visitor.rb
333
334
  lib/cucumber/broadcaster.rb
334
335
  lib/cucumber/cli/configuration.rb
@@ -385,13 +386,14 @@ lib/cucumber/rails/test_unit.rb
385
386
  lib/cucumber/rails/world.rb
386
387
  lib/cucumber/rake/task.rb
387
388
  lib/cucumber/rb_support/rb_dsl.rb
388
- lib/cucumber/rb_support/rb_group.rb
389
389
  lib/cucumber/rb_support/rb_hook.rb
390
390
  lib/cucumber/rb_support/rb_language.rb
391
391
  lib/cucumber/rb_support/rb_step_definition.rb
392
392
  lib/cucumber/rb_support/rb_transform.rb
393
393
  lib/cucumber/rb_support/rb_world.rb
394
+ lib/cucumber/rb_support/regexp_argument_matcher.rb
394
395
  lib/cucumber/rspec_neuter.rb
396
+ lib/cucumber/step_argument.rb
395
397
  lib/cucumber/step_match.rb
396
398
  lib/cucumber/step_mother.rb
397
399
  lib/cucumber/version.rb
@@ -422,6 +424,7 @@ spec/cucumber/ast/scenario_spec.rb
422
424
  spec/cucumber/ast/step_collection_spec.rb
423
425
  spec/cucumber/ast/step_spec.rb
424
426
  spec/cucumber/ast/table_spec.rb
427
+ spec/cucumber/ast/tree_walker_spec.rb
425
428
  spec/cucumber/broadcaster_spec.rb
426
429
  spec/cucumber/cli/configuration_spec.rb
427
430
  spec/cucumber/cli/drb_client_spec.rb
@@ -437,7 +440,9 @@ spec/cucumber/formatter/progress_spec.rb
437
440
  spec/cucumber/parser/feature_parser_spec.rb
438
441
  spec/cucumber/parser/table_parser_spec.rb
439
442
  spec/cucumber/rb_support/rb_step_definition_spec.rb
443
+ spec/cucumber/rb_support/regexp_argument_matcher_spec.rb
440
444
  spec/cucumber/sell_cucumbers.feature
445
+ spec/cucumber/step_match_spec.rb
441
446
  spec/cucumber/step_mother_spec.rb
442
447
  spec/cucumber/treetop_parser/empty_feature.feature
443
448
  spec/cucumber/treetop_parser/empty_scenario.feature
@@ -11,8 +11,8 @@ h2. Running the scenarios
11
11
 
12
12
  Open a shell in this directory (java) and execute the following command:
13
13
 
14
- <pre><code>
15
- ant
16
- </code></pre>
14
+ <pre>ant</pre>
17
15
 
18
16
  There is a deliberate error. See if you can fix it!
17
+
18
+ Also see "Cuke4Duke":http://wiki.github.com/aslakhellesoy/cuke4duke for more powerful JVM and Java support for Cucumber.
@@ -12,7 +12,7 @@ Feature: Sample
12
12
  Given passing
13
13
  |a|b|
14
14
  |c|d|
15
-
15
+
16
16
  @four
17
17
  Scenario: Failing
18
18
  Given failing
@@ -20,7 +20,7 @@ Feature: search examples
20
20
  Examples:
21
21
  | state |
22
22
  | passing |
23
-
23
+
24
24
  Scenario Outline: no match in name but in examples
25
25
  Given <state> without a table
26
26
  Examples: Hantu Pisang
@@ -8,7 +8,7 @@ Feature: Custom Formatter
8
8
  | 1 | 1 | 1 | 1 | 1 | 2 | 1 | 2 | 1 | 2 | 1 |
9
9
 
10
10
  """
11
-
11
+
12
12
  Scenario: my own formatter
13
13
  Given a standard Cucumber project directory structure
14
14
  And a file named "features/f.feature" with:
@@ -25,13 +25,13 @@ Feature: Custom Formatter
25
25
  And a file named "features/support/ze/formator.rb" with:
26
26
  """
27
27
  module Ze
28
- class Formator < Cucumber::Ast::Visitor
28
+ class Formator
29
29
  def initialize(step_mother, io, options)
30
- super(step_mother)
30
+ @step_mother = step_mother
31
31
  @io = io
32
32
  end
33
33
 
34
- def visit_scenario_name(keyword, name, file_colon_line, source_indent)
34
+ def scenario_name(keyword, name, file_colon_line, source_indent)
35
35
  @io.puts "$ #{name.upcase}"
36
36
  end
37
37
  end
data/lib/cucumber/ast.rb CHANGED
@@ -13,6 +13,7 @@ require 'cucumber/ast/py_string'
13
13
  require 'cucumber/ast/outline_table'
14
14
  require 'cucumber/ast/examples'
15
15
  require 'cucumber/ast/visitor'
16
+ require 'cucumber/ast/tree_walker'
16
17
 
17
18
  module Cucumber
18
19
  # Classes in this module represent the Abstract Syntax Tree (AST)
@@ -389,11 +389,11 @@ module Cucumber
389
389
 
390
390
  c = Term::ANSIColor.coloring?
391
391
  Term::ANSIColor.coloring = options[:color]
392
- f = Formatter::Pretty.new(nil, io, options)
393
- f.instance_variable_set('@indent', options[:indent])
394
- f.visit_multiline_arg(self)
392
+ formatter = Formatter::Pretty.new(nil, io, options)
393
+ formatter.instance_variable_set('@indent', options[:indent])
394
+ TreeWalker.new(nil, [formatter]).visit_multiline_arg(self)
395
+
395
396
  Term::ANSIColor.coloring = c
396
-
397
397
  io.rewind
398
398
  s = "\n" + io.read + (" " * (options[:indent] - 2))
399
399
  s
@@ -1,115 +1,11 @@
1
1
  module Cucumber
2
2
  module Ast
3
- # Base class for formatters. This class just walks the tree depth first.
4
- # Just override the methods you care about. Remember to call super if you
5
- # override a method.
6
3
  class Visitor
7
- attr_accessor :options #:nodoc:
8
- attr_reader :step_mother #:nodoc:
4
+ DEPRECATION_WARNING = "Cucumber::Ast::Visitor is deprecated and will be removed. You no longer need to inherit from this class."
9
5
 
10
6
  def initialize(step_mother)
11
- @options = {}
12
- @step_mother = step_mother
7
+ raise(DEPRECATION_WARNING)
13
8
  end
14
-
15
- def visit_features(features)
16
- features.accept(self)
17
- end
18
-
19
- def visit_feature(feature)
20
- feature.accept(self)
21
- end
22
-
23
- def visit_comment(comment)
24
- comment.accept(self)
25
- end
26
-
27
- def visit_comment_line(comment_line)
28
- end
29
-
30
- def visit_tags(tags)
31
- tags.accept(self)
32
- end
33
-
34
- def visit_tag_name(tag_name)
35
- end
36
-
37
- def visit_feature_name(name)
38
- end
39
-
40
- # +feature_element+ is either Scenario or ScenarioOutline
41
- def visit_feature_element(feature_element)
42
- feature_element.accept(self)
43
- end
44
-
45
- def visit_background(background)
46
- background.accept(self)
47
- end
48
-
49
- def visit_background_name(keyword, name, file_colon_line, source_indent)
50
- end
51
-
52
- def visit_examples_array(examples_array)
53
- examples_array.accept(self)
54
- end
55
-
56
- def visit_examples(examples)
57
- examples.accept(self)
58
- end
59
-
60
- def visit_examples_name(keyword, name)
61
- end
62
-
63
- def visit_outline_table(outline_table)
64
- @table = outline_table
65
- outline_table.accept(self)
66
- end
67
-
68
- def visit_scenario_name(keyword, name, file_colon_line, source_indent)
69
- end
70
-
71
- def visit_steps(steps)
72
- steps.accept(self)
73
- end
74
-
75
- def visit_step(step)
76
- step.accept(self)
77
- end
78
-
79
- def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
80
- visit_step_name(keyword, step_match, status, source_indent, background)
81
- visit_multiline_arg(multiline_arg) if multiline_arg
82
- visit_exception(exception, status) if exception
83
- end
84
-
85
- def visit_step_name(keyword, step_match, status, source_indent, background) #:nodoc:
86
- end
87
-
88
- def visit_multiline_arg(multiline_arg) #:nodoc:
89
- multiline_arg.accept(self)
90
- end
91
-
92
- def visit_exception(exception, status) #:nodoc:
93
- end
94
-
95
- def visit_py_string(string)
96
- end
97
-
98
- def visit_table_row(table_row)
99
- table_row.accept(self)
100
- end
101
-
102
- def visit_table_cell(table_cell)
103
- table_cell.accept(self)
104
- end
105
-
106
- def visit_table_cell_value(value, status)
107
- end
108
-
109
- # Print +announcement+. This method can be called from within StepDefinitions.
110
- def announce(announcement)
111
- end
112
-
113
9
  end
114
10
  end
115
11
  end
@@ -57,34 +57,9 @@ module Cucumber
57
57
  def drb_port
58
58
  @options[:drb_port].to_i if @options[:drb_port]
59
59
  end
60
-
61
- def build_formatter_broadcaster(step_mother)
62
- return Formatter::Pretty.new(step_mother, nil, @options) if @options[:autoformat]
63
- formatters = @options[:formats].map do |format_and_out|
64
- format = format_and_out[0]
65
- out = format_and_out[1]
66
- if String === out # file name
67
- unless File.directory?(out)
68
- out = File.open(out, Cucumber.file_mode('w'))
69
- at_exit do
70
- out.flush
71
- out.close
72
- end
73
- end
74
- end
75
-
76
- begin
77
- formatter_class = formatter_class(format)
78
- formatter_class.new(step_mother, out, @options)
79
- rescue Exception => e
80
- e.message << "\nError creating formatter: #{format}"
81
- raise e
82
- end
83
- end
84
-
85
- broadcaster = Broadcaster.new(formatters)
86
- broadcaster.options = @options
87
- return broadcaster
60
+
61
+ def build_runner(step_mother, io)
62
+ Ast::TreeWalker.new(step_mother, formatters(step_mother), @options, io)
88
63
  end
89
64
 
90
65
  def formatter_class(format)
@@ -150,6 +125,31 @@ module Cucumber
150
125
  end
151
126
 
152
127
  private
128
+
129
+ def formatters(step_mother)
130
+ return [Formatter::Pretty.new(step_mother, nil, @options)] if @options[:autoformat]
131
+ @options[:formats].map do |format_and_out|
132
+ format = format_and_out[0]
133
+ out = format_and_out[1]
134
+ if String === out # file name
135
+ unless File.directory?(out)
136
+ out = File.open(out, Cucumber.file_mode('w'))
137
+ at_exit do
138
+ out.flush
139
+ out.close
140
+ end
141
+ end
142
+ end
143
+
144
+ begin
145
+ formatter_class = formatter_class(format)
146
+ formatter_class.new(step_mother, out, @options)
147
+ rescue Exception => e
148
+ e.message << "\nError creating formatter: #{format}"
149
+ raise e
150
+ end
151
+ end
152
+ end
153
153
 
154
154
  class LogFormatter < ::Logger::Formatter
155
155
  def call(severity, time, progname, msg)
@@ -19,7 +19,8 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
19
19
  [lang, Cucumber::LANGUAGES[lang]['name'], Cucumber::LANGUAGES[lang]['native']]
20
20
  end
21
21
  table = Ast::Table.new(raw)
22
- new(nil, io, {:check_lang=>true}).visit_multiline_arg(table)
22
+ formatter = new(nil, io, {:check_lang=>true})
23
+ Ast::TreeWalker.new(nil, [formatter]).visit_multiline_arg(table)
23
24
  end
24
25
 
25
26
  def self.list_keywords(io, lang)
@@ -31,19 +32,17 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
31
32
  new(nil, io, {:incomplete => language.incomplete?}).visit_multiline_arg(table)
32
33
  end
33
34
 
34
- def visit_multiline_arg(table)
35
+ def before_visit_multiline_arg(table)
35
36
  if @options[:incomplete]
36
37
  @io.puts(format_string(INCOMPLETE, :failed))
37
38
  end
38
- super
39
39
  end
40
40
 
41
- def visit_table_row(table_row)
41
+ def before_visit_table_row(table_row)
42
42
  @col = 1
43
- super
44
43
  end
45
44
 
46
- def visit_table_cell_value(value, status)
45
+ def before_visit_table_cell_value(value, status)
47
46
  if @col == 1
48
47
  if(@options[:check_lang])
49
48
  @incomplete = Parser::NaturalLanguage.get(nil, value).incomplete?
@@ -54,7 +53,6 @@ http://wiki.github.com/aslakhellesoy/cucumber/spoken-languages
54
53
  end
55
54
 
56
55
  @col += 1
57
- super(value, status)
58
56
  end
59
57
  end
60
58
  end
@@ -50,9 +50,9 @@ module Cucumber
50
50
 
51
51
  enable_diffing
52
52
 
53
- visitor = configuration.build_formatter_broadcaster(step_mother)
54
- step_mother.visitor = visitor # Needed to support World#announce
55
- visitor.visit_features(features)
53
+ runner = configuration.build_runner(step_mother, @out_stream)
54
+ step_mother.visitor = runner # Needed to support World#announce
55
+ runner.visit_features(features)
56
56
 
57
57
  failure = if exceeded_tag_limts?(features)
58
58
  FAILURE
@@ -1,214 +1,278 @@
1
1
  require 'cucumber/formatter/ordered_xml_markup'
2
2
  require 'cucumber/formatter/duration'
3
+ require 'xml'
4
+ require 'ruby-debug'
3
5
 
4
6
  module Cucumber
5
7
  module Formatter
6
8
  # The formatter used for <tt>--format html</tt>
7
- class Html < Ast::Visitor
9
+ class Html
8
10
  include ERB::Util # for the #h method
9
11
  include Duration
10
12
 
11
13
  def initialize(step_mother, io, options)
12
- super(step_mother)
14
+ @io = io
13
15
  @options = options
14
- @builder = create_builder(io)
16
+ @buffer = {}
17
+ @current_builder = create_builder(@io)
15
18
  end
16
19
 
17
- def create_builder(io)
18
- OrderedXmlMarkup.new(:target => io, :indent => 0)
20
+ def before_features(features)
21
+ start_buffering :features
19
22
  end
20
23
 
21
- def visit_features(features)
24
+ def after_features(features)
25
+ stop_buffering :features
22
26
  # <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
23
- @builder.declare!(
27
+ builder.declare!(
24
28
  :DOCTYPE,
25
29
  :html,
26
30
  :PUBLIC,
27
31
  '-//W3C//DTD XHTML 1.0 Strict//EN',
28
32
  'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
29
33
  )
30
- @builder.html(:xmlns => 'http://www.w3.org/1999/xhtml') do
31
- @builder.head do
32
- @builder.meta(:content => 'text/html;charset=utf-8')
33
- @builder.title 'Cucumber'
34
+ builder.html(:xmlns => 'http://www.w3.org/1999/xhtml') do
35
+ builder.head do
36
+ builder.meta(:content => 'text/html;charset=utf-8')
37
+ builder.title 'Cucumber'
34
38
  inline_css
35
39
  end
36
- @builder.body do
37
- @builder.div(:class => 'cucumber') do
38
- super
39
- @builder.div(format_duration(features.duration), :class => 'duration')
40
+ builder.body do
41
+ builder.div(:class => 'cucumber') do
42
+ builder << buffer(:features)
43
+ builder.div(format_duration(features.duration), :class => 'duration')
40
44
  end
41
45
  end
42
46
  end
43
47
  end
44
-
45
- def visit_comment(comment)
46
- @builder.pre(:class => 'comment') do
47
- super
48
+
49
+ def before_feature(feature)
50
+ start_buffering :feature
51
+ @exceptions = []
52
+ end
53
+
54
+ def after_feature(feature)
55
+ stop_buffering :feature
56
+ builder.div(:class => 'feature') do
57
+ builder << buffer(:feature)
48
58
  end
49
59
  end
50
60
 
51
- def visit_comment_line(comment_line)
52
- @builder.text!(comment_line)
53
- @builder.br
61
+ def before_comment(comment)
62
+ start_buffering :comment
54
63
  end
55
64
 
56
- def visit_feature(feature)
57
- @exceptions = []
58
- @builder.div(:class => 'feature') do
59
- super
65
+ def after_comment(comment)
66
+ stop_buffering :comment
67
+ builder.pre(:class => 'comment') do
68
+ builder << buffer(:comment)
60
69
  end
61
70
  end
62
71
 
63
- def visit_tags(tags)
64
- super
72
+ def comment_line(comment_line)
73
+ builder.text!(comment_line)
74
+ builder.br
75
+ end
76
+
77
+ def after_tags(tags)
65
78
  @tag_spacer = nil
66
79
  end
67
-
68
- def visit_tag_name(tag_name)
69
- @builder.text!(@tag_spacer) if @tag_spacer
80
+
81
+ def tag_name(tag_name)
82
+ builder.text!(@tag_spacer) if @tag_spacer
70
83
  @tag_spacer = ' '
71
- @builder.span(tag_name, :class => 'tag')
84
+ builder.span(tag_name, :class => 'tag')
72
85
  end
73
86
 
74
- def visit_feature_name(name)
87
+ def feature_name(name)
75
88
  lines = name.split(/\r?\n/)
76
89
  return if lines.empty?
77
- @builder.h2 do |h2|
78
- @builder.span(lines[0], :class => 'val')
90
+ builder.h2 do |h2|
91
+ builder.span(lines[0], :class => 'val')
79
92
  end
80
- @builder.p(:class => 'narrative') do
93
+ builder.p(:class => 'narrative') do
81
94
  lines[1..-1].each do |line|
82
- @builder.text!(line.strip)
83
- @builder.br
95
+ builder.text!(line.strip)
96
+ builder.br
84
97
  end
85
98
  end
86
99
  end
87
100
 
88
- def visit_background(background)
89
- @builder.div(:class => 'background') do
90
- @in_background = true
91
- super
92
- @in_background = nil
101
+ def before_background(background)
102
+ @in_background = true
103
+ start_buffering :background
104
+ end
105
+
106
+ def after_background(background)
107
+ stop_buffering :background
108
+ @in_background = nil
109
+ builder.div(:class => 'background') do
110
+ builder << buffer(:background)
93
111
  end
94
112
  end
95
113
 
96
- def visit_background_name(keyword, name, file_colon_line, source_indent)
114
+ def background_name(keyword, name, file_colon_line, source_indent)
97
115
  @listing_background = true
98
- @builder.h3 do |h3|
99
- @builder.span(keyword, :class => 'keyword')
100
- @builder.text!(' ')
101
- @builder.span(name, :class => 'val')
116
+ builder.h3 do |h3|
117
+ builder.span(keyword, :class => 'keyword')
118
+ builder.text!(' ')
119
+ builder.span(name, :class => 'val')
102
120
  end
103
121
  end
104
122
 
105
- def visit_feature_element(feature_element)
123
+ def before_feature_element(feature_element)
124
+ start_buffering :feature_element
125
+ end
126
+
127
+ def after_feature_element(feature_element)
128
+ stop_buffering :feature_element
106
129
  css_class = {
107
130
  Ast::Scenario => 'scenario',
108
131
  Ast::ScenarioOutline => 'scenario outline'
109
132
  }[feature_element.class]
110
- @builder.div(:class => css_class) do
111
- super
133
+
134
+ builder.div(:class => css_class) do
135
+ builder << buffer(:feature_element)
112
136
  end
113
137
  @open_step_list = true
114
138
  end
115
-
116
- def visit_scenario_name(keyword, name, file_colon_line, source_indent)
139
+
140
+ def scenario_name(keyword, name, file_colon_line, source_indent)
117
141
  @listing_background = false
118
- @builder.h3 do
119
- @builder.span(keyword, :class => 'keyword')
120
- @builder.text!(' ')
121
- @builder.span(name, :class => 'val')
142
+ builder.h3 do
143
+ builder.span(keyword, :class => 'keyword')
144
+ builder.text!(' ')
145
+ builder.span(name, :class => 'val')
122
146
  end
123
147
  end
124
-
125
- def visit_outline_table(outline_table)
148
+
149
+ def before_outline_table(outline_table)
126
150
  @outline_row = 0
127
- @builder.table do
128
- super(outline_table)
151
+ start_buffering :outline_table
152
+ end
153
+
154
+ def after_outline_table(outline_table)
155
+ stop_buffering :outline_table
156
+ builder.table do
157
+ builder << buffer(:outline_table)
129
158
  end
130
159
  @outline_row = nil
131
160
  end
132
161
 
133
- def visit_examples(examples)
134
- @builder.div(:class => 'examples') do
135
- super(examples)
162
+ def before_examples(examples)
163
+ start_buffering :examples
164
+ end
165
+
166
+ def after_examples(examples)
167
+ stop_buffering :examples
168
+ builder.div(:class => 'examples') do
169
+ builder << buffer(:examples)
136
170
  end
137
171
  end
138
172
 
139
- def visit_examples_name(keyword, name)
140
- @builder.h4 do
141
- @builder.span(keyword, :class => 'keyword')
142
- @builder.text!(' ')
143
- @builder.span(name, :class => 'val')
173
+ def examples_name(keyword, name)
174
+ builder.h4 do
175
+ builder.span(keyword, :class => 'keyword')
176
+ builder.text!(' ')
177
+ builder.span(name, :class => 'val')
144
178
  end
145
179
  end
146
180
 
147
- def visit_steps(steps)
148
- @builder.ol do
149
- super
181
+ def before_steps(steps)
182
+ start_buffering :steps
183
+ end
184
+
185
+ def after_steps(steps)
186
+ stop_buffering :steps
187
+ builder.ol do
188
+ builder << buffer(:steps)
150
189
  end
151
190
  end
152
-
153
- def visit_step(step)
191
+
192
+ def before_step(step)
154
193
  @step_id = step.dom_id
155
- super
156
194
  end
157
195
 
158
- def visit_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
196
+ def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
197
+ start_buffering :step_result
198
+ @hide_this_step = false
159
199
  if exception
160
- return if @exceptions.index(exception)
200
+ if @exceptions.include?(exception)
201
+ @hide_this_step = true
202
+ return
203
+ end
161
204
  @exceptions << exception
162
205
  end
163
- return if status != :failed && @in_background ^ background
206
+ if status != :failed && @in_background ^ background
207
+ @hide_this_step = true
208
+ return
209
+ end
164
210
  @status = status
165
- @builder.li(:id => @step_id, :class => "step #{status}") do
166
- super(keyword, step_match, multiline_arg, status, exception, source_indent, background)
211
+ end
212
+
213
+ def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background)
214
+ stop_buffering :step_result
215
+ return if @hide_this_step
216
+ builder.li(:id => @step_id, :class => "step #{status}") do
217
+ builder << buffer(:step_result)
167
218
  end
168
219
  end
169
220
 
170
- def visit_step_name(keyword, step_match, status, source_indent, background)
221
+ def step_name(keyword, step_match, status, source_indent, background)
171
222
  @step_matches ||= []
172
223
  background_in_scenario = background && !@listing_background
173
224
  @skip_step = @step_matches.index(step_match) || background_in_scenario
174
225
  @step_matches << step_match
175
-
226
+
176
227
  unless @skip_step
177
228
  build_step(keyword, step_match, status)
178
229
  end
179
230
  end
180
231
 
181
- def visit_exception(exception, status)
182
- @builder.pre(format_exception(exception), :class => status)
232
+ def exception(exception, status)
233
+ return if @hide_this_step
234
+ builder.pre(format_exception(exception), :class => status)
235
+ end
236
+
237
+ def before_multiline_arg(multiline_arg)
238
+ start_buffering :multiline_arg
183
239
  end
184
240
 
185
- def visit_multiline_arg(multiline_arg)
186
- return if @skip_step
241
+ def after_multiline_arg(multiline_arg)
242
+ stop_buffering :multiline_arg
243
+ return if @hide_this_step || @skip_step
187
244
  if Ast::Table === multiline_arg
188
- @builder.table do
189
- super
245
+ builder.table do
246
+ builder << buffer(:multiline_arg)
190
247
  end
191
248
  else
192
- super
249
+ builder << buffer(:multiline_arg)
193
250
  end
194
251
  end
195
252
 
196
- def visit_py_string(string)
197
- @builder.pre(:class => 'val') do |pre|
198
- @builder << string.gsub("\n", '&#x000A;')
253
+ def py_string(string)
254
+ return if @hide_this_step
255
+ builder.pre(:class => 'val') do |pre|
256
+ builder << string.gsub("\n", '&#x000A;')
199
257
  end
200
258
  end
201
259
 
202
- def visit_table_row(table_row)
260
+ def before_table_row(table_row)
203
261
  @row_id = table_row.dom_id
204
262
  @col_index = 0
205
- @builder.tr(:id => @row_id) do
206
- super
263
+ start_buffering :table_row
264
+ end
265
+
266
+ def after_table_row(table_row)
267
+ stop_buffering :table_row
268
+ return if @hide_this_step
269
+ builder.tr(:id => @row_id) do
270
+ builder << buffer(:table_row)
207
271
  end
208
272
  if table_row.exception
209
- @builder.tr do
210
- @builder.td(:colspan => @col_index.to_s, :class => 'failed') do
211
- @builder.pre do |pre|
273
+ builder.tr do
274
+ builder.td(:colspan => @col_index.to_s, :class => 'failed') do
275
+ builder.pre do |pre|
212
276
  pre << format_exception(table_row.exception)
213
277
  end
214
278
  end
@@ -217,45 +281,71 @@ module Cucumber
217
281
  @outline_row += 1 if @outline_row
218
282
  end
219
283
 
220
- def visit_table_cell_value(value, status)
284
+ def table_cell_value(value, status)
285
+ return if @hide_this_step
286
+
221
287
  cell_type = @outline_row == 0 ? :th : :td
222
288
  attributes = {:id => "#{@row_id}_#{@col_index}", :class => 'val'}
223
289
  attributes[:class] += " #{status}" if status
224
290
  build_cell(cell_type, value, attributes)
225
291
  @col_index += 1
226
292
  end
227
-
293
+
228
294
  def announce(announcement)
229
- @builder.pre(announcement, :class => 'announcement')
295
+ builder.pre(announcement, :class => 'announcement')
230
296
  end
231
-
232
- protected
297
+
298
+ private
233
299
 
234
300
  def build_step(keyword, step_match, status)
235
301
  step_name = step_match.format_args(lambda{|param| %{<span class="param">#{param}</span>}})
236
- @builder.div do |div|
237
- @builder.span(keyword, :class => 'keyword')
238
- @builder.text!(' ')
239
- @builder.span(:class => 'step val') do |name|
302
+ builder.div do |div|
303
+ builder.span(keyword, :class => 'keyword')
304
+ builder.text!(' ')
305
+ builder.span(:class => 'step val') do |name|
240
306
  name << h(step_name).gsub(/&lt;span class=&quot;(.*?)&quot;&gt;/, '<span class="\1">').gsub(/&lt;\/span&gt;/, '</span>')
241
307
  end
242
308
  end
243
309
  end
244
-
310
+
245
311
  def build_cell(cell_type, value, attributes)
246
- @builder.__send__(cell_type, value, attributes)
312
+ builder.__send__(cell_type, value, attributes)
247
313
  end
248
-
314
+
249
315
  def inline_css
250
- @builder.style(:type => 'text/css') do
251
- @builder.text!(File.read(File.dirname(__FILE__) + '/cucumber.css'))
316
+ builder.style(:type => 'text/css') do
317
+ builder.text!(File.read(File.dirname(__FILE__) + '/cucumber.css'))
252
318
  end
253
319
  end
254
-
320
+
255
321
  def format_exception(exception)
256
322
  h((["#{exception.message} (#{exception.class})"] + exception.backtrace).join("\n"))
257
323
  end
258
324
 
325
+ def builder
326
+ @current_builder
327
+ end
328
+
329
+ def buffer(label)
330
+ result = @buffer[label]
331
+ @buffer[label] = ''
332
+ result
333
+ end
334
+
335
+ def start_buffering(label)
336
+ @buffer[label] ||= ''
337
+ @parent_builder ||= {}
338
+ @parent_builder[label] = @current_builder
339
+ @current_builder = create_builder(@buffer[label])
340
+ end
341
+
342
+ def stop_buffering(label)
343
+ @current_builder = @parent_builder[label]
344
+ end
345
+
346
+ def create_builder(io)
347
+ OrderedXmlMarkup.new(:target => io, :indent => 0)
348
+ end
259
349
  end
260
350
  end
261
351
  end