gherkin 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/History.txt +12 -0
  2. data/LICENSE +1 -1
  3. data/README.rdoc +2 -2
  4. data/Rakefile +2 -2
  5. data/VERSION.yml +2 -2
  6. data/features/json_formatter.feature +3 -41
  7. data/features/step_definitions/gherkin_steps.rb +5 -6
  8. data/features/step_definitions/json_formatter_steps.rb +3 -2
  9. data/features/step_definitions/json_lexer_steps.rb +5 -5
  10. data/features/step_definitions/pretty_formatter_steps.rb +9 -13
  11. data/features/support/env.rb +1 -1
  12. data/lib/gherkin/formatter/argument.rb +1 -0
  13. data/lib/gherkin/formatter/filter_formatter.rb +149 -0
  14. data/lib/gherkin/formatter/json_formatter.rb +35 -45
  15. data/lib/gherkin/formatter/line_filter.rb +26 -0
  16. data/lib/gherkin/formatter/model.rb +85 -0
  17. data/lib/gherkin/formatter/pretty_formatter.rb +36 -39
  18. data/lib/gherkin/formatter/regexp_filter.rb +17 -0
  19. data/lib/gherkin/formatter/tag_count_formatter.rb +44 -0
  20. data/lib/gherkin/i18n.rb +5 -5
  21. data/lib/gherkin/i18n.yml +13 -0
  22. data/lib/gherkin/i18n_lexer.rb +2 -2
  23. data/lib/gherkin/{json_lexer.rb → json_parser.rb} +17 -5
  24. data/lib/gherkin/{parser → listener}/event.rb +1 -1
  25. data/lib/gherkin/{parser → listener}/formatter_listener.rb +30 -23
  26. data/lib/gherkin/native/java.rb +9 -1
  27. data/lib/gherkin/parser/parser.rb +27 -14
  28. data/lib/gherkin/rubify.rb +5 -1
  29. data/lib/gherkin/tag_expression.rb +62 -0
  30. data/lib/gherkin/tools/files.rb +3 -4
  31. data/lib/gherkin/tools/reformat.rb +2 -2
  32. data/lib/gherkin/tools/stats.rb +3 -4
  33. data/lib/gherkin/tools/stats_listener.rb +1 -1
  34. data/ragel/lexer.c.rl.erb +2 -3
  35. data/ragel/lexer.java.rl.erb +1 -2
  36. data/ragel/lexer.rb.rl.erb +1 -2
  37. data/spec/gherkin/fixtures/complex_for_filtering.feature +60 -0
  38. data/spec/gherkin/fixtures/complex_with_tags.feature +61 -0
  39. data/spec/gherkin/fixtures/hantu_pisang.feature +35 -0
  40. data/spec/gherkin/formatter/filter_formatter_spec.rb +156 -0
  41. data/spec/gherkin/formatter/model_spec.rb +15 -0
  42. data/spec/gherkin/formatter/pretty_formatter_spec.rb +17 -16
  43. data/spec/gherkin/formatter/tag_count_formatter_spec.rb +31 -0
  44. data/spec/gherkin/i18n_lexer_spec.rb +3 -3
  45. data/spec/gherkin/i18n_spec.rb +2 -4
  46. data/spec/gherkin/{json_lexer_spec.rb → json_parser_spec.rb} +13 -8
  47. data/spec/gherkin/sexp_recorder.rb +10 -4
  48. data/spec/gherkin/shared/lexer_group.rb +0 -40
  49. data/spec/gherkin/shared/py_string_group.rb +0 -1
  50. data/spec/gherkin/shared/row_group.rb +1 -2
  51. data/spec/gherkin/tag_expression_spec.rb +137 -0
  52. data/spec/spec_helper.rb +5 -1
  53. data/tasks/bench.rake +5 -9
  54. metadata +35 -25
  55. data/lib/gherkin/parser/filter_listener.rb +0 -203
  56. data/lib/gherkin/parser/row.rb +0 -15
  57. data/lib/gherkin/parser/tag_expression.rb +0 -50
  58. data/spec/gherkin/parser/filter_listener_spec.rb +0 -397
  59. data/spec/gherkin/parser/formatter_listener_spec.rb +0 -134
  60. data/spec/gherkin/parser/parser_spec.rb +0 -50
  61. data/spec/gherkin/parser/tag_expression_spec.rb +0 -116
