phenomenal 0.9.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.
Files changed (43) hide show
  1. data/LICENSE +27 -0
  2. data/README +3 -0
  3. data/Rakefile +11 -0
  4. data/lib/phenomenal.rb +24 -0
  5. data/lib/phenomenal/adaptation.rb +79 -0
  6. data/lib/phenomenal/conflict_policies.rb +20 -0
  7. data/lib/phenomenal/context.rb +269 -0
  8. data/lib/phenomenal/dsl.rb +119 -0
  9. data/lib/phenomenal/feature.rb +8 -0
  10. data/lib/phenomenal/logger.rb +34 -0
  11. data/lib/phenomenal/manager.rb +317 -0
  12. data/lib/phenomenal/proc.rb +34 -0
  13. data/lib/phenomenal/relationships/context_relationships.rb +22 -0
  14. data/lib/phenomenal/relationships/dsl.rb +18 -0
  15. data/lib/phenomenal/relationships/feature_relationships.rb +42 -0
  16. data/lib/phenomenal/relationships/implication.rb +35 -0
  17. data/lib/phenomenal/relationships/relationship.rb +42 -0
  18. data/lib/phenomenal/relationships/relationships_manager.rb +63 -0
  19. data/lib/phenomenal/relationships/relationships_store.rb +73 -0
  20. data/lib/phenomenal/relationships/requirement.rb +26 -0
  21. data/lib/phenomenal/relationships/suggestion.rb +41 -0
  22. data/lib/phenomenal/version.rb +3 -0
  23. data/spec/adaptation_spec.rb +64 -0
  24. data/spec/behavior/adaptation_spec.rb +5 -0
  25. data/spec/behavior/combined_contexts_spec.rb +5 -0
  26. data/spec/behavior/composition_spec.rb +5 -0
  27. data/spec/behavior/conflict_policy_spec.rb +5 -0
  28. data/spec/behavior/open_context.rb +5 -0
  29. data/spec/behavior/relationships_spec.rb +249 -0
  30. data/spec/context_spec.rb +268 -0
  31. data/spec/dsl_spec.rb +181 -0
  32. data/spec/feature_spec.rb +5 -0
  33. data/spec/manager_spec.rb +84 -0
  34. data/spec/proc_spec.rb +20 -0
  35. data/spec/relationships/context_relationships_spec.rb +13 -0
  36. data/spec/relationships/dsl_spec.rb +13 -0
  37. data/spec/relationships/feature_relationships_spec.rb +13 -0
  38. data/spec/relationships/relationship_spec.rb +31 -0
  39. data/spec/relationships/relationships_manager_spec.rb +15 -0
  40. data/spec/relationships/relationships_store_spec.rb +19 -0
  41. data/spec/spec_helper.rb +18 -0
  42. data/spec/test_classes.rb +111 -0
  43. metadata +124 -0
