cop 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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: []