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 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
- require "hooked"
13
-
14
- class Foo
15
- include Hooked
16
-
17
- def breakfast
18
- puts "No milk?!"
19
- end
20
- end
21
-
22
- class BetterFoo
23
- def shower
24
- puts "Oooh..."
25
- end
26
-
27
- def fuuu(inner)
28
- puts "FUUU!"
29
- inner.call
30
- puts "FUUU!"
31
- end
32
- end
33
-
34
- foo, better_foo = Foo.new, BetterFoo.new
35
- foo.before :breakfast, better_foo.method(:shower)
36
- foo.after :breakfast, proc { puts "Mmmh..." }
37
- foo.around :breakfast, better_foo.method(:fuuu)
38
-
39
- foo.breakfast
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
- FUUU!
44
- Oooh...
45
- No milk?!
46
- Mmmh...
47
- FUUU!
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
- # code
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, :aspect); end
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
- @nodes = input.inject({}) do |nodes, node|
28
- children = node.dependencies[:after].map {|d| d.object_id }
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
- @nodes.each do |name, node|
32
- node.aspect.dependencies[:before].each do |dep|
33
- dep_name = dep.object_id
34
- next unless @nodes[dep_name]
35
- @nodes[dep_name].children << name
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
- @pointcut, @graph = pointcut, Graph.new
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)
@@ -1,3 +1,3 @@
1
1
  module Hooked
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
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, 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, nil).before?.should be_true
102
- Hooked::Aspect.new(:foo, nil).before?.should_not be_true
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, nil).after?.should be_true
109
- Hooked::Aspect.new(:foo, nil).after?.should_not be_true
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, nil).around?.should be_true
116
- Hooked::Aspect.new(:foo, nil).around?.should_not be_true
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, nil), Hooked::Graph.new
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..3).map {|i| mock "advice##{i}", :inspect => "advice##{i}" }
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.2.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: 208393711
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: 208393711
76
+ hash: 365797709
77
77
  segments:
78
78
  - 0
79
79
  version: "0"