synvert 0.0.1 → 0.0.2
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.
- 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
|