rspec-kickstarter 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/rspec-kickstarter +2 -2
- data/lib/rspec_kickstarter/generator.rb +205 -141
- data/lib/rspec_kickstarter/version.rb +1 -1
- data/lib/rspec_kickstarter.rb +7 -0
- metadata +5 -7
data/bin/rspec-kickstarter
CHANGED
@@ -12,8 +12,8 @@ spec_dir = './spec'
|
|
12
12
|
|
13
13
|
opt = OptionParser.new
|
14
14
|
opt.on('-f', 'Create if absent or append to the existing spec') { |_| force_write = true }
|
15
|
-
opt.on('-n', 'Dry run mode') { |_| dry_run = true }
|
16
|
-
opt.on('-o VAL', 'Output directory') { |dir| spec_dir = dir }
|
15
|
+
opt.on('-n', 'Dry run mode (shows generated code to console)') { |_| dry_run = true }
|
16
|
+
opt.on('-o VAL', 'Output directory Output directory (default: ./spec)') { |dir| spec_dir = dir }
|
17
17
|
|
18
18
|
args = opt.parse(ARGV)
|
19
19
|
dir_or_file = args.first
|
@@ -4,128 +4,39 @@ require 'rdoc'
|
|
4
4
|
require 'rdoc/parser/ruby'
|
5
5
|
require 'rdoc/options'
|
6
6
|
require 'rdoc/stats'
|
7
|
+
require 'rspec_kickstarter'
|
7
8
|
|
8
|
-
|
9
|
-
class Generator
|
9
|
+
class RSpecKickstarter::Generator
|
10
10
|
|
11
|
-
|
11
|
+
attr_accessor :spec_dir
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def get_target(top_level)
|
18
|
-
c = top_level.classes.first
|
19
|
-
if c.nil?
|
20
|
-
m = top_level.modules.first
|
21
|
-
if m.nil?
|
22
|
-
top_level.is_a?(RDoc::NormalModule) ? top_level : nil
|
23
|
-
else
|
24
|
-
get_target(m)
|
25
|
-
end
|
26
|
-
else
|
27
|
-
c
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def get_complete_class_name(c, name = c.name)
|
32
|
-
if !c.parent.name.nil? && c.parent.is_a?(RDoc::NormalModule)
|
33
|
-
get_complete_class_name(c.parent, "#{c.parent.name}::#{name}")
|
34
|
-
else
|
35
|
-
name
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def instance_name(c)
|
40
|
-
c.name.gsub(/::/, '/').
|
41
|
-
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
42
|
-
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
43
|
-
tr("-", "_").
|
44
|
-
downcase
|
45
|
-
end
|
46
|
-
|
47
|
-
def to_param_names_array(params)
|
48
|
-
params.split(',').map { |p| p.gsub(/[\(\)\s]/, '').gsub(/=.+$/, '') }.reject { |p| p.nil? || p.empty? }
|
49
|
-
end
|
50
|
-
|
51
|
-
def get_params_initialization_code(method)
|
52
|
-
code = to_param_names_array(method.params).map { |p| " #{p} = stub('#{p}')" }.join("\n")
|
53
|
-
code.empty? ? "" : "\n#{code}"
|
54
|
-
end
|
55
|
-
|
56
|
-
def get_instantiation_code(c, method)
|
57
|
-
if method.singleton
|
58
|
-
""
|
59
|
-
else
|
60
|
-
constructor = c.method_list.find { |m| m.name == 'new' }
|
61
|
-
if constructor.nil?
|
62
|
-
"\n #{instance_name(c)} = #{get_complete_class_name(c)}.new"
|
63
|
-
else
|
64
|
-
get_params_initialization_code(constructor) +
|
65
|
-
"\n #{instance_name(c)} = #{get_complete_class_name(c)}.new(#{to_param_names_array(constructor.params).join(', ')})"
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def get_method_invocation_code(c, method)
|
71
|
-
if method.singleton
|
72
|
-
"#{get_complete_class_name(c)}.#{method.name}(#{to_param_names_array(method.params).join(', ')})#{get_block_code(method)}"
|
73
|
-
else
|
74
|
-
"#{instance_name(c)}.#{method.name}(#{to_param_names_array(method.params).join(', ')})#{get_block_code(method)}"
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def get_block_code(method)
|
79
|
-
if method.block_params.nil? || method.block_params.empty?
|
80
|
-
""
|
81
|
-
else
|
82
|
-
" { |#{method.block_params}| }"
|
83
|
-
end
|
84
|
-
end
|
13
|
+
def initialize(spec_dir = './spec')
|
14
|
+
@spec_dir = spec_dir.gsub(/\/$/, '')
|
15
|
+
end
|
85
16
|
|
86
|
-
|
17
|
+
def write_spec(file_path, force_write = false, dry_run = false)
|
87
18
|
|
88
|
-
|
19
|
+
top_level = get_ruby_parser(file_path).scan
|
20
|
+
c = extract_target_class_or_module(top_level)
|
89
21
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
RDoc::TopLevel.reset()
|
94
|
-
end
|
22
|
+
if c.nil?
|
23
|
+
puts "#{file_path} skipped (Class/Module not found)."
|
24
|
+
else
|
95
25
|
|
96
|
-
|
97
|
-
if RUBY_VERSION.to_f >= 2.0
|
98
|
-
# Ruby 2.0 requires RDoc::Store internally.
|
99
|
-
store = RDoc::Store.new
|
100
|
-
top_level.store = store
|
101
|
-
stats = RDoc::Stats.new(store, 1)
|
102
|
-
else
|
103
|
-
stats = RDoc::Stats.new(1)
|
104
|
-
end
|
26
|
+
spec_path = get_spec_path(file_path)
|
105
27
|
|
106
|
-
|
107
|
-
|
108
|
-
file_path,
|
109
|
-
body,
|
110
|
-
RDoc::Options.new,
|
111
|
-
stats
|
112
|
-
)
|
113
|
-
top_level = parser.scan
|
114
|
-
c = get_target(top_level)
|
115
|
-
|
116
|
-
if c.nil?
|
117
|
-
puts "#{file_path} skipped (Class/Module not found)."
|
118
|
-
else
|
28
|
+
if force_write && File.exist?(spec_path)
|
29
|
+
# Append to the existing spec or skip
|
119
30
|
|
120
|
-
|
31
|
+
existing_spec = File.read(spec_path)
|
32
|
+
racking_methods = c.method_list
|
33
|
+
.select { |m| m.visibility == :public }
|
34
|
+
.reject { |m| existing_spec.match(m.name) }
|
121
35
|
|
122
|
-
if
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
puts "#{spec_path} skipped."
|
127
|
-
else
|
128
|
-
additional_spec = <<SPEC
|
36
|
+
if racking_methods.empty?
|
37
|
+
puts "#{spec_path} skipped."
|
38
|
+
else
|
39
|
+
additional_spec = <<SPEC
|
129
40
|
#{racking_methods.map { |method|
|
130
41
|
<<EACH_SPEC
|
131
42
|
# TODO auto-generated
|
@@ -138,34 +49,38 @@ module RSpecKickstarter
|
|
138
49
|
EACH_SPEC
|
139
50
|
}.join("\n")}
|
140
51
|
SPEC
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
else
|
147
|
-
false
|
148
|
-
end
|
149
|
-
}.reverse.join("\n") + "\n" + additional_spec + "\nend\n"
|
150
|
-
if dry_run
|
151
|
-
puts "----- #{spec_path} -----"
|
152
|
-
puts code
|
52
|
+
last_end_not_found = true
|
53
|
+
code = existing_spec.split("\n").reverse.reject { |line|
|
54
|
+
if last_end_not_found
|
55
|
+
last_end_not_found = line.gsub(/#.+$/, '').strip != "end"
|
56
|
+
true
|
153
57
|
else
|
154
|
-
|
58
|
+
false
|
155
59
|
end
|
156
|
-
|
60
|
+
}.reverse.join("\n") + "\n" + additional_spec + "\nend\n"
|
61
|
+
if dry_run
|
62
|
+
puts "----- #{spec_path} -----"
|
63
|
+
puts code
|
64
|
+
else
|
65
|
+
File.open(spec_path, 'w') { |f| f.write(code) }
|
157
66
|
end
|
67
|
+
puts "#{spec_path} modified."
|
68
|
+
end
|
158
69
|
|
159
|
-
|
160
|
-
|
161
|
-
|
70
|
+
else
|
71
|
+
# Create a new spec
|
72
|
+
|
73
|
+
self_path = to_string_value_to_require(file_path)
|
74
|
+
code = <<SPEC
|
162
75
|
# -*- encoding: utf-8 -*-
|
163
76
|
require 'spec_helper'
|
164
77
|
require '#{self_path}'
|
165
78
|
|
166
79
|
describe #{get_complete_class_name(c)} do
|
167
80
|
|
168
|
-
#{c.method_list
|
81
|
+
#{c.method_list
|
82
|
+
.select { |m| m.visibility == :public }
|
83
|
+
.map { |method|
|
169
84
|
<<EACH_SPEC
|
170
85
|
# TODO auto-generated
|
171
86
|
describe '#{method.name}' do
|
@@ -178,24 +93,173 @@ EACH_SPEC
|
|
178
93
|
}.join("\n")}
|
179
94
|
end
|
180
95
|
SPEC
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
96
|
+
if dry_run
|
97
|
+
puts "----- #{spec_path} -----"
|
98
|
+
puts code
|
99
|
+
else
|
100
|
+
if File.exist?(spec_path)
|
101
|
+
puts "#{spec_path} already exists."
|
185
102
|
else
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
FileUtils.mkdir_p(File.dirname(spec_path))
|
190
|
-
File.open(spec_path, 'w') { |f| f.write(code) }
|
191
|
-
puts "#{spec_path} created."
|
192
|
-
end
|
103
|
+
FileUtils.mkdir_p(File.dirname(spec_path))
|
104
|
+
File.open(spec_path, 'w') { |f| f.write(code) }
|
105
|
+
puts "#{spec_path} created."
|
193
106
|
end
|
194
107
|
end
|
195
108
|
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Creates new RDoc::Parser::Ruby instance.
|
115
|
+
#
|
116
|
+
def get_ruby_parser(file_path)
|
117
|
+
top_level = RDoc::TopLevel.new(file_path)
|
118
|
+
if RUBY_VERSION.to_f < 2.0
|
119
|
+
# reset is removed since 2.0
|
120
|
+
RDoc::TopLevel.reset()
|
121
|
+
end
|
122
|
+
|
123
|
+
# RDoc::Stats initialization
|
124
|
+
if RUBY_VERSION.to_f >= 2.0
|
125
|
+
# Ruby 2.0 requires RDoc::Store internally.
|
126
|
+
store = RDoc::Store.new
|
127
|
+
top_level.store = store
|
128
|
+
stats = RDoc::Stats.new(store, 1)
|
129
|
+
else
|
130
|
+
stats = RDoc::Stats.new(1)
|
131
|
+
end
|
132
|
+
|
133
|
+
RDoc::Parser::Ruby.new(
|
134
|
+
top_level,
|
135
|
+
file_path,
|
136
|
+
File.read(file_path),
|
137
|
+
RDoc::Options.new,
|
138
|
+
stats
|
139
|
+
)
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Extracts RDoc::NormalClass/RDoc::NormalModule from RDoc::TopLevel.
|
144
|
+
#
|
145
|
+
def extract_target_class_or_module(top_level)
|
146
|
+
c = top_level.classes.first
|
147
|
+
if c.nil?
|
148
|
+
m = top_level.modules.first
|
149
|
+
if m.nil?
|
150
|
+
top_level.is_a?(RDoc::NormalModule) ? top_level : nil
|
151
|
+
else
|
152
|
+
extract_target_class_or_module(m)
|
153
|
+
end
|
154
|
+
else
|
155
|
+
c
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Gets the complete class name from RDoc::NormalClass/RDoc::NormalModule instance.
|
161
|
+
#
|
162
|
+
def get_complete_class_name(c, name = c.name)
|
163
|
+
if !c.parent.name.nil? && c.parent.is_a?(RDoc::NormalModule)
|
164
|
+
get_complete_class_name(c.parent, "#{c.parent.name}::#{name}")
|
165
|
+
else
|
166
|
+
name
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
#
|
171
|
+
# Returns spec file path.
|
172
|
+
# e.g. "lib/foo/bar_baz.rb" -> "spec/foo/bar_baz_spec.rb"
|
173
|
+
#
|
174
|
+
def get_spec_path(file_path)
|
175
|
+
spec_dir + '/' + file_path.gsub(/^\.\//, '').gsub(/^(lib\/)|(app\/)/, '').gsub(/\.rb$/, '_spec.rb')
|
176
|
+
end
|
177
|
+
|
178
|
+
#
|
179
|
+
# Returns string value to require.
|
180
|
+
# e.g. "lib/foo/bar_baz.rb" -> "foo/bar_baz"
|
181
|
+
#
|
182
|
+
def to_string_value_to_require(file_path)
|
183
|
+
file_path.gsub(/^(lib\/)|(app\/)/, '').gsub(/\.rb$/, '')
|
184
|
+
end
|
185
|
+
|
186
|
+
#
|
187
|
+
# Returns snake_case name.
|
188
|
+
# e.g. FooBar -> "foo_bar"
|
189
|
+
#
|
190
|
+
def instance_name(c)
|
191
|
+
c.name
|
192
|
+
.gsub(/::/, '/')
|
193
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
194
|
+
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
|
195
|
+
.tr("-", "_")
|
196
|
+
.downcase
|
197
|
+
end
|
198
|
+
|
199
|
+
#
|
200
|
+
# Extracts parameter names as an *Array*.
|
201
|
+
# e.g. "()" -> []
|
202
|
+
# e.g. "(a, b = 'foo')" -> ["a", "b"]
|
203
|
+
#
|
204
|
+
def to_param_names_array(params)
|
205
|
+
params.split(',').map { |p| p.gsub(/[\(\)\s]/, '').gsub(/=.+$/, '') }.reject { |p| p.nil? || p.empty? }
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# Code generation
|
210
|
+
#
|
211
|
+
|
212
|
+
#
|
213
|
+
# e.g.
|
214
|
+
# a = stub('a')
|
215
|
+
# b = stub('b')
|
216
|
+
# bar_baz = BarBaz.new(a, b)
|
217
|
+
#
|
218
|
+
def get_instantiation_code(c, method)
|
219
|
+
if method.singleton
|
220
|
+
""
|
221
|
+
else
|
222
|
+
constructor = c.method_list.find { |m| m.name == 'new' }
|
223
|
+
if constructor.nil?
|
224
|
+
"\n #{instance_name(c)} = #{get_complete_class_name(c)}.new"
|
225
|
+
else
|
226
|
+
get_params_initialization_code(constructor) +
|
227
|
+
"\n #{instance_name(c)} = #{get_complete_class_name(c)}.new(#{to_param_names_array(constructor.params).join(', ')})"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
196
231
|
|
232
|
+
#
|
233
|
+
# e.g.
|
234
|
+
# a = stub('a')
|
235
|
+
# b = stub('b')
|
236
|
+
#
|
237
|
+
def get_params_initialization_code(method)
|
238
|
+
code = to_param_names_array(method.params).map { |p| " #{p} = stub('#{p}')" }.join("\n")
|
239
|
+
code.empty? ? "" : "\n#{code}"
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# e.g. BarBaz.do_something(a, b) { |c| }
|
244
|
+
#
|
245
|
+
def get_method_invocation_code(c, method)
|
246
|
+
if method.singleton
|
247
|
+
"#{get_complete_class_name(c)}.#{method.name}(#{to_param_names_array(method.params).join(', ')})#{get_block_code(method)}"
|
248
|
+
else
|
249
|
+
"#{instance_name(c)}.#{method.name}(#{to_param_names_array(method.params).join(', ')})#{get_block_code(method)}"
|
197
250
|
end
|
251
|
+
end
|
198
252
|
|
253
|
+
#
|
254
|
+
# e.g. { |a, b| }
|
255
|
+
#
|
256
|
+
def get_block_code(method)
|
257
|
+
if method.block_params.nil? || method.block_params.empty?
|
258
|
+
""
|
259
|
+
else
|
260
|
+
" { |#{method.block_params}| }"
|
261
|
+
end
|
199
262
|
end
|
263
|
+
|
200
264
|
end
|
201
265
|
|
data/lib/rspec_kickstarter.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
+
require "rspec_kickstarter/generator"
|
3
4
|
require "rspec_kickstarter/version"
|
4
5
|
|
5
6
|
module RSpecKickstarter
|
7
|
+
|
8
|
+
def self.write_spec(file_path, spec_dir, force_write = false, dry_run = false)
|
9
|
+
generator = RSpecKickstarter::Generator.new(spec_dir)
|
10
|
+
generator.write_spec(file_path, force_write, dry_run)
|
11
|
+
end
|
12
|
+
|
6
13
|
end
|
7
14
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-kickstarter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,13 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-28 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description:
|
14
|
+
description: rspec-kickstarter supports you writing tests for existing code.
|
15
15
|
email:
|
16
|
-
-
|
17
|
-
- - MIT
|
18
|
-
- RSpec kickstarter generates spec files for existing code.
|
16
|
+
- seratch@gmail.com
|
19
17
|
executables:
|
20
18
|
- rspec-kickstarter
|
21
19
|
extensions: []
|
@@ -49,5 +47,5 @@ rubyforge_project:
|
|
49
47
|
rubygems_version: 1.8.24
|
50
48
|
signing_key:
|
51
49
|
specification_version: 3
|
52
|
-
summary:
|
50
|
+
summary: rspec-kickstarter supports you writing tests for existing code.
|
53
51
|
test_files: []
|