ruby_scribe 0.0.1
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/LICENSE +20 -0
- data/README.rdoc +104 -0
- data/Rakefile +14 -0
- data/TODO.rdoc +2 -0
- data/lib/ruby_scribe.rb +8 -0
- data/lib/ruby_scribe/emitter.rb +446 -0
- data/lib/ruby_scribe/emitter_helpers.rb +28 -0
- data/lib/ruby_scribe/ext/sexp.rb +9 -0
- data/lib/ruby_scribe/preprocessor.rb +9 -0
- data/lib/ruby_scribe/runner.rb +32 -0
- data/lib/ruby_scribe/version.rb +3 -0
- data/lib/tasks/scribe.rake +29 -0
- data/spec/examples/simple_class_with_methods.rb +14 -0
- data/spec/proprocessor_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/strategy_spec.rb +9 -0
- metadata +129 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Ben Hughes
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
= Ruby Scribe
|
2
|
+
|
3
|
+
== Introduction
|
4
|
+
|
5
|
+
Seattle RB's ruby_parser is a great tool for parsing ruby code into syntax trees. Ruby Scribe is the reverse of this - a tool for taking an S-expression in Ruby and emitting clean code. It does this by providing a series of rules that intelligently formats code much as a real developer would.
|
6
|
+
|
7
|
+
== Example
|
8
|
+
|
9
|
+
Imagine this crappily-formatted Ruby code:
|
10
|
+
|
11
|
+
module RubyScribe
|
12
|
+
|
13
|
+
|
14
|
+
# My Comment
|
15
|
+
class Sample < Base;
|
16
|
+
def method; do_something_here; end
|
17
|
+
|
18
|
+
if(new_record?) then
|
19
|
+
puts "Yes"
|
20
|
+
else
|
21
|
+
puts 'No'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Here is the syntax tree for the above:
|
27
|
+
|
28
|
+
s(:module,
|
29
|
+
:RubyScribe,
|
30
|
+
s(:scope,
|
31
|
+
s(:class,
|
32
|
+
:Sample,
|
33
|
+
s(:const, :Base),
|
34
|
+
s(:scope,
|
35
|
+
s(:block,
|
36
|
+
s(:defn,
|
37
|
+
:method,
|
38
|
+
s(:args),
|
39
|
+
s(:scope, s(:block, s(:call, nil, :do_something_here, s(:arglist))))),
|
40
|
+
s(:if,
|
41
|
+
s(:call, nil, :new_record?, s(:arglist)),
|
42
|
+
s(:call, nil, :puts, s(:arglist, s(:str, "Yes"))),
|
43
|
+
s(:call, nil, :puts, s(:arglist, s(:str, "No")))))))))
|
44
|
+
|
45
|
+
Parse that with RubyParser, then emit it with RubyScribe:
|
46
|
+
|
47
|
+
sexp = RubyParser.new.parse(File.read("bad_code.rb"))
|
48
|
+
RubyScribe::Emitter.new.emit(sexp)
|
49
|
+
|
50
|
+
And out pops this:
|
51
|
+
|
52
|
+
module RubyScribe
|
53
|
+
# My Comment
|
54
|
+
class Sample < Base
|
55
|
+
def method
|
56
|
+
do_something_here
|
57
|
+
end
|
58
|
+
|
59
|
+
if new_record?
|
60
|
+
puts "Yes"
|
61
|
+
else
|
62
|
+
puts "No"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
== Applications
|
69
|
+
|
70
|
+
The combination of these two tools allows some other interesting tools to be written. One of example tool (and indeed the entire reason I started this project) is my rspecify tool, which can take a test directory with code written using Test::Unit and, via a Ruby Scribe Preprocessor, convert it into RSpec, automating 99% of what a human developer would normally have to do.
|
71
|
+
|
72
|
+
Future projects using these two gems could be:
|
73
|
+
* Automated refactoring tools
|
74
|
+
* Tools that take a project and try to apply a set of "coding standards"
|
75
|
+
* Tools that produce metrics on how much your code differs from the project's "coding standards" implementation.
|
76
|
+
|
77
|
+
== Usage
|
78
|
+
|
79
|
+
The entire project simply takes an incoming Sexp object (from the ruby_parser project) and emits a single string. To do this just use an Emitter:
|
80
|
+
|
81
|
+
RubyScribe::Emitter.new.emit(sexp) # => "module Something..."
|
82
|
+
|
83
|
+
== Emitter Implementation
|
84
|
+
|
85
|
+
The +Emitter+ class is nothing but a bunch of recursion. The main emit method is a big case block to offload handling of individual types to separate methods which handle and compose a big string, all through recursion.
|
86
|
+
|
87
|
+
To extend or implement your own Emitter, just subclass +RubyScribe::Emitter+ and override the necessary methods.
|
88
|
+
|
89
|
+
== Preprocess Implementation
|
90
|
+
|
91
|
+
This feature is not developed yet, but is intended on presenting a standard recursion-based class for implementing transformations of S-expressions.
|
92
|
+
|
93
|
+
== Known Issues
|
94
|
+
|
95
|
+
* Anything involving order of operations currently much surround the expression in ( ). Will probably expand later to omit this when order of operations is implied, but this requires a context stack.
|
96
|
+
* Elsif currently does not work as you'd expect and instead embeds another if block inside of the outer one's "else". This is how if statements are presented via ruby_parser.
|
97
|
+
* Some of the more obscure types are not implemented.
|
98
|
+
* Only comments on methods, class, and module declarations are retained. This is actually a limitation of ruby_parser as for whatever reason
|
99
|
+
in-line comments cannot be parsed correctly.
|
100
|
+
|
101
|
+
== Future Features
|
102
|
+
|
103
|
+
* Maintain a context stack so emit methods can emit different content dependent on context in the stack
|
104
|
+
* Configuration options for things such as block style, preference for quote style, etc.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
require "rake/rdoctask"
|
4
|
+
|
5
|
+
desc "Generate documentation for the plugin."
|
6
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
7
|
+
rdoc.rdoc_dir = "rdoc"
|
8
|
+
rdoc.title = "has_draft"
|
9
|
+
rdoc.options << "--line-numbers" << "--inline-source"
|
10
|
+
rdoc.rdoc_files.include('README')
|
11
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each { |ext| load ext }
|
data/TODO.rdoc
ADDED
data/lib/ruby_scribe.rb
ADDED
@@ -0,0 +1,446 @@
|
|
1
|
+
require "active_support/core_ext"
|
2
|
+
|
3
|
+
module RubyScribe
|
4
|
+
# Takes a proprocessed S-expression and emits formatted Ruby code
|
5
|
+
class Emitter
|
6
|
+
include EmitterHelpers
|
7
|
+
|
8
|
+
cattr_accessor :methods_without_parenthesis
|
9
|
+
self.methods_without_parenthesis = %w(require gem puts attr_accessor cattr_accessor delegate alias_method alias)
|
10
|
+
|
11
|
+
SYNTACTIC_METHODS = ['+', '-', '<<', '==', '===', '>', '<']
|
12
|
+
|
13
|
+
def emit(e)
|
14
|
+
return "" unless e
|
15
|
+
return e if e.is_a?(String)
|
16
|
+
return e.to_s if e.is_a?(Symbol)
|
17
|
+
|
18
|
+
case e.kind
|
19
|
+
when :block
|
20
|
+
emit_block(e)
|
21
|
+
when :scope
|
22
|
+
emit_scope(e)
|
23
|
+
when :rescue
|
24
|
+
emit_rescue(e)
|
25
|
+
when :module
|
26
|
+
emit_module_definition(e)
|
27
|
+
when :class
|
28
|
+
emit_class_definition(e)
|
29
|
+
when :sclass
|
30
|
+
emit_self_class_definition(e)
|
31
|
+
when :defn
|
32
|
+
emit_method_definition(e)
|
33
|
+
when :defs
|
34
|
+
emit_method_with_receiver_definition(e)
|
35
|
+
when :args
|
36
|
+
emit_method_argument_list(e)
|
37
|
+
when :call
|
38
|
+
emit_method_call(e)
|
39
|
+
when :arglist
|
40
|
+
emit_argument_list(e)
|
41
|
+
when :attrasgn
|
42
|
+
emit_attribute_assignment(e)
|
43
|
+
when :masgn
|
44
|
+
emit_multiple_assignment(e)
|
45
|
+
when :cdecl
|
46
|
+
emit_constant_declaration(e)
|
47
|
+
when :if, :unless
|
48
|
+
emit_conditional_block(e)
|
49
|
+
when :case
|
50
|
+
emit_case_statement(e)
|
51
|
+
when :when
|
52
|
+
emit_case_when_statement(e)
|
53
|
+
when :while, :until
|
54
|
+
emit_loop_block(e)
|
55
|
+
when :for
|
56
|
+
emit_for_block(e)
|
57
|
+
when :lasgn, :iasgn
|
58
|
+
emit_assignment_expression(e)
|
59
|
+
when :op_asgn_or
|
60
|
+
emit_optional_assignment_or_expression(e)
|
61
|
+
when :op_asgn_and
|
62
|
+
emit_optional_assignment_and_expression(e)
|
63
|
+
when :or, :and
|
64
|
+
emit_binary_expression(e)
|
65
|
+
when :iter
|
66
|
+
emit_block_invocation(e)
|
67
|
+
when :defined
|
68
|
+
emit_defined_invocation(e)
|
69
|
+
else
|
70
|
+
emit_token(e)
|
71
|
+
end || ""
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
protected
|
76
|
+
|
77
|
+
def emit_comments(comments)
|
78
|
+
comments.present? ? (comments.split("\n").join(nl) + nl) : ""
|
79
|
+
end
|
80
|
+
|
81
|
+
def emit_block(e)
|
82
|
+
return "" if e.body[0] == s(:nil)
|
83
|
+
|
84
|
+
# Special case for handling rescue blocks around entire methods (excluding the indent):
|
85
|
+
return emit_method_rescue(e.body[0]) if e.body.size == 1 and e.body[0].kind == :rescue
|
86
|
+
|
87
|
+
e.body.map do |child|
|
88
|
+
emit_block_member_prefix(e.body, child) +
|
89
|
+
emit(child)
|
90
|
+
end.join(nl)
|
91
|
+
end
|
92
|
+
|
93
|
+
def emit_block_member_prefix(members, current_member)
|
94
|
+
previous_member_index = members.index(current_member) - 1
|
95
|
+
previous_member = previous_member_index >= 0 ? members[previous_member_index] : nil
|
96
|
+
return "" unless previous_member
|
97
|
+
|
98
|
+
[
|
99
|
+
[[:defn, :defs, :iter, :class, :module, :rescue], [:defn, :defs, :iter, :class, :module, :call, :rescue]],
|
100
|
+
[[:call], [:defn, :defs, :iter, :class, :module]]
|
101
|
+
].each do |from, to|
|
102
|
+
return nl if (from == :any || from.include?(previous_member.kind)) && (to == :any || to.include?(current_member.kind))
|
103
|
+
end
|
104
|
+
|
105
|
+
if current_member.kind == :if && [:block_if, :block_unless].include?(determine_if_type(current_member))
|
106
|
+
return nl
|
107
|
+
elsif previous_member.kind == :if && [:block_if, :block_unless].include?(determine_if_type(previous_member))
|
108
|
+
return nl
|
109
|
+
end
|
110
|
+
|
111
|
+
""
|
112
|
+
end
|
113
|
+
|
114
|
+
def emit_scope(e)
|
115
|
+
emit(e.body[0])
|
116
|
+
end
|
117
|
+
|
118
|
+
def emit_rescue(e)
|
119
|
+
"begin" + indent { nl + emit(e.body[0]) } +
|
120
|
+
nl("rescue ") + indent { nl + emit(e.body[1].body[1]) } +
|
121
|
+
nl("end")
|
122
|
+
end
|
123
|
+
|
124
|
+
def emit_method_rescue(e)
|
125
|
+
emit(e.body[0]) +
|
126
|
+
indent(-2) { nl("rescue ") } +
|
127
|
+
nl + emit(e.body[1].body[1])
|
128
|
+
end
|
129
|
+
|
130
|
+
def emit_class_definition(e)
|
131
|
+
emit_comments(e.comments) +
|
132
|
+
"#{e.kind} #{e.body[0]}" +
|
133
|
+
(e.body[1] ? " < #{emit(e.body[1])}" : "") +
|
134
|
+
indent { nl + emit(e.body[2]) } +
|
135
|
+
nl("end")
|
136
|
+
end
|
137
|
+
|
138
|
+
def emit_self_class_definition(e)
|
139
|
+
"class << #{e.body[0]}" +
|
140
|
+
indent { nl + emit(e.body[1]) } +
|
141
|
+
nl("end")
|
142
|
+
end
|
143
|
+
|
144
|
+
def emit_module_definition(e)
|
145
|
+
emit_comments(e.comments) +
|
146
|
+
"#{e.kind} #{e.body[0]}" +
|
147
|
+
indent { nl + emit(e.body[1]) } +
|
148
|
+
nl("end")
|
149
|
+
end
|
150
|
+
|
151
|
+
def emit_method_definition(e)
|
152
|
+
emit_comments(e.comments) +
|
153
|
+
"def #{e.body[0]}" +
|
154
|
+
(e.body[1].body.empty? ? "" : "(#{emit(e.body[1])})") +
|
155
|
+
indent { nl + emit(e.body[2]) } +
|
156
|
+
nl("end")
|
157
|
+
end
|
158
|
+
|
159
|
+
def emit_method_with_receiver_definition(e)
|
160
|
+
emit_comments(e.comments) +
|
161
|
+
"def #{emit(e.body[0])}.#{e.body[1]}" +
|
162
|
+
(e.body[2].body.empty? ? "" : "(#{emit(e.body[2])})") +
|
163
|
+
indent { nl + emit(e.body[3]) } +
|
164
|
+
nl("end")
|
165
|
+
end
|
166
|
+
|
167
|
+
def emit_method_argument_list(e)
|
168
|
+
[].tap do |array|
|
169
|
+
e.body.each do |child|
|
170
|
+
if child.is_a?(Sexp) and child.kind == :block
|
171
|
+
child.body.each do |body_child|
|
172
|
+
array[array.index(body_child.body[0])] = emit(body_child)
|
173
|
+
end
|
174
|
+
else
|
175
|
+
array << child
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end.join(", ")
|
179
|
+
end
|
180
|
+
|
181
|
+
def emit_method_call(e)
|
182
|
+
return emit_method_call_hash_access(e) if e.body[1] == :[]
|
183
|
+
return emit_method_call_hash_assignment(e) if e.body[1] == :[]=
|
184
|
+
|
185
|
+
emit_method_call_receiver(e) +
|
186
|
+
emit_method_call_name(e) +
|
187
|
+
emit_method_call_arguments(e)
|
188
|
+
end
|
189
|
+
|
190
|
+
def emit_method_call_receiver(e)
|
191
|
+
if e.body[0] && SYNTACTIC_METHODS.include?(e.body[1].to_s)
|
192
|
+
"#{emit(e.body[0])} "
|
193
|
+
elsif e.body[0]
|
194
|
+
"#{emit(e.body[0])}."
|
195
|
+
else
|
196
|
+
""
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def emit_method_call_name(e)
|
201
|
+
emit(e.body[1])
|
202
|
+
end
|
203
|
+
|
204
|
+
def emit_method_call_arguments(e)
|
205
|
+
if e.body[2].body.empty?
|
206
|
+
""
|
207
|
+
elsif self.class.methods_without_parenthesis.include?(e.body[1].to_s)
|
208
|
+
" " + emit(e.body[2])
|
209
|
+
elsif SYNTACTIC_METHODS.include?(e.body[1].to_s)
|
210
|
+
" " + emit(e.body[2])
|
211
|
+
else
|
212
|
+
"(" + emit(e.body[2]) + ")"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def emit_method_call_hash_access(e)
|
217
|
+
emit(e.body[0]) + "[" + emit(e.body[2]) + "]"
|
218
|
+
end
|
219
|
+
|
220
|
+
def emit_method_call_hash_assignment(e)
|
221
|
+
emit(e.body[0]) + "[" + emit(e.body[2].body[0]) + "] = " + emit(e.body[2].body[1])
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
def emit_argument_list(e)
|
226
|
+
e.body.map do |child|
|
227
|
+
if child == e.body[-1] && child.kind == :hash
|
228
|
+
emit_hash_body(child)
|
229
|
+
else
|
230
|
+
emit(child)
|
231
|
+
end
|
232
|
+
end.join(", ")
|
233
|
+
end
|
234
|
+
|
235
|
+
def emit_attribute_assignment(e)
|
236
|
+
return emit_method_call(e) if ['[]='].include?(e.body[1].to_s)
|
237
|
+
|
238
|
+
emit(e.body[0]) + "." + e.body[1].to_s.gsub(/=$/, "") + " = " + emit(e.body[2])
|
239
|
+
end
|
240
|
+
|
241
|
+
def emit_multiple_assignment(e)
|
242
|
+
left = e.body[0].body
|
243
|
+
right = e.body[1].body
|
244
|
+
|
245
|
+
left.map {|c| c.body[0] }.join(", ") + " = " + right.map {|c| emit(c) }.join(", ")
|
246
|
+
end
|
247
|
+
|
248
|
+
def emit_constant_declaration(e)
|
249
|
+
emit(e.body[0]) + " = " + emit(e.body[1])
|
250
|
+
end
|
251
|
+
|
252
|
+
def determine_if_type(e)
|
253
|
+
if e.body[1] && e.body[2] && e.body[0].line == e.body[1].try(:line) && e.line == e.body[2].try(:line)
|
254
|
+
:terinary
|
255
|
+
elsif e.body[1] && !e.body[2] && e.line == e.body[1].line && e.body[1].kind != :block
|
256
|
+
:dangling_if
|
257
|
+
elsif !e.body[1] && e.body[2] && e.line == e.body[2].line && e.body[2].kind != :block
|
258
|
+
:dangling_unless
|
259
|
+
elsif e.body[1]
|
260
|
+
:block_if
|
261
|
+
elsif e.body[2]
|
262
|
+
:block_unless
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def emit_conditional_block(e)
|
267
|
+
case determine_if_type(e)
|
268
|
+
when :terinary
|
269
|
+
"#{emit(e.body[0])} ? #{emit(e.body[1] || s(:nil))} : #{emit(e.body[2] || s(:nil))}"
|
270
|
+
when :dangling_if
|
271
|
+
"#{emit(e.body[1])} if #{emit(e.body[0])}"
|
272
|
+
when :dangling_unless
|
273
|
+
"#{emit(e.body[2])} unless #{emit(e.body[0])}"
|
274
|
+
when :block_if
|
275
|
+
"if #{emit(e.body[0])}" + indent { nl + emit(e.body[1]) } +
|
276
|
+
(e.body[2] ? (nl("else") + indent { nl + emit(e.body[2]) }) : "") +
|
277
|
+
nl("end")
|
278
|
+
when :block_unless
|
279
|
+
"unless #{emit(e.body[0])}" + indent { nl + emit(e.body[2]) } +
|
280
|
+
nl("end")
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def emit_case_statement(e)
|
285
|
+
"case #{emit(e.body.first)}" + e.body[1..-2].map {|c| emit(c) }.join + emit_case_else_statement(e.body[-1]) + nl("end")
|
286
|
+
end
|
287
|
+
|
288
|
+
def emit_case_when_statement(e)
|
289
|
+
nl("when #{emit_case_when_argument(e.body.first)}") + indent { nl + emit(e.body[1]) }
|
290
|
+
end
|
291
|
+
|
292
|
+
def emit_case_else_statement(e)
|
293
|
+
if e
|
294
|
+
nl("else") + indent { nl + emit(e) }
|
295
|
+
else
|
296
|
+
""
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def emit_case_when_argument(e)
|
301
|
+
emit(e).gsub(/^\[/, '').gsub(/\]$/, '')
|
302
|
+
end
|
303
|
+
|
304
|
+
def emit_loop_block(e)
|
305
|
+
"#{e.kind} #{e.body.first}" +
|
306
|
+
indent { emit(e.body[1]) } +
|
307
|
+
nl("end")
|
308
|
+
end
|
309
|
+
|
310
|
+
def emit_for_block(e)
|
311
|
+
"for #{e.body[1].body[0]} in #{emit(e.body[0])}" +
|
312
|
+
indent { nl + emit(e.body[2]) } +
|
313
|
+
nl("end")
|
314
|
+
end
|
315
|
+
|
316
|
+
def emit_assignment_expression(e)
|
317
|
+
"#{e.body[0]} = #{emit(e.body[1])}"
|
318
|
+
end
|
319
|
+
|
320
|
+
def emit_optional_assignment_or_expression(e)
|
321
|
+
emit(e.body[0]) + " ||= " + emit(e.body[1].body[1])
|
322
|
+
end
|
323
|
+
|
324
|
+
def emit_optional_assignment_and_expression(e)
|
325
|
+
emit(e.body[0]) + " &&= " + emit(e.body[1].body[1])
|
326
|
+
end
|
327
|
+
|
328
|
+
def emit_binary_expression(e)
|
329
|
+
"(" + emit(e.body[0]) + " " +
|
330
|
+
(e.kind == :and ? "&&" : "||") +
|
331
|
+
" " + emit(e.body[1]) + ")"
|
332
|
+
end
|
333
|
+
|
334
|
+
def emit_block_invocation(e)
|
335
|
+
emit(e.body[0]) + emit_block_invocation_body(e)
|
336
|
+
end
|
337
|
+
|
338
|
+
def emit_block_invocation_body(e)
|
339
|
+
# If it's on the same line, it should probably be shorthand form:
|
340
|
+
if e.line == e.body[2].try(:line)
|
341
|
+
" {#{emit_block_invocation_arguments(e)} #{emit(e.body[2])} }"
|
342
|
+
else
|
343
|
+
" do #{emit_block_invocation_arguments(e)}".gsub(/ +$/, '') +
|
344
|
+
indent { nl + emit(e.body[2]) } +
|
345
|
+
nl("end")
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def emit_block_invocation_arguments(e)
|
350
|
+
if e.body[1]
|
351
|
+
"|" + emit_assignments_as_arguments(e.body[1]) + "| "
|
352
|
+
else
|
353
|
+
""
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def emit_assignments_as_arguments(e)
|
358
|
+
if e.kind == :masgn
|
359
|
+
e.body[0].body.map {|c| emit_assignments_as_arguments(c) }.join(", ")
|
360
|
+
elsif e.kind == :lasgn
|
361
|
+
e.body[0].to_s
|
362
|
+
elsif e.kind == :splat
|
363
|
+
"*" + emit_assignments_as_arguments(e.body[0])
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def emit_defined_invocation(e)
|
368
|
+
"defined?(#{emit(e.body[0])})"
|
369
|
+
end
|
370
|
+
|
371
|
+
def emit_token(e)
|
372
|
+
case e.kind
|
373
|
+
when :str
|
374
|
+
'"' + e.body[0] + '"'
|
375
|
+
when :lit
|
376
|
+
e.body[0].inspect
|
377
|
+
when :const
|
378
|
+
e.body[0].to_s
|
379
|
+
when :lvar
|
380
|
+
e.body[0].to_s
|
381
|
+
when :ivar
|
382
|
+
e.body[0].to_s
|
383
|
+
when :not
|
384
|
+
"!" + emit(e.body[0])
|
385
|
+
when :true
|
386
|
+
"true"
|
387
|
+
when :false
|
388
|
+
"false"
|
389
|
+
when :nil
|
390
|
+
"nil"
|
391
|
+
when :self
|
392
|
+
"self"
|
393
|
+
when :zsuper
|
394
|
+
"super"
|
395
|
+
when :super
|
396
|
+
"super(" + e.body.map {|c| emit(c) }.join(", ") + ")"
|
397
|
+
when :yield
|
398
|
+
"yield #{emit_argument_list(e)}".strip
|
399
|
+
when :next
|
400
|
+
"next"
|
401
|
+
when :retry
|
402
|
+
"retry"
|
403
|
+
when :return
|
404
|
+
"return #{emit_argument_list(e)}".strip
|
405
|
+
when :alias
|
406
|
+
"alias #{emit(e.body[0])} #{emit(e.body[1])}"
|
407
|
+
when :block_pass
|
408
|
+
"&" + emit(e.body[0])
|
409
|
+
when :splat
|
410
|
+
"*" + emit(e.body[0])
|
411
|
+
when :colon2
|
412
|
+
"#{emit(e.body[0])}::#{e.body[1].to_s}"
|
413
|
+
when :hash
|
414
|
+
"{" + emit_hash_body(e) + "}"
|
415
|
+
when :array
|
416
|
+
"[" + e.body.map {|c| emit(c)}.join(", ") + "]"
|
417
|
+
when :gvar
|
418
|
+
e.body[0].to_s
|
419
|
+
when :dstr
|
420
|
+
'"' + literalize_strings(e.body).map {|c| emit(c) }.join + '"'
|
421
|
+
when :evstr
|
422
|
+
'#{' + emit(e.body[0]) + '}'
|
423
|
+
when :xstr
|
424
|
+
'`' + emit(e.body[0]) + '`'
|
425
|
+
when :dxstr
|
426
|
+
'`' + literalize_strings(e.body).map {|c| emit(c) }.join + '`'
|
427
|
+
when :dsym
|
428
|
+
':"' + literalize_strings(e.body).map {|c| emit(c) }.join + '"'
|
429
|
+
when :match3
|
430
|
+
emit(e.body[1]) + " =~ " + emit(e.body[0])
|
431
|
+
when :cvdecl
|
432
|
+
emit(e.body[0].to_s) + " = " + emit(e.body[1])
|
433
|
+
else
|
434
|
+
emit_unknown_expression(e)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def emit_hash_body(e)
|
439
|
+
e.body.in_groups_of(2).map {|g| "#{emit(g[0])} => #{emit(g[1])}" }.join(", ")
|
440
|
+
end
|
441
|
+
|
442
|
+
def emit_unknown_expression(e)
|
443
|
+
nl("## UNKNOWN: #{e.kind} ##")
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module RubyScribe
|
2
|
+
module EmitterHelpers
|
3
|
+
def indents
|
4
|
+
@indents ||= []
|
5
|
+
end
|
6
|
+
|
7
|
+
def indent_level
|
8
|
+
indents.inject(0) {|b,i| b + i } || 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def indent(level = 2)
|
12
|
+
indents.push(level)
|
13
|
+
output = yield
|
14
|
+
indents.pop
|
15
|
+
output
|
16
|
+
end
|
17
|
+
|
18
|
+
def nl(text = "")
|
19
|
+
"\n" + (" " * indent_level) + text
|
20
|
+
end
|
21
|
+
|
22
|
+
def literalize_strings(sexps)
|
23
|
+
sexps.map do |sexp|
|
24
|
+
sexp.is_a?(Sexp) && sexp.kind == :str ? sexp.body[0] : sexp
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
module RubyScribe
|
4
|
+
class Runner < Thor
|
5
|
+
desc :cat, "Takes a single ruby file, parses it, and outputs the scribed version."
|
6
|
+
def cat(path)
|
7
|
+
sexp = RubyParser.new.parse(File.read(path))
|
8
|
+
puts RubyScribe::Emitter.new.emit(sexp)
|
9
|
+
end
|
10
|
+
|
11
|
+
desc :convert, "Takes a single file or multiple files, parses them, then replaces the original file(s) with the scribed version."
|
12
|
+
def convert(*paths)
|
13
|
+
expand_paths(paths).each do |path|
|
14
|
+
sexp = RubyParser.new.parse(File.read(path))
|
15
|
+
output = RubyScribe::Emitter.new.emit(sexp)
|
16
|
+
|
17
|
+
File.open(path, "w") do |file|
|
18
|
+
file.write(output)
|
19
|
+
file.flush
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def expand_paths(paths = [])
|
27
|
+
paths.map do |path|
|
28
|
+
[path] + Dir[path + "**/*.rb"]
|
29
|
+
end.flatten.uniq.reject {|f| File.directory?(f) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
namespace :scribe do
|
2
|
+
desc "Run Scribe Examples"
|
3
|
+
task :examples do
|
4
|
+
$:.unshift(File.join(File.dirname(__FILE__), ".."))
|
5
|
+
require "ruby_parser"
|
6
|
+
require "ruby_scribe"
|
7
|
+
require "pp"
|
8
|
+
|
9
|
+
original_file = File.read(File.join(File.dirname(__FILE__), "../../spec/examples/simple_class_with_methods.rb"))
|
10
|
+
sexp = RubyParser.new.parse(original_file)
|
11
|
+
|
12
|
+
puts "Parsed S-Expresssion"
|
13
|
+
puts "======================================"
|
14
|
+
pp sexp
|
15
|
+
puts
|
16
|
+
|
17
|
+
puts "Original File"
|
18
|
+
puts "======================================"
|
19
|
+
puts original_file
|
20
|
+
|
21
|
+
puts
|
22
|
+
puts
|
23
|
+
|
24
|
+
puts "Parsed File"
|
25
|
+
puts "======================================"
|
26
|
+
parsed_file = RubyScribe::Emitter.new.emit(sexp)
|
27
|
+
puts parsed_file
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), "../lib"))
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "rspec"
|
5
|
+
require "ruby_scribe"
|
6
|
+
|
7
|
+
# Requires supporting files with custom matchers and macros, etc,
|
8
|
+
# in ./support/ and its subdirectories.
|
9
|
+
Dir["#{File.dirname(__FILE__)}/matchers/**/*.rb"].each {|f| require f}
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_scribe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ben Hughes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-25 00:00:00 +03:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: ruby_parser
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 2.0.4
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: thor
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 17
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 13
|
49
|
+
version: "0.13"
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
name: rspec
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 2
|
63
|
+
- 0
|
64
|
+
version: "2.0"
|
65
|
+
type: :development
|
66
|
+
version_requirements: *id003
|
67
|
+
description: A ruby formatting tool that takes S-expression as input and intelligently outputs formatted Ruby code.
|
68
|
+
email: ben@railsgarden.com
|
69
|
+
executables: []
|
70
|
+
|
71
|
+
extensions: []
|
72
|
+
|
73
|
+
extra_rdoc_files: []
|
74
|
+
|
75
|
+
files:
|
76
|
+
- lib/ruby_scribe/emitter.rb
|
77
|
+
- lib/ruby_scribe/emitter_helpers.rb
|
78
|
+
- lib/ruby_scribe/ext/sexp.rb
|
79
|
+
- lib/ruby_scribe/preprocessor.rb
|
80
|
+
- lib/ruby_scribe/runner.rb
|
81
|
+
- lib/ruby_scribe/version.rb
|
82
|
+
- lib/ruby_scribe.rb
|
83
|
+
- lib/tasks/scribe.rake
|
84
|
+
- spec/examples/simple_class_with_methods.rb
|
85
|
+
- spec/proprocessor_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
- spec/strategy_spec.rb
|
88
|
+
- LICENSE
|
89
|
+
- Rakefile
|
90
|
+
- README.rdoc
|
91
|
+
- TODO.rdoc
|
92
|
+
has_rdoc: true
|
93
|
+
homepage: http://github.com/rubiety/ruby_scribe
|
94
|
+
licenses: []
|
95
|
+
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
hash: 19
|
116
|
+
segments:
|
117
|
+
- 1
|
118
|
+
- 3
|
119
|
+
- 4
|
120
|
+
version: 1.3.4
|
121
|
+
requirements: []
|
122
|
+
|
123
|
+
rubyforge_project: ruby_scribe
|
124
|
+
rubygems_version: 1.3.7
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Generates formatted ruby code from S-expressions (like from ruby_parser).
|
128
|
+
test_files: []
|
129
|
+
|