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 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"