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.
@@ -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: []