@@ -1,3 +1,15 @@
1
+ == 2.1.0 (2010-07-12)
2
+
3
+ === New Features
4
+ * Pirate! (anteaya)
5
+ * Tag limits for negative tags (Aslak Hellesøy)
6
+
7
+ === Changed Features
8
+ * The formatter API has changed and the listener API is now only used internally. (Aslak Hellesøy)
9
+
10
+ === Removed Features
11
+ * FilterListener has been replaced with FilterFormatter. Currently only in Ruby (no Java impl yet). (Aslak Hellesøy)
12
+
1
13
  == 2.0.2 (2010-06-16)
2
14
 
3
15
  === New Features
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
1
+ Copyright (c) 2009-2010 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -45,7 +45,7 @@ http://crossgcc.rts-software.org/doku.php - just add the bin folder to your PATH
45
45
  == Note on Patches/Pull Requests
46
46
 
47
47
  * Fork the project.
48
- * Run rake ragel:rb to generate all the I18N lexers
48
+ * Run rake ragel:rb to generate all the I18n lexers
49
49
  * Make your feature addition or bug fix.
50
50
  * Add tests for it. This is important so I don't break it in a
51
51
  future version unintentionally.
@@ -56,4 +56,4 @@ http://crossgcc.rts-software.org/doku.php - just add the bin folder to your PATH
56
56
 
57
57
  == Copyright
58
58
 
59
- Copyright (c) 2009 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy. See LICENSE for details.
59
+ Copyright (c) 2009-2010 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy. See LICENSE for details.
data/Rakefile CHANGED
@@ -19,8 +19,8 @@ begin
19
19
  gem.authors = ["Mike Sassak", "Gregory Hnatiuk", "Aslak Hellesøy"]
20
20
  gem.executables = ["gherkin"]
21
21
  gem.add_dependency "trollop", "~> 1.16.2"
22
- gem.add_development_dependency 'rspec', '~> 2.0.0.beta.11'
23
- gem.add_development_dependency "cucumber", "~> 0.8.1"
22
+ gem.add_development_dependency 'rspec', '~> 2.0.0.beta.15'
23
+ gem.add_development_dependency "cucumber", "~> 0.8.4"
24
24
  gem.add_development_dependency "rake-compiler", "~> 0.7.0" unless defined?(JRUBY_VERSION)
25
25
 
26
26
  gem.files -= FileList['ikvm/**/*']
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :major: 2
3
- :minor: 0
3
+ :minor: 1
4
+ :patch: 0
4
5
  :build:
5
- :patch: 2
@@ -17,10 +17,9 @@ Feature: JSON formatter
17
17
  """
18
18
  {
19
19
  "comments": ["# language: no", "# Another comment"],
20
- "description": "",
21
20
  "keyword": "Egenskap",
22
21
  "name": "Kjapp",
23
- "tags": [],
22
+ "line": 3,
24
23
  "uri": "test.feature"
25
24
  }
26
25
  """
@@ -72,30 +71,24 @@ Feature: JSON formatter
72
71
  Then the outputted JSON should be:
