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 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