-
Notifications
You must be signed in to change notification settings - Fork 0
/
cop.rb
161 lines (137 loc) · 4.06 KB
/
cop.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
require 'sourcify'
class Context
# Context + Implementation for a klass method
class CI
attr_accessor :ctx, :impl
def initialize(ctx, impl)
@ctx = ctx
@impl = impl
end
end
def initialize
Context.set_vars
end
def self.set_vars
@@adaptations = Hash.new do |klass,methods|
klass[methods] = Hash.new do |method,impls|
method[impls] = Array.new
end
end
@@count = 0
end
def active?
@@count > 0
end
def activate
@@count += 1
self.dynamic_adapt
end
def deactivate
if !active?; return end
# Remove adaptations
@@adaptations.each do |klass,methods|
methods.each do |m,impls|
impls.delete_if do |ci|
ci.ctx == self
end
self.send_method(klass, m, impls.last.impl)
end
end
@@count = 0
end
# Add a method to the class
# adaptations are stored in a hashmap containing a stack of adaptations
# for each method
def adapt(klass, method, &impl)
# If this is the first adapt, start by adding base methods
self.add_base_methods(klass)
# Define a proceed method
latest = self.proceed(klass, method)
# XXX (´・_・`) hack
hack = impl.to_source(:ignore_nested => true).gsub(/proceed/, latest.to_s)
# Add the adaptation
self.push_adapt(klass, method, CI.new(self, eval(hack)))
# Apply
self.dynamic_adapt
end
# Get to the previous adaptation
def unadapt(klass, method)
self.pop_adapt(klass, method, self)
self.dynamic_adapt(false)
end
# Define the next most prioritary method
def proceed(klass, method)
previous_method = @@adaptations[klass][method].last.impl
count = @@adaptations[klass][method].size
raise Exception, "Proceed on base method" if previous_method.nil?
latest = ["proceed", method.to_s, count.to_s].join('_').to_sym
self.send_method(klass, latest, previous_method)
latest
end
# Define a method in klass
def send_method(klass, method, impl)
klass.send(:define_method, method, impl)
end
# Remove a method in klass
def remove_method(klass, method)
klass.send(:remove_method, method)
end
# Define an adaptation if the context is active
def dynamic_adapt(activate_self = true)
if !active?; return end
# Define most prioritary implementation of context for each method
@@adaptations.each do |klass, methods|
methods.each do |m,impls|
cimpls = impls.select do |ci|
ci.ctx == self if activate_self
end
# Define the base methods if no other context is available
cimpls = impls if cimpls.empty?
self.send_method(klass, m, cimpls.last.impl)
end
end
end
def add_base_methods(klass)
if @@adaptations[klass].empty?
methods = klass.instance_methods(false)
raise Exception, "No methods in class" if methods.empty?
methods.each do |name|
# Ignore leftover proceed methods
next if name.to_s.include? "proceed"
meth = klass.instance_method(name)
method_bound = meth.bind(klass.class == Module ? klass : klass.new)
self.push_adapt(klass, name, CI.new(nil, method_bound.to_proc))
end
end
end
# Add an adaptation to the stack
def push_adapt(klass, method, obj)
@@adaptations[klass][method].push(obj)
end
# Remove an adaptation from the stack
def pop_adapt(klass, method, ctx)
self_adapts = @@adaptations[klass][method].select do |ci|
ci.ctx == ctx
end
@@adaptations[klass][method].delete(self_adapts.last)
end
# Reset global changes made by the framework
def self.reset_cop_state
# Reset methods to the base implementation
if defined? @@adaptations
@@adaptations.each do |klass, methods|
klass.instance_methods(false).each do |name|
Context.new.remove_method(klass, name) if name.to_s.include? "proceed"
end
methods.each do |m,impls|
Context.new.send_method(klass, m, impls.first.impl)
end
end
end
# Reset variables
Context.set_vars
end
end
def reset_cop_state
Context.reset_cop_state
end