73
72
  """
74
73
  {
75
- "comments": [],
76
74
  "keyword": "Feature",
77
75
  "name": "OH HAI",
78
76
  "tags": ["@one"],
77
+ "line": 2,
79
78
  "uri": "test.feature",
80
- "description": "",
81
79
  "elements":[
82
80
  {
83
- "comments": [],
84
- "tags": [],
85
81
  "keyword": "Scenario",
86
82
  "name": "Fujin",
87
- "description": "",
88
83
  "type": "scenario",
89
84
  "line": 4,
90
85
  "steps": [
91
86
  {
92
- "comments": [],
93
87
  "keyword": "Given ",
94
88
  "name": "wind",
95
89
  "line": 5
96
90
  },
97
91
  {
98
- "comments": [],
99
92
  "keyword": "Then ",
100
93
  "name": "spirit",
101
94
  "line": 6
@@ -103,22 +96,18 @@ Feature: JSON formatter
103
96
  ]
104
97
  },
105
98
  {
106
- "comments": [],
107
99
  "tags": ["@two"],
108
100
  "keyword": "Scenario",
109
101
  "name": "_why",
110
- "description": "",
111
102
  "type": "scenario",
112
103
  "line": 9,
113
104
  "steps": [
114
105
  {
115
- "comments": [],
116
106
  "keyword": "Given ",
117
107
  "name": "chunky",
118
108
  "line": 10
119
109
  },
120
110
  {
121
- "comments": [],
122
111
  "keyword": "Then ",
123
112
  "name": "bacon",
124
113
  "line": 11
@@ -126,16 +115,13 @@ Feature: JSON formatter
126
115
  ]
127
116
  },
128
117
  {
129
- "comments": [],
130
118
  "tags": ["@three", "@four"],
131
119
  "keyword": "Scenario Outline",
132
120
  "name": "Life",
133
- "description": "",
134
121
  "type": "scenario_outline",
135
122
  "line": 14,
136
123
  "steps": [
137
124
  {
138
- "comments": [],
139
125
  "keyword": "Given ",
140
126
  "name": "some <boredom>",
141
127
  "line": 15
@@ -143,25 +129,20 @@ Feature: JSON formatter
143
129
  ],
144
130
  "examples": [
145
131
  {
146
- "comments": [],
147
132
  "tags": ["@five"],
148
133
  "keyword": "Examples",
149
134
  "name": "Real life",
150
- "description": "",
151
135
  "line": 18,
152
136
  "table": [
153
137
  {
154
- "comments": [],
155
138
  "cells": ["boredom"],
156
139
  "line": 19
157
140
  },
158
141
  {
159
- "comments": [],
160
142
  "cells": ["airport"],
161
143
  "line": 20
162
144
  },
163
145
  {
164
- "comments": [],
165
146
  "cells": ["meeting"],
166
147
  "line": 21
167
148
  }
@@ -170,29 +151,23 @@ Feature: JSON formatter
170
151
  ]
171
152
  },
172
153
  {
173
- "comments": [],
174
- "tags": [],
175
154
  "keyword": "Scenario",
176
155
  "name": "who stole my mojo?",
177
- "description": "",
178
156
  "type": "scenario",
179
157
  "line": 23,
180
158
  "steps": [
181
159
  {
182
- "comments": [],
183
160
  "keyword": "When ",
184
161
  "name": "I was",
185
162
  "line": 24,
186
163
  "table": [
187
164
  {
188
- "comments": [],
189
165
  "line": 25,
190
166
  "cells": ["asleep"]
191
167
  }
192
168
  ]
193
169
  },
194
170
  {
195
- "comments": [],
196
171
  "keyword": "And ",
197
172
  "name": "so",
198
173
  "line": 26,
@@ -202,9 +177,7 @@ Feature: JSON formatter
202
177
  },
203
178
  {
204
179
  "comments": ["# The"],
205
- "tags": [],
206
180
  "keyword": "Scenario Outline",
207
- "description": "",
208
181
  "type": "scenario_outline",
209
182
  "line": 32,
210
183
  "name": "with",
@@ -219,10 +192,8 @@ Feature: JSON formatter
219
192
  "examples": [
220
193
  {
221
194
  "comments": ["# comments", "# everywhere"],
222
- "tags": [],
223
195
  "keyword": "Examples",
224
196
  "name": "An example",
225
- "description": "",
226
197
  "line": 38,
227
198
  "table": [
228
199
  {
@@ -253,21 +224,16 @@ Feature: JSON formatter
253
224
  Then the outputted JSON should be:
254
225
  """
