rspec-kickstarter 0.0.9 → 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/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: []
|