@@ -0,0 +1,35 @@
1
+ class Phenomenal::Implication < Phenomenal::Relationship
2
+ attr_accessor :activation_counter
3
+ def initialize(source,target,feature)
4
+ super(source,target,feature)
5
+ @activation_counter=0
6
+ end
7
+
8
+ def activate_feature
9
+ if source.active?
10
+ target.activate
11
+ activation_counter+=1
12
+ end
13
+ end
14
+
15
+ def deactivate_feature
16
+ if activation_counter>0
17
+ target.deactivate
18
+ activation_coutner-=1
19
+ end
20
+ end
21
+
22
+ def activate_context(context)
23
+ if source==context
24
+ target.activate
25
+ end
26
+ end
27
+
28
+ def deactivate_context(context)
29
+ if source==context
30
+ target.deactivate
31
+ else
32
+ source.deactivate
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ class Phenomenal::Relationship
2
+ attr_accessor :source,:target,:manager,:feature
3
+
4
+ def initialize(source,target,feature)
5
+ @source=source
6
+ @target=target
7
+ @manager=Phenomenal::Manager.instance
8
+ @feature=feature
9
+ refresh
10
+ end
11
+
12
+ def ==(other)
13
+ self.class==other.class &&
14
+ self.source==other.source &&
15
+ self.target==other.target &&
16
+ self.feature==other.feature
17
+ end
18
+
19
+ def refresh
20
+ s = manager.context_defined?(source)
21
+ self.source=s if !s.nil?
22
+
23
+ t = manager.context_defined?(target)
24
+ self.target=t if !t.nil?
25
+ end
26
+
27
+ def activate_context(context)
28
+ end
29
+
30
+ def deactivate_context(context)
31
+ end
32
+
33
+ def activate_feature
34
+ end
35
+
36
+ def deactivate_feature
37
+ end
38
+
39
+ def to_s
40
+ "#{self.class.name} between #{source.class.name}:#{source} and #{target.class.name}:#{target}"
41
+ end
42
+ end
@@ -0,0 +1,63 @@
1
+ require 'singleton'
2
+
3
+ class Phenomenal::RelationshipsManager
4
+ include Singleton
5
+
6
+ attr_accessor :relationships
7
+
8
+ def activate_relationships(context)
9
+ # Step 1: Import the new relationships for a feature
10
+ if context.is_a?(Phenomenal::Feature)
11
+ import_relationships(context)
12
+ end
13
+ # Step 2: Apply relationships
14
+ relationships.get_for(context).each do |relationship|
15
+ relationship.activate_context(context)
16
+ end
17
+ end
18
+
19
+ def deactivate_relationships(context)
20
+ # Step 1: Unapply relationships
21
+ relationships.get_for(context).each do |relationship|
22
+ relationship.deactivate_context(context)
23
+ end
24
+ # Step 2: Remove relationships
25
+ if context.is_a?(Phenomenal::Feature)
26
+ remove_relationships(context)
27
+ end
28
+ end
29
+
30
+ # Called when a context is defined in the manager
31
+ def update_relationships_references(context)
32
+ relationships.update_references(context)
33
+ end
34
+
35
+ private
36
+ def import_relationships(feature)
37
+ begin
38
+ feature.relationships.each do |relationship|
39
+ relationship.refresh # Update references
40
+ relationship.activate_feature # Activate relationship
41
+ relationships.add(relationship)
42
+ end
43
+ rescue Phenomenal::Error => m
44
+ feature.deactivate
45
+ Phenomenal::Logger.instance.debug(
46
+ "Unable to activate the feature #{feature} \n #{m}"
47
+ )
48
+ end
49
+ end
50
+
51
+ def remove_relationships(feature)
52
+ feature.relationships.each do |relationship|
53
+ if relationships.include?(relationship)
54
+ relationship.deactivate_feature
55
+ relationships.remove(relationship)
56
+ end
57
+ end
58
+ end
59
+
60
+ def initialize
61
+ @relationships = Phenomenal::RelationshipsStore.new
62
+ end
63
+ end
@@ -0,0 +1,73 @@
1
+ class Phenomenal::RelationshipsStore
2
+ attr_accessor :sources, :targets
3
+
4
+ def initialize
5
+ @sources = {}
6
+ @targets = {}
7
+ end
8
+
9
+ def add(relationship)
10
+ if @sources[relationship.source].nil?
11
+ @sources[relationship.source] = Array.new
12
+ end
13
+ @sources[relationship.source].push(relationship)
14
+
15
+ if @targets[relationship.target].nil?
16
+ @targets[relationship.target] = Array.new
17
+ end
18
+ @targets[relationship.target].push(relationship)
19
+ end
20
+
21
+ def remove(relationship)
22
+ @sources[relationship.source].delete(relationship) if @sources[relationship.source] # In case of rollback
23
+ @targets[relationship.target].delete(relationship) if @targets[relationship.target]
24
+ end
25
+
26
+ def include?(relationship)
27
+ if @sources[relationship.source]
28
+ @sources[relationship.source].include?(relationship)
29
+ else
30
+ false
31
+ end
32
+ end
33
+
34
+ def update_references(context)
35
+ # Do nothing when anonymous, references are already valid
36
+ return if context.anonymous?
37
+ # Update sources
38
+ @sources[context.name].each do |relationship|
39
+ relationship.source=context
40
+ end
41
+ @sources[context]=@source.delete(context.name)
42
+ # Update targets
43
+ @targets[context.name].each do |relationship|
44
+ relationship.target=context
45
+ end
46
+ @targets[context]=@targets.delete(context.name)
47
+ end
48
+
49
+ def get_for(context)
50
+ get_for_source(context).concat(get_for_target(context))
51
+ end
52
+
53
+ private
54
+ # Return an array of relationships
55
+ def get_for_source(source)
56
+ rel = @sources[source]
57
+ if rel.nil?
58
+ Array.new
59
+ else
60
+ rel
61
+ end
62
+ end
63
+
64
+ # Return an array of relationships
65
+ def get_for_target(target)
66
+ rel = @targets[target]
67
+ if rel.nil?
68
+ Array.new
69
+ else
70
+ rel
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,26 @@
1
+ class Phenomenal::Requirement < Phenomenal::Relationship
2
+ def activate_feature
3
+ check_requirement
4
+ end
5
+
6
+ def activate_context(context)
7
+ if(source==context)
8
+ check_requirement
9
+ end
10
+ end
11
+
12
+ def deactivate_context(context)
13
+ if(target==context)
14
+ source.deactivate
15
+ end
16
+ end
17
+
18
+ private
19
+ def check_requirement
20
+ if source.active? && !target.active?
21
+ Phenomenal::Logger.instance.error(
22
+ "Requirement of #{target} for #{source} is not satisfied"
23
+ )
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ class Phenomenal::Suggestion < Phenomenal::Relationship
2
+ attr_accessor :activation_counter
3
+
4
+ def initialize(source,target,feature)
5
+ super(source,target,feature)
6
+ @activation_counter=0
7
+ end
8
+
9
+ def activate_feature
10
+ begin
11
+ if source.active?
12
+ target.activate
13
+ self.activation_counter+=1
14
+ end
15
+ rescue
16
+ end
17
+ end
18
+
19
+ def deactivate_feature
20
+ begin
21
+ if activation_counter>0
22
+ target.deactivate
23
+ self.activation_coutner-=1
24
+ end
25
+ rescue
26
+ end
27
+ end
28
+
29
+
30
+ def activate_context(context)
31
+ if source==context
32
+ target.activate
33
+ end
34
+ end
35
+
36
+ def deactivate_context(context)
37
+ if source==context
38
+ target.deactivate
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module Phenomenal
2
+ VERSION = "0.9.0"
3
+ end
@@ -0,0 +1,64 @@
1
+ require "spec_helper"
2
+ describe Phenomenal::Adaptation do
3
+ before :each do
4
+ define_test_classes
5
+ end
6
+
7
+ describe "#deploy" do
8
+ it "should be able to deploy itself in instances to override default implementation" do
9
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-1})
10
+ t = TestString.new("1234")
11
+ t.length.should == 4
12
+ expect {adaptation.deploy}.to_not raise_error
13
+ t.length.should == -1
14
+ end
15
+
16
+ it "should be able to deploy itself in classes to override default implementation" do
17
+ TestString.name.should == "TestString"
18
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:name,false,Proc.new{"TEST"})
19
+ expect {adaptation.deploy}.to_not raise_error
20
+ TestString.name.should == "TEST"
21
+ end
22
+ end
23
+
24
+ describe "#bind" do
25
+ it "should be possible to temporary bind adapation as instance methods" do
26
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-10})
27
+ t = TestString.new("1234")
28
+ t.length.should == -1
29
+ adaptation.bind(t).should == -10
30
+ t.length.should == -1
31
+ end
32
+
33
+ it "should be possible to temporary bind adapatation as class methods" do
34
+ TestString.name.should == "TEST"
35
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:name,false,Proc.new{"TEST2"})
36
+ adaptation.bind(TestString).should == "TEST2"
37
+ TestString.name.should == "TEST"
38
+ end
39
+ end
40
+
41
+ describe "#instance_adaptation?" do
42
+ it "should return true if the method is a instance method" do
43
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-10})
44
+ adaptation.instance_adaptation?.should be_true
45
+ end
46
+
47
+ it "should return false if the method is a class method" do
48
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:name,false,Proc.new{"TEST2"})
49
+ adaptation.instance_adaptation?.should be_false
50
+ end
51
+ end
52
+
53
+ describe "#concern" do
54
+ it "should return true if the adaptation concern the class n_klass and method n_method and is instance method if instance=true" do
55
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:length,true,Proc.new{-10})
56
+ adaptation.concern?(TestString,:length,true).should be_true
57
+ end
58
+
59
+ it "should return false if the adaptation doesn't concern the class n_klass and method n_method and is instance method if instance=true" do
60
+ adaptation = Phenomenal::Adaptation.new(nil,TestString,:size,true,Proc.new{-10})
61
+ adaptation.concern?(TestString,:length,true).should be_false
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Simple adaptations" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Combined contexts" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Composition of adaptations" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Conflict policies" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,5 @@
1
+ require "spec_helper"
2
+
3
+ describe "Open context facilities" do
4
+ pending "TODO"
5
+ end
@@ -0,0 +1,249 @@
1
+ require "spec_helper"
2
+
3
+ describe "Relationships" do
4
+ before :each do
5
+ @manager = Phenomenal::Manager.instance
6
+ @feature = Phenomenal::Feature.new(:feature)
7
+ @context_names = [:a,:b,:c,:d,:e,:f,:g]
8
+ @context_names.each do |name|
9
+ @feature.context(name)
10
+ end
11
+
12
+ end
13
+
14
+ after :each do
15
+ force_forget_context(@feature)
16
+ @context_names.each do |name|
17
+ force_forget_context(name)
18
+ end
19
+ @manager.default_context.deactivate
20
+ @manager.default_context.forget
21
+ end
22
+
23
+ describe Phenomenal::Feature do
24
+ it "should be able to add requirements for contexts" do
25
+ @feature.should respond_to :requirements_for
26
+ @feature.method(:requirements_for).arity.should be(2),
27
+ "Bad arity, should be 2"
28
+ end
29
+ it "should be able to add implications for contexts" do
30
+ @feature.should respond_to :implications_for
31
+ @feature.method(:implications_for).arity.should be(2),
32
+ "Bad arity, should be 2"
33
+ end
34
+ it "should be able to add suggestions for contexts" do
35
+ @feature.should respond_to :suggestions_for
36
+ @feature.method(:suggestions_for).arity.should be(2),
37
+ "Bad arity, should be 2"
38
+ end
39
+
40
+ describe "Requirements" do
41
+ it "should store requirements" do
42
+ @feature.requirements_for :a, :on=>[:b,:c,:d]
43
+ @feature.requirements_for :a, :on=>:e
44
+ @feature.relationships.should have(4).items
45
+ end
46
+
47
+ it "should avoid activation with missing requirements" do
48
+ @feature.requirements_for :a, :on=>[:b,:c]
49
+ phen_activate_context(:feature)
50
+ expect {phen_activate_context(:a)}.to raise_error Phenomenal::Error
51
+ phen_context_active?(:a).should be_false
52
+ end
53
+
54
+ it "should allow activation with all requirements" do
55
+ phen_activate_context(:feature)
56
+ phen_activate_context(:b)
57
+ phen_activate_context(:c)
58
+ expect {phen_activate_context(:a)}.to_not raise_error
59
+ phen_context_active?(:a).should be_true
60
+ end
61
+
62
+ it "should deactivate source when target requirement is deactivated" do
63
+ @feature.requirements_for :a, :on=>[:b,:c]
64
+ phen_activate_context(:feature)
65
+ phen_activate_context(:b)
66
+ phen_activate_context(:c)
67
+ phen_activate_context(:a)
68
+ expect {phen_deactivate_context(:b)}.to_not raise_error
69
+ phen_context_active?(:a).should be_false
70
+ end
71
+
72
+ it "should avoid feature activation when adding a not satisfied requirement" do
73
+ @feature.requirements_for :a, :on=>[:b,:c]
74
+ phen_activate_context(:a)
75
+ expect{phen_activate_context(:feature)}.to_not raise_error
76
+ phen_context_active?(:feature).should be_false
77
+ end
78
+
79
+ after do
80
+ @manager.default_context.deactivate
81
+ @manager.default_context.forget
82
+ end
83
+ it "should be possible to add requirements to the default context" do
84
+ requirements_for :a, :on=>[:b,:c,:d]
85
+ requirements_for :a, :on=>:e
86
+ @manager.default_context.relationships.should have(4).items
87
+ end
88
+
89
+ it "should be possible to put requirements in the nested contexts" do
90
+ feature :feature do
91
+ context :a do
92
+ requires :b
93
+ end
94
+ end
95
+ @feature.relationships.should have(1).items
96
+ end
97
+
98
+ it "should be possible to put requirements in combined nested contexts" do
99
+ feature :feature do
100
+ context :a,:b do
101
+ requires :c,:d,:e
102
+ end
103
+ end
104
+ @feature.relationships.should have(3).items
105
+ end
106
+ end
107
+ describe "Implications" do
108
+ it "should store implications" do
109
+ @feature.implications_for :a, :on=>[:b,:c,:d]
110
+ @feature.implications_for :a, :on=>:e
111
+ @feature.relationships.should have(4).items
112
+ end
113
+
114
+ it "should activate target when source is activated" do
115
+ @feature.implications_for :a, :on=>:b
116
+ @feature.activate
117
+ expect {phen_activate_context(:a)}.to_not raise_error
118
+ phen_context_active?(:a).should be_true
119
+ phen_context_active?(:b).should be_true
120
+ end
121
+
122
+ it "should deactivate target when source is deactivated" do
123
+ @feature.implications_for :a, :on=>:b
124
+ @feature.activate
125
+ phen_activate_context(:a)
126
+ phen_deactivate_context(:a)
127
+ phen_context_active?(:b).should be_false
128
+ end
129
+
130
+ it "should deactivate source when target is deactivated" do
131
+ @feature.implications_for :a, :on=>:b
132
+ @feature.activate
133
+ phen_activate_context(:a)
134
+ phen_deactivate_context(:b)
135
+ phen_context_active?(:a).should be_false
136
+ end
137
+
138
+ it "should be possible to put implications in the nested contexts" do
139
+ feature :feature do
140
+ context :a do
141
+ implies :b
142
+ end
143
+ end
144
+ @feature.relationships.should have(1).items
145
+ end
146
+
147
+ it "should be possible to put implications in combined nested contexts" do
148
+ feature :feature do
149
+ context :a,:b do
150
+ implies :c,:d,:e
151
+ end
152
+ end
153
+ @feature.relationships.should have(3).items
154
+ end
155
+ end
156
+
157
+ describe "Suggestions" do
158
+ it "should be working on the default feature" do
159
+ suggestions_for :a,:on=>:b
160
+ @manager.default_context.relationships.should have(1).items
161
+ context(:a).active?.should be_false
162
+ context(:b).active?.should be_false
163
+ expect {activate_context :a}.to_not raise_error
164
+ context(:a).active?.should be_true
165
+ context(:b).active?.should be_true
166
+ expect {deactivate_context :a}.to_not raise_error
167
+ context(:a).active?.should be_false
168
+ context(:b).active?.should be_false
169
+ #TODO forget default
170
+ end
171
+
172
+ it "should be possible to add relationships on active features" do
173
+ context(:a).active?.should be_false
174
+ context(:b).active?.should be_false
175
+ expect {activate_context :a}.to_not raise_error
176
+ suggestions_for :a,:on=>:b
177
+ @manager.default_context.relationships.should have(1).items
178
+ context(:a).active?.should be_true
179
+ context(:b).active?.should be_true
180
+ expect {deactivate_context :a}.to_not raise_error
181
+ context(:a).active?.should be_false
182
+ context(:b).active?.should be_false
183
+ end
184
+
185
+ it "should apply the relationship on the activation of the feature that contain it" do
186
+ context(:a).active?.should be_false
187
+ context(:b).active?.should be_false
188
+ expect {activate_context :a}.to_not raise_error
189
+ @feature.suggestions_for :a,:on=>:b
190
+ context(:a).active?.should be_true
191
+ context(:b).active?.should be_false
192
+ expect {activate_context @feature}.to_not raise_error
193
+ context(:a).active?.should be_true
194
+ context(:b).active?.should be_true
195
+ expect {deactivate_context @feature}.to_not raise_error
196
+ context(:a).active?.should be_true
197
+ context(:b).active?.should be_false
198
+ end
199
+
200
+ it "should store suggestions" do
201
+ @feature.suggestions_for :a, :on=>[:b,:c,:d]
202
+ @feature.suggestions_for :a, :on=>:e
203
+ @feature.relationships.should have(4).items
204
+ end
205
+
206
+ it "should activate target when source is activated" do
207
+ @feature.suggestions_for :a, :on=>:b
208
+ @feature.activate
209
+ expect {phen_activate_context(:a)}.to_not raise_error
210
+ phen_context_active?(:a).should be_true
211
+ phen_context_active?(:b).should be_true
212
+ end
213
+
214
+ it "should deactivate target when source is deactivated" do
215
+ @feature.suggestions_for :a, :on=>:b
216
+ @feature.activate
217
+ phen_activate_context(:a)
218
+ phen_deactivate_context(:a)
219
+ phen_context_active?(:b).should be_false
220
+ end
221
+
222
+ it "should not deactivate source when target is deactivated" do
223
+ @feature.suggestions_for :a, :on=>:b
224
+ @feature.activate
225
+ phen_activate_context(:a)
226
+ phen_deactivate_context(:b)
227
+ phen_context_active?(:a).should be_true
228
+ end
229
+
230
+ it "should be possible to put suggestions in the nested contexts" do
231
+ feature :feature do
232
+ context :a do
233
+ suggests :b
234
+ end
235
+ end
236
+ @feature.relationships.should have(1).items
237
+ end
238
+
239
+ it "should be possible to put suggestions in combined nested contexts" do
240
+ feature :feature do
241
+ context :a,:b do
242
+ suggests :c,:d,:e
243
+ end
244
+ end
245
+ @feature.relationships.should have(3).items
246
+ end
247
+ end
248
+ end
249
+ end