metamorpher 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +3 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +541 -0
- data/Rakefile +23 -0
- data/examples/refactorings/rails/where_first/app.rb +50 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_mocks.rb +31 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_not_called_expectations.rb +14 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_strict_mocks.rb +27 -0
- data/examples/refactorings/rails/where_first/refactorers/refactor_where_first_to_find_by.rb +14 -0
- data/examples/refactorings/rails/where_first/sample_controller.rb +184 -0
- data/lib/metamorpher/builders/ast/builder.rb +50 -0
- data/lib/metamorpher/builders/ast/derivation_builder.rb +20 -0
- data/lib/metamorpher/builders/ast/greedy_variable_builder.rb +29 -0
- data/lib/metamorpher/builders/ast/literal_builder.rb +31 -0
- data/lib/metamorpher/builders/ast/variable_builder.rb +29 -0
- data/lib/metamorpher/builders/ast.rb +11 -0
- data/lib/metamorpher/builders/ruby/builder.rb +38 -0
- data/lib/metamorpher/builders/ruby/deriving_visitor.rb +13 -0
- data/lib/metamorpher/builders/ruby/ensuring_visitor.rb +13 -0
- data/lib/metamorpher/builders/ruby/term.rb +35 -0
- data/lib/metamorpher/builders/ruby/uppercase_constant_rewriter.rb +31 -0
- data/lib/metamorpher/builders/ruby/uppercase_rewriter.rb +28 -0
- data/lib/metamorpher/builders/ruby/variable_replacement_visitor.rb +32 -0
- data/lib/metamorpher/builders/ruby.rb +11 -0
- data/lib/metamorpher/drivers/parse_error.rb +5 -0
- data/lib/metamorpher/drivers/ruby.rb +78 -0
- data/lib/metamorpher/matcher/match.rb +26 -0
- data/lib/metamorpher/matcher/matching.rb +61 -0
- data/lib/metamorpher/matcher/no_match.rb +18 -0
- data/lib/metamorpher/matcher.rb +6 -0
- data/lib/metamorpher/refactorer/merger.rb +18 -0
- data/lib/metamorpher/refactorer/site.rb +29 -0
- data/lib/metamorpher/refactorer.rb +48 -0
- data/lib/metamorpher/rewriter/replacement.rb +18 -0
- data/lib/metamorpher/rewriter/rule.rb +38 -0
- data/lib/metamorpher/rewriter/substitution.rb +45 -0
- data/lib/metamorpher/rewriter/traverser.rb +26 -0
- data/lib/metamorpher/rewriter.rb +12 -0
- data/lib/metamorpher/support/map_at.rb +8 -0
- data/lib/metamorpher/terms/derived.rb +13 -0
- data/lib/metamorpher/terms/literal.rb +47 -0
- data/lib/metamorpher/terms/term.rb +40 -0
- data/lib/metamorpher/terms/variable.rb +17 -0
- data/lib/metamorpher/version.rb +3 -0
- data/lib/metamorpher/visitable/visitable.rb +7 -0
- data/lib/metamorpher/visitable/visitor.rb +21 -0
- data/lib/metamorpher.rb +30 -0
- data/metamorpher.gemspec +30 -0
- data/spec/integration/ast/builder_spec.rb +13 -0
- data/spec/integration/ast/matcher_spec.rb +132 -0
- data/spec/integration/ast/rewriter_spec.rb +138 -0
- data/spec/integration/ruby/builder_spec.rb +125 -0
- data/spec/integration/ruby/refactorer_spec.rb +192 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/helpers/silence_stream.rb +10 -0
- data/spec/support/matchers/have_matched_matcher.rb +22 -0
- data/spec/support/matchers/have_substitution_matcher.rb +15 -0
- data/spec/support/shared_examples/shared_examples_for_derivation_builders.rb +53 -0
- data/spec/support/shared_examples/shared_examples_for_greedy_variable_builders.rb +49 -0
- data/spec/support/shared_examples/shared_examples_for_literal_builders.rb +93 -0
- data/spec/support/shared_examples/shared_examples_for_variable_builders.rb +49 -0
- data/spec/unit/builders/ast/derivation_builder_spec.rb +5 -0
- data/spec/unit/builders/ast/greedy_variable_builder_spec.rb +9 -0
- data/spec/unit/builders/ast/literal_builder_spec.rb +9 -0
- data/spec/unit/builders/ast/variable_builder_spec.rb +9 -0
- data/spec/unit/builders/ruby/variable_replacement_visitor_spec.rb +48 -0
- data/spec/unit/drivers/ruby_spec.rb +91 -0
- data/spec/unit/matcher/matching_spec.rb +230 -0
- data/spec/unit/metamorpher_spec.rb +22 -0
- data/spec/unit/refactorer/merger_spec.rb +84 -0
- data/spec/unit/refactorer/site_spec.rb +52 -0
- data/spec/unit/rewriter/replacement_spec.rb +73 -0
- data/spec/unit/rewriter/substitution_spec.rb +97 -0
- data/spec/unit/rewriter/traverser_spec.rb +51 -0
- data/spec/unit/support/map_at_spec.rb +18 -0
- data/spec/unit/terms/literal_spec.rb +60 -0
- data/spec/unit/terms/term_spec.rb +59 -0
- data/spec/unit/visitable/visitor_spec.rb +35 -0
- metadata +269 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require_relative "refactorers/refactor_where_first_to_find_by.rb"
|
3
|
+
require_relative "refactorers/refactor_where_first_not_called_expectations.rb"
|
4
|
+
require_relative "refactorers/refactor_where_first_mocks.rb"
|
5
|
+
require_relative "refactorers/refactor_where_first_strict_mocks.rb"
|
6
|
+
|
7
|
+
options = { overwrite: true }
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: refactorer.rb [options]"
|
10
|
+
|
11
|
+
opts.on("-d", "--dry-run", "Write changes to console, rather than to source files.") do |v|
|
12
|
+
options[:overwrite] = false
|
13
|
+
end
|
14
|
+
end.parse!
|
15
|
+
|
16
|
+
source_dir = ARGV.first || "."
|
17
|
+
base = File.expand_path(File.join("**", "*.rb"), source_dir)
|
18
|
+
puts "Refactoring in source directory: #{base}"
|
19
|
+
|
20
|
+
[
|
21
|
+
# Refactor "where(...).first -> find_by(...)"
|
22
|
+
RefactorWhereFirstToFindBy,
|
23
|
+
|
24
|
+
# Refactor ".expect(:where).never" to ".expect(:find_by).never"
|
25
|
+
RefactorWhereFirstNotCalledExpectations,
|
26
|
+
|
27
|
+
# Refactor ".expect(:where).return([X])" to ".expect(:find_by).return(X)"
|
28
|
+
# and ".stubs(:where).return([X])" to ".stubs(:find_by).return(X)"
|
29
|
+
RefactorWhereFirstMocks,
|
30
|
+
|
31
|
+
# Refactor ".expect(:where).with(...).return([X])" to ".expect(:find_by).with(...).return(X)"
|
32
|
+
RefactorWhereFirstStrictMocks
|
33
|
+
|
34
|
+
].each do |refactorer|
|
35
|
+
refactorer.new.refactor_files(Dir.glob(base)) do |path, refactored, changes|
|
36
|
+
if changes.empty?
|
37
|
+
puts "No changes in #{path}"
|
38
|
+
|
39
|
+
else
|
40
|
+
puts "In #{path}:"
|
41
|
+
|
42
|
+
changes.each do |change|
|
43
|
+
puts "\tAt #{change.original_position}, inserting:\n\t\t#{change.refactored_code}"
|
44
|
+
puts ""
|
45
|
+
end
|
46
|
+
|
47
|
+
File.open(path, "w") { |f| f.write(refactored) } if options[:overwrite]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
|
3
|
+
class RefactorWhereFirstMocks
|
4
|
+
include Metamorpher::Refactorer
|
5
|
+
include Metamorpher::Builders::Ruby
|
6
|
+
|
7
|
+
def pattern
|
8
|
+
builder
|
9
|
+
.build("TYPE.DOUBLE_METHOD(:where).returns(ARRAY_VALUE)")
|
10
|
+
.ensuring("DOUBLE_METHOD") { |m| m.name == :expects || m.name == :stubs }
|
11
|
+
.ensuring("ARRAY_VALUE") { |v| v.name == :array }
|
12
|
+
# Doesn't match non-array return types, such as Topic.stubs(:where).returns(Topic)
|
13
|
+
end
|
14
|
+
|
15
|
+
def replacement
|
16
|
+
builder
|
17
|
+
.build("TYPE.DOUBLE_METHOD(:find_by).returns(SINGLE_VALUE)")
|
18
|
+
.deriving("SINGLE_VALUE", "ARRAY_VALUE") { |array_value| take_first(array_value) }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Refactor the argument from [] to nil, or from [X] to X
|
24
|
+
def take_first(array_value)
|
25
|
+
if array_value.children.empty?
|
26
|
+
builder.build("nil")
|
27
|
+
else
|
28
|
+
array_value.children.first
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
|
3
|
+
class RefactorWhereFirstNotCalledExpectations
|
4
|
+
include Metamorpher::Refactorer
|
5
|
+
include Metamorpher::Builders::Ruby
|
6
|
+
|
7
|
+
def pattern
|
8
|
+
builder.build("TYPE.expects(:where).never")
|
9
|
+
end
|
10
|
+
|
11
|
+
def replacement
|
12
|
+
builder.build("TYPE.expects(:find_by).never")
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
|
3
|
+
class RefactorWhereFirstStrictMocks
|
4
|
+
include Metamorpher::Refactorer
|
5
|
+
include Metamorpher::Builders::Ruby
|
6
|
+
|
7
|
+
def pattern
|
8
|
+
builder.build("TYPE.expects(:where).with(PARAMS_).returns(ARRAY_VALUE)")
|
9
|
+
end
|
10
|
+
|
11
|
+
def replacement
|
12
|
+
builder
|
13
|
+
.build("TYPE.expects(:find_by).with(PARAMS_).returns(SINGLE_VALUE)")
|
14
|
+
.deriving("SINGLE_VALUE", "ARRAY_VALUE") { |array_value| take_first(array_value) }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Refactor the argument from [] to nil, or from [X] to X
|
20
|
+
def take_first(array_value)
|
21
|
+
if array_value.children.empty?
|
22
|
+
builder.build("nil")
|
23
|
+
else
|
24
|
+
array_value.children.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "metamorpher"
|
2
|
+
|
3
|
+
class RefactorWhereFirstToFindBy
|
4
|
+
include Metamorpher::Refactorer
|
5
|
+
include Metamorpher::Builders::Ruby
|
6
|
+
|
7
|
+
def pattern
|
8
|
+
builder.build("TYPE.where(PARAMS_).first")
|
9
|
+
end
|
10
|
+
|
11
|
+
def replacement
|
12
|
+
builder.build("TYPE.find_by(PARAMS_)")
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# Taken from Discourse: https://github.com/discourse/discourse
|
2
|
+
|
3
|
+
require_dependency "user_destroyer"
|
4
|
+
require_dependency "admin_user_index_query"
|
5
|
+
require_dependency "boost_trust_level"
|
6
|
+
|
7
|
+
class Admin::UsersController < Admin::AdminController
|
8
|
+
before_filter :fetch_user, only: [:suspend,
|
9
|
+
:unsuspend,
|
10
|
+
:refresh_browsers,
|
11
|
+
:revoke_admin,
|
12
|
+
:grant_admin,
|
13
|
+
:revoke_moderation,
|
14
|
+
:grant_moderation,
|
15
|
+
:approve,
|
16
|
+
:activate,
|
17
|
+
:deactivate,
|
18
|
+
:block,
|
19
|
+
:unblock,
|
20
|
+
:trust_level,
|
21
|
+
:primary_group,
|
22
|
+
:generate_api_key,
|
23
|
+
:revoke_api_key]
|
24
|
+
|
25
|
+
def index
|
26
|
+
query = ::AdminUserIndexQuery.new(params)
|
27
|
+
render_serialized(query.find_users, AdminUserSerializer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def show
|
31
|
+
@user = User.where(username_lower: params[:id]).first
|
32
|
+
fail Discourse::NotFound.new unless @user
|
33
|
+
render_serialized(@user, AdminDetailedUserSerializer, root: false)
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete_all_posts
|
37
|
+
@user = User.where(id: params[:user_id]).first
|
38
|
+
@user.delete_all_posts!(guardian)
|
39
|
+
render nothing: true
|
40
|
+
end
|
41
|
+
|
42
|
+
def suspend
|
43
|
+
guardian.ensure_can_suspend!(@user)
|
44
|
+
@user.suspended_till = params[:duration].to_i.days.from_now
|
45
|
+
@user.suspended_at = DateTime.now
|
46
|
+
@user.save!
|
47
|
+
StaffActionLogger.new(current_user).log_user_suspend(@user, params[:reason])
|
48
|
+
render nothing: true
|
49
|
+
end
|
50
|
+
|
51
|
+
def unsuspend
|
52
|
+
guardian.ensure_can_suspend!(@user)
|
53
|
+
@user.suspended_till = nil
|
54
|
+
@user.suspended_at = nil
|
55
|
+
@user.save!
|
56
|
+
StaffActionLogger.new(current_user).log_user_unsuspend(@user)
|
57
|
+
render nothing: true
|
58
|
+
end
|
59
|
+
|
60
|
+
def refresh_browsers
|
61
|
+
MessageBus.publish "/file-change", ["refresh"], user_ids: [@user.id]
|
62
|
+
render nothing: true
|
63
|
+
end
|
64
|
+
|
65
|
+
def revoke_admin
|
66
|
+
guardian.ensure_can_revoke_admin!(@user)
|
67
|
+
@user.revoke_admin!
|
68
|
+
render nothing: true
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_api_key
|
72
|
+
api_key = @user.generate_api_key(current_user)
|
73
|
+
render_serialized(api_key, ApiKeySerializer)
|
74
|
+
end
|
75
|
+
|
76
|
+
def revoke_api_key
|
77
|
+
@user.revoke_api_key
|
78
|
+
render nothing: true
|
79
|
+
end
|
80
|
+
|
81
|
+
def grant_admin
|
82
|
+
guardian.ensure_can_grant_admin!(@user)
|
83
|
+
@user.grant_admin!
|
84
|
+
render_serialized(@user, AdminUserSerializer)
|
85
|
+
end
|
86
|
+
|
87
|
+
def revoke_moderation
|
88
|
+
guardian.ensure_can_revoke_moderation!(@user)
|
89
|
+
@user.revoke_moderation!
|
90
|
+
render nothing: true
|
91
|
+
end
|
92
|
+
|
93
|
+
def grant_moderation
|
94
|
+
guardian.ensure_can_grant_moderation!(@user)
|
95
|
+
@user.grant_moderation!
|
96
|
+
render_serialized(@user, AdminUserSerializer)
|
97
|
+
end
|
98
|
+
|
99
|
+
def primary_group
|
100
|
+
guardian.ensure_can_change_primary_group!(@user)
|
101
|
+
@user.primary_group_id = params[:primary_group_id]
|
102
|
+
@user.save!
|
103
|
+
render nothing: true
|
104
|
+
end
|
105
|
+
|
106
|
+
def trust_level
|
107
|
+
guardian.ensure_can_change_trust_level!(@user)
|
108
|
+
logger = StaffActionLogger.new(current_user)
|
109
|
+
BoostTrustLevel.new(user: @user, level: params[:level], logger: logger).save!
|
110
|
+
render_serialized(@user, AdminUserSerializer)
|
111
|
+
end
|
112
|
+
|
113
|
+
def approve
|
114
|
+
guardian.ensure_can_approve!(@user)
|
115
|
+
@user.approve(current_user)
|
116
|
+
render nothing: true
|
117
|
+
end
|
118
|
+
|
119
|
+
def approve_bulk
|
120
|
+
User.where(id: params[:users]).each do |u|
|
121
|
+
u.approve(current_user) if guardian.can_approve?(u)
|
122
|
+
end
|
123
|
+
render nothing: true
|
124
|
+
end
|
125
|
+
|
126
|
+
def activate
|
127
|
+
guardian.ensure_can_activate!(@user)
|
128
|
+
@user.activate
|
129
|
+
render nothing: true
|
130
|
+
end
|
131
|
+
|
132
|
+
def deactivate
|
133
|
+
guardian.ensure_can_deactivate!(@user)
|
134
|
+
@user.deactivate
|
135
|
+
render nothing: true
|
136
|
+
end
|
137
|
+
|
138
|
+
def block
|
139
|
+
guardian.ensure_can_block_user! @user
|
140
|
+
UserBlocker.block(@user, current_user)
|
141
|
+
render nothing: true
|
142
|
+
end
|
143
|
+
|
144
|
+
def unblock
|
145
|
+
guardian.ensure_can_unblock_user! @user
|
146
|
+
UserBlocker.unblock(@user, current_user)
|
147
|
+
render nothing: true
|
148
|
+
end
|
149
|
+
|
150
|
+
def reject_bulk
|
151
|
+
d = UserDestroyer.new(current_user)
|
152
|
+
success_count = 0
|
153
|
+
User.where(id: params[:users]).each do |u|
|
154
|
+
success_count += 1 if guardian.can_delete_user?(u) and d.destroy(u, params.slice(:context)) rescue UserDestroyer::PostsExistError
|
155
|
+
end
|
156
|
+
render json: { success: success_count, failed: (params[:users].try(:size) || 0) - success_count }
|
157
|
+
end
|
158
|
+
|
159
|
+
def destroy
|
160
|
+
user = User.where(id: params[:id]).first
|
161
|
+
guardian.ensure_can_delete_user!(user)
|
162
|
+
begin
|
163
|
+
if UserDestroyer.new(current_user).destroy(user, params.slice(:delete_posts, :block_email, :block_urls, :block_ip, :context))
|
164
|
+
render json: { deleted: true }
|
165
|
+
else
|
166
|
+
render json: { deleted: false, user: AdminDetailedUserSerializer.new(user, root: false).as_json }
|
167
|
+
end
|
168
|
+
rescue UserDestroyer::PostsExistError
|
169
|
+
raise Discourse::InvalidAccess.new("User #{user.username} has #{user.post_count} posts, so can't be deleted.")
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def badges
|
174
|
+
end
|
175
|
+
|
176
|
+
def leader_requirements
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def fetch_user
|
182
|
+
@user = User.where(id: params[:user_id]).first
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "metamorpher/builders/ast/literal_builder"
|
2
|
+
require "metamorpher/builders/ast/variable_builder"
|
3
|
+
require "metamorpher/builders/ast/greedy_variable_builder"
|
4
|
+
require "metamorpher/builders/ast/derivation_builder"
|
5
|
+
|
6
|
+
module Metamorpher
|
7
|
+
module Builders
|
8
|
+
module AST
|
9
|
+
class Builder
|
10
|
+
extend Forwardable
|
11
|
+
def_delegator :literal_builder, :literal!
|
12
|
+
def_delegator :variable_builder, :variable!
|
13
|
+
def_delegator :greedy_variable_builder, :greedy_variable!
|
14
|
+
def_delegator :derivation_builder, :derivation!
|
15
|
+
|
16
|
+
def method_missing(method, *arguments, &block)
|
17
|
+
builders_with_shorthand
|
18
|
+
.find { |builder| builder.shorthand?(method, *arguments, &block) }
|
19
|
+
.method_missing(method, *arguments, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def builders_with_shorthand
|
25
|
+
@builders ||= [
|
26
|
+
literal_builder,
|
27
|
+
variable_builder,
|
28
|
+
greedy_variable_builder
|
29
|
+
]
|
30
|
+
end
|
31
|
+
|
32
|
+
def literal_builder
|
33
|
+
@literal_builder ||= LiteralBuilder.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def variable_builder
|
37
|
+
@variable_builder ||= VariableBuilder.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def greedy_variable_builder
|
41
|
+
@greedy_variable_builder ||= GreedyVariableBuilder.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def derivation_builder
|
45
|
+
@derivation_builder ||= DerivationBuilder.new
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "metamorpher/terms/derived"
|
2
|
+
require "metamorpher/builders/ast/builder"
|
3
|
+
|
4
|
+
module Metamorpher
|
5
|
+
module Builders
|
6
|
+
module AST
|
7
|
+
class DerivationBuilder
|
8
|
+
def derivation!(*base, &block)
|
9
|
+
fail ArgumentError, "wrong number of arguments (0)" if base.empty?
|
10
|
+
fail ArgumentError, "a block must be provided" if block.nil?
|
11
|
+
|
12
|
+
Terms::Derived.new(
|
13
|
+
base: base,
|
14
|
+
derivation: ->(*args) { block.call(*args, Builder.new) }
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "metamorpher/terms/variable"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Builders
|
5
|
+
module AST
|
6
|
+
class GreedyVariableBuilder
|
7
|
+
def greedy_variable!(name, &block)
|
8
|
+
if block
|
9
|
+
Terms::Variable.new(name: name, greedy?: true, condition: block)
|
10
|
+
else
|
11
|
+
Terms::Variable.new(name: name, greedy?: true)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def shorthand?(method, *arguments, &block)
|
16
|
+
!method[/\p{Lower}/] && method.to_s.end_with?("_")
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method, *arguments, &block)
|
20
|
+
if shorthand?(method, *arguments, &block)
|
21
|
+
greedy_variable!(method.to_s.chomp("_").downcase.to_sym, *arguments, &block)
|
22
|
+
else
|
23
|
+
super.method_missing(method, *arguments, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "metamorpher/terms/literal"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Builders
|
5
|
+
module AST
|
6
|
+
class LiteralBuilder
|
7
|
+
def literal!(name, *children)
|
8
|
+
Terms::Literal.new(name: name, children: children.map { |c| termify(c) })
|
9
|
+
end
|
10
|
+
|
11
|
+
def shorthand?(method, *arguments, &block)
|
12
|
+
!method[/\p{Upper}/]
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(method, *arguments, &block)
|
16
|
+
if shorthand?(method, *arguments, &block)
|
17
|
+
literal!(method, *arguments)
|
18
|
+
else
|
19
|
+
super.method_missing(method, *arguments, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def termify(item)
|
26
|
+
item.kind_of?(Terms::Term) ? item : literal!(item)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "metamorpher/terms/variable"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Builders
|
5
|
+
module AST
|
6
|
+
class VariableBuilder
|
7
|
+
def variable!(name, &block)
|
8
|
+
if block
|
9
|
+
Terms::Variable.new(name: name, condition: block)
|
10
|
+
else
|
11
|
+
Terms::Variable.new(name: name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def shorthand?(method, *arguments, &block)
|
16
|
+
!method[/\p{Lower}/] && !method.to_s.end_with?("_")
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(method, *arguments, &block)
|
20
|
+
if shorthand?(method, *arguments, &block)
|
21
|
+
variable!(method.downcase.to_sym, *arguments, &block)
|
22
|
+
else
|
23
|
+
super.method_missing(method, *arguments, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "metamorpher/drivers/ruby"
|
2
|
+
require "metamorpher/builders/ruby/term"
|
3
|
+
require "metamorpher/builders/ruby/uppercase_constant_rewriter"
|
4
|
+
require "metamorpher/builders/ruby/uppercase_rewriter"
|
5
|
+
|
6
|
+
module Metamorpher
|
7
|
+
module Builders
|
8
|
+
module Ruby
|
9
|
+
class Builder
|
10
|
+
def build(source)
|
11
|
+
decorate(rewrite(parse(source)))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def decorate(term)
|
17
|
+
term.extend(Term)
|
18
|
+
end
|
19
|
+
|
20
|
+
def rewrite(parsed)
|
21
|
+
rewriters.reduce(parsed) { |a, e| e.reduce(a) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(source)
|
25
|
+
driver.parse(source)
|
26
|
+
end
|
27
|
+
|
28
|
+
def rewriters
|
29
|
+
@rewriters ||= [UppercaseConstantRewriter.new, UppercaseRewriter.new]
|
30
|
+
end
|
31
|
+
|
32
|
+
def driver
|
33
|
+
@driver ||= Drivers::Ruby.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "metamorpher/builders/ruby/variable_replacement_visitor"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Builders
|
5
|
+
module Ruby
|
6
|
+
class DerivingVisitor < VariableReplacementVisitor
|
7
|
+
def initialize(variable_name, *base, derivation)
|
8
|
+
super(variable_name, Terms::Derived.new(base: base, derivation: derivation))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "metamorpher/builders/ruby/variable_replacement_visitor"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Builders
|
5
|
+
module Ruby
|
6
|
+
class EnsuringVisitor < VariableReplacementVisitor
|
7
|
+
def initialize(variable_name, condition)
|
8
|
+
super(variable_name, Terms::Variable.new(name: variable_name, condition: condition))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "metamorpher/builders/ruby/ensuring_visitor"
|
2
|
+
require "metamorpher/builders/ruby/deriving_visitor"
|
3
|
+
|
4
|
+
module Metamorpher
|
5
|
+
module Builders
|
6
|
+
module Ruby
|
7
|
+
module Term
|
8
|
+
def ensuring(variable_name, &condition)
|
9
|
+
accept_and_decorate(
|
10
|
+
EnsuringVisitor.new(
|
11
|
+
variable_name.downcase.to_sym,
|
12
|
+
condition
|
13
|
+
)
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deriving(variable_name, *base_names, &derivation)
|
18
|
+
accept_and_decorate(
|
19
|
+
DerivingVisitor.new(
|
20
|
+
variable_name.downcase.to_sym,
|
21
|
+
*base_names.map { |n| n.downcase.to_sym },
|
22
|
+
derivation
|
23
|
+
)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def accept_and_decorate(visitor)
|
30
|
+
accept(visitor).extend(Term)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "metamorpher/builders/ast"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Builders
|
5
|
+
module Ruby
|
6
|
+
class UppercaseConstantRewriter
|
7
|
+
include Metamorpher::Rewriter
|
8
|
+
include Metamorpher::Builders::AST
|
9
|
+
|
10
|
+
def pattern
|
11
|
+
builder.const(
|
12
|
+
builder.literal!(nil),
|
13
|
+
builder.VARIABLE_TO_BE { |v| v.name && v.name.to_s[/^[A-Z_]*$/] }
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def replacement
|
18
|
+
builder.derivation!(:variable_to_be) do |variable_to_be, builder|
|
19
|
+
name = variable_to_be.name.to_s
|
20
|
+
|
21
|
+
if name.end_with?("_")
|
22
|
+
builder.greedy_variable! name.chomp("_").downcase.to_sym
|
23
|
+
else
|
24
|
+
builder.variable! name.downcase.to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "metamorpher/builders/ast"
|
2
|
+
|
3
|
+
module Metamorpher
|
4
|
+
module Builders
|
5
|
+
module Ruby
|
6
|
+
class UppercaseRewriter
|
7
|
+
include Metamorpher::Rewriter
|
8
|
+
include Metamorpher::Builders::AST
|
9
|
+
|
10
|
+
def pattern
|
11
|
+
builder.VARIABLE_TO_BE { |v| v.name && v.name.to_s[/^[A-Z_]*$/] }
|
12
|
+
end
|
13
|
+
|
14
|
+
def replacement
|
15
|
+
builder.derivation!(:variable_to_be) do |variable_to_be, builder|
|
16
|
+
name = variable_to_be.name.to_s
|
17
|
+
|
18
|
+
if name.end_with?("_")
|
19
|
+
builder.greedy_variable! name.chomp("_").downcase.to_sym
|
20
|
+
else
|
21
|
+
builder.variable! name.downcase.to_sym
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|