cucumber-in-the-yard 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/History.txt +3 -0
  2. data/Manifest +51 -0
  3. data/README.txt +55 -0
  4. data/Rakefile +28 -0
  5. data/cucumber-in-the-yard.gemspec +42 -0
  6. data/example/example.feature +13 -0
  7. data/example/example.step.rb +48 -0
  8. data/example/example.third.feature +30 -0
  9. data/example/second_example.feature +36 -0
  10. data/lib/city.rb +23 -0
  11. data/lib/cucumber/city_builder.rb +130 -0
  12. data/lib/yard/code_objects/cucumber_location_helper.rb +15 -0
  13. data/lib/yard/code_objects/feature.rb +24 -0
  14. data/lib/yard/code_objects/scenario.rb +25 -0
  15. data/lib/yard/code_objects/step.rb +25 -0
  16. data/lib/yard/code_objects/tags.rb +63 -0
  17. data/lib/yard/extensions.rb +38 -0
  18. data/lib/yard/handlers/base.rb +21 -0
  19. data/lib/yard/handlers/feature_handler.rb +41 -0
  20. data/lib/yard/parser/feature.rb +46 -0
  21. data/lib/yard/rake/city_task.rb +12 -0
  22. data/lib/yard/rb_extensions.rb +152 -0
  23. data/lib/yard/templates/default/feature/html/feature.erb +243 -0
  24. data/lib/yard/templates/default/feature/setup.rb +5 -0
  25. data/lib/yard/templates/default/fulldoc/html/css/common.css +182 -0
  26. data/lib/yard/templates/default/fulldoc/html/full_list.erb +34 -0
  27. data/lib/yard/templates/default/fulldoc/html/full_list_features.erb +10 -0
  28. data/lib/yard/templates/default/fulldoc/html/full_list_scenarios.erb +10 -0
  29. data/lib/yard/templates/default/fulldoc/html/full_list_stepdefinitions.erb +11 -0
  30. data/lib/yard/templates/default/fulldoc/html/full_list_steps.erb +13 -0
  31. data/lib/yard/templates/default/fulldoc/html/full_list_tagusages.erb +10 -0
  32. data/lib/yard/templates/default/fulldoc/html/index.erb +24 -0
  33. data/lib/yard/templates/default/fulldoc/html/js/cucumber.js +13 -0
  34. data/lib/yard/templates/default/fulldoc/html/setup.rb +73 -0
  35. data/lib/yard/templates/default/layout/html/headers.erb +14 -0
  36. data/lib/yard/templates/default/layout/html/search.erb +8 -0
  37. data/lib/yard/templates/default/module/html/step_transforms.erb +23 -0
  38. data/lib/yard/templates/default/module/setup.rb +5 -0
  39. data/lib/yard/templates/default/scenario/html/scenario.erb +26 -0
  40. data/lib/yard/templates/default/scenario/setup.rb +5 -0
  41. data/lib/yard/templates/default/steptransformers/html/stepdefinition.erb +38 -0
  42. data/lib/yard/templates/default/steptransformers/setup.rb +5 -0
  43. data/lib/yard/templates/default/tagusage/html/tagusage.erb +65 -0
  44. data/lib/yard/templates/default/tagusage/setup.rb +5 -0
  45. data/spec/city/feature_parser_spec_examples.rb +153 -0
  46. data/spec/city/gherkin_loader_spec.rb +39 -0
  47. data/spec/city/test.feature +36 -0
  48. data/spec/city/yard_handlers_cucumber_spec.rb +24 -0
  49. data/spec/city/yard_namespace_object_spec.rb +8 -0
  50. data/spec/city/yard_parser_cucumber_spec.rb +215 -0
  51. data/spec/city/yard_rb_extensions_spec.rb +128 -0
  52. data/spec/spec_helper.rb +5 -0
  53. data.tar.gz.sig +0 -0
  54. metadata +201 -0
  55. metadata.gz.sig +0 -0
