hooked 0.2.0 → 0.3.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.
- 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"
|