ruby_traverser 0.1.1 → 0.2.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/README.markdown +27 -253
- data/TODO.txt +12 -0
- data/VERSION +1 -1
- data/lib/config/api.rb +11 -0
- data/lib/config/has_ext.rb +44 -0
- data/lib/{traversal → config}/misc.rb +4 -4
- data/lib/config/mixin.rb +15 -0
- data/lib/{traversal → config}/module.rb +1 -1
- data/lib/{traversal → config}/patch.rb +4 -4
- data/lib/manipulation/api.rb +30 -0
- data/lib/manipulation/delete/api.rb +10 -0
- data/lib/manipulation/helpers.rb +30 -0
- data/lib/manipulation/insert/api.rb +57 -0
- data/lib/manipulation/position.rb +44 -0
- data/lib/{mutate/replacer.rb → manipulation/update/api.rb} +25 -14
- data/lib/query/api.rb +7 -0
- data/lib/query/find.rb +73 -0
- data/lib/rails/api.rb +13 -0
- data/lib/rails/{api_wrapper.rb → gemfile.rb} +1 -1
- data/lib/ruby_traverser.rb +1 -3
- data/ruby_traverser.gemspec +21 -12
- data/test/mutate/delete_test.rb +25 -0
- data/test/mutate/mutate_class_test.rb +7 -7
- data/test/mutate/mutate_test.rb +18 -17
- data/test/traversal/gemfile_test.rb +6 -6
- metadata +23 -14
- data/lib/mutate/api.rb +0 -124
- data/lib/traversal/api/finders.rb +0 -41
- data/lib/traversal/api/inside.rb +0 -36
- data/lib/traversal/api/traversal.rb +0 -20
- data/lib/traversal/mixin.rb +0 -50
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'manipulation/update/api'
|
2
|
+
require 'manipulation/delete/api'
|
3
|
+
require 'manipulation/insert/api'
|
4
|
+
require 'manipulation/position'
|
5
|
+
require 'manipulation/helpers'
|
6
|
+
|
7
|
+
module RubyCodeAPI
|
8
|
+
module Manipulation
|
9
|
+
include Insert
|
10
|
+
include Update
|
11
|
+
include Delete
|
12
|
+
include RubyCodeAPI::Misc::Position
|
13
|
+
include RubyCodeAPI::Misc::Helper
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Ruby
|
18
|
+
class Node
|
19
|
+
include RubyCodeAPI::Manipulation
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Core extensions
|
24
|
+
|
25
|
+
class String
|
26
|
+
# Returns an indented string, all lines of string will be indented with count of chars
|
27
|
+
def indent(char, count)
|
28
|
+
(char * count) + gsub(/(\n+)/) { $1 + (char * count) }
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module RubyCodeAPI
|
2
|
+
module Misc
|
3
|
+
module Helper
|
4
|
+
def find_index(obj)
|
5
|
+
get_elements.each_with_index do |elem, i|
|
6
|
+
if elem == obj
|
7
|
+
return i
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def elemental?
|
13
|
+
respond_to?(:body) || respond_to?(:elements)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_elements
|
17
|
+
case self
|
18
|
+
when Ruby::Class
|
19
|
+
body.elements
|
20
|
+
else
|
21
|
+
elements
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def object
|
26
|
+
self.respond_to?(:block) ? self.block : self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module RubyCodeAPI
|
2
|
+
module Manipulation
|
3
|
+
module Insert
|
4
|
+
def append_code(code)
|
5
|
+
return append_code_simple(code) if !elemental?
|
6
|
+
obj = object
|
7
|
+
indentation = obj.last_indent
|
8
|
+
code = "\n#{code}\n".indent(' ', indentation)
|
9
|
+
ruby_code = Ripper::RubyBuilder.build(code)
|
10
|
+
inject_code = ruby_code.elements[0]
|
11
|
+
obj.get_elements << inject_code
|
12
|
+
inject_code
|
13
|
+
end
|
14
|
+
|
15
|
+
def append_code_simple(code)
|
16
|
+
indentation = position.col
|
17
|
+
code = "\n#{code}\n".indent(' ', indentation)
|
18
|
+
ruby_code = Ripper::RubyBuilder.build(code)
|
19
|
+
inject_code = ruby_code.elements[0]
|
20
|
+
index = parent.find_index(self)
|
21
|
+
parent.get_elements.insert(index+1, inject_code)
|
22
|
+
inject_code
|
23
|
+
end
|
24
|
+
|
25
|
+
def prepend_code(code)
|
26
|
+
obj = object
|
27
|
+
indentation = obj.first_indent
|
28
|
+
code = "\n#{code}\n".indent(' ', indentation)
|
29
|
+
ruby_code = Ripper::RubyBuilder.build(code)
|
30
|
+
inject_code = ruby_code.elements[0]
|
31
|
+
obj.get_elements.insert(0, inject_code)
|
32
|
+
obj
|
33
|
+
end
|
34
|
+
|
35
|
+
def insert_comment(position, text, &block)
|
36
|
+
insert(position, "# #{text}", &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def insert(position, code, &block)
|
40
|
+
case position
|
41
|
+
when :after
|
42
|
+
s = append_code(code)
|
43
|
+
when :before
|
44
|
+
s = prepend_code(code)
|
45
|
+
else
|
46
|
+
raise Error, "Invalid position given: #{position}, must be either :before or :after"
|
47
|
+
end
|
48
|
+
|
49
|
+
if block_given?
|
50
|
+
block.arity < 1 ? s.instance_eval(&block) : block.call(s)
|
51
|
+
else
|
52
|
+
s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RubyCodeAPI
|
2
|
+
module Misc
|
3
|
+
module Position
|
4
|
+
protected
|
5
|
+
|
6
|
+
def last_indent
|
7
|
+
case self
|
8
|
+
when Ruby::Block, Ruby::Class
|
9
|
+
last_position(get_elements)
|
10
|
+
else
|
11
|
+
puts "unknown: #{obj.class}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def last_position(elements)
|
16
|
+
last_element = elements.last
|
17
|
+
return position.col
|
18
|
+
return position.col if simple_pos?(last_element)
|
19
|
+
return last_element.identifier.position.col if elements && elements.size > 0
|
20
|
+
inside_indent
|
21
|
+
end
|
22
|
+
|
23
|
+
def simple_pos?(elem)
|
24
|
+
[Ruby::Token, Ruby::Variable].include?(elem.class)
|
25
|
+
end
|
26
|
+
|
27
|
+
def first_indent
|
28
|
+
case self
|
29
|
+
when Ruby::Block, Ruby::Class
|
30
|
+
first_position(get_elements)
|
31
|
+
else
|
32
|
+
puts "unknown: #{obj.class}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def first_position(elements)
|
37
|
+
first_element = elements.first
|
38
|
+
return position.col if simple_pos?(first_element)
|
39
|
+
return first_element.identifier.position.col if elements && elements.size > 0
|
40
|
+
inside_indent
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -7,7 +7,7 @@ module PositionReplacer
|
|
7
7
|
def replace_pos_argument(options)
|
8
8
|
case self.arg
|
9
9
|
when Ruby::String
|
10
|
-
replace_arg_token(options[:
|
10
|
+
replace_arg_token(options[:with])
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
@@ -88,24 +88,35 @@ module ValueReplacer
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
-
module
|
92
|
-
module
|
93
|
-
module
|
91
|
+
module RubyCodeAPI
|
92
|
+
module Manipulation
|
93
|
+
module Update
|
94
94
|
include PositionReplacer
|
95
95
|
include TokenReplacer
|
96
96
|
include HashReplacer
|
97
97
|
include ValueReplacer
|
98
|
+
|
99
|
+
# update :select => {...}, :with => {...}
|
100
|
+
# update :select => {...}, :with_code => 'code'
|
101
|
+
def update(options, &block)
|
102
|
+
s = replace_value(options) if options[:value]
|
103
|
+
s = replace options[:select].merge(options) if options[:select]
|
104
|
+
if block_given?
|
105
|
+
block.arity < 1 ? s.instance_eval(&block) : block.call(s)
|
106
|
+
else
|
107
|
+
s
|
108
|
+
end
|
109
|
+
end
|
98
110
|
|
99
111
|
# :arg => 'ripper', :replace_arg => 'rapper'
|
100
|
-
def replace(options)
|
101
|
-
if options[:value]
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
112
|
+
def replace(options)
|
113
|
+
return replace_value(options) if options[:value]
|
114
|
+
return replace_position_arg(options) if position_arg?(options[:arg])
|
115
|
+
return replace_arg(options) if options[:arg]
|
116
|
+
raise Error, "Invalid options: #{options}"
|
117
|
+
end
|
118
|
+
|
119
|
+
def replace_arg(options)
|
109
120
|
self.arguments.elements.each_with_index do |elem, i|
|
110
121
|
case elem
|
111
122
|
when Ruby::Arg
|
@@ -120,7 +131,7 @@ module RubyAPI
|
|
120
131
|
def replace_argument(options)
|
121
132
|
case self.arg
|
122
133
|
when Ruby::String
|
123
|
-
replace_arg_token(options[:
|
134
|
+
replace_arg_token(options[:with]) if matching_string_arg?(options[:arg])
|
124
135
|
end
|
125
136
|
end
|
126
137
|
|
data/lib/query/api.rb
ADDED
data/lib/query/find.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module RubyCodeAPI
|
4
|
+
module Query
|
5
|
+
# Use this to generate all methods except for find_block which is a bit special
|
6
|
+
NODES = {
|
7
|
+
:module => Ruby::Module,
|
8
|
+
:class => Ruby::Class,
|
9
|
+
:variable => [Ruby::Variable, :token],
|
10
|
+
:assignment => [Ruby::Assignment, :left_token],
|
11
|
+
:call => Ruby::Call,
|
12
|
+
:def => Ruby::Method
|
13
|
+
}
|
14
|
+
|
15
|
+
def find(type, name, options = {}, &block)
|
16
|
+
s = send :"find_#{type.to_s}", name, options
|
17
|
+
s.extend(options[:extend]) if options[:extend]
|
18
|
+
if block_given?
|
19
|
+
block.arity < 1 ? s.instance_eval(&block) : block.call(s)
|
20
|
+
else
|
21
|
+
s
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def inside(type, name, options = {}, &block)
|
26
|
+
raise StandardError, "Must have block argument" if !block_given?
|
27
|
+
find(type, name, options = {}, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
instance_eval do
|
31
|
+
NODES.each_pair do |key, value|
|
32
|
+
case value
|
33
|
+
when Array
|
34
|
+
define_method :"find_#{key}" do |name, options|
|
35
|
+
find_by(value[1], value[0], name, options)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
define_method :"find_#{key}" do |name, options|
|
39
|
+
find_by(:identifier, value, name, options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_by(key, type, name, options)
|
46
|
+
options.merge!(key => name)
|
47
|
+
get_obj.select(type, options).first
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_block(name, options = {})
|
51
|
+
options.merge!(:block => true) if !options.has_key?(:block_params)
|
52
|
+
find_by(:identifier, Ruby::Call, name, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :find_method, :find_def
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def inside_indent
|
60
|
+
if self.class == Ruby::Class
|
61
|
+
pos = ldelim.position.col
|
62
|
+
return pos
|
63
|
+
end
|
64
|
+
2
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_obj(options = {})
|
68
|
+
return self.block if self.class == Ruby::Method
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
data/lib/rails/api.rb
ADDED
data/lib/ruby_traverser.rb
CHANGED
data/ruby_traverser.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ruby_traverser}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Kristian Mandrup"]
|
12
|
-
s.date = %q{2010-04-
|
12
|
+
s.date = %q{2010-04-28}
|
13
13
|
s.description = %q{traverse a ruby code model and optionally mutate it along the way using a nice rubyish DSL}
|
14
14
|
s.email = %q{kmandrup@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -21,22 +21,30 @@ Gem::Specification.new do |s|
|
|
21
21
|
"LICENSE",
|
22
22
|
"README.markdown",
|
23
23
|
"Rakefile",
|
24
|
+
"TODO.txt",
|
24
25
|
"VERSION",
|
25
|
-
"lib/
|
26
|
-
"lib/
|
27
|
-
"lib/
|
26
|
+
"lib/config/api.rb",
|
27
|
+
"lib/config/has_ext.rb",
|
28
|
+
"lib/config/misc.rb",
|
29
|
+
"lib/config/mixin.rb",
|
30
|
+
"lib/config/module.rb",
|
31
|
+
"lib/config/patch.rb",
|
32
|
+
"lib/manipulation/api.rb",
|
33
|
+
"lib/manipulation/delete/api.rb",
|
34
|
+
"lib/manipulation/helpers.rb",
|
35
|
+
"lib/manipulation/insert/api.rb",
|
36
|
+
"lib/manipulation/position.rb",
|
37
|
+
"lib/manipulation/update/api.rb",
|
38
|
+
"lib/query/api.rb",
|
39
|
+
"lib/query/find.rb",
|
40
|
+
"lib/rails/api.rb",
|
41
|
+
"lib/rails/gemfile.rb",
|
28
42
|
"lib/ruby_traverser.rb",
|
29
|
-
"lib/traversal/api/finders.rb",
|
30
|
-
"lib/traversal/api/inside.rb",
|
31
|
-
"lib/traversal/api/traversal.rb",
|
32
|
-
"lib/traversal/misc.rb",
|
33
|
-
"lib/traversal/mixin.rb",
|
34
|
-
"lib/traversal/module.rb",
|
35
|
-
"lib/traversal/patch.rb",
|
36
43
|
"ruby_traverser.gemspec",
|
37
44
|
"spec/ruby_traverser_spec.rb",
|
38
45
|
"spec/spec.opts",
|
39
46
|
"spec/spec_helper.rb",
|
47
|
+
"test/mutate/delete_test.rb",
|
40
48
|
"test/mutate/mutate_class_test.rb",
|
41
49
|
"test/mutate/mutate_test.rb",
|
42
50
|
"test/rails_api/gemfile_api.rb",
|
@@ -52,6 +60,7 @@ Gem::Specification.new do |s|
|
|
52
60
|
s.test_files = [
|
53
61
|
"spec/ruby_traverser_spec.rb",
|
54
62
|
"spec/spec_helper.rb",
|
63
|
+
"test/mutate/delete_test.rb",
|
55
64
|
"test/mutate/mutate_class_test.rb",
|
56
65
|
"test/mutate/mutate_test.rb",
|
57
66
|
"test/rails_api/gemfile_api.rb",
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class TraversalTest < Test::Unit::TestCase
|
5
|
+
include TestHelper
|
6
|
+
|
7
|
+
|
8
|
+
define_method :"test find assignment in method definition and replace value of right side" do
|
9
|
+
src = %q{
|
10
|
+
def hello_world(a)
|
11
|
+
my_var = 2
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
code = Ripper::RubyBuilder.build(src)
|
16
|
+
|
17
|
+
code.inside(:def, 'hello_world', :params => ['a']) do |b|
|
18
|
+
# call_node = b.find_call('gem', :args => ['ripper', {:src => 'github'}], :verbose => true)
|
19
|
+
ass_node = b.find(:assignment, 'my_var')
|
20
|
+
assert_equal Ruby::Assignment, ass_node.class
|
21
|
+
ass_node.delete
|
22
|
+
puts b.to_ruby
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|