@@ -0,0 +1,38 @@
1
+
2
+ module YARD::CodeObjects
3
+
4
+ NamespaceObject.class_eval do
5
+
6
+ attr_accessor :features
7
+
8
+ def features(opts = {})
9
+ children.select {|child| child.type == :feature }
10
+ end
11
+
12
+ attr_accessor :scenarios
13
+
14
+ def scenarios(opts = {})
15
+ children.select {|child| child.type == :scenario }
16
+ end
17
+
18
+ attr_accessor :steps
19
+
20
+ def steps(opts = {})
21
+ children.select {|child| child.type == :step }
22
+ end
23
+
24
+ attr_accessor :step_definitions
25
+
26
+ def step_definitions(opts = {})
27
+ children.select {|child| child.type == :stepdefinition }
28
+ end
29
+
30
+ attr_accessor :step_transforms
31
+
32
+ def step_transforms(opts = {})
33
+ children.select {|child| child.type == :steptransform }
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,21 @@
1
+ module YARD
2
+ module Handlers
3
+ module Cucumber
4
+
5
+ class Base < Handlers::Base
6
+ class << self
7
+ include Parser::Cucumber
8
+ def handles?(node)
9
+ handlers.any? do |a_handler|
10
+ log.debug "YARD::Handlers::Cucumber::Base#handles?"
11
+ end
12
+ end
13
+ include Parser::Cucumber
14
+ end
15
+
16
+ end
17
+
18
+ Processor.register_handler_namespace :feature, Cucumber
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,41 @@
1
+ module YARD
2
+ module Handlers
3
+ module Cucumber
4
+
5
+ class FeatureHandler < Base
6
+
7
+ handles CodeObjects::Cucumber::Feature
8
+
9
+ def process
10
+
11
+ # For the background and the scenario, find the steps that have definitions
12
+ process_scenario(statement.background) if statement.background
13
+
14
+ statement.scenarios.each do |scenario|
15
+ process_scenario(scenario)
16
+ end
17
+
18
+ rescue YARD::Handlers::NamespaceMissingError
19
+ end
20
+
21
+
22
+ def process_scenario(scenario)
23
+ scenario.steps.each do |step|
24
+ owner.step_definitions.each do |stepdef|
25
+ if %r{#{stepdef.compare_value}}.match(step.value)
26
+ step.definition = stepdef
27
+ stepdef.steps << step
28
+ log.info "STEP #{step} has found its definition #{stepdef}"
29
+ break
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,46 @@
1
+ module YARD::Parser::Cucumber
2
+
3
+ class FeatureParser < YARD::Parser::Base
4
+
5
+ def initialize(source, file = '(stdin)')
6
+
7
+ @builder = Cucumber::Parser::CityBuilder.new(file)
8
+ @tag_counts = {}
9
+ @tag_formatter = Gherkin::Formatter::TagCountFormatter.new(@builder, @tag_counts)
10
+ @parser = Gherkin::Parser::Parser.new(@tag_formatter, true, "root", false)
11
+
12
+ @source = source
13
+ @file = file
14
+
15
+ @feature = nil
16
+ end
17
+
18
+ def parse
19
+ begin
20
+ @parser.parse(@source, @file, 0)
21
+ @feature = @builder.ast
22
+ return nil if @feature.nil? # Nothing matched
23
+ @feature.language = @parser.i18n_language
24
+ rescue Gherkin::Lexer::LexingError, Gherkin::Parser::ParseError => e
25
+ e.message.insert(0, "#{@file}: ")
26
+ raise e
27
+ end
28
+
29
+ self
30
+ end
31
+
32
+ def tokenize
33
+
34
+ end
35
+
36
+
37
+ def enumerator
38
+ [@feature]
39
+ end
40
+
41
+ end
42
+
43
+
44
+ YARD::Parser::SourceParser.register_parser_type :feature, FeatureParser, 'feature'
45
+
46
+ end
@@ -0,0 +1,12 @@
1
+ module YARD::Rake
2
+
3
+ class CitydocTask < YardocTask
4
+
5
+ def initialize(name = :yard)
6
+ super
7
+ self.options += [ "-e", "#{ File.dirname(__FILE__)}/../../city.rb", "-p", "#{File.dirname(__FILE__)}/../templates" ]
8
+ end
9
+
10
+ end
11
+
12
+ end
@@ -0,0 +1,152 @@
1
+
2
+ module YARD::CodeObjects
3
+
4
+
5
+ #
6
+ # StepDefinitions, as implemented in a ruby file
7
+ #
8
+ class StepDefinitionObject < Base
9
+
10
+ attr_reader :keyword, :value, :compare_value, :source
11
+ attr_accessor :constants, :steps
12
+
13
+ def value=(value)
14
+ @value = format_source(value)
15
+ @constants = {}
16
+ @steps = []
17
+ end
18
+
19
+ def compare_value
20
+ base_value = value.gsub(/^\/|\/$/,'')
21
+ @constants.each do |name,value|
22
+ base_value.gsub!(/\#\{\s*#{name.to_s}\s*\}/,value.gsub(/^\/|\/$/,''))
23
+ end
24
+ base_value
25
+ end
26
+
27
+ def _value_constants(data=@value)
28
+ #Hash[*data.scan(/\#\{([^\}]+)\}/).flatten.collect {|value| [value.strip,nil]}.flatten]
29
+ data.scan(/\#\{([^\}]+)\}/).flatten.collect { |value| value.strip }
30
+ end
31
+
32
+ def constants=(value)
33
+ value.each do |val|
34
+ @constants[val.name.to_s] = val if val.respond_to?(:name) && val.respond_to?(:value)
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ class StepTransformersObject < Base
41
+
42
+ attr_reader :source
43
+ attr_accessor :definitions, :transforms
44
+
45
+ def push(stepobject)
46
+ if stepobject.is_a?(StepDefinitionObject)
47
+ @definitions = [] unless @definitions
48
+ @definitions << stepobject
49
+ elsif stepobject.ia_a?(StepTransformObject)
50
+ @transforms = [] unless @transforms
51
+ @transforms << stepobject
52
+ end
53
+ end
54
+
55
+ alias_method :<< , :push
56
+
57
+ def filename
58
+ "#{name}.html"
59
+ end
60
+
61
+ end
62
+
63
+
64
+ #
65
+ # Transforms
66
+ #
67
+ class StepTransformObject < Base
68
+ attr_reader :value
69
+
70
+ def value=(value)
71
+ @value = format_source(value)
72
+ end
73
+ end
74
+
75
+
76
+
77
+
78
+ end
79
+
80
+
81
+ class StepDefinitionHandler < YARD::Handlers::Ruby::Legacy::Base
82
+ MATCH = /^((When|Given|And|Then)\s*(\/[^\/]+\/).+)$/
83
+ handles MATCH
84
+
85
+ @@unique_name = 0
86
+
87
+ def process
88
+
89
+ keyword = statement.tokens.to_s[MATCH,2]
90
+ step_definition = statement.tokens.to_s[MATCH,3]
91
+ @@unique_name = @@unique_name + 1
92
+
93
+ stepdef_instance = StepDefinitionObject.new(namespace, "StepDefinition_#{@@unique_name}") {|o| o.source = statement.block.to_s ; o.value = step_definition ; o.keyword = keyword}
94
+
95
+ begin
96
+ # Look for all constants within the step definitions
97
+ stepdef_instance.constants = stepdef_instance._value_constants.each do |stepdef_constant|
98
+ owner.constants.each do |constant|
99
+ if stepdef_constant.to_sym == constant.name
100
+ log.info "Replacing #{constant.name} with its value in the step definition #{stepdef_instance.value}"
101
+ stepdef_instance.constants[constant.name] = unpack_constants(constant.value)
102
+ end
103
+ end
104
+ end
105
+
106
+ rescue Exception => e
107
+ log.error "Failed to link step definition to constants. This will make step definition to step linking impossible if constants are present. #{e}"
108
+ end
109
+
110
+
111
+ obj = register stepdef_instance
112
+
113
+
114
+ parse_block :owner => obj
115
+ rescue YARD::Handlers::NamespaceMissingError
116
+ end
117
+
118
+
119
+ def unpack_constants(constant_value)
120
+ constant_value.scan(/\#\{([^\}]+)\}/).flatten.collect { |value| value.strip }.each do |inner_constant|
121
+ inner_constant_match = owner.constants.find {|constant| constant.name.to_s == inner_constant }
122
+ if inner_constant_match
123
+ constant_value.gsub!(/\#\{#{inner_constant}\}/,unpack_constants(inner_constant_match.value))
124
+ end
125
+ end
126
+
127
+ constant_value.gsub!(/^('|"|\/)|('|"|\/)$/,'')
128
+ constant_value
129
+ end
130
+
131
+ end
132
+
133
+
134
+ class StepTransformHandler < YARD::Handlers::Ruby::Legacy::Base
135
+ MATCH = /^Transform\s*(\/[^\/]+\/).+$/
136
+ handles MATCH
137
+
138
+ @@unique_name = 0
139
+
140
+ def process
141
+ transformDefinition = statement.tokens.to_s[MATCH,1]
142
+ #log.debug "#process - transformDefinition = #{transformDefinition}"
143
+ @@unique_name = @@unique_name + 1
144
+
145
+ obj = register StepTransformObject.new(namespace, "StepTransform_#{@@unique_name}") {|o| o.source = statement.block.to_s ; o.value = transformDefinition }
146
+
147
+ parse_block :owner => obj
148
+
149
+ rescue YARD::Handlers::NamespaceMissingError
150
+ end
151
+ end
152
+
@@ -0,0 +1,243 @@
1
+ <div class="feature">
2
+ <div class="title">
3
+ <span class="pre"><%= @feature.keyword %>:</span>
4
+ <span class="name"><%= @feature.value %></span>
5
+ </div>
6
+
7
+ <div class="meta">
8
+ <div class="file"><%= h(@feature.file) %></div>
9
+ <div class="tags">
10
+ <% @feature.tags.each do |tag| %>
11
+ <a href="tag_<%= tag.value %>.html"><%= tag.value %></a>
12
+ <% end %>
13
+ </div>
14
+ </div>
15
+
16
+ <div class="description">
17
+ <%= @feature.description %>
18
+ </div>
19
+
20
+ <% if @feature.background %>
21
+
22
+ <script type="text/javascript" charset="utf-8">
23
+ $(function() {
24
+ $('#background .toggle').click(function() {
25
+ $("#backgroundSteps").toggle('blind');
26
+
27
+ var stateIndicator = $('#background a.toggle')[0]
28
+
29
+ stateIndicator.innerHTML = (stateIndicator.innerHTML === '+' ? '-' : '+');
30
+ return false;
31
+ });
32
+ });
33
+ </script>
34
+
35
+ <div id="background">
36
+ <div class="title">
37
+ <a class="toggle">-</a>
38
+ <span class="pre">Background</span>
39
+ </div>
40
+ <% unless @feature.background.description.empty? %>
41
+ <div class="description">
42
+ <%= h(@feature.background.description.join("\n<br/>")) %>
43
+ </div>
44
+ <% end %>
45
+
46
+ <!-- Background and scenario should have the same display with some minor changes -->
47
+
48
+ <div id="backgroundSteps" class="steps">
49
+ <% if @feature.background.steps %>
50
+ <% @feature.background.steps.each_with_index do |step,index| %>
51
+ <div <%= "id='backgroundStep#{index}'" %> class="step <%= (index + 1) % 2 == 0 ? 'even' : 'odd' %>">
52
+ <span class="predicate"><%= step.keyword %></span>
53
+
54
+ <% if step.definition %>
55
+ <script type="text/javascript" charset="utf-8">
56
+ $('<%= "#backgroundStep#{index}" %>').hover(
57
+ function () { $('<%= "#backgroundStep#{index} div.details" %>').toggle('slide'); },
58
+ function () { $('<%= "#backgroundStep#{index} div.details" %>').toggle('slide'); }
59
+ );
60
+
61
+ $('<%= "#backgroundStep#{index} div.details" %>').show();
62
+
63
+ </script>
64
+ <span class="defined">
65
+ <a href="steptransformers.html#<%= step.definition.name %>"><%= h(step.value) %></a>
66
+ <div class="details" style="display: none;">
67
+ (<%= h("#{step.definition.files.first.first}:#{step.definition.files.first.last}") %>)
68
+ </div>
69
+ </span>
70
+ <% else %>
71
+ <span class="undefined"><%= h(step.value) %></span>
72
+ <% end %>
73
+
74
+ <% if step.has_text? %>
75
+ <div class="text">
76
+ <%= h(step.text) %>
77
+ </div>
78
+ <% end %>
79
+
80
+ <% if step.has_table? %>
81
+ <div class="multiline">
82
+ <table style="">
83
+
84
+ <thead>
85
+ <tr>
86
+ <% step.table.each_with_index do |column,column_index| %>
87
+ <th class="<%= (column_index + 1) % 2 == 0 ? 'even' : 'odd' %>"><%= h(column.to_s.strip) %></td>
88
+ <% end %>
89
+ </tr>
90
+ </thead>
91
+
92
+ <% step.table[1..-1].each do |row| %>
93
+ <tr>
94
+ <% row.split("|").each_with_index do |column,column_index| %>
95
+ <td class="<%= (column_index + 1) % 2 == 0 ? 'even' : 'odd' %>"><%= h(column.to_s.strip) %></td>
96
+ <% end %>
97
+ </tr>
98
+ <% end %>
99
+ </table>
100
+ </div>
101
+
102
+ <% end %>
103
+
104
+ </div>
105
+
106
+ <% end %>
107
+ <% else %>
108
+ <span>No Steps Defined</span>
109
+ <% end %>
110
+ </div>
111
+
112
+ </div>
113
+ <% end %>
114
+
115
+ <% if @feature.scenarios %>
116
+ <% @feature.scenarios.each_with_index do |scenario,scenario_index| %>
117
+
118
+ <script type="text/javascript" charset="utf-8">
119
+ $(function() {
120
+ $('#scenario<%=scenario_index%> .toggle').click(function() {
121
+ $("#scenario<%=scenario_index%>Steps").toggle('blind');
122
+
123
+ var stateIndicator = $('#scenario<%=scenario_index%> a.toggle')[0]
124
+
125
+ stateIndicator.innerHTML = (stateIndicator.innerHTML === '+' ? '-' : '+');
126
+ return false;
127
+ });
128
+ });
129
+ </script>
130
+
131
+ <div <%= "id='scenario#{scenario_index}'" %> class="scenario">
132
+ <div class="title">
133
+ <a class="toggle">-</a>
134
+ <span class="pre">Scenario<%= !scenario.examples.empty? ? " Outline" : "" %>:</span>
135
+ <span class="name"><%= h(scenario.value) %></span>
136
+ </div>
137
+ <div class="meta">
138
+ <div class="file">Line: <%= scenario.files.first.last %></div>
139
+ <% unless scenario.tags.empty? %>
140
+ <div class="tags">
141
+ <% scenario.tags.each do |tag| %>
142
+ <a href="tag_<%= tag.value %>.html"><%= tag.value %></a>
143
+ <% end %>
144
+ </div>
145
+ <% end%>
146
+ </div>
147
+ <% unless scenario.description.empty? %>
148
+ <div class="description">
149
+ <%= h(scenario.description.join("\n<br/>")) %>
150
+ </div>
151
+ <% end %>
152
+
153
+ <div <%= "id='scenario#{scenario_index}Steps'" %> class="steps">
154
+ <% if scenario.steps %>
155
+ <% scenario.steps.each_with_index do |step,index| %>
156
+ <div <%= "id='scenario#{scenario_index}Step#{index}'" %> class="step <%= (index + 1) % 2 == 0 ? 'even' : 'odd' %>">
157
+ <span class="predicate"><%= step.keyword %></span>
158
+
159
+ <% if step.definition %>
160
+ <script type="text/javascript" charset="utf-8">
161
+ $('<%= "#scenario#{scenario_index}Step#{index}" %>').hover(
162
+ function () { $('<%= "#scenario#{scenario_index}Step#{index} div.details" %>').toggle('slide'); },
163
+ function () { $('<%= "#scenario#{scenario_index}Step#{index} div.details" %>').toggle('slide'); }
164
+ );
165
+
166
+ $('<%= "#scenario#{scenario_index}Step#{index} div.details" %>').show();
167
+
168
+ </script>
169
+ <span class="defined">
170
+ <a href="steptransformers.html#<%= step.definition.name %>"><%= h(step.value) %></a>
171
+ <div class="details" style="display: none;">
172
+ (<%= h("#{step.definition.files.first.first}:#{step.definition.files.first.last}") %>)
173
+ </div>
174
+ </span>
175
+ <% else %>
176
+ <span class="undefined"><%= h(step.value) %></span>
177
+ <% end %>
178
+
179
+ <% if step.has_text? %>
180
+ <div class="text">
181
+ <%= h(step.text) %>
182
+ </div>
183
+ <% end %>
184
+
185
+ <% if step.has_table? %>
186
+ <div class="multiline">
187
+ <table style="">
188
+
189
+ <thead>
190
+ <tr>
191
+ <% step.table.first.each_with_index do |column,column_index| %>
192
+ <th class="<%= (column_index + 1) % 2 == 0 ? 'even' : 'odd' %>"><%= h(column.strip) %></td>
193
+ <% end %>
194
+ </tr>
195
+ </thead>
196
+
197
+ <% step.table[1..-1].each do |row| %>
198
+ <tr>
199
+ <% row.each_with_index do |column,column_index| %>
200
+ <td class="<%= (column_index + 1) % 2 == 0 ? 'even' : 'odd' %>"><%= h(column.strip) %></td>
201
+ <% end %>
202
+ </tr>
203
+ <% end %>
204
+ </table>
205
+ </div>
206
+
207
+ <% end %>
208
+
209
+
210
+ </div>
211
+
212
+ <% end %>
213
+ <% else %>
214
+ <span>No Steps Defined</span>
215
+ <% end %>
216
+ </div>
217
+
218
+ <% if scenario.examples && !scenario.examples.empty? %>
219
+ <div class="outline">
220
+ <table>
221
+ <thead>
222
+ <tr>
223
+ <th class="odd" colspan="10"><%= h(scenario.examples.first.first.to_s.strip) %></td>
224
+ </tr>
225
+ </thead>
226
+ <% scenario.examples.first.find {|example| example.is_a?(Array) }.each_with_index do |row,row_index| %>
227
+ <tr>
228
+ <% row.each do |column| %>
229
+ <td class="<%= (row_index + 1) % 2 == 0 ? 'even' : 'odd' %>"><%= h(column.to_s.strip) %></td>
230
+ <% end %>
231
+ </tr>
232
+ <% end %>
233
+ </table>
234
+ </div>
235
+
236
+ <% end %>
237
+
238
+ </div>
239
+ <% end %>
240
+ <% end %>
241
+
242
+ </div>
243
+
@@ -0,0 +1,5 @@
1
+ def init
2
+ super
3
+ sections.push :feature
4
+ @feature = object
5
+ end