hooked 0.1.2 → 0.2.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/.rspec +1 -0
- data/Gemfile +1 -0
- data/LICENSE +1 -1
- data/README.md +46 -131
- data/Rakefile +6 -7
- data/hooked.gemspec +3 -8
- data/lib/hooked/aspect.rb +35 -0
- data/lib/hooked/graph.rb +57 -0
- data/lib/hooked/hook.rb +13 -25
- data/lib/hooked/version.rb +1 -1
- data/lib/hooked.rb +37 -6
- data/spec/aspect_spec.rb +119 -0
- data/spec/graph_spec.rb +66 -0
- data/spec/hook_spec.rb +69 -0
- data/spec/hooked_spec.rb +64 -0
- data/spec/spec_helper.rb +6 -0
- metadata +52 -85
- data/lib/hooked/container.rb +0 -52
- data/lib/hooked/context.rb +0 -39
- data/lib/hooked/controller.rb +0 -80
- data/lib/hooked/hookable.rb +0 -20
- data/test/container_test.rb +0 -71
- data/test/controller_test.rb +0 -234
- data/test/helper.rb +0 -10
- data/test/hook_test.rb +0 -90
- data/test/hookable_test.rb +0 -66
data/spec/hook_spec.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Hooked::Hook do
|
4
|
+
describe "#initialize" do
|
5
|
+
it "sets the pointcut and creates the graph" do
|
6
|
+
pointcut, graph = double("pointcut"), double("graph")
|
7
|
+
Hooked::Graph.stub :new => graph
|
8
|
+
|
9
|
+
obj = Hooked::Hook.new pointcut
|
10
|
+
obj.pointcut.should == pointcut
|
11
|
+
obj.graph.should == graph
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#add_aspect" do
|
16
|
+
it "adds an aspect to the graph" do
|
17
|
+
args = [:before, proc {}, {:after => [proc {}]}]
|
18
|
+
aspect = double "aspect"
|
19
|
+
obj = Hooked::Hook.new double("pointcut")
|
20
|
+
|
21
|
+
Hooked::Aspect.should_receive(:new).with(*args).and_return aspect
|
22
|
+
obj.graph.should_receive(:<<).with aspect
|
23
|
+
obj.add_aspect *args
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#call" do
|
28
|
+
it "rebuilds the chain if it hasn't been built yet or the graph changed" do
|
29
|
+
obj = Hooked::Hook.new double("pointcut")
|
30
|
+
chain = stub "chain", :call => nil
|
31
|
+
|
32
|
+
obj.graph.stub :changed? => false
|
33
|
+
obj.should_receive(:build_chain).once.and_return chain
|
34
|
+
obj.call
|
35
|
+
|
36
|
+
obj.call
|
37
|
+
|
38
|
+
obj.graph.stub :changed? => true
|
39
|
+
obj.should_receive(:build_chain).once.and_return chain
|
40
|
+
obj.call
|
41
|
+
end
|
42
|
+
|
43
|
+
it "forwards to the chain" do
|
44
|
+
obj = Hooked::Hook.new double("pointcut")
|
45
|
+
|
46
|
+
chain = stub "chain"
|
47
|
+
obj.stub :build_chain => chain
|
48
|
+
chain.should_receive(:call).with("foo").and_return "bar"
|
49
|
+
|
50
|
+
obj.call("foo").should == "bar"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#build_chain" do
|
55
|
+
it "builds the chain" do
|
56
|
+
obj = Hooked::Hook.new double("pointcut")
|
57
|
+
|
58
|
+
aspect = Struct.new :pos, :pointcut
|
59
|
+
obj.graph.should_receive :sort
|
60
|
+
obj.graph.stub :output => [aspect.new(1), aspect.new(2), aspect.new(3)]
|
61
|
+
chain = obj.build_chain
|
62
|
+
|
63
|
+
chain.pos.should == 1
|
64
|
+
chain.pointcut.pos.should == 2
|
65
|
+
chain.pointcut.pointcut.pos.should == 3
|
66
|
+
chain.pointcut.pointcut.pointcut.should == obj.pointcut
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/hooked_spec.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Hooked do
|
4
|
+
klass = Class.new do
|
5
|
+
include Hooked
|
6
|
+
def foo; end
|
7
|
+
end
|
8
|
+
|
9
|
+
[:before, :after, :around].each do |type|
|
10
|
+
describe "##{type}" do
|
11
|
+
it "hooks the method" do
|
12
|
+
obj = klass.new
|
13
|
+
obj.stub :hooked => {:foo => double("hooked", :add_aspect => nil)}
|
14
|
+
|
15
|
+
obj.should_receive(:hook!).with(:foo)
|
16
|
+
obj.send type, :foo, nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it "adds an aspect to the hook" do
|
20
|
+
obj = klass.new
|
21
|
+
advice, deps = stub("advice"), stub("deps")
|
22
|
+
|
23
|
+
obj.hook! :foo
|
24
|
+
obj.hooked[:foo].should_receive(:add_aspect).with type, advice, deps
|
25
|
+
|
26
|
+
obj.send type, :foo, advice, deps
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#hook!" do
|
32
|
+
it "replaces the original method with a hook" do
|
33
|
+
obj = klass.new
|
34
|
+
args, result = ["bar", 123], stub("result")
|
35
|
+
|
36
|
+
obj.hook! :foo
|
37
|
+
obj.hooked[:foo].should_receive(:call).with(*args).and_return result
|
38
|
+
obj.foo(*args).should == result
|
39
|
+
end
|
40
|
+
|
41
|
+
it "doesn't hook a method twice" do
|
42
|
+
obj = klass.new
|
43
|
+
obj.hook! :foo
|
44
|
+
|
45
|
+
Hooked::Hook.should_not_receive :new
|
46
|
+
obj.hook! :foo
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#unhook!" do
|
51
|
+
it "brings the original back in place" do
|
52
|
+
obj = klass.new
|
53
|
+
|
54
|
+
obj.hook! :foo
|
55
|
+
hook = obj.hooked[:foo]
|
56
|
+
obj.unhook! :foo
|
57
|
+
|
58
|
+
hook.should_not_receive :call
|
59
|
+
obj.foo
|
60
|
+
|
61
|
+
obj.hooked[:foo].should be_nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,64 +1,32 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hooked
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 1
|
8
|
-
- 2
|
9
|
-
version: 0.1.2
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.0
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
|
-
|
8
|
+
- Lars Gierth
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
12
|
|
17
|
-
date: 2011-
|
13
|
+
date: 2011-04-20 00:00:00 +02:00
|
18
14
|
default_executable:
|
19
15
|
dependencies:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
- !ruby/object:Gem::Dependency
|
33
|
-
name: test-unit
|
34
|
-
prerelease: false
|
35
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - ">="
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
segments:
|
40
|
-
- 0
|
41
|
-
version: "0"
|
42
|
-
type: :development
|
43
|
-
version_requirements: *id002
|
44
|
-
- !ruby/object:Gem::Dependency
|
45
|
-
name: mocha
|
46
|
-
prerelease: false
|
47
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
48
|
-
requirements:
|
49
|
-
- - ">="
|
50
|
-
- !ruby/object:Gem::Version
|
51
|
-
segments:
|
52
|
-
- 0
|
53
|
-
version: "0"
|
54
|
-
type: :development
|
55
|
-
version_requirements: *id003
|
56
|
-
description: |-
|
57
|
-
Hooked makes AOP a breeze. It lets you define and invoke
|
58
|
-
code that gems or other parts of your application can hook
|
59
|
-
onto.
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rspec
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
description: Hooked lets you transparently aspectify your methods and blocks.
|
60
28
|
email:
|
61
|
-
|
29
|
+
- lars.gierth@gmail.com
|
62
30
|
executables: []
|
63
31
|
|
64
32
|
extensions: []
|
@@ -66,23 +34,22 @@ extensions: []
|
|
66
34
|
extra_rdoc_files: []
|
67
35
|
|
68
36
|
files:
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
- test/hookable_test.rb
|
37
|
+
- .rspec
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- hooked.gemspec
|
43
|
+
- lib/hooked.rb
|
44
|
+
- lib/hooked/aspect.rb
|
45
|
+
- lib/hooked/graph.rb
|
46
|
+
- lib/hooked/hook.rb
|
47
|
+
- lib/hooked/version.rb
|
48
|
+
- spec/aspect_spec.rb
|
49
|
+
- spec/graph_spec.rb
|
50
|
+
- spec/hook_spec.rb
|
51
|
+
- spec/hooked_spec.rb
|
52
|
+
- spec/spec_helper.rb
|
86
53
|
has_rdoc: true
|
87
54
|
homepage: http://rubygems.org/gems/hooked
|
88
55
|
licenses: []
|
@@ -91,31 +58,31 @@ post_install_message:
|
|
91
58
|
rdoc_options: []
|
92
59
|
|
93
60
|
require_paths:
|
94
|
-
|
61
|
+
- lib
|
95
62
|
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
96
64
|
requirements:
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 208393711
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
102
71
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
103
73
|
requirements:
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 208393711
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
109
80
|
requirements: []
|
110
81
|
|
111
82
|
rubyforge_project:
|
112
|
-
rubygems_version: 1.
|
83
|
+
rubygems_version: 1.6.2
|
113
84
|
signing_key:
|
114
85
|
specification_version: 3
|
115
|
-
summary:
|
116
|
-
test_files:
|
117
|
-
|
118
|
-
- test/controller_test.rb
|
119
|
-
- test/helper.rb
|
120
|
-
- test/hook_test.rb
|
121
|
-
- test/hookable_test.rb
|
86
|
+
summary: Aspect Orientation Made Simple
|
87
|
+
test_files: []
|
88
|
+
|
data/lib/hooked/container.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
module Hooked
|
2
|
-
module Container
|
3
|
-
attr_accessor :hooked
|
4
|
-
|
5
|
-
def hookable(name, *args, &block)
|
6
|
-
hooked.invoke(name, *args, &block)
|
7
|
-
end
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
attr_reader :hookables, :hooks
|
11
|
-
|
12
|
-
def hookable(name, &block)
|
13
|
-
@hookables << Hookable.new(name, &block)
|
14
|
-
end
|
15
|
-
|
16
|
-
def hook(type, hookable, name, opts = {}, &block)
|
17
|
-
opts[:before] ||= []
|
18
|
-
unless opts[:before].respond_to?(:each) or opts[:before] == :all
|
19
|
-
opts[:before] = [opts[:before]]
|
20
|
-
end
|
21
|
-
|
22
|
-
opts[:after] ||= []
|
23
|
-
unless opts[:after].respond_to?(:each) or opts[:after] == :all
|
24
|
-
opts[:after] = [opts[:after]]
|
25
|
-
end
|
26
|
-
|
27
|
-
@hooks[hookable] ||= []
|
28
|
-
@hooks[hookable] << Hook.new(type, name, opts, &block)
|
29
|
-
end
|
30
|
-
|
31
|
-
def before(*args, &block)
|
32
|
-
hook(:before, *args, &block)
|
33
|
-
end
|
34
|
-
|
35
|
-
def around(*args, &block)
|
36
|
-
hook(:around, *args, &block)
|
37
|
-
end
|
38
|
-
|
39
|
-
def after(*args, &block)
|
40
|
-
hook(:after, *args, &block)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.included(klass)
|
45
|
-
klass.class_eval do
|
46
|
-
extend(ClassMethods)
|
47
|
-
@hookables = []
|
48
|
-
@hooks = {}
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
data/lib/hooked/context.rb
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
module Hooked
|
2
|
-
class Context
|
3
|
-
attr_reader :args
|
4
|
-
attr_accessor :result
|
5
|
-
|
6
|
-
def initialize(args)
|
7
|
-
@args = args
|
8
|
-
end
|
9
|
-
|
10
|
-
def next(depth = 1)
|
11
|
-
return if depth < 1
|
12
|
-
throw(:hooked, {
|
13
|
-
:type => :next,
|
14
|
-
:depth => depth
|
15
|
-
})
|
16
|
-
end
|
17
|
-
|
18
|
-
def return(result)
|
19
|
-
@result = result
|
20
|
-
self.next
|
21
|
-
end
|
22
|
-
|
23
|
-
def break(depth = 1)
|
24
|
-
return if depth < 1
|
25
|
-
throw(:hooked, {
|
26
|
-
:type => :break,
|
27
|
-
:depth => depth
|
28
|
-
})
|
29
|
-
end
|
30
|
-
|
31
|
-
def skip(*hooks)
|
32
|
-
raise "Not yet implemented."
|
33
|
-
end
|
34
|
-
|
35
|
-
def skip_other(hookable, *hooks)
|
36
|
-
raise "Not yet implemented."
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
data/lib/hooked/controller.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
module Hooked
|
2
|
-
Result = Struct.new(:type, :value)
|
3
|
-
|
4
|
-
class Controller
|
5
|
-
attr_reader :containers, :hookables, :hooks
|
6
|
-
|
7
|
-
def initialize(*containers)
|
8
|
-
@containers = []
|
9
|
-
@hookables = {}
|
10
|
-
@hooks = {}
|
11
|
-
|
12
|
-
add(*containers)
|
13
|
-
refresh
|
14
|
-
end
|
15
|
-
|
16
|
-
def add(*containers)
|
17
|
-
@containers.push(*containers)
|
18
|
-
containers.each do |c|
|
19
|
-
c.hooked = self
|
20
|
-
c.class.hookables.each do |hkbl|
|
21
|
-
hkbl.container = c
|
22
|
-
hookables[hkbl.name] = hkbl
|
23
|
-
end
|
24
|
-
c.class.hooks.each do |hkbl_name, hs|
|
25
|
-
hs.each {|h| h.container = c }
|
26
|
-
@hooks[hkbl_name] ||= []
|
27
|
-
@hooks[hkbl_name].push(*hs)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def refresh
|
33
|
-
hooks.each {|hkbl_name, hs| hooks[hkbl_name] = Depression.process(hs) }
|
34
|
-
end
|
35
|
-
|
36
|
-
def invoke(name, args = nil, &block)
|
37
|
-
if block
|
38
|
-
hookable = Hookable.new(name, &block)
|
39
|
-
else
|
40
|
-
hookable = hookables[name] || raise("Unknown hookable: #{name}")
|
41
|
-
end
|
42
|
-
|
43
|
-
context = Context.new(args)
|
44
|
-
chain = (hooks[name] || []).reverse.inject(hookable) do |next_item, item|
|
45
|
-
proc {|ctx| call_hook(item, next_item, ctx) }
|
46
|
-
end
|
47
|
-
|
48
|
-
ctrl = catch(:hooked) { chain.call(context); nil }
|
49
|
-
|
50
|
-
if ctrl.respond_to?(:[])
|
51
|
-
ctrl[:depth] -= 1
|
52
|
-
throw(:hooked, ctrl) if ctrl[:depth] > 0
|
53
|
-
end
|
54
|
-
|
55
|
-
context.result
|
56
|
-
end
|
57
|
-
|
58
|
-
def call_hook(hook, hookable, context)
|
59
|
-
wrap { hook.call(context) } if hook.before?
|
60
|
-
|
61
|
-
wrap do
|
62
|
-
if hook.around?
|
63
|
-
hook.call(context, hookable)
|
64
|
-
else
|
65
|
-
hookable.call(context)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
wrap { hook.call(context) } if hook.after?
|
70
|
-
end
|
71
|
-
|
72
|
-
def wrap(&block)
|
73
|
-
ctrl = catch(:hooked) { block.call; nil }
|
74
|
-
if ctrl.respond_to?(:[]) && (
|
75
|
-
ctrl[:type] == :break || ctrl[:depth] > 1)
|
76
|
-
throw(:hooked, ctrl)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
data/lib/hooked/hookable.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
module Hooked
|
2
|
-
class Hookable
|
3
|
-
attr_reader :name
|
4
|
-
attr_accessor :container
|
5
|
-
|
6
|
-
def initialize(name, container = nil, &block)
|
7
|
-
raise ArgumentError, "Hooked::Hookable.new expects a block." unless block
|
8
|
-
|
9
|
-
@name, @container, @block = name.to_sym, container, block
|
10
|
-
end
|
11
|
-
|
12
|
-
def call(context)
|
13
|
-
if container
|
14
|
-
container.instance_exec(context, &@block)
|
15
|
-
else
|
16
|
-
@block.call(context)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
data/test/container_test.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
require File.expand_path("../helper", __FILE__)
|
2
|
-
|
3
|
-
class ContainerTest < Test::Unit::TestCase
|
4
|
-
def setup
|
5
|
-
@container = Class.new { include(Hooked::Container) }
|
6
|
-
end
|
7
|
-
|
8
|
-
def teardown
|
9
|
-
Mocha::Mockery.instance.teardown
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_has_hookables
|
13
|
-
block = proc {}
|
14
|
-
@container.hookable(:foo, &block)
|
15
|
-
|
16
|
-
assert_equal :foo, @container.hookables[0].name
|
17
|
-
assert @container.hookables[0].respond_to?(:call)
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_has_a_shorthand_method_for_invoking_hookables
|
21
|
-
container = @container.new
|
22
|
-
container.hooked = stub("controller") do
|
23
|
-
expects(:invoke).once.with(:foo, 123).returns("asdf")
|
24
|
-
end
|
25
|
-
|
26
|
-
assert_equal "asdf", container.hookable(:foo, 123)
|
27
|
-
end
|
28
|
-
|
29
|
-
def test_has_hooks
|
30
|
-
block = proc {}
|
31
|
-
@container.hook(:after, :work, :start_revolution, {
|
32
|
-
:before => :utopia
|
33
|
-
}, &block)
|
34
|
-
@container.hook(:around, :foo, :do_something, {
|
35
|
-
:before => :all,
|
36
|
-
:after => [:do_something_else, :snooze]
|
37
|
-
}, &block)
|
38
|
-
|
39
|
-
h = @container.hooks[:work][0]
|
40
|
-
assert_equal :after, h.type
|
41
|
-
assert_equal :start_revolution, h.name
|
42
|
-
assert_equal({
|
43
|
-
:before => [:utopia],
|
44
|
-
:after => []
|
45
|
-
}, h.relations)
|
46
|
-
assert h.respond_to?(:call)
|
47
|
-
assert h.respond_to?(:container=)
|
48
|
-
|
49
|
-
h = @container.hooks[:foo][0]
|
50
|
-
assert_equal :around, h.type
|
51
|
-
assert_equal :do_something, h.name
|
52
|
-
assert_equal({
|
53
|
-
:before => :all,
|
54
|
-
:after => [:do_something_else, :snooze]
|
55
|
-
}, h.relations)
|
56
|
-
end
|
57
|
-
|
58
|
-
def test_has_shorthand_methods_for_before_around_and_after
|
59
|
-
args = [:foo, :bar, {:before => :all}]
|
60
|
-
block = proc {}
|
61
|
-
|
62
|
-
@container.expects(:hook).once.with(:before, *args)
|
63
|
-
@container.before(*args, &block)
|
64
|
-
|
65
|
-
@container.expects(:hook).once.with(:around, *args)
|
66
|
-
@container.around(*args, &block)
|
67
|
-
|
68
|
-
@container.expects(:hook).once.with(:after, *args)
|
69
|
-
@container.after(*args, &block)
|
70
|
-
end
|
71
|
-
end
|