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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4aefc7425f57552493435e868b3bbaae081a97ba
4
- data.tar.gz: d679d3dd07f996bf238f540a842c9f99a758d7d4
3
+ metadata.gz: cf669115cf6d65444d15559b42836533143c76e2
4
+ data.tar.gz: b89a1714b73c29a5f66b4a320a3fa5c76b6feeb8
5
5
  SHA512:
6
- metadata.gz: b213405f6988e2041a4e087128319ed1cc5fead180dc58a269f6852c426f50b5bb5b322fc93839a2055d6d5f01809086ba6bd3d95052c47fcf64396af60bc6bc
7
- data.tar.gz: 630fbc831d76015d8ca0ea2ff958a87efab5a7be7ff5afd1d9d3a3a766039ad5ad59d67c8be6b7f969f48e96dff58cc65e28499ba5eea3ba00845716026e1f5d
6
+ metadata.gz: 3d4dac67ebafd169d5f44e4022d2102fa4de6d6d35954830583ce8277851eb945c7039263eda058992979814c6e10945bcde74533c59f4872dedad304b61fbbf
7
+ data.tar.gz: cbd540c9732b62c3fb6fdd6897fdc538aa17dbde38b037acfae1240bd3b89617485ac32631d9e1a381cbaa48bb940d02044f0163185a096d86e715f548257d28
@@ -0,0 +1,9 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.0.2
4
+
5
+ * Rewrite all stuff, dsl style rather than inherit Parser::Rewriter
6
+
7
+ ## 0.0.1
8
+
9
+ * First version
data/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  # Synvert
2
2
 
3
- TODO: Write a gem description
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
- TODO: Write usage instructions here
24
+ synvert PROJECT_PATH
25
+
26
+ Currently it supports
27
+
28
+ * FactoryGirl short syntax
22
29
 
23
30
  ## Contributing
24
31
 
@@ -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 :SexpHelper, 'synvert/sexp_helper'
11
- module FactoryGirl
12
- autoload :SyntaxMethodsConverter, 'synvert/factory_girl/syntax_methods_converter'
13
- end
11
+ autoload :Configuration, 'synvert/configuration'
12
+ autoload :Rewriter, 'synvert/rewriter'
14
13
  end
@@ -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 initialize
11
- @checking_visitor = CheckingVisitor.new
12
- end
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
- def run
15
- Find.find(".") do |path|
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,16 @@
1
+ # encoding: utf-8
2
+ require 'singleton'
3
+
4
+ module Synvert
5
+ class Configuration < Hash
6
+ include Singleton
7
+
8
+ def set(key, value)
9
+ self[key] = value
10
+ end
11
+
12
+ def get(key)
13
+ self[key]
14
+ end
15
+ end
16
+ 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