metamorpher 0.1.0
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 +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
|