cop 0.0.1
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.
- data/lib/context_adaptation.rb +45 -0
- data/lib/context_manager.rb +70 -0
- data/lib/cop.rb +174 -0
- metadata +47 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "cop"
|
4
|
+
|
5
|
+
class ContextAdaptation
|
6
|
+
|
7
|
+
attr_accessor :context, :adapted_class, :adapted_selector, :adapted_implementation
|
8
|
+
|
9
|
+
def self.in_context(a_context, a_class, a_selector, a_method)
|
10
|
+
ca = self.new
|
11
|
+
ca.context= a_context
|
12
|
+
ca.adapted_class= a_class
|
13
|
+
ca.adapted_selector= a_selector
|
14
|
+
ca.adapted_implementation= a_method
|
15
|
+
ca
|
16
|
+
end
|
17
|
+
|
18
|
+
def deploy
|
19
|
+
x = @adapted_implementation
|
20
|
+
y = self
|
21
|
+
if @context != Context.default
|
22
|
+
@adapted_class.send(:define_method, @adapted_selector, lambda do |args = x.parameters, ca = y|
|
23
|
+
Context.stack= Context.stack.push(ca)
|
24
|
+
r = x.call(args)
|
25
|
+
Context.stack.pop()
|
26
|
+
r
|
27
|
+
end)
|
28
|
+
else
|
29
|
+
@adapted_class.send(:define_method, @adapted_selector, @adapted_implementation)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def adapts_class?(a_class, a_symbol)
|
34
|
+
self.adapted_class == a_class and self.adapted_selector == a_symbol
|
35
|
+
end
|
36
|
+
|
37
|
+
def same_target?(other)
|
38
|
+
self.adapts_class? other.adapted_class, other.adapted_selector
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
"for #{@adapted_selector.to_s} of #{@adapted_class.to_s} using #{@adapted_implementation.nil? ? "no implementation" : @adapted_implementation.to_s} in #{@context.to_s}"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
class ContextManager
|
4
|
+
|
5
|
+
@@default = nil
|
6
|
+
|
7
|
+
attr_accessor :directory, :adaptations
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@directory = Hash.new
|
11
|
+
@adaptations = Array.new
|
12
|
+
@total_activations = 0
|
13
|
+
@activation_stamps = Hash.new
|
14
|
+
self.resolution_policy= self.age_resolution_policy
|
15
|
+
end
|
16
|
+
|
17
|
+
def discard_context(context)
|
18
|
+
raise Exception "can't discard outside context manager" if context.manager != self
|
19
|
+
raise Exception, "can't discard an active context" if context.active?
|
20
|
+
@directory.delete(self)
|
21
|
+
@activation_stamps.delete(context)
|
22
|
+
end
|
23
|
+
|
24
|
+
def activate_adaptation(context_adaptation)
|
25
|
+
@adaptations.push(context_adaptation)
|
26
|
+
self.deploy_best_adaptation_for(context_adaptation.adapted_class, context_adaptation.adapted_selector)
|
27
|
+
end
|
28
|
+
|
29
|
+
def deactivate_adaptation(context_adaptation)
|
30
|
+
raise Exception, "can't deactivate unmanaged adaptation" if @adaptations.delete(context_adaptation).nil?
|
31
|
+
self.deploy_best_adaptation_for(context_adaptation.adapted_class, context_adaptation.adapted_selector) unless @adaptations.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def deploy_best_adaptation_for(a_class, a_symbol)
|
35
|
+
self.adaptation_chain(a_class, a_symbol).first.deploy
|
36
|
+
end
|
37
|
+
|
38
|
+
def adaptation_chain(a_class, a_symbol)
|
39
|
+
a = @adaptations.select do |adaptation|
|
40
|
+
adaptation.adapts_class? a_class, a_symbol
|
41
|
+
end
|
42
|
+
raise Exception, "no adaptation found for #{a_class.to_s}.#{a_symbol.to_s}" if a.empty?
|
43
|
+
a.sort do |a1, a2|
|
44
|
+
@resolution_policy.call(a1.context, a2.context)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def resolution_policy=(policy)
|
49
|
+
@resolution_policy = policy
|
50
|
+
@adaptations.each do |adaptation|
|
51
|
+
self.deploy_best_adaptation_for(adaptation.adapted_class, adaptation.adapted_selector)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def age_resolution_policy
|
56
|
+
Proc.new { |a1, a2|
|
57
|
+
(self.context_activation_age(a1) < self.context_activation_age(a2)) ? -1 : 1
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def context_activation_age(context)
|
62
|
+
@total_activations - (@activation_stamps[context].nil? ? 0 : @activation_stamps[context])
|
63
|
+
end
|
64
|
+
|
65
|
+
def signal_activation_request(context)
|
66
|
+
@total_activations += 1
|
67
|
+
@activation_stamps[context] = @total_activations
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/lib/cop.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "context_manager"
|
4
|
+
require_relative "context_adaptation"
|
5
|
+
|
6
|
+
class Context
|
7
|
+
|
8
|
+
@@default = nil
|
9
|
+
@@stack = Array.new
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_writer :stack
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_writer :manager
|
16
|
+
attr_accessor :adaptations
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@count = 0
|
20
|
+
@adaptations = Array.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.default(*args)
|
24
|
+
return @@default = args.first unless args.size == 0
|
25
|
+
if @@default.nil?
|
26
|
+
@@default = self.new
|
27
|
+
@@default.activate
|
28
|
+
end
|
29
|
+
@@default
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.stack
|
33
|
+
@@stack
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.named(name)
|
37
|
+
c = self.new
|
38
|
+
c.name= name
|
39
|
+
c
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.proceed
|
43
|
+
ca = @@stack.last
|
44
|
+
raise Exception, "proceed should only be called from adapted methods" if ca.nil?
|
45
|
+
adaptations = self.default.manager.adaptations.select do |adaptation|
|
46
|
+
adaptation.adapts_class? ca.adapted_class, ca.adapted_selector
|
47
|
+
end
|
48
|
+
raise Exception, "no adaptation found" if adaptations.empty?
|
49
|
+
meth = @@default.manager.adaptation_chain(ca.adapted_class, ca.adapted_selector)[1].adapted_implementation
|
50
|
+
if meth.is_a? Proc
|
51
|
+
meth.call(meth.parameters)
|
52
|
+
else
|
53
|
+
meth.bind(ca.adapted_class.class_eval("self.new")).call(meth.parameters)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def activation_age
|
58
|
+
self.manager.context_activation_age(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
def activate
|
62
|
+
self.manager.signal_activation_request(self)
|
63
|
+
self.activate_adaptations if @count == 0
|
64
|
+
@count += 1
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def deactivate
|
69
|
+
self.deactivate_adaptations if @count == 1
|
70
|
+
@count -= 1 unless @count == 0
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def active?
|
75
|
+
@count > 0
|
76
|
+
end
|
77
|
+
|
78
|
+
def name
|
79
|
+
self.manager.directory[self]
|
80
|
+
end
|
81
|
+
|
82
|
+
def name=(new_name)
|
83
|
+
new_name.nil? ? self.manager.directory.delete(self) : self.manager.directory[self] = new_name
|
84
|
+
end
|
85
|
+
|
86
|
+
def manager
|
87
|
+
return @manager unless @manager.nil?
|
88
|
+
return @manager = Context.default.manager unless self == Context.default
|
89
|
+
@manager = ContextManager.new
|
90
|
+
self.name= "default"
|
91
|
+
@manager
|
92
|
+
end
|
93
|
+
|
94
|
+
def discard
|
95
|
+
self.manager.discard_context(self)
|
96
|
+
Context.default(nil) if self == Context.default
|
97
|
+
@adaptations.each do |adaptation|
|
98
|
+
self.remove_existing_adaptation(adaptation)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_s
|
103
|
+
(self.name.nil? ? "anonymous" : "#{self.name}") + " context"
|
104
|
+
end
|
105
|
+
|
106
|
+
def adapt_class(a_class, a_selector, a_method)
|
107
|
+
begin
|
108
|
+
method = a_class.instance_method(a_selector)
|
109
|
+
rescue NameError
|
110
|
+
raise Exception, "can't adapt inexistent method #{a_selector.to_s} in #{a_method.to_s}"
|
111
|
+
end
|
112
|
+
default = ContextAdaptation.in_context(Context.default, a_class, a_selector, method)
|
113
|
+
Context.default.add_adaptation(default) { :preserve }
|
114
|
+
adaptation = ContextAdaptation.in_context(self, a_class, a_selector, a_method)
|
115
|
+
self.add_adaptation(adaptation) { raise Exception, "an adaptation already exists for #{self.to_s}" }
|
116
|
+
end
|
117
|
+
|
118
|
+
def add_adaptation(context_adaptation, &block)
|
119
|
+
existing = @adaptations.index do |adaptation|
|
120
|
+
adaptation.same_target? context_adaptation
|
121
|
+
end
|
122
|
+
if existing.nil?
|
123
|
+
self.add_inexistent_adaptation(context_adaptation)
|
124
|
+
return self
|
125
|
+
end
|
126
|
+
existing = @adaptations[existing]
|
127
|
+
action = yield
|
128
|
+
if action == :overwrite
|
129
|
+
self.remove_existing_adaptation(existing)
|
130
|
+
self.add_inexistent_adaptation(context_adaptation)
|
131
|
+
else
|
132
|
+
raise Exception, "unknown overriding action #{action.to_s}" unless action == :preserve
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def add_inexistent_adaptation(context_adaptation)
|
137
|
+
raise Exception, "can't add foreign adaptation" unless self == context_adaptation.context
|
138
|
+
@adaptations.push(context_adaptation)
|
139
|
+
self.manager.activate_adaptation(context_adaptation) if self.active?
|
140
|
+
end
|
141
|
+
|
142
|
+
def remove_existing_adaptation(context_adaptation)
|
143
|
+
raise Exception, "can't remove foreign adaptation" unless self == context_adaptation.context
|
144
|
+
self.manager.deactivate_adaptation(context_adaptation) if self.active?
|
145
|
+
raise Exception, "can't remove adaptation" if @adaptations.delete(context_adaptation).nil?
|
146
|
+
end
|
147
|
+
|
148
|
+
def activate_adaptations
|
149
|
+
@adaptations.each do |adaptation|
|
150
|
+
begin
|
151
|
+
self.manager.activate_adaptation(adaptation)
|
152
|
+
rescue Exception
|
153
|
+
self.rollback_adaptations
|
154
|
+
raise Exception, $!
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def deactivate_adaptations
|
160
|
+
@adaptations.each do |adaptation|
|
161
|
+
self.manager.deactivate_adaptation(adaptation)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def rollback_adaptations
|
166
|
+
deployed = self.manager.adaptations.select do |adaptation|
|
167
|
+
adaptation.context == self
|
168
|
+
end
|
169
|
+
deployed.each do |adaptation|
|
170
|
+
self.manager.deactivate_adaptation(adaptation)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
metadata
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Julien Odent
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-25 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Context-Oriented Programming framework for Ruby
|
15
|
+
email: julien@odent.net
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/cop.rb
|
21
|
+
- lib/context_adaptation.rb
|
22
|
+
- lib/context_manager.rb
|
23
|
+
homepage: http://github.com/patapizza/COP-Ruby
|
24
|
+
licenses: []
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 1.8.24
|
44
|
+
signing_key:
|
45
|
+
specification_version: 3
|
46
|
+
summary: COP framework
|
47
|
+
test_files: []
|