hooked 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +41 -37
- data/lib/hooked.rb +3 -2
- data/lib/hooked/aspect.rb +6 -2
- data/lib/hooked/graph.rb +26 -16
- data/lib/hooked/hook.rb +8 -4
- data/lib/hooked/version.rb +1 -1
- data/spec/aspect_spec.rb +24 -7
- data/spec/graph_spec.rb +19 -2
- data/spec/hook_spec.rb +17 -0
- metadata +3 -3
data/README.md
CHANGED
@@ -9,42 +9,46 @@ Getting Started
|
|
9
9
|
By including `Hooked` into a class you basically say "everything can attach code
|
10
10
|
to instance methods of this class".
|
11
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
|
-
|
12
|
+
``` ruby
|
13
|
+
require "hooked"
|
14
|
+
|
15
|
+
class Foo
|
16
|
+
include Hooked
|
17
|
+
|
18
|
+
def breakfast
|
19
|
+
puts "No milk?!"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class BetterFoo
|
24
|
+
def shower
|
25
|
+
puts "Oooh..."
|
26
|
+
end
|
27
|
+
|
28
|
+
def fuuu(inner)
|
29
|
+
puts "FUUU!"
|
30
|
+
inner.call
|
31
|
+
puts "FUUU!"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
foo, better_foo = Foo.new, BetterFoo.new
|
36
|
+
foo.before :breakfast, better_foo.method(:shower)
|
37
|
+
foo.after :breakfast, proc { puts "Mmmh..." }
|
38
|
+
foo.around :breakfast, better_foo.method(:fuuu)
|
39
|
+
|
40
|
+
foo.breakfast
|
41
|
+
```
|
42
|
+
|
41
43
|
This will output:
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
```
|
46
|
+
FUUU!
|
47
|
+
Oooh...
|
48
|
+
No milk?!
|
49
|
+
Mmmh...
|
50
|
+
FUUU!
|
51
|
+
```
|
48
52
|
|
49
53
|
Execution Model
|
50
54
|
---------------
|
@@ -61,7 +65,9 @@ Possible Use Cases
|
|
61
65
|
|
62
66
|
You can use the `Hook` class to programmatically build graphs of aspects.
|
63
67
|
|
64
|
-
|
68
|
+
``` ruby
|
69
|
+
# code
|
70
|
+
```
|
65
71
|
|
66
72
|
TODO
|
67
73
|
|
@@ -76,8 +82,6 @@ Specs pass on MRI 1.9.2, others have not been tested yet.
|
|
76
82
|
To do & Ideas
|
77
83
|
-------------
|
78
84
|
|
79
|
-
* Let before, after, around and Hook.new also take a block
|
80
|
-
* Use insertion order as execution order if no aspect has dependencies
|
81
85
|
* Visualization of hook flow
|
82
86
|
* Make backtraces more readable
|
83
87
|
|
data/lib/hooked.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
require "hooked/aspect"
|
2
2
|
require "hooked/graph"
|
3
3
|
require "hooked/hook"
|
4
|
+
require "hooked/version"
|
4
5
|
|
5
6
|
module Hooked
|
6
7
|
attr_reader :hooked
|
7
8
|
|
8
9
|
[:before, :after, :around].each do |type|
|
9
|
-
define_method type do |pointcut, *args|
|
10
|
+
define_method type do |pointcut, *args, &block|
|
10
11
|
hook! pointcut
|
11
|
-
hooked[pointcut].add_aspect type, *args
|
12
|
+
hooked[pointcut].add_aspect type, *args, &block
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
data/lib/hooked/aspect.rb
CHANGED
@@ -3,9 +3,13 @@ module Hooked
|
|
3
3
|
attr_reader :type, :advice, :dependencies
|
4
4
|
attr_accessor :pointcut
|
5
5
|
|
6
|
-
def initialize(type, advice, dependencies = {})
|
6
|
+
def initialize(type, advice = nil, dependencies = {}, &block)
|
7
|
+
if (advice && block) || (!advice && !block)
|
8
|
+
raise ArgumentError, "Pass either an advice argument or a block"
|
9
|
+
end
|
10
|
+
|
7
11
|
[:before, :after].each {|d| dependencies[d] ||= [] }
|
8
|
-
@type, @advice, @dependencies = type, advice, dependencies
|
12
|
+
@type, @advice, @dependencies = type, (advice || block), dependencies
|
9
13
|
end
|
10
14
|
|
11
15
|
def call(*args, &block)
|
data/lib/hooked/graph.rb
CHANGED
@@ -2,7 +2,7 @@ require "tsort"
|
|
2
2
|
|
3
3
|
module Hooked
|
4
4
|
class Graph
|
5
|
-
class Node < Struct.new(:children, :
|
5
|
+
class Node < Struct.new(:children, :node); end
|
6
6
|
|
7
7
|
class CircularDependencyError < TSort::Cyclic; end
|
8
8
|
|
@@ -24,25 +24,35 @@ module Hooked
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def sort
|
27
|
-
|
28
|
-
|
29
|
-
nodes[node.advice.object_id] = Node.new(children, node); nodes
|
27
|
+
do_sort = input.any? do |n|
|
28
|
+
!n.dependencies[:before].empty? || !n.dependencies[:after].empty?
|
30
29
|
end
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
30
|
+
|
31
|
+
if do_sort
|
32
|
+
@nodes = input.inject({}) do |nodes, node|
|
33
|
+
children = node.dependencies[:after].map {|d| d.object_id }
|
34
|
+
nodes[node.advice.object_id] = Node.new(children, node); nodes
|
36
35
|
end
|
36
|
+
|
37
|
+
@nodes.each do |name, node|
|
38
|
+
node.node.dependencies[:before].each do |dep|
|
39
|
+
dep_name = dep.object_id
|
40
|
+
next unless @nodes[dep_name]
|
41
|
+
@nodes[dep_name].children << name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
strongly_connected_components.each do |name|
|
46
|
+
next unless Array === name && name.length > 1
|
47
|
+
raise CircularDependencyError, "Sorting failed: #{name.map {|n|
|
48
|
+
@nodes[n].node.advice.inspect }.join ', '}"
|
49
|
+
end
|
50
|
+
|
51
|
+
@output = tsort.map {|name| @nodes[name].node }
|
52
|
+
else
|
53
|
+
@output = input.reverse
|
37
54
|
end
|
38
55
|
|
39
|
-
strongly_connected_components.each do |name|
|
40
|
-
next unless Array === name && name.length > 1
|
41
|
-
raise CircularDependencyError,
|
42
|
-
"Sorting failed: #{name.map {|n| @nodes[n].aspect.advice.inspect }.join ', '}"
|
43
|
-
end
|
44
|
-
|
45
|
-
@output = tsort.map {|name| @nodes[name].aspect }
|
46
56
|
@changed = false
|
47
57
|
end
|
48
58
|
|
data/lib/hooked/hook.rb
CHANGED
@@ -2,12 +2,16 @@ module Hooked
|
|
2
2
|
class Hook
|
3
3
|
attr_reader :pointcut, :graph
|
4
4
|
|
5
|
-
def initialize(pointcut)
|
6
|
-
|
5
|
+
def initialize(pointcut = nil, &block)
|
6
|
+
if (pointcut && block) || (!pointcut && !block)
|
7
|
+
raise ArgumentError, "Pass either an argument or a block"
|
8
|
+
end
|
9
|
+
|
10
|
+
@pointcut, @graph = (pointcut || block), Graph.new
|
7
11
|
end
|
8
12
|
|
9
|
-
def add_aspect(*args)
|
10
|
-
graph << Aspect.new(*args)
|
13
|
+
def add_aspect(*args, &block)
|
14
|
+
graph << Aspect.new(*args, &block)
|
11
15
|
end
|
12
16
|
|
13
17
|
def call(*args, &block)
|
data/lib/hooked/version.rb
CHANGED
data/spec/aspect_spec.rb
CHANGED
@@ -13,10 +13,27 @@ describe Hooked::Aspect do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
it "sets default dependency lists" do
|
16
|
-
obj = Hooked::Aspect.new nil,
|
16
|
+
obj = Hooked::Aspect.new nil, stub("advice")
|
17
17
|
obj.dependencies[:before].should == []
|
18
18
|
obj.dependencies[:after].should == []
|
19
19
|
end
|
20
|
+
|
21
|
+
it "can take a block instead of the advice argument" do
|
22
|
+
block = double "block", :to_proc => proc {}
|
23
|
+
obj = Hooked::Aspect.new nil, &block
|
24
|
+
|
25
|
+
obj.advice.should == block.to_proc
|
26
|
+
end
|
27
|
+
|
28
|
+
it "fails if both a block and the advice argument were passed" do
|
29
|
+
proc do
|
30
|
+
Hooked::Aspect.new nil, stub("advice") do; end
|
31
|
+
end.should raise_error(ArgumentError)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "fails if neither a block nor the advice argument were passed" do
|
35
|
+
proc { Hooked::Aspect.new nil }.should raise_error(ArgumentError)
|
36
|
+
end
|
20
37
|
end
|
21
38
|
|
22
39
|
describe "#call" do
|
@@ -98,22 +115,22 @@ describe Hooked::Aspect do
|
|
98
115
|
|
99
116
|
describe "#before?" do
|
100
117
|
it "returns true if #type == :before" do
|
101
|
-
Hooked::Aspect.new(:before,
|
102
|
-
Hooked::Aspect.new(:foo,
|
118
|
+
Hooked::Aspect.new(:before, stub("advice")).before?.should be_true
|
119
|
+
Hooked::Aspect.new(:foo, stub("advice")).before?.should_not be_true
|
103
120
|
end
|
104
121
|
end
|
105
122
|
|
106
123
|
describe "#after?" do
|
107
124
|
it "returns true if #type == :after" do
|
108
|
-
Hooked::Aspect.new(:after,
|
109
|
-
Hooked::Aspect.new(:foo,
|
125
|
+
Hooked::Aspect.new(:after, stub("advice")).after?.should be_true
|
126
|
+
Hooked::Aspect.new(:foo, stub("advice")).after?.should_not be_true
|
110
127
|
end
|
111
128
|
end
|
112
129
|
|
113
130
|
describe "#around?" do
|
114
131
|
it "returns true if #type == :around" do
|
115
|
-
Hooked::Aspect.new(:around,
|
116
|
-
Hooked::Aspect.new(:foo,
|
132
|
+
Hooked::Aspect.new(:around, stub("advice")).around?.should be_true
|
133
|
+
Hooked::Aspect.new(:foo, stub("advice")).around?.should_not be_true
|
117
134
|
end
|
118
135
|
end
|
119
136
|
end
|
data/spec/graph_spec.rb
CHANGED
@@ -9,7 +9,7 @@ describe Hooked::Graph do
|
|
9
9
|
|
10
10
|
describe "#<<" do
|
11
11
|
before :each do
|
12
|
-
@aspect, @graph = Hooked::Aspect.new(nil,
|
12
|
+
@aspect, @graph = Hooked::Aspect.new(nil, stub("advice")), Hooked::Graph.new
|
13
13
|
@graph << @aspect
|
14
14
|
end
|
15
15
|
|
@@ -47,7 +47,7 @@ describe Hooked::Graph do
|
|
47
47
|
end
|
48
48
|
|
49
49
|
it "detects circular dependencies" do
|
50
|
-
advices = (0..
|
50
|
+
advices = (0..2).map {|i| mock "advice##{i}", :inspect => "advice##{i}" }
|
51
51
|
aspects = [
|
52
52
|
Hooked::Aspect.new(nil, advices[0], :before => [advices[1]]),
|
53
53
|
Hooked::Aspect.new(nil, advices[1], :before => [advices[2]]),
|
@@ -62,5 +62,22 @@ describe Hooked::Graph do
|
|
62
62
|
error.message =~ /failed: advice#0, advice#2, advice#1/
|
63
63
|
}
|
64
64
|
end
|
65
|
+
|
66
|
+
it "only reverses the input if no node has dependencies" do
|
67
|
+
advices = (0..2).map {|i| mock "advice##{i}", :inspect => "advice##{i}" }
|
68
|
+
aspects = [
|
69
|
+
Hooked::Aspect.new(nil, advices[0]),
|
70
|
+
Hooked::Aspect.new(nil, advices[1]),
|
71
|
+
Hooked::Aspect.new(nil, advices[2])
|
72
|
+
]
|
73
|
+
|
74
|
+
|
75
|
+
graph = Hooked::Graph.new
|
76
|
+
aspects.each {|a| graph << a }
|
77
|
+
|
78
|
+
graph.should_not_receive :tsort
|
79
|
+
graph.sort
|
80
|
+
graph.output.should == [aspects[2], aspects[1], aspects[0]]
|
81
|
+
end
|
65
82
|
end
|
66
83
|
end
|
data/spec/hook_spec.rb
CHANGED
@@ -10,6 +10,23 @@ describe Hooked::Hook do
|
|
10
10
|
obj.pointcut.should == pointcut
|
11
11
|
obj.graph.should == graph
|
12
12
|
end
|
13
|
+
|
14
|
+
it "can take a block instead of the pointcut argument" do
|
15
|
+
block = double "block", :to_proc => proc {}
|
16
|
+
obj = Hooked::Hook.new &block
|
17
|
+
|
18
|
+
obj.pointcut.should == block.to_proc
|
19
|
+
end
|
20
|
+
|
21
|
+
it "fails if both a block and the pointcut argument were passed" do
|
22
|
+
proc do
|
23
|
+
Hooked::Hook.new stub("pointcut") do; end
|
24
|
+
end.should raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "fails if neither a block nor the pointcut argument were passed" do
|
28
|
+
proc { Hooked::Hook.new }.should raise_error(ArgumentError)
|
29
|
+
end
|
13
30
|
end
|
14
31
|
|
15
32
|
describe "#add_aspect" do
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: hooked
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.
|
5
|
+
version: 0.3.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Lars Gierth
|
@@ -64,7 +64,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
65
65
|
- - ">="
|
66
66
|
- !ruby/object:Gem::Version
|
67
|
-
hash:
|
67
|
+
hash: 365797709
|
68
68
|
segments:
|
69
69
|
- 0
|
70
70
|
version: "0"
|
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
73
73
|
requirements:
|
74
74
|
- - ">="
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
hash:
|
76
|
+
hash: 365797709
|
77
77
|
segments:
|
78
78
|
- 0
|
79
79
|
version: "0"
|