255
226
  {
256
- "comments": [],
257
- "description": "",
258
227
  "keyword": "Feature",
259
228
  "name": "Kjapp",
260
- "tags": [],
229
+ "line": 1,
261
230
  "uri": "test.feature",
262
231
  "background": {
263
- "comments": [],
264
- "description": "",
265
232
  "keyword": "Background",
266
233
  "line": 3,
267
234
  "name": "No idea what Kjapp means",
268
235
  "steps": [
269
236
  {
270
- "comments": [],
271
237
  "keyword": "Given ",
272
238
  "line": 4,
273
239
  "name": "I Google it"
@@ -277,15 +243,11 @@ Feature: JSON formatter
277
243
  "elements": [
278
244
  {
279
245
  "comments": ["# Writing JSON by hand sucks"],
280
- "tags": [],
281
246
  "keyword": "Scenario",
282
- "name": "",
283
- "description": "",
284
247
  "type": "scenario",
285
248
  "line": 7,
286
249
  "steps": [
287
250
  {
288
- "comments": [],
289
251
  "keyword": "Then ",
290
252
  "name": "I think it means \"fast\"",
291
253
  "line": 8
@@ -1,18 +1,17 @@
1
- Given /^a "([^\"]*)" "([^\"]*)" parser$/ do |ruby_or_native, parser_name|
2
- parser = Gherkin::Parser::Parser.new(@listener, false, parser_name)
3
- @lexer = Gherkin::I18nLexer.new(parser, ruby_or_native == "ruby")
1
+ Given /^a "(ruby|native)" "([^\"]*)" parser$/ do |ruby_or_native, parser_name|
2
+ @parser = Gherkin::Parser::Parser.new(@formatter, false, parser_name, ruby_or_native=="ruby")
4
3
  end
5
4
 
6
5
  Given "the following text is parsed:" do |text|
7
- @lexer.scan(text, "test.feature", 0)
6
+ @parser.parse(text, "test.feature", 0)
8
7
  end
9
8
 
10
9
  Then "there should be no parse errors" do
11
- @listener.errors.should == []
10
+ @formatter.errors.should == []
12
11
  end
13
12
 
14
13
  Then /^there should be a parse error on (line \d+)$/ do |line|
15
- @listener.line(line).should include(:syntax_error, line)
14
+ @formatter.line(line).should include(:syntax_error, line)
16
15
  end
17
16
 
18
17
  Then /^there should be parse errors on (lines .*)$/ do |lines|
@@ -1,6 +1,6 @@
1
1
  require 'stringio'
2
2
  require 'gherkin/formatter/json_formatter'
3
- require 'gherkin/parser/formatter_listener'
3
+ require 'gherkin/listener/formatter_listener'
4
4
 
5
5
  # Monkey patching so that Hash.to_json has a predictable result.
6
6
  class Hash
@@ -12,13 +12,14 @@ end
12
12
 
13
13
  Given /^a JSON formatter$/ do
14
14
  @io = StringIO.new
15
- @listener = Gherkin::Parser::FormatterListener.new(Gherkin::Formatter::JSONFormatter.new(@io))
15
+ @formatter = Gherkin::Formatter::JSONFormatter.new(@io)
16
16
  end
17
17
 
18
18
  Then /^the outputted JSON should be:$/ do |expected_json|
19
19
  require 'json'
20
20
  expected = JSON.pretty_generate(JSON.parse(expected_json))
21
21
  actual = JSON.pretty_generate(JSON.parse(@io.string))
22
+ announce actual
22
23
  begin
23
24
  actual.should == expected
24
25
  rescue # Haven't figured out how to order Hash on JRuby (JSON pure). Retry with possibly worse error message.
@@ -1,20 +1,20 @@
1
1
  require 'stringio'
2
2
  require 'gherkin/formatter/pretty_formatter'
3
- require 'gherkin/json_lexer'
3
+ require 'gherkin/json_parser'
4
4
 
5
5
  Given /^a PrettyFormatter$/ do
6
6
  @io = StringIO.new
7
- @formatter = Gherkin::Parser::FormatterListener.new(Gherkin::Formatter::PrettyFormatter.new(@io, true))
7
+ @formatter = Gherkin::Formatter::PrettyFormatter.new(@io, true)
8
8
  end
9
9
 
10
10
  Given /^a JSON lexer$/ do
11
- @json_parser = Gherkin::JSONLexer.new(@formatter)
11
+ @json_parser = Gherkin::JSONParser.new(@formatter)
12
12
  end
13
13
 
14
14
  Given /^the following JSON is parsed:$/ do |text|
15
- @json_parser.scan(JSON.pretty_generate(JSON.parse(text)))
15
+ @json_parser.parse(JSON.pretty_generate(JSON.parse(text)))
16
16
  end
17
17
 
18
18
  Then /^the outputted text should be:$/ do |expected_text|
19
- expected_text.should == @io.string.strip
19
+ @io.string.strip.should == expected_text
20
20
  end
@@ -3,38 +3,34 @@ require 'fileutils'
3
3
  require 'gherkin'
4
4
  require 'gherkin/formatter/pretty_formatter'
5
5
  require 'gherkin/formatter/json_formatter'
6
- require 'gherkin/json_lexer'
6
+ require 'gherkin/json_parser'
7
7
 
8
8
  module PrettyPlease
9
9
  def pretty_machinery(gherkin, feature_path)
10
10
  io = StringIO.new
11
11
  formatter = Gherkin::Formatter::PrettyFormatter.new(io, false)
12
- listener = Gherkin::Parser::FormatterListener.new(formatter)
13
- parser = Gherkin::Parser::Parser.new(listener, true)
14
- lexer = Gherkin::I18nLexer.new(parser)
15
- scan(lexer, gherkin, feature_path)
12
+ parser = Gherkin::Parser::Parser.new(formatter, true)
13
+ parse(parser, gherkin, feature_path)
16
14
  io.string
17
15
  end
18
16
 
19
17
  def json_machinery(gherkin, feature_path)
20
18
  json = StringIO.new
21
19
  json_formatter = Gherkin::Formatter::JSONFormatter.new(json)
22
- formatter_listener = Gherkin::Parser::FormatterListener.new(json_formatter)
23
- gherkin_lexer = Gherkin::I18nLexer.new(formatter_listener)
24
- scan(gherkin_lexer, gherkin, feature_path)
20
+ gherkin_parser = Gherkin::Parser::Parser.new(json_formatter, true)
21
+ parse(gherkin_parser, gherkin, feature_path)
25
22
 
26
23
  result = StringIO.new
27
24
  pretty_formatter = Gherkin::Formatter::PrettyFormatter.new(result, false)
28
- formatter_listener = Gherkin::Parser::FormatterListener.new(pretty_formatter)
29
- json_lexer = Gherkin::JSONLexer.new(formatter_listener)
30
- json_lexer.scan(json.string)
25
+ json_parser = Gherkin::JSONParser.new(pretty_formatter)
26
+ json_parser.parse(json.string)
31
27
 
32
28
  result.string
33
29
  end
34
30
 
35
- def scan(lexer, gherkin, feature_path)
31
+ def parse(parser, gherkin, feature_path)
36
32
  begin
37
- lexer.scan(gherkin, feature_path, 0)
33
+ parser.parse(gherkin, feature_path, 0)
38
34
  rescue => e
39
35
  if e.message =~ /Lexing error/
40
36
  FileUtils.mkdir "tmp" unless File.directory?("tmp")
@@ -24,7 +24,7 @@ class GherkinWorld
24
24
  include TransformHelpers
25
25
 
26
26
  def initialize
27
- @listener = Gherkin::SexpRecorder.new
27
+ @formatter = Gherkin::SexpRecorder.new
28
28
  end
29
29
  end
30
30
 
@@ -11,6 +11,7 @@ module Gherkin
11
11
  end
12
12
 
13
13
  def self.format(string, argument_format, arguments)
14
+ arguments ||= []
14
15
  s = string.dup
15
16
  offset = past_offset = 0
16
17
  arguments.each do |arg|
@@ -0,0 +1,149 @@
1
+ require 'gherkin/rubify'
2
+ require 'gherkin/tag_expression'
3
+ require 'gherkin/formatter/regexp_filter'
4
+ require 'gherkin/formatter/line_filter'
5
+ require 'gherkin/formatter/model'
6
+
7
+ module Gherkin
8
+ module Formatter
9
+ class FilterFormatter
10
+ include Rubify
11
+
12
+ def initialize(formatter, filters)
13
+ @formatter = formatter
14
+ @filter = detect_filter(filters)
15
+
16
+
17
+ @feature_tags = []
18
+ @feature_element_tags = []
19
+ @examples_tags = []
20
+
21
+ @feature_events = []
22
+ @background_events = []
23
+ @feature_element_events = []
24
+ @examples_events = []
25
+ end
26
+
27
+ def feature(statement, uri)
28
+ @feature_tags = statement.tags
29
+ @feature_name = statement.name
30
+ @feature_events = [[:feature, statement, uri]]
31
+ end
32
+
33
+ def background(statement)
34
+ @feature_element_name = statement.name
35
+ @feature_element_range = statement.line_range
36
+ @background_events = [[:background, statement]]
37
+ end
38
+
39
+ def scenario(statement)
40
+ replay!
41
+ @feature_element_tags = statement.tags
42
+ @feature_element_name = statement.name
43
+ @feature_element_range = statement.line_range
44
+ @feature_element_events = [[:scenario, statement]]
45
+ end
46
+
47
+ def scenario_outline(statement)
48
+ replay!
49
+ @feature_element_tags = statement.tags
50
+ @feature_element_name = statement.name
51
+ @feature_element_range = statement.line_range
52
+ @feature_element_events = [[:scenario_outline, statement]]
53
+ @examples_events.clear
54
+ end
55
+
56
+ def examples(statement, examples_rows)
57
+ replay!
58
+ @examples_tags = statement.tags
59
+ @examples_name = statement.name
60
+
61
+ table_body_range = examples_rows.to_a[1].line..examples_rows.to_a[-1].line
62
+ @examples_range = statement.line_range.first..table_body_range.last
63
+ if(LineFilter === @filter && @filter.eval([table_body_range]))
64
+ examples_rows = @filter.filter_table_body_rows(examples_rows)
65
+ end
66
+ @examples_events = [[:examples, statement, examples_rows]]
67
+ end
68
+
69
+ def step(statement, multiline_arg, result)
70
+ args = [:step, statement, multiline_arg, result]
71
+ if @feature_element_events.any?
72
+ @feature_element_events << args
73
+ else
74
+ @background_events << args
75
+ end
76
+
77
+ if LineFilter === @filter
78
+ step_range = statement.line_range
79
+ case rubify(multiline_arg)
80
+ when Model::PyString
81
+ step_range = step_range.first..multiline_arg.line_range.last
82
+ when Array
83
+ step_range = step_range.first..multiline_arg.to_a[-1].line
84
+ end
85
+ @feature_element_range = @feature_element_range.first..step_range.last
86
+ end
87
+ end
88
+
89
+ def eof
90
+ replay!
91
+ @formatter.eof
92
+ end
93
+
94
+ private
95
+
96
+ def detect_filter(filters)
97
+ raise "Inconsistent filters: #{filters.inspect}" if filters.map{|filter| filter.class}.uniq.length > 1
98
+ case(filters[0])
99
+ when Fixnum
100
+ LineFilter.new(filters)
101
+ when Regexp
102
+ RegexpFilter.new(filters)
103
+ when String
104
+ TagExpression.new(filters)
105
+ end
106
+ end
107
+
108
+ def replay!
109
+ case @filter
110
+ when TagExpression
111
+ background_ok = false
112
+ feature_element_ok = @filter.eval(tag_names(@feature_tags.to_a + @feature_element_tags.to_a))
113
+ examples_ok = @filter.eval(tag_names(@feature_tags.to_a + @feature_element_tags.to_a + @examples_tags.to_a)) if @examples_tags
114
+ when RegexpFilter
115
+ background_ok = @filter.eval([@background_name]) if @background_name
116
+ feature_element_ok = @filter.eval([@feature_element_name])
117
+ examples_ok = @filter.eval([@feature_element_name, @examples_name]) if @examples_name
118
+ when LineFilter
119
+ background_ok = @filter.eval([@background_range]) if @background_range
120
+ feature_element_ok = @filter.eval([@feature_element_range]) if @feature_element_range
121
+ examples_ok = @filter.eval([@feature_element_range, @examples_range]) if @examples_range
122
+ end
123
+
124
+ if background_ok || feature_element_ok || examples_ok
125
+ replay_events!(@feature_events)
126
+ replay_events!(@background_events)
127
+
128
+ if feature_element_ok || examples_ok
129
+ replay_events!(@feature_element_events)
130
+ if examples_ok
131
+ replay_events!(@examples_events)
132
+ end
133
+ end
134
+ end
135
+ end
136
+
137
+ def tag_names(tags)
138
+ tags.to_a.uniq.map{|tag| tag.name}
139
+ end
140
+
141
+ def replay_events!(events)
142
+ events.each do |event|
143
+ @formatter.__send__(*event)
144
+ end
145
+ events.clear
146
+ end
147
+ end
148
+ end
149
+ end