cucumber2rspec 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|