yard-cucumber 2.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ === 2.1.0 / 2011-05-22
2
+
3
+ * YARD 0.7.0 compatibility (removed some monkey-patching)
4
+ * Add more menus (Steps and Step Definitions)
5
+ * Define step definitions in your language Internalization (Ruby 1.9.2)
6
+
1
7
  === 2.0.3 / 2011-05-22
2
8
 
3
9
  * Updated Cucumber links to the new github organization
data/README.md CHANGED
@@ -40,19 +40,23 @@ The implemented example has been deployed at [http://recursivegames.com/cukes/](
40
40
 
41
41
  **9. Feature directories with a README.md will be parsed into the description** [example](http://recursivegames.com/cukes/requirements/example/child_feature.html)
42
42
 
43
+ **10. Configurable Menus - want a searchable steps menu and remove the tags menu**
44
+
45
+ **11. Step definitions in your language (Ruby 1.9.2 - Internationalization)**
46
+
43
47
  Installation
44
48
  ------------
45
49
 
46
- Cucumber-In-The-Yard (CITY) requires the following gems installed:
50
+ YARD-Cucumber requires the following gems installed:
47
51
 
48
- Gherkin - http://cukes.info
49
- Cucumber - http://cukes.info
50
- YARD - http://yardoc.org
52
+ Gherkin 2.3.5 - http://cukes.info
53
+ Cucumber 0.7.5 - http://cukes.info
54
+ YARD 0.7.0 - http://yardoc.org
51
55
 
52
- To install CITY use the following command:
56
+ To install `yard-cucumber` use the following command:
57
+
58
+ $ gem install yard-cucumber
53
59
 
54
- $ gem install yard-cucumber
55
-
56
60
  (Add `sudo` if you're installing under a POSIX system as root)
57
61
 
58
62
  Usage
@@ -61,25 +65,61 @@ Usage
61
65
  YARD supports for automatically including gems with the prefix `yard-`
62
66
  as a plugin. To enable automatic loading yard-cucumber.
63
67
 
64
- 1. Edit `~/.yard/config` and insert the following line:
68
+ $ yard config load_plugins true
69
+ $ yardoc 'example/**/*.rb' 'example/**/*.feature'
65
70
 
66
- load_plugins: true
71
+ Now you can run YARD as you [normally](https://github.com/lsegal/yard) would and
72
+ have your features, step definitions and transforms captured.
67
73
 
68
- 2. Run `yardoc`, use the rake task, or run `yard server`, as would [normally](https://github.com/lsegal/yard).
74
+ An example with the rake task:
69
75
 
70
- Be sure to update any file patterns so that they do not exclude `feature`
71
- files. yard-cucumber will even process your step definitions and transforms.
76
+ require 'yard'
72
77
 
73
- $ yardoc 'example/**/*.rb' 'example/**/*.feature'
78
+ YARD::Rake::YardocTask.new do |t|
79
+ t.files = ['features/**/*.feature', 'features/**/*.rb']
80
+ t.options = ['--any', '--extra', '--opts'] # optional
81
+ end
74
82
 
75
- An example with the rake task:
76
83
 
77
- require 'yard'
84
+ Configuration
85
+ -------------
86
+
87
+ * Adding or Removing search fields (yardoc)
88
+
89
+ Be default the yardoc output will generate a search field for features and tags.
90
+ This can be configured through the yard configuration file `~./yard/config` to
91
+ add or remove these search fields.
92
+
93
+ --- !map:SymbolHash
94
+ :load_plugins: true
95
+ :ignored_plugins: []
96
+
97
+ :autoload_plugins: []
98
+
99
+ :safe_mode: false
100
+
101
+ :"yard-cucumber":
102
+ menus: [ 'features', 'tags', 'steps', 'stepdefinitions' ]
103
+
104
+
105
+ By default the configuration, yaml format, that is generate by the `yard config`
106
+ command will save a `SymbolHash`. You can still edit this file add the entry for
107
+ `:"yard-cucumber":` and the sub-entry `menus:` which can contain all of the above
108
+ mentioned menus or simply an empty array `[]` if you want no additional menus.
109
+
110
+ * Step definitions in your language (Ruby 1.9.2)
111
+
112
+ Again the yard configuration file you can define additional step definitions that
113
+ can be matched.
114
+
115
+ :"yard-cucumber":
116
+ language:
117
+ step_definitions: [ 'Given', 'When', 'Then', 'And', 'Soit', 'Etantdonné', 'Lorsque', 'Lorsqu', 'Alors', 'Et' ]
78
118
 
79
- YARD::Rake::YardocTask.new do |t|
80
- t.files = ['features/**/*.feature', 'features/**/*.rb']
81
- t.options = ['--any', '--extra', '--opts'] # optional
82
- end
119
+ In this example, I have included the French step definition words alongside the
120
+ English step definitions. Even without specifying this feature files in other
121
+ languages are found, this provides the ability for the step definitions to match
122
+ correctly to step definitions.
83
123
 
84
124
  Details
85
125
  --------
data/city.gemspec CHANGED
@@ -56,7 +56,7 @@ Gem::Specification.new do |s|
56
56
 
57
57
  s.add_dependency 'gherkin', '>= 2.2.9'
58
58
  s.add_dependency 'cucumber', '>= 0.7.5'
59
- s.add_dependency 'yard', '>= 0.6.3'
59
+ s.add_dependency 'yard', '>= 0.7.0'
60
60
 
61
61
  s.rubygems_version = "1.3.7"
62
62
  s.files = `git ls-files`.split("\n")
@@ -0,0 +1,18 @@
1
+ # language: fr
2
+ Fonctionnalité: Addition
3
+ Afin de gagner du temps lors du calcul de la facture
4
+ En tant que commerçant
5
+ Je souhaite pouvoir faire une addition
6
+
7
+ Plan du Scénario: Addition de deux nombres
8
+ Soit une calculatrice
9
+ Et que j'entre <a> pour le premier nombre
10
+ Et que je tape sur la touche "+"
11
+ Et que j'entre <b> pour le second nombre
12
+ Lorsque je tape sur la touche "="
13
+ Alors le résultat affiché doit être <somme>
14
+
15
+ Exemples:
16
+ | a | b | somme |
17
+ | 2 | 2 | 4 |
18
+ | 2 | 3 | 5 |
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+ Soit /^une calculatrice$/ do
3
+ @calc = Calculatrice.new
4
+ end
5
+
6
+ Etantdonné /^qu'on tape (.*)$/ do |n|
7
+ @calc.push n.to_i
8
+ end
9
+
10
+ Etantdonné /^que j'entre (\d+) pour le (.*) nombre/ do |n, x|
11
+ @calc.push n.to_i
12
+ end
13
+
14
+ Lorsque /^je tape sur la touche "="$/ do
15
+ @expected_result = @calc.additionner
16
+ end
17
+
18
+ Lorsqu /on tape additionner/ do
19
+ @expected_result = @calc.additionner
20
+ end
21
+
22
+ Alors /le résultat affiché doit être (\d*)/ do |result|
23
+ result.to_i.should == @expected_result
24
+ end
25
+
26
+ Alors /le résultat doit être (\d*)/ do |result|
27
+ result.to_i.should == @expected_result
28
+ end
29
+
30
+ Soit /^que je tape sur la touche "\+"$/ do
31
+ # noop
32
+ end
@@ -3,17 +3,48 @@ module Cucumber
3
3
  module Parser
4
4
  class CityBuilder
5
5
  include Gherkin::Rubify
6
-
6
+
7
+ #
8
+ # The Gherkin Parser is going to call the various methods within this
9
+ # class as it finds items. This is similar to how Cucumber generates
10
+ # it's Abstract Syntax Tree (AST). Here instead this generates the
11
+ # various YARD::CodeObjects defined within this template.
12
+ #
13
+ # A namespace is specified and that is the place in the YARD namespacing
14
+ # where all cucumber features generated will reside. The namespace specified
15
+ # is the root namespaces.
16
+ #
17
+ # @param [String] file the name of the file which the content belongs
18
+ #
7
19
  def initialize(file)
8
20
  @namespace = YARD::CodeObjects::Cucumber::CUCUMBER_NAMESPACE
9
21
  find_or_create_namespace(file)
10
22
  @file = file
11
23
  end
12
24
 
25
+ # Return the feature that has been defined. This method is the final
26
+ # method that is called when all the work is done. It is called by
27
+ # the feature parser to return the complete Feature object that was created
28
+ #
29
+ # @return [YARD::CodeObject::Cucumber::Feature] the completed feature
30
+ #
31
+ # @see YARD::Parser::Cucumber::FeatureParser
13
32
  def ast
14
- @feature || @multiline_arg
33
+ @feature
15
34
  end
16
-
35
+
36
+ #
37
+ # Feature that are found in sub-directories are considered, in the way
38
+ # that I chose to implement it, in another namespace. This is because
39
+ # when you execute a cucumber test run on a directory any sub-directories
40
+ # of features will be executed with that directory so the file is split
41
+ # and then namespaces are generated if they have not already have been.
42
+ #
43
+ # The other duty that this does is look for a README.md file within the
44
+ # specified directory of the file and loads it as the description for the
45
+ # namespace. This is useful if you want to give a particular directory
46
+ # some flavor or text to describe what is going on.
47
+ #
17
48
  def find_or_create_namespace(file)
18
49
  @namespace = YARD::CodeObjects::Cucumber::CUCUMBER_NAMESPACE
19
50
 
@@ -26,7 +57,20 @@ module Cucumber
26
57
  @namespace.description = File.read("#{File.dirname(file)}/README.md")
27
58
  end
28
59
  end
29
-
60
+
61
+ #
62
+ # Find the tag if it exists within the YARD Registry, if it doesn' t then
63
+ # create it.
64
+ #
65
+ # We note that the tag was used in this file at the current line.
66
+ #
67
+ # Then we add the tag to the current scenario or feature. We also add the
68
+ # feature or scenario to the tag.
69
+ #
70
+ # @param [String] tag_name the name of the tag
71
+ # @param [parent] parent the scenario or feature that is going to adopt
72
+ # this tag.
73
+ #
30
74
  def find_or_create_tag(tag_name,parent)
31
75
  #log.debug "Processing tag #{tag_name}"
32
76
  tag_code_object = YARD::Registry.all(:tag).find {|tag| tag.value == tag_name } ||
@@ -37,7 +81,12 @@ module Cucumber
37
81
  parent.tags << tag_code_object unless parent.tags.find {|tag| tag == tag_code_object }
38
82
  tag_code_object.owners << parent unless tag_code_object.owners.find {|owner| owner == parent}
39
83
  end
40
-
84
+
85
+ #
86
+ # Each feature found will call this method, generating the feature object.
87
+ # This is once, as the gherking parser does not like multiple feature per
88
+ # file.
89
+ #
41
90
  def feature(feature)
42
91
  #log.debug "FEATURE"
43
92
 
@@ -52,7 +101,11 @@ module Cucumber
52
101
  feature.tags.each {|feature_tag| find_or_create_tag(feature_tag.name,f) }
53
102
  end
54
103
  end
55
-
104
+
105
+ #
106
+ # Called when a background has been found
107
+ #
108
+ # @see #scenario
56
109
  def background(background)
57
110
  #log.debug "BACKGROUND"
58
111
 
@@ -68,7 +121,20 @@ module Cucumber
68
121
  @background.feature = @feature
69
122
  @step_container = @background
70
123
  end
71
-
124
+
125
+ #
126
+ # Called when a scenario has been found
127
+ # - create a scenario
128
+ # - assign the scenario to the feature
129
+ # - assign the feature to the scenario
130
+ # - find or create tags associated with the scenario
131
+ #
132
+ # The scenario is set as the @step_container, which means that any steps
133
+ # found before another scenario is defined belong to this scenario
134
+ #
135
+ # @param [Scenario] statement is a scenario object returned from Gherkin
136
+ # @see #find_or_create_tag
137
+ #
72
138
  def scenario(statement)
73
139
  #log.debug "SCENARIO"
74
140
 
@@ -86,7 +152,14 @@ module Cucumber
86
152
  @feature.scenarios << scenario
87
153
  @step_container = scenario
88
154
  end
89
-
155
+
156
+ #
157
+ # Called when a scenario outline is found. Very similar to a scenario,
158
+ # the ScenarioOutline is still a distinct object as it can contain
159
+ # multiple different example groups that can contain different values.
160
+ #
161
+ # @see #scenario
162
+ #
90
163
  def scenario_outline(statement)
91
164
  #log.debug "SCENARIO OUTLINE"
92
165
 
@@ -105,6 +178,13 @@ module Cucumber
105
178
  @step_container = outline
106
179
  end
107
180
 
181
+ #
182
+ # Examples for a scenario outline are called here. This section differs
183
+ # from the Cucumber parser because here each of the examples are exploded
184
+ # out here as individual scenarios and step definitions. This is so that
185
+ # later we can ensure that we have all the variations of the scenario
186
+ # outline defined to be displayed.
187
+ #
108
188
  def examples(examples)
109
189
  #log.debug "EXAMPLES"
110
190
 
@@ -157,6 +237,14 @@ module Cucumber
157
237
 
158
238
  end
159
239
 
240
+ #
241
+ # Called when a step is found. The step is refered to a table owner, though
242
+ # not all steps have a table or multliline arguments associated with them.
243
+ #
244
+ # If a multiline string is present with the step it is included as the text
245
+ # of the step. If the step has a table it is added to the step using the
246
+ # same method used by the Cucumber Gherkin model.
247
+ #
160
248
  def step(step)
161
249
  #log.debug "STEP"
162
250
 
@@ -182,9 +270,11 @@ module Cucumber
182
270
  @step_container.steps << @table_owner
183
271
  end
184
272
 
273
+ # Defined in the cucumber version so left here. No events for the end-of-file
185
274
  def eof
186
275
  end
187
276
 
277
+ # When a syntax error were to occurr. This parser is not interested in errors
188
278
  def syntax_error(state, event, legal_events, line)
189
279
  # raise "SYNTAX ERROR"
190
280
  end
@@ -1,22 +1,30 @@
1
1
  function cucumberSearchFrameLinks() {
2
- $('#features_list_link').click(function() {
2
+ $('#feature_list_link').click(function() {
3
3
  toggleSearchFrame(this, relpath + 'feature_list.html');
4
4
  });
5
- $('#tags_list_link').click(function() {
5
+ $('#tag_list_link').click(function() {
6
6
  toggleSearchFrame(this, relpath + 'tag_list.html');
7
7
  });
8
+ $('#step_list_link').click(function() {
9
+ toggleSearchFrame(this, relpath + 'step_list.html');
10
+ });
11
+ $('#stepdefinition_list_link').click(function() {
12
+ toggleSearchFrame(this, relpath + 'stepdefinition_list.html');
13
+ });
8
14
  }
9
15
 
10
16
  function cucumberKeyboardShortcuts() {
11
17
  if (window.top.frames.main) return;
12
18
  $(document).keypress(function(evt) {
13
19
  if (evt.altKey || evt.ctrlKey || evt.metaKey || evt.shiftKey) return;
14
- if (typeof evt.orignalTarget !== "undefined" &&
15
- (evt.originalTarget.nodeName == "INPUT" ||
16
- evt.originalTarget.nodeName == "TEXTAREA")) return;
20
+ if (typeof evt.target !== "undefined" &&
21
+ (evt.target.nodeName == "INPUT" ||
22
+ evt.target.nodeName == "TEXTAREA")) return;
17
23
  switch (evt.charCode) {
18
- case 82: case 114: $('#features_list_link').click(); break; // 'r'
19
- case 84: case 116: $('#tags_list_link').click(); break; // 't'
24
+ case 68: case 100: $('#stepdefinition_list_link').click(); break; // 'd'
25
+ case 82: case 114: $('#feature_list_link').click(); break; // 'r'
26
+ case 83: case 115: $('#step_list_link').click(); break; // 's'
27
+ case 84: case 116: $('#tag_list_link').click(); break; // 't'
20
28
  }
21
29
  });
22
30
  }
@@ -15,7 +15,7 @@ def init
15
15
 
16
16
  if @features
17
17
  @features.each {|feature| serialize(feature) }
18
- generate_full_list @features.sort {|x,y| x.value.to_s <=> y.value.to_s }, :feature
18
+ #generate_full_list @features.sort {|x,y| x.value.to_s <=> y.value.to_s }, :feature
19
19
  end
20
20
 
21
21
  #
@@ -28,7 +28,7 @@ def init
28
28
 
29
29
  if @tags
30
30
  @tags.each {|tag| serialize(tag) }
31
- generate_full_list @tags.sort {|x,y| y.all_scenarios.size <=> x.all_scenarios.size }, :tag
31
+ #generate_full_list @tags.sort {|x,y| y.all_scenarios.size <=> x.all_scenarios.size }, :tag
32
32
  end
33
33
 
34
34
  # Generates the requirements splash page with the 'requirements' template
@@ -46,15 +46,43 @@ def init
46
46
 
47
47
  end
48
48
 
49
- #
50
- # Generate a full_list page of the specified objects with the specified type.
51
- #
49
+
50
+ # Generate feature list
51
+ # @note this method is called automically by YARD based on the menus defined in the layout
52
+ def generate_feature_list
53
+ @features = Registry.all(:feature)
54
+ generate_full_list @features.sort {|x,y| x.value.to_s <=> y.value.to_s }, :feature
55
+ end
56
+
57
+ # Generate tag list
58
+ # @note this method is called automically by YARD based on the menus defined in the layout
59
+ def generate_tag_list
60
+ @tags = Registry.all(:tag)
61
+ generate_full_list @tags.sort {|x,y| y.all_scenarios.size <=> x.all_scenarios.size }, :tag
62
+ end
63
+
64
+ # Generate a step definition list
65
+ # @note this menu is not automatically added until yard configuration has this menu added
66
+ # See the layout template method that loads the menus
67
+ def generate_stepdefinition_list
68
+ generate_full_list YARD::Registry.all(:stepdefinition), :stepdefinition
69
+ end
70
+
71
+ # Generate a step list
72
+ # @note this menu is not automatically added until yard configuration has this menu added
73
+ # See the layout template method that loads the menus
74
+ def generate_step_list
75
+ generate_full_list YARD::Registry.all(:step), :step
76
+ end
77
+
78
+ # Helpler method to generate a full_list page of the specified objects with the
79
+ # specified type.
52
80
  def generate_full_list(objects,list_type,friendly_name=nil)
53
- @items = objects
54
- @list_type = "#{list_type}s"
55
- @list_title = "#{friendly_name || list_type.to_s.capitalize} List"
56
- @list_class = "class"
57
- asset("#{list_type}_list.html",erb(:full_list))
81
+ @items = objects
82
+ @list_type = "#{list_type}s"
83
+ @list_title = "#{friendly_name || list_type.to_s.capitalize} List"
84
+ @list_class = "class"
85
+ asset("#{list_type}_list.html",erb(:full_list))
58
86
  end
59
87
 
60
88
  #
@@ -2,12 +2,54 @@ def init
2
2
  super
3
3
  end
4
4
 
5
- def javascript_files
6
- existing_js = super rescue [ 'js/jquery.js','js/app.js' ]
7
- existing_js + [ 'js/cucumber.js' ]
5
+ #
6
+ # Append yard-cucumber stylesheet to yard core stylesheets
7
+ #
8
+ def stylesheets
9
+ super + %w(css/cucumber.css)
8
10
  end
9
11
 
10
- def search_fields
11
- existing_fields = super rescue [ [ 'class_list_link', 'Class List' ],[ 'method_list_link', 'Method List' ],[ 'file_list_link', 'File List' ] ]
12
- existing_fields + [ [ 'features_list_link', 'Features' ],[ 'tags_list_link', 'Tags' ] ]
13
- end
12
+ #
13
+ # Append yard-cucumber javascript to yard core javascripts
14
+ #
15
+ def javascripts
16
+ super + %w(js/cucumber.js)
17
+ end
18
+
19
+ #
20
+ # Append yard-cucumber specific menus 'features' and 'tags'
21
+ #
22
+ # 'features' and 'tags' are enabled by default.
23
+ #
24
+ # 'step definitions' and 'steps' may be enabled by setting up a value in
25
+ # yard configuration file '~/.yard/config'
26
+ #
27
+ # @example `~/.yard.config`
28
+ #
29
+ # yard-cucumber:
30
+ # menus: [ 'features', 'tags', 'step definitions', 'steps' ]
31
+ #
32
+ def menu_lists
33
+
34
+ menus = [ "features", "tags" ]
35
+
36
+ # load the yard-cucumber menus defined in the configuration file
37
+ if YARD::Config.options["yard-cucumber"] and YARD::Config.options["yard-cucumber"]["menus"]
38
+ menus = YARD::Config.options["yard-cucumber"]["menus"]
39
+ end
40
+
41
+ menus.map {|menu_name| yard_cucumber_menus[menu_name] }.compact + super
42
+ end
43
+
44
+ #
45
+ # When a menu is specified in the yard configuration file, this hash contains
46
+ # the details about the menu necessary for it to be displayed.
47
+ #
48
+ # @see #menu_lists
49
+ #
50
+ def yard_cucumber_menus
51
+ { "features" => { :type => 'feature', :title => 'Features', :search_title => 'Features' },
52
+ "tags" => { :type => 'tag', :title => 'Tags', :search_title => 'Tags' },
53
+ "step definitions" => { :type => 'stepdefinition', :title => 'Step Definitions', :search_title => 'Step Defs' },
54
+ "steps" => { :type => 'step', :title => 'Steps', :search_title => 'Steps' } }
55
+ end
data/lib/yard-cucumber.rb CHANGED
@@ -4,7 +4,7 @@ require 'gherkin/formatter/tag_count_formatter'
4
4
 
5
5
 
6
6
  module CucumberInTheYARD
7
- VERSION = '2.0.3'
7
+ VERSION = '2.1.0'
8
8
  end
9
9
 
10
10
  require File.dirname(__FILE__) + "/yard/code_objects/cucumber/base.rb"
@@ -34,7 +34,7 @@ end
34
34
  require File.dirname(__FILE__) + "/yard/handlers/legacy/step_definition_handler.rb"
35
35
  require File.dirname(__FILE__) + "/yard/handlers/legacy/step_transform_handler.rb"
36
36
 
37
- require File.dirname(__FILE__) + "/yard/parser/source_parser.rb"
37
+ #require File.dirname(__FILE__) + "/yard/parser/source_parser.rb"
38
38
  require File.dirname(__FILE__) + "/yard/templates/helpers/base_helper.rb"
39
39
 
40
40
  require File.dirname(__FILE__) + "/yard/server/adapter.rb"
@@ -7,45 +7,89 @@ module YARD::CodeObjects
7
7
 
8
8
  attr_reader :constants, :keyword, :source, :value, :literal_value
9
9
  attr_accessor :steps
10
-
10
+
11
+ # This defines an escape pattern within a string or regex:
12
+ # /^the first #{CONSTANT} step$/
13
+ #
14
+ # This is used below in the value to process it if there happen to be
15
+ # constants defined here.
16
+ #
17
+ # @note this does not handle the result of method calls
18
+ # @note this does not handle multiple constants within the same escaped area
19
+ #
11
20
  ESCAPE_PATTERN = /#\{\s*(\w+)\s*\}/ unless defined?(ESCAPE_PATTERN)
12
21
 
22
+ #
23
+ # When requesting a step tranformer object value, process it, it it hasn't
24
+ # alredy been processed, replacing any constants that may be lurking within
25
+ # the value.
26
+ #
27
+ # Processing it means looking for any escaped characters that happen to be
28
+ # CONSTANTS that could be matched and then replaced. This is done recursively
29
+ # as CONSTANTS can be defined with more CONSTANTS.
30
+ #
31
+ def value
32
+ unless @processed
33
+ @processed = true
34
+ until (nested = constants_from_value).empty?
35
+ nested.each {|n| @value.gsub!(value_regex(n),find_value_for_constant(n)) }
36
+ end
37
+ end
38
+
39
+ @value
40
+ end
41
+
42
+ #
43
+ # Set the literal value and the value of the step definition.
44
+ #
45
+ # The literal value is as it appears in the step definition file with any
46
+ # constants. The value, when retrieved will attempt to replace those
47
+ # constants with their regex or string equivalents to hopefully match more
48
+ # steps and step definitions.
49
+ #
50
+ #
13
51
  def value=(value)
14
52
  @literal_value = format_source(value)
15
53
  @value = format_source(value)
16
54
 
17
- until (nested = constants_from_value).empty?
18
- nested.each {|n| @value.gsub!(value_regex(n),find_value_for_constant(n)) }
19
- end
20
-
21
55
  @steps = []
56
+ value
22
57
  end
23
58
 
59
+ # Generate a regex with the step transformers value
24
60
  def regex
25
- @regex ||= /#{strip_regex_from(@value)}/
61
+ @regex ||= /#{strip_regex_from(value)}/
26
62
  end
27
-
63
+
64
+ # Look through the specified data for the escape pattern and return an array
65
+ # of those constants found. This defaults to the @value within step transformer
66
+ # as it is used internally, however, it can be called externally if it's
67
+ # needed somewhere.
28
68
  def constants_from_value(data=@value)
29
69
  data.scan(ESCAPE_PATTERN).flatten.collect { |value| value.strip }
30
70
  end
31
71
 
32
72
  protected
33
73
 
74
+ #
75
+ # Looking through all the constants in the registry and returning the value
76
+ # with the regex items replaced from the constnat if present
77
+ #
34
78
  def find_value_for_constant(name)
35
79
  constant = YARD::Registry.all(:constant).find{|c| c.name == name.to_sym }
36
80
  log.warn "StepTransformer#find_value_for_constant : Could not find the CONSTANT [#{name}] using the string value." unless constant
37
81
  constant ? strip_regex_from(constant.value) : name
38
82
  end
39
83
 
84
+ # Return a regex of the value
40
85
  def value_regex(value)
41
86
  /#\{\s*#{value}\s*\}/
42
87
  end
43
88
 
89
+ # Step the regex starting / and ending / from the value
44
90
  def strip_regex_from(value)
45
91
  value.gsub(/^\/|\/$/,'')
46
92
  end
47
-
48
-
49
93
  end
50
94
 
51
95
  end
@@ -13,9 +13,8 @@ module YARD
13
13
  end
14
14
  include Parser::Cucumber
15
15
  end
16
-
17
16
  end
18
-
17
+
19
18
  Processor.register_handler_namespace :feature, Cucumber
20
19
  end
21
20
  end
@@ -5,87 +5,125 @@ module YARD
5
5
 
6
6
  handles CodeObjects::Cucumber::Feature
7
7
 
8
- @@step_definitions = nil
9
- @@step_transforms = nil
10
-
11
8
  def process
9
+ #
10
+ # Features have already been created when they were parsed. So there
11
+ # is no need to process the feature furhter. Previously this is where
12
+ # feature steps were matched to step definitions and step definitions
13
+ # were matched to step transforms. This only worked if the feature
14
+ # files were were assured to be processed last which was accomplished
15
+ # by overriding YARD::SourceParser to make it load file in a similar
16
+ # order as Cucumber.
17
+ #
18
+ # As of YARD 0.7.0 this is no longer necessary as there are callbacks
19
+ # that can be registered after all the files have been loaded. That
20
+ # callback _after_parse_list_ is defined below and performs the
21
+ # functionality described above.
22
+ #
23
+ end
24
+
25
+ #
26
+ # Register, once, when that when all files are finished to perform
27
+ # the final matching of feature steps to step definitions and step
28
+ # definitions to step transforms.
29
+ #
30
+ YARD::Parser::SourceParser.after_parse_list do |files,globals|
31
+ # For every feature found in the Registry, find their steps and step
32
+ # definitions...
33
+ YARD::Registry.all(:feature).each do |feature|
34
+ log.debug "Finding #{feature.file} - steps, step definitions, and step transforms"
35
+ FeatureHandler.match_steps_to_step_definitions(feature)
36
+ end
37
+
38
+ end
39
+
40
+ class << self
12
41
 
13
- # Create a cache of all of the step definitions and the step transforms
14
- @@step_definitions = cache(:stepdefinition) unless @@step_definitions
15
- @@step_transforms = cache(:steptransform) unless @@step_transforms
42
+ @@step_definitions = nil
43
+ @@step_transforms = nil
16
44
 
17
- if statement
18
- # For the background and the scenario, find the steps that have definitions
19
- process_scenario(statement.background) if statement.background
45
+ def match_steps_to_step_definitions(statement)
46
+ # Create a cache of all of the step definitions and the step transforms
47
+ @@step_definitions = cache(:stepdefinition) unless @@step_definitions
48
+ @@step_transforms = cache(:steptransform) unless @@step_transforms
20
49
 
21
- statement.scenarios.each do |scenario|
22
- if scenario.outline?
23
- #log.info "Scenario Outline: #{scenario.value}"
24
- scenario.scenarios.each_with_index do |example,index|
25
- #log.info " * Processing Example #{index + 1}"
26
- process_scenario(example)
50
+ if statement
51
+ # For the background and the scenario, find the steps that have definitions
52
+ process_scenario(statement.background) if statement.background
53
+
54
+ statement.scenarios.each do |scenario|
55
+ if scenario.outline?
56
+ #log.info "Scenario Outline: #{scenario.value}"
57
+ scenario.scenarios.each_with_index do |example,index|
58
+ #log.info " * Processing Example #{index + 1}"
59
+ process_scenario(example)
60
+ end
61
+ else
62
+ process_scenario(scenario)
27
63
  end
28
- else
29
- process_scenario(scenario)
30
64
  end
31
- end
32
65
 
33
66
 
34
- else
35
- log.warn "Empty feature file. A feature failed to process correctly or contains no feature"
36
- end
37
-
38
- rescue YARD::Handlers::NamespaceMissingError
39
- rescue Exception => exception
40
- log.error "Skipping feature because an error has occurred."
41
- log.debug "\n#{exception}\n#{exception.backtrace.join("\n")}\n"
42
- end
67
+ else
68
+ log.warn "Empty feature file. A feature failed to process correctly or contains no feature"
69
+ end
43
70
 
44
- #
45
- # Store all comparable items with their compare_value as the key and the item as the value
46
- # - Reject any compare values that contain escapes #{} as that means they have unpacked constants
47
- #
48
- def cache(type)
49
- YARD::Registry.all(type).inject({}) do |hash,item|
50
- hash[item.regex] = item if item.regex
51
- hash
71
+ rescue YARD::Handlers::NamespaceMissingError
72
+ rescue Exception => exception
73
+ log.error "Skipping feature because an error has occurred."
74
+ log.debug "\n#{exception}\n#{exception.backtrace.join("\n")}\n"
52
75
  end
53
- end
54
-
55
76
 
56
- def process_scenario(scenario)
57
- scenario.steps.each {|step| process_step(step) }
58
- end
77
+ #
78
+ # Store all comparable items with their compare_value as the key and the item as the value
79
+ # - Reject any compare values that contain escapes #{} as that means they have unpacked constants
80
+ #
81
+ def cache(type)
82
+ YARD::Registry.all(type).inject({}) do |hash,item|
83
+ hash[item.regex] = item if item.regex
84
+ hash
85
+ end
86
+ end
59
87
 
60
- def process_step(step)
61
- match_step_to_step_definition_and_transforms(step)
88
+ # process a scenario
89
+ def process_scenario(scenario)
90
+ scenario.steps.each {|step| process_step(step) }
91
+ end
62
92
 
63
- end
93
+ # process a step
94
+ def process_step(step)
95
+ match_step_to_step_definition_and_transforms(step)
96
+ end
64
97
 
65
- def match_step_to_step_definition_and_transforms(step)
66
- @@step_definitions.each_pair do |stepdef,stepdef_object|
67
- stepdef_matches = step.value.match(stepdef)
68
-
69
- if stepdef_matches
70
- step.definition = stepdef_object
71
- stepdef_matches[-1..1].each do |match|
72
- @@step_transforms.each do |steptrans,steptransform_object|
73
- if steptrans.match(match)
74
- step.transforms << steptransform_object
75
- steptransform_object.steps << step
98
+ #
99
+ # Given a step object, attempt to match that step to a step
100
+ # transformation
101
+ #
102
+ def match_step_to_step_definition_and_transforms(step)
103
+ @@step_definitions.each_pair do |stepdef,stepdef_object|
104
+ stepdef_matches = step.value.match(stepdef)
105
+
106
+ if stepdef_matches
107
+ step.definition = stepdef_object
108
+ stepdef_matches[-1..1].each do |match|
109
+ @@step_transforms.each do |steptrans,steptransform_object|
110
+ if steptrans.match(match)
111
+ step.transforms << steptransform_object
112
+ steptransform_object.steps << step
113
+ end
76
114
  end
77
115
  end
116
+
117
+ # Step has been matched to step definition and step transforms
118
+ # TODO: If the step were to match again then we would be able to display ambigous step definitions
119
+ break
120
+
78
121
  end
79
-
80
- # Step has been matched to step definition and step transforms
81
- # TODO: If the step were to match again then we would be able to display ambigous step definitions
82
- break
83
-
122
+
84
123
  end
85
124
 
86
125
  end
87
126
 
88
-
89
127
  end
90
128
  end
91
129
  end
@@ -1,5 +1,4 @@
1
1
  class YARD::Handlers::Ruby::Legacy::StepDefinitionHandler < YARD::Handlers::Ruby::Legacy::Base
2
- #TODO: This needs to become language independent.
3
2
  STEP_DEFINITION_MATCH = /^((When|Given|And|Then)\s*(\/.+\/)\s+do(?:\s*\|.+\|)?\s*)$/ unless defined?(STEP_DEFINITION_MATCH)
4
3
  handles STEP_DEFINITION_MATCH
5
4
 
@@ -1,6 +1,42 @@
1
-
1
+ #
2
+ # Finds and processes all the step definitions defined in the ruby source
3
+ # code. By default the english gherkin language will be parsed.
4
+ #
5
+ # To override the language you can define the step definitions in the YARD
6
+ # configuration file `~./yard/config`:
7
+ #
8
+ # @example `~/.yard/config` with LOLCatz step definitions
9
+ #
10
+ # :"yard-cucumber":
11
+ # language:
12
+ # step_definitions: [ 'WEN', 'I CAN HAZ', 'AN', 'DEN' ]
13
+ #
14
+ # @example `~/.yard/config` with French step definitions
15
+ #
16
+ # :"yard-cucumber":
17
+ # language:
18
+ # step_definitions: [ 'Soit', 'Etantdonné', 'Lorsque', 'Lorsqu', 'Alors', 'Et' ]
19
+ #
2
20
  class YARD::Handlers::Ruby::StepDefinitionHandler < YARD::Handlers::Ruby::Base
3
- handles method_call(:When),method_call(:Given),method_call(:And),method_call(:Then)
21
+
22
+ #
23
+ # By default the english gherkin language will be parsed, however, if the
24
+ # YARD configuration file `~./yard/config` defines different step definition
25
+ # handlers those are used.
26
+ #
27
+ #
28
+ if YARD::Config.options["yard-cucumber"] and
29
+ YARD::Config.options["yard-cucumber"]["language"] and
30
+ YARD::Config.options["yard-cucumber"]["language"]["step_definitions"]
31
+
32
+ YARD::Config.options["yard-cucumber"]["language"]["step_definitions"].each do |step_word|
33
+ handles method_call(step_word.to_sym)
34
+ end
35
+
36
+ else
37
+ handles method_call(:When),method_call(:Given),method_call(:And),method_call(:Then)
38
+ end
39
+
4
40
 
5
41
  @@unique_name = 0
6
42
 
@@ -1,7 +1,18 @@
1
1
  module YARD::Parser::Cucumber
2
2
 
3
3
  class FeatureParser < YARD::Parser::Base
4
-
4
+
5
+ #
6
+ # Each found feature found is creates a new FeatureParser
7
+ #
8
+ # This logic was copied from the logic found in Cucumber to create the builder
9
+ # and then set up the formatter and parser. The difference is really the
10
+ # custom Cucumber::Parser::CityBuilder that is being used to parse the elements
11
+ # of the feature into YARD::CodeObjects.
12
+ #
13
+ # @param [<String>] source containing the string conents of the feauture file
14
+ # @param [<String>] file the filename that contains the source
15
+ #
5
16
  def initialize(source, file = '(stdin)')
6
17
 
7
18
  @builder = Cucumber::Parser::CityBuilder.new(file)
@@ -15,6 +26,13 @@ module YARD::Parser::Cucumber
15
26
  @feature = nil
16
27
  end
17
28
 
29
+ #
30
+ # When parse is called, the gherkin parser is executed and all the feature
31
+ # elements that are found are sent to the various methods in the
32
+ # Cucumber::Parser::CityBuilder. The result of which is the feature element
33
+ # that contains all the scenarios, steps, etc. associated with that feature.
34
+ #
35
+ # @see Cucumber::Parser::CityBuilder
18
36
  def parse
19
37
  begin
20
38
  @parser.parse(@source, @file, 0)
@@ -29,18 +47,24 @@ module YARD::Parser::Cucumber
29
47
  self
30
48
  end
31
49
 
50
+ #
51
+ # This is not used as all the work is done in the parse method
52
+ #
32
53
  def tokenize
33
54
 
34
55
  end
35
56
 
36
-
57
+ #
58
+ # The only enumeration that can be done here is returning the feature itself
59
+ #
37
60
  def enumerator
38
61
  [@feature]
39
62
  end
40
63
 
41
64
  end
42
65
 
43
-
66
+ #
67
+ # Register all feature files (.feature) to be processed with the above FeatureParser
44
68
  YARD::Parser::SourceParser.register_parser_type :feature, FeatureParser, 'feature'
45
69
 
46
70
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: yard-cucumber
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 2.0.3
5
+ version: 2.1.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Franklin Webber
@@ -43,7 +43,7 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: 0.6.3
46
+ version: 0.7.0
47
47
  type: :runtime
48
48
  version_requirements: *id003
49
49
  description: " \n YARD-Cucumber is a YARD extension that processes Cucumber Features, Scenarios, Steps,\n Step Definitions, Transforms, and Tags and provides a documentation interface that allows you\n easily view and investigate the test suite. This tools hopes to bridge the gap of being able\n to provide your feature descriptions to your Product Owners and Stakeholders. "
@@ -66,10 +66,12 @@ files:
66
66
  - example/child_feature/child.feature
67
67
  - example/child_feature/grandchild_feature/grandchild.feature
68
68
  - example/empty.feature
69
+ - example/french.feature
69
70
  - example/scenario.feature
70
71
  - example/scenario_outline.feature
71
72
  - example/step_definitions/example.step.rb
72
73
  - example/step_definitions/first.step.rb
74
+ - example/step_definitions/french_steps.rb
73
75
  - example/step_definitions/support/env.rb
74
76
  - example/step_definitions/support/env_support.rb
75
77
  - example/step_definitions/support/support.rb
@@ -93,17 +95,13 @@ files:
93
95
  - lib/templates/default/featuredirectory/html/setup.rb
94
96
  - lib/templates/default/featuretags/html/namespace.erb
95
97
  - lib/templates/default/featuretags/html/setup.rb
96
- - lib/templates/default/fulldoc/html/css/common.css
97
- - lib/templates/default/fulldoc/html/full_list.erb
98
+ - lib/templates/default/fulldoc/html/css/cucumber.css
98
99
  - lib/templates/default/fulldoc/html/full_list_features.erb
99
100
  - lib/templates/default/fulldoc/html/full_list_stepdefinitions.erb
100
101
  - lib/templates/default/fulldoc/html/full_list_steps.erb
101
102
  - lib/templates/default/fulldoc/html/full_list_tags.erb
102
- - lib/templates/default/fulldoc/html/index.erb
103
103
  - lib/templates/default/fulldoc/html/js/cucumber.js
104
104
  - lib/templates/default/fulldoc/html/setup.rb
105
- - lib/templates/default/layout/html/headers.erb
106
- - lib/templates/default/layout/html/search.erb
107
105
  - lib/templates/default/layout/html/setup.rb
108
106
  - lib/templates/default/requirements/html/alpha_table.erb
109
107
  - lib/templates/default/requirements/html/requirements.erb
@@ -134,7 +132,6 @@ files:
134
132
  - lib/yard/handlers/step_definition_handler.rb
135
133
  - lib/yard/handlers/step_transform_handler.rb
136
134
  - lib/yard/parser/cucumber/feature.rb
137
- - lib/yard/parser/source_parser.rb
138
135
  - lib/yard/server/adapter.rb
139
136
  - lib/yard/server/commands/list_command.rb
140
137
  - lib/yard/server/router.rb
@@ -144,7 +141,7 @@ homepage: http://github.com/burtlo/yard-cucumber
144
141
  licenses: []
145
142
 
146
143
  post_install_message: "\n\
147
- (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)\n\n Thank you for installing yard-cucumber 2.0.3 / 2011-05-22.\n \n Changes:\n \n * Updated Cucumber links to the new github organization\n \n\n\
144
+ (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)\n\n Thank you for installing yard-cucumber 2.1.0 / 2011-05-22.\n \n Changes:\n \n * YARD 0.7.0 compatibility (removed some monkey-patching)\n * Add more menus (Steps and Step Definitions)\n * Define step definitions in your language Internalization (Ruby 1.9.2)\n \n\n\
148
145
  (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::) (::)\n\n"
149
146
  rdoc_options:
150
147
  - --charset=UTF-8
@@ -1,36 +0,0 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
- <html>
4
- <head>
5
- <meta name="Content-Type" content="text/html; charset=<%= charset %>" />
6
- <link rel="stylesheet" href="css/full_list.css" type="text/css" media="screen" charset="utf-8" />
7
- <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
8
- <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
9
- <script type="text/javascript" charset="utf-8" src="js/full_list.js"></script>
10
- <base id="base_target" target="_parent" />
11
- </head>
12
- <body>
13
- <script type="text/javascript" charset="utf-8">
14
- if (window.top.frames.main) {
15
- document.getElementById('base_target').target = 'main';
16
- document.body.className = 'frames';
17
- }
18
- </script>
19
- <div id="content">
20
- <h1 id="full_list_header"><%= @list_title %></h1>
21
- <div id="nav">
22
- <a target="_self" href="feature_list.html">Features</a> |
23
- <a target="_self" href="tag_list.html">Tags</a> |
24
- <a target="_self" href="class_list.html">Classes</a> |
25
- <a target="_self" href="method_list.html">Methods</a> |
26
- <a target="_self" href="file_list.html">Files</a>
27
- </div>
28
- <div id="search">Search: <input type="text" /></div>
29
-
30
- <ul id="full_list" class="<%= @list_class || @list_type %>">
31
- <%= erb "full_list_#{@list_type}" %>
32
- </ul>
33
- </div>
34
- </body>
35
- </html>
36
-
@@ -1,24 +0,0 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
- <head>
5
- <%= T('layout/headers') %>
6
- </head>
7
- <body>
8
- <script type="text/javascript" charset="utf-8">
9
- if (window.top.frames.main) document.body.className = 'frames';
10
- </script>
11
-
12
- <div id="header">
13
- <%= T('layout/breadcrumb') %>
14
- <%= T('layout/search') %>
15
- <div class="clear"></div>
16
- </div>
17
-
18
- <iframe id="search_frame"></iframe>
19
-
20
- <div id="content"><%= yieldall %></div>
21
-
22
- <%= T('layout/footer') %>
23
- </body>
24
- </html>
@@ -1,14 +0,0 @@
1
- <meta http-equiv="Content-Type" content="text/html; charset=<%= charset %>" />
2
- <title><%= @page_title %></title>
3
- <link rel="stylesheet" href="<%= url_for("css/style.css") %>" type="text/css" media="screen" charset="utf-8" />
4
- <link rel="stylesheet" href="<%= url_for("css/common.css") %>" type="text/css" media="screen" charset="utf-8" />
5
- <% if @extra_css %>
6
- <link rel="stylesheet" href="<%= url_for @extra_css %>" type="text/css" media="screen" charset="utf-8" />
7
- <% end %>
8
- <script type="text/javascript" charset="utf-8">
9
- relpath = '<%= url_for('') %>';
10
- if (relpath != '') relpath += '/';
11
- </script>
12
- <% javascript_files.each do |js_file| %>
13
- <script type="text/javascript" charset="utf-8" src="<%= url_for(js_file) %>"></script>
14
- <% end %>
@@ -1,5 +0,0 @@
1
- <div id="search">
2
- <% search_fields.each do |id,name| %>
3
- <a id="<%= id %>" href="#"><%= name %></a>
4
- <% end %>
5
- </div>
@@ -1,58 +0,0 @@
1
- module YARD::Parser
2
-
3
- class SourceParser
4
- class << self
5
-
6
- #
7
- # Following the conventions of ordering as Cucumber performs it
8
- #
9
- # environment files - 'directory/support/env.rb,env_*.rb'
10
- # support files - 'directory/support/*.rb' (but not env prefixed)
11
- # directory files - 'directory/*.rb'
12
- #
13
- # feature files - 'directory/*.feature'
14
- #
15
- def order_by_cucumber_standards(*files)
16
-
17
- non_feature_files = files.reject {|x| x =~ /^.+\.feature$/}
18
- feature_files = files.find_all {|x| x =~ /^.+\.feature$/ }
19
-
20
- support_files = non_feature_files.find_all {|file| file =~ /support\/.+\.rb$/ }
21
- other_files = non_feature_files - support_files
22
-
23
- environment_files = support_files.find_all {|file| file =~ /support\/env.*\.rb$/ }
24
- environment_files.sort_by {|x| x.length if x }
25
-
26
- support_files = support_files - environment_files
27
-
28
- environment_files + support_files + other_files + feature_files
29
- end
30
-
31
- #
32
- # Overriding the parse_in_order method was necessary so that step definitions
33
- # match to steps utilizing the load ordering that is used by Cucumber.
34
- #
35
- def parse_in_order(*files)
36
-
37
- files = order_by_cucumber_standards(*files)
38
-
39
- while file = files.shift
40
- begin
41
- if file.is_a?(Array) && file.last.is_a?(Continuation)
42
- log.debug("Re-processing #{file.first}")
43
- file.last.call
44
- elsif file.is_a?(String)
45
- log.debug("Processing #{file}...")
46
- new(parser_type, true).parse(file)
47
- end
48
- rescue LoadOrderError => e
49
- # Out of order file. Push the context to the end and we'll call it
50
- files.push([file, e.message])
51
- end
52
- end
53
- end
54
-
55
- end
56
- end
57
-
58
- end