synvert 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/README.md +9 -2
- data/lib/synvert.rb +3 -4
- data/lib/synvert/cli.rb +13 -12
- data/lib/synvert/configuration.rb +16 -0
- data/lib/synvert/factory_girl/syntax_methods.rb +37 -0
- data/lib/synvert/node_ext.rb +134 -0
- data/lib/synvert/rewriter.rb +56 -0
- data/lib/synvert/rewriter/action.rb +48 -0
- data/lib/synvert/rewriter/conditions.rb +39 -0
- data/lib/synvert/rewriter/instances.rb +67 -0
- data/lib/synvert/rewriter/scopes.rb +39 -0
- data/lib/synvert/version.rb +1 -1
- data/spec/spec_helper.rb +2 -2
- data/spec/support/parser_helper.rb +5 -0
- data/spec/synvert/node_ext_spec.rb +88 -0
- data/spec/synvert/rewriter/action_spec.rb +51 -0
- data/spec/synvert/rewriter/conditions_spec.rb +31 -0
- data/spec/synvert/rewriter/instances_spec.rb +113 -0
- data/spec/synvert/rewriter/scopes_spec.rb +79 -0
- data/spec/synvert/rewriter_spec.rb +31 -0
- data/synvert.gemspec +0 -1
- metadata +25 -28
- data/lib/synvert/base_converter.rb +0 -36
- data/lib/synvert/checking_visitor.rb +0 -37
- data/lib/synvert/factory_girl/syntax_methods_converter.rb +0 -49
- data/lib/synvert/sexp_helper.rb +0 -51
- data/spec/support/shared_context.rb +0 -41
- data/spec/synvert/base_converter_spec.rb +0 -0
- data/spec/synvert/checking_visitor_spec.rb +0 -42
- data/spec/synvert/factory_girl/syntax_methods_converter_spec.rb +0 -148
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf669115cf6d65444d15559b42836533143c76e2
|
4
|
+
data.tar.gz: b89a1714b73c29a5f66b4a320a3fa5c76b6feeb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d4dac67ebafd169d5f44e4022d2102fa4de6d6d35954830583ce8277851eb945c7039263eda058992979814c6e10945bcde74533c59f4872dedad304b61fbbf
|
7
|
+
data.tar.gz: cbd540c9732b62c3fb6fdd6897fdc538aa17dbde38b037acfae1240bd3b89617485ac32631d9e1a381cbaa48bb940d02044f0163185a096d86e715f548257d28
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Synvert
|
2
2
|
|
3
|
-
|
3
|
+
synvert = syntax + convert, makes it easy to rewrite ruby code
|
4
|
+
automatically.
|
5
|
+
|
6
|
+
**synvert is still in alpha stage**
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
@@ -18,7 +21,11 @@ Or install it yourself as:
|
|
18
21
|
|
19
22
|
## Usage
|
20
23
|
|
21
|
-
|
24
|
+
synvert PROJECT_PATH
|
25
|
+
|
26
|
+
Currently it supports
|
27
|
+
|
28
|
+
* FactoryGirl short syntax
|
22
29
|
|
23
30
|
## Contributing
|
24
31
|
|
data/lib/synvert.rb
CHANGED
@@ -3,12 +3,11 @@ require "synvert/version"
|
|
3
3
|
require 'parser'
|
4
4
|
require 'parser/current'
|
5
5
|
require 'ast'
|
6
|
+
require 'synvert/node_ext'
|
6
7
|
|
7
8
|
module Synvert
|
8
9
|
autoload :BaseConverter, 'synvert/base_converter'
|
9
10
|
autoload :CheckingVisitor, 'synvert/checking_visitor'
|
10
|
-
autoload :
|
11
|
-
|
12
|
-
autoload :SyntaxMethodsConverter, 'synvert/factory_girl/syntax_methods_converter'
|
13
|
-
end
|
11
|
+
autoload :Configuration, 'synvert/configuration'
|
12
|
+
autoload :Rewriter, 'synvert/rewriter'
|
14
13
|
end
|
data/lib/synvert/cli.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
require 'optparse'
|
2
3
|
require 'find'
|
3
4
|
|
4
5
|
module Synvert
|
5
6
|
class CLI
|
6
|
-
def self.run
|
7
|
-
new.run
|
7
|
+
def self.run(args = ARGV)
|
8
|
+
new.run(args)
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
11
|
+
def run(args)
|
12
|
+
optparse = OptionParser.new do |opts|
|
13
|
+
opts.banner = "Usage: synvert path"
|
14
|
+
end
|
15
|
+
paths = optparse.parse(args)
|
16
|
+
Configuration.instance.set :path, paths.first || Dir.pwd
|
17
|
+
|
18
|
+
load(File.join(File.dirname(__FILE__), 'factory_girl/syntax_methods.rb'))
|
13
19
|
|
14
|
-
|
15
|
-
|
16
|
-
if FileTest.directory?(path)
|
17
|
-
next
|
18
|
-
else
|
19
|
-
@checking_visitor.convert_file(path) if path =~ /\.rb$/
|
20
|
-
end
|
20
|
+
ObjectSpace.each_object Synvert::Rewriter do |rewriter|
|
21
|
+
rewriter.process
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Synvert::Rewriter.new "use short syntax" do
|
2
|
+
from_version '2.0.0'
|
3
|
+
|
4
|
+
within_file 'spec/spec_helper.rb' do
|
5
|
+
within_node type: 'block', caller: {receiver: 'RSpec', message: 'configure'} do
|
6
|
+
unless_exist_node type: 'send', message: 'include', arguments: {first: {to_s: 'FactoryGirl::Syntax::Methods'}} do
|
7
|
+
insert "{{node.arguments.first}}.include FactoryGirl::Syntax::Methods"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
%w(Test::Unit::TestCase ActiveSupport::TestCase MiniTest::Unit::TestCase MiniTest::Spec MiniTest::Rails::ActiveSupport::TestCase).each do |class_name|
|
13
|
+
within_file 'test/test_helper.rb' do
|
14
|
+
within_node type: 'class', name: class_name do
|
15
|
+
unless_exist_node type: 'send', message: 'include', arguments: {first: {to_s: 'FactoryGirl::Syntax::Methods'}} do
|
16
|
+
insert "include FactoryGirl::Syntax::Methods"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
within_file 'features/support/env.rb' do
|
23
|
+
unless_exist_node type: 'send', message: 'World', arguments: {first: {to_s: 'FactoryGirl::Syntax::Methods'}} do
|
24
|
+
insert "World(FactoryGirl::Syntax::Methods)"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
%w(test/**/*.rb spec/**/*.rb features/**/*.rb).each do |file_pattern|
|
29
|
+
%w(create build attributes_for build_stubbed create_list build_list ccreate_pair build_pair).each do |message|
|
30
|
+
within_files file_pattern do
|
31
|
+
with_node type: 'send', receiver: 'FactoryGirl', message: message do
|
32
|
+
replace_with "#{message}({{node.arguments}})"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
class Parser::AST::Node
|
2
|
+
def name
|
3
|
+
if :class == self.type
|
4
|
+
self.children[0]
|
5
|
+
else
|
6
|
+
raise NotImplementedError.new "name is not handled for #{self.inspect}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def receiver
|
11
|
+
if :send == self.type
|
12
|
+
self.children[0]
|
13
|
+
else
|
14
|
+
raise NotImplementedError.new "receiver is not handled for #{self.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
if :send == self.type
|
20
|
+
self.children[1]
|
21
|
+
else
|
22
|
+
raise NotImplementedError.new "message is not handled for #{self.inspect}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def arguments
|
27
|
+
case self.type
|
28
|
+
when :send
|
29
|
+
self.children[2..-1]
|
30
|
+
when :block
|
31
|
+
self.children[1].children
|
32
|
+
else
|
33
|
+
raise NotImplementedError.new "arguments is not handled for #{self.inspect}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def caller
|
38
|
+
if :block == self.type
|
39
|
+
self.children[0]
|
40
|
+
else
|
41
|
+
raise NotImplementedError.new "caller is not handled for #{self.inspect}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
case self.type
|
47
|
+
when :const
|
48
|
+
self.children.compact.map(&:to_s).join('::')
|
49
|
+
when :sym
|
50
|
+
':' + self.children[0].to_s
|
51
|
+
when :str, :arg, :lvar, :ivar
|
52
|
+
self.children[0].to_s
|
53
|
+
else
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def indent
|
58
|
+
self.loc.expression.column
|
59
|
+
end
|
60
|
+
|
61
|
+
def recursive_children
|
62
|
+
self.children.each do |child|
|
63
|
+
if Parser::AST::Node === child
|
64
|
+
yield child
|
65
|
+
child.recursive_children { |c| yield c }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def grep_node(options)
|
71
|
+
self.recursive_children do |child|
|
72
|
+
return child if child.match?(options)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def match?(options)
|
77
|
+
flat_hash(options).keys.all? do |key|
|
78
|
+
actual = actual_value(self, key)
|
79
|
+
expected = expected_value(options, key)
|
80
|
+
match_value?(actual, expected)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_source(code)
|
85
|
+
code.gsub(/{{(.*)}}/) do
|
86
|
+
node = self # node is used in eval
|
87
|
+
evaluated = eval($1)
|
88
|
+
if Parser::AST::Node === evaluated
|
89
|
+
source = evaluated.loc.expression.source_buffer.source
|
90
|
+
source[evaluated.loc.expression.begin_pos...evaluated.loc.expression.end_pos]
|
91
|
+
else # Array
|
92
|
+
source = evaluated.first.loc.expression.source_buffer.source
|
93
|
+
source[evaluated.first.loc.expression.begin_pos...evaluated.last.loc.expression.end_pos]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def match_value?(actual, expected)
|
101
|
+
case actual
|
102
|
+
when Symbol
|
103
|
+
actual == expected.to_sym
|
104
|
+
when Array
|
105
|
+
actual.zip(expected).all? { |a, e| match_value?(a, e) }
|
106
|
+
else
|
107
|
+
actual.to_s == expected
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def flat_hash(h, k = [])
|
112
|
+
new_hash = {}
|
113
|
+
h.each_pair do |key, val|
|
114
|
+
if val.is_a?(Hash)
|
115
|
+
new_hash.merge!(flat_hash(val, k + [key]))
|
116
|
+
else
|
117
|
+
new_hash[k + [key]] = val
|
118
|
+
end
|
119
|
+
end
|
120
|
+
new_hash
|
121
|
+
end
|
122
|
+
|
123
|
+
def actual_value(node, multi_keys)
|
124
|
+
multi_keys.inject(node) { |n, key| n.send(key) }
|
125
|
+
end
|
126
|
+
|
127
|
+
def expected_value(options, multi_keys)
|
128
|
+
multi_keys.inject(options) { |o, key| o[key] }
|
129
|
+
end
|
130
|
+
|
131
|
+
#def to_ast(str)
|
132
|
+
#Parser::CurrentRuby.parse(str)
|
133
|
+
#end
|
134
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Synvert
|
2
|
+
class Rewriter
|
3
|
+
autoload :Action, 'synvert/rewriter/action'
|
4
|
+
autoload :ReplaceWithAction, 'synvert/rewriter/action'
|
5
|
+
autoload :InsertAction, 'synvert/rewriter/action'
|
6
|
+
|
7
|
+
autoload :Instances, 'synvert/rewriter/instances'
|
8
|
+
|
9
|
+
autoload :Scopes, 'synvert/rewriter/scopes'
|
10
|
+
autoload :Scope, 'synvert/rewriter/scopes'
|
11
|
+
|
12
|
+
autoload :Conditions, 'synvert/rewriter/conditions'
|
13
|
+
autoload :Condition, 'synvert/rewriter/conditions'
|
14
|
+
autoload :UnlessExistCondition, 'synvert/rewriter/conditions'
|
15
|
+
|
16
|
+
attr_reader :description, :version, :instances
|
17
|
+
|
18
|
+
def initialize(description, &block)
|
19
|
+
@description = description
|
20
|
+
@instances = Instances.new
|
21
|
+
instance_eval &block if block_given?
|
22
|
+
end
|
23
|
+
|
24
|
+
def process
|
25
|
+
@instances.process
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_version(version)
|
29
|
+
@version = Version.from_string(version)
|
30
|
+
end
|
31
|
+
|
32
|
+
def within_file(file, &block)
|
33
|
+
@instances.add(file, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def within_files(files, &block)
|
37
|
+
@instances.add(files, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
class Version
|
41
|
+
def self.from_string(version)
|
42
|
+
self.new *version.split('.')
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(major, minor, patch)
|
46
|
+
@major = major
|
47
|
+
@minor = minor
|
48
|
+
@patch = patch
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
[@major, @minor, @patch].join('.')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Synvert
|
4
|
+
class Rewriter::Action
|
5
|
+
def initialize(code)
|
6
|
+
@code = code
|
7
|
+
end
|
8
|
+
|
9
|
+
def rewrite(source, node)
|
10
|
+
raise NotImplementedError.new 'rewrite method is not implemented'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Rewriter::ReplaceWithAction < Rewriter::Action
|
15
|
+
def rewrite(source, node)
|
16
|
+
begin_pos = node.loc.expression.begin_pos
|
17
|
+
end_pos = node.loc.expression.end_pos
|
18
|
+
source[begin_pos...end_pos] = node.to_source(@code)
|
19
|
+
source
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Rewriter::InsertAction < Rewriter::Action
|
24
|
+
def rewrite(source, node)
|
25
|
+
source[insert_position(node), 0] = "\n" + insert_indent(node) + node.to_source(@code)
|
26
|
+
source
|
27
|
+
end
|
28
|
+
|
29
|
+
def insert_position(node)
|
30
|
+
case node.type
|
31
|
+
when :block
|
32
|
+
node.children[1].loc.expression.end_pos
|
33
|
+
when :class
|
34
|
+
node.children[0].loc.expression.end_pos
|
35
|
+
else
|
36
|
+
node.children.last.loc.expression.end_pos
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert_indent(node)
|
41
|
+
if [:block, :class].include? node.type
|
42
|
+
' ' * (node.indent + 2)
|
43
|
+
else
|
44
|
+
' ' * node.indent
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Synvert
|
4
|
+
class Rewriter::Conditions
|
5
|
+
def initialize
|
6
|
+
@conditions = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(condition)
|
10
|
+
@conditions << condition
|
11
|
+
end
|
12
|
+
|
13
|
+
def matching_nodes(nodes)
|
14
|
+
@conditions.each do |condition|
|
15
|
+
break if nodes.empty?
|
16
|
+
nodes = condition.matching_nodes(nodes)
|
17
|
+
end
|
18
|
+
nodes
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Rewriter::Condition
|
23
|
+
def initialize(options)
|
24
|
+
@options = options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Rewriter::UnlessExistCondition < Rewriter::Condition
|
29
|
+
def matching_nodes(nodes)
|
30
|
+
nodes.find_all { |node|
|
31
|
+
match = false
|
32
|
+
node.recursive_children do |child_node|
|
33
|
+
match = match || (child_node && child_node.match?(@options))
|
34
|
+
end
|
35
|
+
!match
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Synvert
|
4
|
+
class Rewriter::Instances
|
5
|
+
def initialize
|
6
|
+
@instances = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(file_pattern, &block)
|
10
|
+
instance = Rewriter::Instance.new(file_pattern)
|
11
|
+
instance.instance_eval &block if block_given?
|
12
|
+
@instances << instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def process
|
16
|
+
@instances.each { |instance| instance.process }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Rewriter::Instance
|
21
|
+
def initialize(file_pattern)
|
22
|
+
@file_pattern = file_pattern
|
23
|
+
@scopes = Rewriter::Scopes.new
|
24
|
+
@conditions = Rewriter::Conditions.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def process
|
28
|
+
parser = Parser::CurrentRuby.new
|
29
|
+
file_pattern = File.join(Configuration.instance.get(:path), @file_pattern)
|
30
|
+
Dir.glob(file_pattern).each do |path|
|
31
|
+
source = File.read(path)
|
32
|
+
buffer = Parser::Source::Buffer.new path
|
33
|
+
buffer.source = source
|
34
|
+
|
35
|
+
parser.reset
|
36
|
+
ast = parser.parse buffer
|
37
|
+
|
38
|
+
scoped_nodes = @scopes.matching_nodes(ast)
|
39
|
+
matching_nodes = @conditions.matching_nodes(scoped_nodes)
|
40
|
+
matching_nodes.reverse.each do |node|
|
41
|
+
source = @action.rewrite(source, node)
|
42
|
+
end
|
43
|
+
File.write path, source
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def within_node(options, &block)
|
48
|
+
@scopes.add(options)
|
49
|
+
instance_eval &block if block_given?
|
50
|
+
end
|
51
|
+
|
52
|
+
alias with_node within_node
|
53
|
+
|
54
|
+
def unless_exist_node(options, &block)
|
55
|
+
@conditions.add(Rewriter::UnlessExistCondition.new(options))
|
56
|
+
instance_eval &block if block_given?
|
57
|
+
end
|
58
|
+
|
59
|
+
def insert(code)
|
60
|
+
@action = Rewriter::InsertAction.new(code)
|
61
|
+
end
|
62
|
+
|
63
|
+
def replace_with(code)
|
64
|
+
@action = Rewriter::ReplaceWithAction.new(code)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|