hooked 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
@@ -0,0 +1,6 @@
1
+ require "bundler"
2
+ Bundler.setup :default, :development
3
+
4
+ require "hooked"
5
+
6
+ require "awesome_print"
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: false
5
- segments:
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
- - Lars Gierth
8
+ - Lars Gierth
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
12
 
17
- date: 2011-01-13 00:00:00 +01:00
13
+ date: 2011-04-20 00:00:00 +02:00
18
14
  default_executable:
19
15
  dependencies:
20
- - !ruby/object:Gem::Dependency
21
- name: depression
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- segments:
28
- - 0
29
- version: "0"
30
- type: :runtime
31
- version_requirements: *id001
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
- - lars.gierth@gmail.com
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
- - Gemfile
70
- - LICENSE
71
- - README.md
72
- - Rakefile
73
- - hooked.gemspec
74
- - lib/hooked.rb
75
- - lib/hooked/container.rb
76
- - lib/hooked/context.rb
77
- - lib/hooked/controller.rb
78
- - lib/hooked/hook.rb
79
- - lib/hooked/hookable.rb
80
- - lib/hooked/version.rb
81
- - test/container_test.rb
82
- - test/controller_test.rb
83
- - test/helper.rb
84
- - test/hook_test.rb
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
- - lib
61
+ - lib
95
62
  required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
96
64
  requirements:
97
- - - ">="
98
- - !ruby/object:Gem::Version
99
- segments:
100
- - 0
101
- version: "0"
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
- - !ruby/object:Gem::Version
106
- segments:
107
- - 0
108
- version: "0"
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.3.6
83
+ rubygems_version: 1.6.2
113
84
  signing_key:
114
85
  specification_version: 3
115
- summary: Ruby Library For Aspect Oriented Programming
116
- test_files:
117
- - test/container_test.rb
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
+
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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