cucumber2rspec 0.1.0
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.
- data/bin/cucumber2rspec +20 -0
- data/lib/cucumber2rspec.rb +87 -0
- data/lib/cucumber2rspec/background.rb +29 -0
- data/lib/cucumber2rspec/feature.rb +35 -0
- data/lib/cucumber2rspec/scenario.rb +38 -0
- data/lib/cucumber2rspec/step.rb +159 -0
- metadata +108 -0
data/bin/cucumber2rspec
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#! /usr/bin/env ruby
|
|
2
|
+
require File.dirname(__FILE__) + '/../lib/cucumber2rspec'
|
|
3
|
+
require 'logger'
|
|
4
|
+
Cucumber2RSpec.logger = Logger.new(STDERR)
|
|
5
|
+
Cucumber2RSpec.logger.level = Logger::DEBUG
|
|
6
|
+
|
|
7
|
+
unless ARGV.length >= 2
|
|
8
|
+
puts "Usage: cucumber2rspec dir/with/step_definitions feature1 feature2 ..."
|
|
9
|
+
exit
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
steps_dir = ARGV.shift
|
|
13
|
+
Cucumber2RSpec.log { "Loading steps ..." }
|
|
14
|
+
Dir[File.join(steps_dir, '**', '*_steps.rb')].each {|step_file| require step_file }
|
|
15
|
+
|
|
16
|
+
feature = ARGV.shift
|
|
17
|
+
Cucumber2RSpec.log { "Feature to convert: #{ File.basename(feature) }" }
|
|
18
|
+
puts Cucumber2RSpec.translate_file feature
|
|
19
|
+
|
|
20
|
+
# vim:set ft=ruby:
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
|
2
|
+
|
|
3
|
+
# Last tested with cucumber (0.3.11, 0.3.3)
|
|
4
|
+
#
|
|
5
|
+
# Note: String#indent comes from cucumber
|
|
6
|
+
|
|
7
|
+
require 'rubygems'
|
|
8
|
+
require 'parse_tree'
|
|
9
|
+
require 'parse_tree_extensions'
|
|
10
|
+
require 'ruby2ruby'
|
|
11
|
+
require 'cucumber'
|
|
12
|
+
|
|
13
|
+
$step_match = method(:step_match)
|
|
14
|
+
|
|
15
|
+
# ...
|
|
16
|
+
module Cucumber2RSpec
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
attr_accessor :logger
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.log &block
|
|
23
|
+
logger.debug(&block) if logger
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.parser
|
|
27
|
+
Cucumber.load_language('en')
|
|
28
|
+
@parser ||= Cucumber::Parser::FeatureParser.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.step_match text
|
|
32
|
+
$step_match.call(text)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.translate feature_text
|
|
36
|
+
Cucumber2RSpec::Feature.new(feature_text).code
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.translate_file filepath
|
|
40
|
+
Cucumber2RSpec.log { "translate_file(#{filepath.inspect})" }
|
|
41
|
+
translate File.read(filepath)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.replace_in_sexp sexp, to_replace, replace_with
|
|
45
|
+
while sexp.include?(to_replace)
|
|
46
|
+
index = sexp.index(to_replace)
|
|
47
|
+
sexp.delete_at index
|
|
48
|
+
sexp.insert index, replace_with
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sexp.each do|obj|
|
|
52
|
+
replace_in_sexp(obj, to_replace, replace_with) if obj.is_a?(Sexp)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sexp
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.block_variable_names a_proc
|
|
59
|
+
sexp = a_proc.to_sexp
|
|
60
|
+
local_assignments = []
|
|
61
|
+
|
|
62
|
+
# this is where the variables hang out ...
|
|
63
|
+
if sexp[2] and sexp[2].is_a?(Sexp)
|
|
64
|
+
|
|
65
|
+
# there's only 1 local assigned variable
|
|
66
|
+
if sexp[2][0] == :lasgn
|
|
67
|
+
local_assignments << sexp[2][1]
|
|
68
|
+
|
|
69
|
+
# there's an array of locally assigned variables
|
|
70
|
+
elsif sexp[2][0] == :masgn and sexp[2][1][0] == :array # mass assignment using an array
|
|
71
|
+
array_of_variables = a_proc.to_sexp[2][1]
|
|
72
|
+
the_type = array_of_variables.shift
|
|
73
|
+
array_of_variables.each do |sub_sexp|
|
|
74
|
+
local_assignments << sub_sexp[1] if sub_sexp[0] == :lasgn
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
local_assignments
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
require 'cucumber2rspec/feature'
|
|
85
|
+
require 'cucumber2rspec/background'
|
|
86
|
+
require 'cucumber2rspec/scenario'
|
|
87
|
+
require 'cucumber2rspec/step'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Cucumber2RSpec #:nodoc:
|
|
2
|
+
|
|
3
|
+
# ...
|
|
4
|
+
class Background
|
|
5
|
+
attr_reader :feature, :_background
|
|
6
|
+
|
|
7
|
+
def initialize feature, cucumber_ast_background
|
|
8
|
+
@feature = feature
|
|
9
|
+
@_background = cucumber_ast_background # Cucumber::Ast::Background
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def steps
|
|
13
|
+
_background.instance_variable_get('@steps').map {|step| Step.new(self, step) }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def code_without_block
|
|
17
|
+
Step.code_for_steps(steps)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def code
|
|
21
|
+
Cucumber2RSpec.log { ' Background' }
|
|
22
|
+
the_code = " before do\n"
|
|
23
|
+
the_code << code_without_block.sub(/\n$/, '') # kill the last newline
|
|
24
|
+
the_code << ' end'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module Cucumber2RSpec #:nodoc:
|
|
2
|
+
|
|
3
|
+
# ...
|
|
4
|
+
class Feature
|
|
5
|
+
attr_reader :raw, :name, :_feature
|
|
6
|
+
|
|
7
|
+
def initialize raw_feature_text
|
|
8
|
+
@raw = raw_feature_text
|
|
9
|
+
@_feature = Cucumber2RSpec.parser.parse_or_fail(raw) # Cucumber::Ast::Feature
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
# @_feature.name returns "Feature: Foo\n In order to ..."
|
|
14
|
+
match = _feature.name.match(/Feature: ([\w ]+)/)
|
|
15
|
+
match[1] if match
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def background
|
|
19
|
+
_background = _feature.instance_variable_get('@background')
|
|
20
|
+
Background.new self, _background if _background
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def scenarios
|
|
24
|
+
_feature.instance_variable_get('@feature_elements').map {|scenario| Scenario.new(self, scenario) }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def code
|
|
28
|
+
Cucumber2RSpec.log { name }
|
|
29
|
+
the_code = 'describe ' + name.inspect + ' do' + "\n\n"
|
|
30
|
+
the_code << background.code + "\n\n" if background
|
|
31
|
+
scenarios.each {|scenario| the_code << scenario.code + "\n\n" }
|
|
32
|
+
the_code << "end"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Cucumber2RSpec #:nodoc:
|
|
2
|
+
|
|
3
|
+
# ...
|
|
4
|
+
class Scenario
|
|
5
|
+
attr_reader :feature, :_scenario
|
|
6
|
+
|
|
7
|
+
def initialize feature, cucumber_ast_scenario
|
|
8
|
+
@feature = feature
|
|
9
|
+
@_scenario = cucumber_ast_scenario # Cucumber::Ast::Scenario
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name
|
|
13
|
+
_scenario.name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def steps
|
|
17
|
+
_scenario.instance_variable_get('@steps').map {|step| Step.new(self, step) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def code_without_block
|
|
21
|
+
code = Step.code_for_steps(steps)
|
|
22
|
+
|
|
23
|
+
if background = feature.background
|
|
24
|
+
# remove the background code from the scenario
|
|
25
|
+
code = code.sub background.code_without_block.sub(/\n$/, ''), ''
|
|
26
|
+
else
|
|
27
|
+
code
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def code
|
|
32
|
+
Cucumber2RSpec.log { ' ' + name }
|
|
33
|
+
the_code = ' it ' + name.inspect + " do\n"
|
|
34
|
+
the_code << code_without_block
|
|
35
|
+
the_code << ' end'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
module Cucumber2RSpec #:nodoc:
|
|
2
|
+
|
|
3
|
+
# ...
|
|
4
|
+
class Step
|
|
5
|
+
attr_reader :scenario, :_step
|
|
6
|
+
|
|
7
|
+
def self.code_for_steps steps
|
|
8
|
+
the_code = ''
|
|
9
|
+
groups = {
|
|
10
|
+
'Given' => [],
|
|
11
|
+
'When' => [],
|
|
12
|
+
'Then' => []
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
last_keyword = 'Given'
|
|
16
|
+
steps.each do |step|
|
|
17
|
+
keyword = (step.keyword == 'And') ? last_keyword : step.keyword
|
|
18
|
+
groups[keyword] << step.code
|
|
19
|
+
last_keyword = step.keyword unless step.keyword == 'And'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
groups['Given'].each {|code| the_code << (code.indent(4) + "\n") }
|
|
23
|
+
the_code << "\n" if groups['Given'].length > 0
|
|
24
|
+
|
|
25
|
+
groups['When' ].each {|code| the_code << (code.indent(4) + "\n") }
|
|
26
|
+
the_code << "\n" if groups['When'].length > 0
|
|
27
|
+
|
|
28
|
+
groups['Then' ].each {|code| the_code << (code.indent(4) + "\n") }
|
|
29
|
+
the_code
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def initialize scenario, cucumber_ast_step_invocation
|
|
33
|
+
@scenario = scenario
|
|
34
|
+
@_step = cucumber_ast_step_invocation # Cucumber::Ast::StepInvocation
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def keyword
|
|
38
|
+
_step.respond_to?(:actual_keyword) ? _step.actual_keyword : _step.keyword
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def table
|
|
42
|
+
_step.multiline_arg if _step.respond_to?(:multiline_arg)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def has_table?
|
|
46
|
+
!! table
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def text
|
|
50
|
+
if has_table?
|
|
51
|
+
_step.to_sexp[ _step.to_sexp.length - 2 ] # the last argument will be the table!
|
|
52
|
+
else
|
|
53
|
+
_step.to_sexp.last
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def full_text
|
|
58
|
+
"#{keyword} #{text}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def _step_definition
|
|
62
|
+
Cucumber2RSpec.step_match(text).instance_variable_get('@step_definition')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def regexp
|
|
66
|
+
Regexp.new _step_definition.instance_variable_get('@regexp')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def raw_variable_names
|
|
70
|
+
Cucumber2RSpec.block_variable_names(the_proc)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def variable_names
|
|
74
|
+
# if has_table? then we ignore the last block variable (which is the table)
|
|
75
|
+
variables = raw_variable_names
|
|
76
|
+
variables.pop if has_table?
|
|
77
|
+
variables
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def table_variable_name
|
|
81
|
+
raw_variable_names.last if has_table?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def the_proc
|
|
85
|
+
_step_definition.instance_variable_get('@proc')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def matches
|
|
89
|
+
return [] if variable_names.empty?
|
|
90
|
+
|
|
91
|
+
match = text.match(regexp)
|
|
92
|
+
if match
|
|
93
|
+
matches = {}
|
|
94
|
+
if match.captures.length != variable_names.length
|
|
95
|
+
raise "Problem getting #{ variable_names.inspect } from #{ text.inspect }."
|
|
96
|
+
end
|
|
97
|
+
variable_names.each_with_index do |name, i|
|
|
98
|
+
matches[name] = match.captures[i]
|
|
99
|
+
end
|
|
100
|
+
matches
|
|
101
|
+
else
|
|
102
|
+
[]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def code
|
|
107
|
+
Cucumber2RSpec.log { ' ' + text }
|
|
108
|
+
|
|
109
|
+
if has_table?
|
|
110
|
+
# require cucumber and create a local variable defining the cucumber table
|
|
111
|
+
header = "require 'cucumber'\n"
|
|
112
|
+
header << "#{table_variable_name} = Cucumber::Ast::Table.new(#{ table.raw.inspect })\n"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
if variable_names.empty?
|
|
116
|
+
ruby = the_proc.to_ruby
|
|
117
|
+
|
|
118
|
+
if has_table?
|
|
119
|
+
# get rid of proc { |table| }
|
|
120
|
+
ruby = ruby.sub(/^proc \{ \|#{table_variable_name}\|\s+/, '').sub(/\s\}$/, '')
|
|
121
|
+
else
|
|
122
|
+
# get rid of proc { }
|
|
123
|
+
ruby = ruby.sub(/^proc \{\s+/, '').sub(/\s\}$/, '')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
else
|
|
127
|
+
# TODO replace with the_proc.to_sexp
|
|
128
|
+
sexp_for_proc = ParseTree.new.process(the_proc.to_ruby) # turn the proc into an Sexp
|
|
129
|
+
matches.each do |name, value|
|
|
130
|
+
str = Sexp.new(:str, value)
|
|
131
|
+
|
|
132
|
+
# s(:lvar, :var) => s(:str, "dogs")
|
|
133
|
+
Cucumber2RSpec.replace_in_sexp sexp_for_proc, Sexp.new(:lvar, name), str
|
|
134
|
+
|
|
135
|
+
# s(:evstr, s(:str, "dogs")) => s(:str, "dogs")
|
|
136
|
+
Cucumber2RSpec.replace_in_sexp sexp_for_proc, Sexp.new(:evstr, str), str
|
|
137
|
+
end
|
|
138
|
+
ruby = Ruby2Ruby.new.process(sexp_for_proc) # turn it back into ruby
|
|
139
|
+
if ruby =~ /^proc do/
|
|
140
|
+
# get rid of the proc do |x| end
|
|
141
|
+
ruby = ruby.sub(/^proc do \|([\w+, ]+)\|\n /, '').sub(/\send$/, '')
|
|
142
|
+
elsif ruby =~ /^proc \{ |/
|
|
143
|
+
# get rid of proc { |whatever, ...| }
|
|
144
|
+
ruby = ruby.sub(/^proc \{ \|([^\|]+)\|\s+/, '').sub(/\s\}$/, '')
|
|
145
|
+
elsif ruby =~ /^proc \{/
|
|
146
|
+
# get rid of proc { }
|
|
147
|
+
ruby = ruby.sub(/^proc \{\s+/, '').sub(/\s\}$/, '')
|
|
148
|
+
else
|
|
149
|
+
raise "Not sure how to clean up the code for: #{ ruby }"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
ruby = header + ruby if has_table?
|
|
154
|
+
|
|
155
|
+
# get rid of any spaces after any newlines
|
|
156
|
+
ruby.gsub(/\n[ ]+/, "\n")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cucumber2rspec
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 1
|
|
8
|
+
- 0
|
|
9
|
+
version: 0.1.0
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- remi
|
|
13
|
+
autorequire:
|
|
14
|
+
bindir: bin
|
|
15
|
+
cert_chain: []
|
|
16
|
+
|
|
17
|
+
date: 2010-03-31 00:00:00 -07:00
|
|
18
|
+
default_executable:
|
|
19
|
+
dependencies:
|
|
20
|
+
- !ruby/object:Gem::Dependency
|
|
21
|
+
name: cucumber
|
|
22
|
+
prerelease: false
|
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
24
|
+
requirements:
|
|
25
|
+
- - ">="
|
|
26
|
+
- !ruby/object:Gem::Version
|
|
27
|
+
segments:
|
|
28
|
+
- 0
|
|
29
|
+
- 3
|
|
30
|
+
- 11
|
|
31
|
+
version: 0.3.11
|
|
32
|
+
type: :runtime
|
|
33
|
+
version_requirements: *id001
|
|
34
|
+
- !ruby/object:Gem::Dependency
|
|
35
|
+
name: ParseTree
|
|
36
|
+
prerelease: false
|
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
segments:
|
|
42
|
+
- 3
|
|
43
|
+
- 0
|
|
44
|
+
- 3
|
|
45
|
+
version: 3.0.3
|
|
46
|
+
type: :runtime
|
|
47
|
+
version_requirements: *id002
|
|
48
|
+
- !ruby/object:Gem::Dependency
|
|
49
|
+
name: ruby2ruby
|
|
50
|
+
prerelease: false
|
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
segments:
|
|
56
|
+
- 1
|
|
57
|
+
- 2
|
|
58
|
+
- 2
|
|
59
|
+
version: 1.2.2
|
|
60
|
+
type: :runtime
|
|
61
|
+
version_requirements: *id003
|
|
62
|
+
description: Convert Cucumber features to RSpec
|
|
63
|
+
email: remi@remitaylor.com
|
|
64
|
+
executables:
|
|
65
|
+
- cucumber2rspec
|
|
66
|
+
extensions: []
|
|
67
|
+
|
|
68
|
+
extra_rdoc_files: []
|
|
69
|
+
|
|
70
|
+
files:
|
|
71
|
+
- lib/cucumber2rspec/background.rb
|
|
72
|
+
- lib/cucumber2rspec/step.rb
|
|
73
|
+
- lib/cucumber2rspec/scenario.rb
|
|
74
|
+
- lib/cucumber2rspec/feature.rb
|
|
75
|
+
- lib/cucumber2rspec.rb
|
|
76
|
+
- bin/cucumber2rspec
|
|
77
|
+
has_rdoc: true
|
|
78
|
+
homepage: http://github.com/devfu/cucumber2rspec
|
|
79
|
+
licenses: []
|
|
80
|
+
|
|
81
|
+
post_install_message:
|
|
82
|
+
rdoc_options: []
|
|
83
|
+
|
|
84
|
+
require_paths:
|
|
85
|
+
- lib
|
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
87
|
+
requirements:
|
|
88
|
+
- - ">="
|
|
89
|
+
- !ruby/object:Gem::Version
|
|
90
|
+
segments:
|
|
91
|
+
- 0
|
|
92
|
+
version: "0"
|
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
segments:
|
|
98
|
+
- 0
|
|
99
|
+
version: "0"
|
|
100
|
+
requirements: []
|
|
101
|
+
|
|
102
|
+
rubyforge_project:
|
|
103
|
+
rubygems_version: 1.3.6
|
|
104
|
+
signing_key:
|
|
105
|
+
specification_version: 3
|
|
106
|
+
summary: Convert Cucumber features to RSpec
|
|
107
|
+
test_files: []
|
|
108
|
+
|