aspekt 0.0.1 → 0.0.2
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/Gemfile +8 -0
- data/lib/aspekt/advice.rb +57 -0
- data/lib/aspekt/helpers/shorthand_methods.rb +80 -0
- data/lib/aspekt/joinpoint.rb +18 -0
- data/lib/aspekt/object.rb +44 -0
- data/lib/aspekt/pointcut.rb +157 -0
- data/lib/aspekt/weaver.rb +192 -0
- data/lib/aspekt.rb +28 -124
- metadata +39 -11
- data/README.rdoc +0 -19
data/Gemfile
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Aspekt
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
class Advice < Aspekt::Object
|
6
|
+
|
7
|
+
# Creates new Advice Object
|
8
|
+
#
|
9
|
+
# == Example usage
|
10
|
+
#
|
11
|
+
# advice = Aspekt::Advice.new type: :before, pointcut: pointcut do |joinpoint|
|
12
|
+
# puts "before #{joinpoint}"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# advice = Aspekt::Advice.new type: :around, pointcuts: [pointcut1, pointcut2] do |joinpoint|
|
16
|
+
# puts "around :start for #{joinpoint}"
|
17
|
+
# return_value = joinpoint.proceed
|
18
|
+
# puts "around :end for #{joinpoint}"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @param {:type => :before|:after|:around, :pointcut|:pointcuts => Pointcut.new(...)|[Pointcut.new(...)]}
|
22
|
+
|
23
|
+
def initialize opts, &block
|
24
|
+
raise ArgumentError, "type has to be Symbol: before, after or around, but got '#{opts[:type]}'" unless [:before, :after, :around].include?(opts[:type])
|
25
|
+
raise ArgumentError, "has to define at least one Pointcut with :pointcut or :pointcuts" unless opts.has_key?(:pointcut) or opts.has_key?(:pointcuts)
|
26
|
+
|
27
|
+
# get the module in which advice was created
|
28
|
+
begin
|
29
|
+
opts[:module] = caller.including_same(/<module:/).collect {|l| /<module:(.*)>/.match(l)[1] }.reverse.inject(Object) {|a, b| a.const_get(b) }
|
30
|
+
rescue
|
31
|
+
end
|
32
|
+
|
33
|
+
# add block info to opts
|
34
|
+
opts[:proc] = block
|
35
|
+
|
36
|
+
# register opts values
|
37
|
+
super
|
38
|
+
|
39
|
+
# weave into the code
|
40
|
+
weave &block
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Weaves advice into the code.
|
45
|
+
#
|
46
|
+
# @see Aspekt::Weaver
|
47
|
+
|
48
|
+
def weave &block
|
49
|
+
Aspekt::Weaver.send(self[:type], self.values_at(:pointcut, :pointcuts).flatten.compact, &block)
|
50
|
+
end
|
51
|
+
private :weave
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Aspekt
|
2
|
+
module Helpers
|
3
|
+
module ShorthandMethods
|
4
|
+
|
5
|
+
# Shorthand for before advice.
|
6
|
+
#
|
7
|
+
# === Examples
|
8
|
+
#
|
9
|
+
# before type: Test, methods: /to_/ do
|
10
|
+
# puts "Before called on a object #{self}"
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# before(
|
14
|
+
# {type: Class1, method: :method1},
|
15
|
+
# {types: [Class2, Class3], methods: [/cotaining/, :method3]},
|
16
|
+
# {instances: [Class3, Class4, some_object], method: :lal}
|
17
|
+
# ) do |joinpoint|
|
18
|
+
# puts "Before #{joinpoint[:method]} on #{self}. Other information of joinpoint: #{joinpoint}."
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# class TestMe
|
22
|
+
# before methods: [:a, /b/] do |joinpoint| # note how type or instance is not specified, it is automatically resolved to self (what is TestMe in class body)
|
23
|
+
# puts "Before #{joinpoint}!"
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
|
27
|
+
def before *pointcuts, &block
|
28
|
+
Aspekt::Advice.new type: :before, pointcuts: pointcuts, &block
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
|
33
|
+
# Shorthand for after advice.
|
34
|
+
#
|
35
|
+
# === Examples
|
36
|
+
#
|
37
|
+
# after type: Test, methods: /to_/ do
|
38
|
+
# puts "Before called on a object #{self}"
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# class TestMe
|
42
|
+
# after methods: [:a, /b/] do |joinpoint| # note how type or instance is not specified, it is automatically resolved to self (what is TestMe in class body)
|
43
|
+
# puts "After #{joinpoint}!"
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
|
47
|
+
def after *pointcuts, &block
|
48
|
+
Aspekt::Advice.new type: :after, pointcuts: pointcuts, &block
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
# Shorthand for around advice.
|
54
|
+
#
|
55
|
+
# === Example
|
56
|
+
#
|
57
|
+
# around type: Test, methods: /to_/ do |joinpoint|
|
58
|
+
# puts "Before called on a object #{self} and method #{joinpoint[:method]}"
|
59
|
+
# result = joinpoint.proceed
|
60
|
+
# puts "After called on a object #{self} and method #{joinpoint[:method]}"
|
61
|
+
# result
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# class TestMe
|
65
|
+
# around methods: [:a, /b/] do |joinpoint| # note how type or instance is not specified, it is automatically resolved to self (what is TestMe in class body)
|
66
|
+
# puts "Before around #{joinpoint}!"
|
67
|
+
# result = joinpoint.proceed
|
68
|
+
# puts "After around #{joinpoint}!"
|
69
|
+
# result
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
|
73
|
+
def around *pointcuts, &block
|
74
|
+
Aspekt::Advice.new type: :around, pointcuts: pointcuts, &block
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Aspekt
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
# All Aspekt's objects are extended from Aspekt::Object.
|
6
|
+
#
|
7
|
+
# It extends Hash.
|
8
|
+
class Object < Hash
|
9
|
+
|
10
|
+
|
11
|
+
# Creates a new Aspekt::Object with base class Hash and registres self in class :@all.
|
12
|
+
#
|
13
|
+
# === Example
|
14
|
+
#
|
15
|
+
# class MyClass < Aspekt::Object; end
|
16
|
+
# obj = MyClass.new a: 'b', c: 'd'
|
17
|
+
# obj # {a: 'b', c: 'd'}
|
18
|
+
|
19
|
+
def initialize opts = {}
|
20
|
+
raise ArgumentError, "should be Hash" unless opts.is_a?(Hash)
|
21
|
+
|
22
|
+
self.merge! opts
|
23
|
+
self.class.all << self
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Will be probleby deparated and implemented as aspect
|
28
|
+
#
|
29
|
+
# === Example
|
30
|
+
#
|
31
|
+
# class MyClass < Aspekt::Object; end
|
32
|
+
# object1 = MyClass.new
|
33
|
+
# object2 = MyClass.new
|
34
|
+
# MyClass.all # will contain object1 and object2
|
35
|
+
|
36
|
+
def self.all
|
37
|
+
@all ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module Aspekt
|
2
|
+
|
3
|
+
|
4
|
+
# Examples of usage can also be found in https://github.com/tione/aspekt/blob/development/spec/aspekt/pointcut_spec.rb
|
5
|
+
|
6
|
+
class Pointcut < Aspekt::Object
|
7
|
+
|
8
|
+
|
9
|
+
# Constructor takes for arguments Hash with keys class(es) or object(s) and method(s), or Array of Hash-es.
|
10
|
+
#
|
11
|
+
# === Example
|
12
|
+
# ==== Pointcut for object method
|
13
|
+
#
|
14
|
+
# pointcut = Aspekt::Pointcut.new(type: :String, method: :to_s)
|
15
|
+
# pointcut = Aspekt::Pointcut.new(type: /String/, method: /to_s/)
|
16
|
+
# pointcut = Aspekt::Pointcut.new(type: String, method: 'to_s')
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# ==== Pointcut for class method (singleton method)
|
20
|
+
#
|
21
|
+
# pointcut = Aspekt::Pointcut.new(instance: String, method: :new)
|
22
|
+
# pointcut = Aspekt::Pointcut.new(type: (class String; class<<self; self; end; end, method: :new)
|
23
|
+
# pointcut = Aspekt::Pointcut.new(instance: some_object, method: /some_singleton_method/)
|
24
|
+
#
|
25
|
+
#
|
26
|
+
# ==== Pointcut with more than a one matcher
|
27
|
+
#
|
28
|
+
# pointcut = Aspekt::Pointcut.new(
|
29
|
+
# {type: String, method: :capitalize},
|
30
|
+
# {types: [String, Array], methods: [/a/, /o/]},
|
31
|
+
# {instances: [String, /Arr/], method: /new|try/}
|
32
|
+
# )
|
33
|
+
#
|
34
|
+
# ==== Reasoning behind :type and :instance
|
35
|
+
#
|
36
|
+
# - using :type or :types (see: http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-instance_exec)
|
37
|
+
# - using :instance or :instances (see: http://ruby-doc.org/core-1.9.3/Module.html#method-i-module_exec)
|
38
|
+
#
|
39
|
+
# @param Array<{:type => Class|::Object|String|Symbol|Regexp, :instance => ::Object}>
|
40
|
+
|
41
|
+
def initialize *matchers
|
42
|
+
raise ArgumentError, "must have matchers" if matchers.length == 0
|
43
|
+
|
44
|
+
matchers.each { |matcher| add_matcher matcher }
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Add matcher for a pointcut.
|
49
|
+
#
|
50
|
+
# Restructures matchers for faster searches.
|
51
|
+
# Structure will be in self[:matchers] {instances: {Object1: [:method1, :method2], Object2: [:method3, :method4]}, types: {Class1: [:method5], ...}}
|
52
|
+
#
|
53
|
+
# @private
|
54
|
+
# @param {:method => Symbol, :methods => Array<Symbol>, :instance|:type => Object, :instances|:types => Array<Object> }
|
55
|
+
|
56
|
+
def add_matcher matcher
|
57
|
+
raise ArgumentError, "matcher must be a Hash" unless matcher.is_a?(Hash)
|
58
|
+
|
59
|
+
# extract values
|
60
|
+
matcher_methods = matcher.values_at(:method, :methods).flatten.compact
|
61
|
+
matcher_instances = matcher.values_at(:instance, :instances).flatten.compact
|
62
|
+
matcher_types = matcher.values_at(:type, :types).flatten.compact
|
63
|
+
|
64
|
+
raise ArgumentError, "must include :method|:methods" if matcher_methods.length == 0
|
65
|
+
raise ArgumentError, "must include :type(s)|:instance(s)" if matcher_instances.length == 0 and matcher_types.length == 0
|
66
|
+
|
67
|
+
# add values to structure
|
68
|
+
matcher_instances.each do |matcher_instance|
|
69
|
+
self[:instances] ||= {}
|
70
|
+
self[:instances][matcher_instance] ||= []
|
71
|
+
self[:instances][matcher_instance].concat matcher_methods
|
72
|
+
end
|
73
|
+
matcher_types.each do |matcher_type|
|
74
|
+
self[:types] ||= {}
|
75
|
+
self[:types][matcher_type] ||= []
|
76
|
+
self[:types][matcher_type].concat matcher_methods
|
77
|
+
end
|
78
|
+
|
79
|
+
return true
|
80
|
+
end
|
81
|
+
private :add_matcher
|
82
|
+
|
83
|
+
|
84
|
+
# Argument Hash with keys :class or :object and :method for checking if Pointcut is matching with a object or a class.
|
85
|
+
#
|
86
|
+
# === Example
|
87
|
+
#
|
88
|
+
# pointcut = Aspekt::Pointcut.new(class: :SomeClass, method: :some_method).has_matchers_for?(class: SomeClass, method: :some_method)
|
89
|
+
# pointcut = Aspekt::Pointcut.new(object: :SomeClass, method: :some_method).has_matchers_for?(object: SomeClass, method: :some_method)
|
90
|
+
#
|
91
|
+
# @param Array<{:type => Class|::Object|Symbol|Regexp, :instance => ::Object, :method => Symbol}>
|
92
|
+
|
93
|
+
def has_matchers_for? opts
|
94
|
+
raise ArgumentError, "has to include keys 'instance' or 'type', may include 'method'." unless opts[:instance] or opts[:type]
|
95
|
+
|
96
|
+
# matcher_branch - from where to search matcher, thing - class or object
|
97
|
+
matcher_branch, thing = case
|
98
|
+
when opts[:instance]; [self[:instances], opts[:instance]];
|
99
|
+
when opts[:type]; [self[:types], opts[:type]];
|
100
|
+
end
|
101
|
+
|
102
|
+
matchers_for_thing = matcher_branch.keys.including_same thing
|
103
|
+
|
104
|
+
unless matchers_for_thing.length == 0
|
105
|
+
return true unless opts[:method] # we have no method matching and object is matching
|
106
|
+
matcher_objects_methods = matcher_branch.values_at(*matchers_for_thing).flatten
|
107
|
+
return true if matcher_objects_methods.include_same?(opts[:method]) # one of the matching object has matching method
|
108
|
+
end
|
109
|
+
|
110
|
+
return false # opts[:object] existed but no matcher was found
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# Finds currently existing all Objects for this Pointcut. Returns Object and its method matchers.
|
115
|
+
#
|
116
|
+
# === Example
|
117
|
+
#
|
118
|
+
# pointcut = Pointcut.new(types: [/Strin/, /^Hash$/, :ThisClassDoesNotExist)], instance: Array, methods: [/to/, :send])
|
119
|
+
# pointcut_matching_types = pointcut.matching[:types] # will be: {String: [/to/, :send], Hash: [/to/, :send]}
|
120
|
+
# pointcut_matching_instances = pointcut.matching[:instances] # will be: {Array: [/to/, :send]}
|
121
|
+
#
|
122
|
+
# # if now ThisClassDoesNotExist will be defined and #matching is called again, then it will also contain it.
|
123
|
+
|
124
|
+
def matching
|
125
|
+
matching = {}
|
126
|
+
|
127
|
+
defined_classes = nil # this will be created for Regexp object matcher, if needed
|
128
|
+
|
129
|
+
self.each do |type, object_matchers|
|
130
|
+
matching[type] = {}
|
131
|
+
|
132
|
+
object_matchers.each do |object_matcher, methods|
|
133
|
+
case object_matcher
|
134
|
+
when Symbol
|
135
|
+
begin
|
136
|
+
matching_class = object_matcher.to_s.split(/::/).inject(Object) {|a, b| a.const_get(b) } # throws NameError if does not exist
|
137
|
+
matching[type][matching_class] = methods
|
138
|
+
rescue NameError
|
139
|
+
end
|
140
|
+
when Regexp
|
141
|
+
defined_classes ||= ObjectSpace.each_object(Class).to_a # do it when need it
|
142
|
+
defined_classes.select {|defined_class| matching[type][defined_class] = methods if object_matcher.match(defined_class.name) }
|
143
|
+
else
|
144
|
+
matching[type][object_matcher] = methods
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
return matching
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module Aspekt
|
2
|
+
|
3
|
+
|
4
|
+
# Responsible for weaving code into methods.
|
5
|
+
#
|
6
|
+
# == You can use Weaver so:
|
7
|
+
#
|
8
|
+
# === ... in method call to Aspekt::Weaver
|
9
|
+
#
|
10
|
+
# Aspekt::Weaver.before class: ExistingClass, method: :existing_method do |joinpoint|
|
11
|
+
# puts "before context: '#{joinpoint[:context]}' and method '#{joinpoint[:method]}'"
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
#
|
15
|
+
# === ... in where you want to (for example class definition body).
|
16
|
+
#
|
17
|
+
# include Aspekt::Weaver
|
18
|
+
#
|
19
|
+
# # for object derived from this class:
|
20
|
+
# before method: :my_method |joinpoint| # if :type(s)|:method(s) is missing, then automatically type will be self.
|
21
|
+
# puts "before #{joinpoint[:method]}"
|
22
|
+
# end
|
23
|
+
# before type: self, method: :my_method do |joinpoint| # remember that self in class body will be Class
|
24
|
+
# puts "before #{joinpoint[:method]}"
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # singleton
|
28
|
+
# before instance: self, method: :my_method do |joinpoint| # remember that self in class body will be Class and its treated as instance now
|
29
|
+
# puts "before #{joinpoint[:method]}"
|
30
|
+
# end
|
31
|
+
# before type: (class<<self; self; end), method: :my_method do |joinpoint| # accessing self metaclass
|
32
|
+
# puts "before #{joinpoint[:method]}"
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
#
|
36
|
+
# === More complicated examples:
|
37
|
+
#
|
38
|
+
# Aspekt::Weaver.before instances: [Object1, :ClassHereForSingletonMethods], types: [Class1, :Class2], methods: [:method1, 'method2', /d3/] do |joinpoint|
|
39
|
+
# puts "Called in the context of a object: #{joinpoint[:context]} and method is: #{joinpoint[:method]}"
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# Aspekt::Weaver.before types: //, methods: /secure/ do |joinpoint|
|
43
|
+
# throw SecurityException, "not secure enough" unless authenticated_user.is_secured?
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# More examples can be found in Rspec tests: https://github.com/tione/aspekt/blob/development/spec/aspekt/weaver_spec.rb
|
49
|
+
#
|
50
|
+
# === Weaver does not support Regexp in its object and method matching
|
51
|
+
#
|
52
|
+
# Therefore its recommended to use Aspects, Advices and Pointcuts that do support Regexp.
|
53
|
+
|
54
|
+
module Weaver
|
55
|
+
extend self
|
56
|
+
|
57
|
+
|
58
|
+
# When included to a Class or Object, there will be Weaver methods available.
|
59
|
+
|
60
|
+
def self.included base #:nodoc:
|
61
|
+
base.extend self
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Weaves Proc to method.
|
66
|
+
#
|
67
|
+
# If argument matcher is a Hash not a Aspekt::Pointcut, it will be converted to it.
|
68
|
+
#
|
69
|
+
# @param Array<Hash|Aspekt::Pointcut> for hash see Aspekt::Pointcut.new
|
70
|
+
|
71
|
+
def weave *pointcuts_or_matchers
|
72
|
+
|
73
|
+
# if not pointcut, convert to it. (it might be a regexp matcher?)
|
74
|
+
pointcuts = pointcuts_or_matchers.flatten.collect do |pom|
|
75
|
+
case pom
|
76
|
+
when Aspekt::Pointcut
|
77
|
+
pom
|
78
|
+
when Hash
|
79
|
+
# Add DSL support for if include Aspekt::Weaver was called in a class.
|
80
|
+
pom[:type] = self if !(pom[:type] or pom[:types] or pom[:instance] or pom[:instances]) and self != Aspekt::Weaver
|
81
|
+
Aspekt::Pointcut.new pom
|
82
|
+
else
|
83
|
+
raise ArgumentError, "should be Hash or Aspekt::Pointcut but was #{pom.class} (#{pom})"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# iterate over pointcuts
|
88
|
+
pointcuts.each do |pointcut|
|
89
|
+
# objects to be weaved into
|
90
|
+
matching = pointcut.matching
|
91
|
+
objects = matching[:types] || {}
|
92
|
+
matching[:instances].each do |instance, methods|
|
93
|
+
singleton = class<<instance; self; end
|
94
|
+
objects[singleton] ||= []
|
95
|
+
objects[singleton].concat methods
|
96
|
+
end if matching[:instances]
|
97
|
+
|
98
|
+
# iterate through objects (classes and singleton classes)
|
99
|
+
objects.each do |object, method_matchers|
|
100
|
+
object.module_exec do
|
101
|
+
weave_methods = object.instance_methods.select {|meth| method_matchers.include_same? meth }
|
102
|
+
|
103
|
+
weave_methods.each do |weave_method|
|
104
|
+
m = instance_method(weave_method)
|
105
|
+
|
106
|
+
# replace orginal method with orginal method + what we got as block to yield
|
107
|
+
define_method(weave_method) do |*args, &block|
|
108
|
+
bm = m.bind self
|
109
|
+
joinpoint = Aspekt::Joinpoint.new args: args, block: block, context: self, method: weave_method, bound_method: bm
|
110
|
+
yield(joinpoint)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
# Weaves Proc before the method.
|
121
|
+
#
|
122
|
+
# == Example usage for object
|
123
|
+
#
|
124
|
+
# string = "trolololo"
|
125
|
+
#
|
126
|
+
# Aspekt::Weaver.before object: string, method: :to_s do |joinpoint|
|
127
|
+
# puts "Before self: #{self} joinpoint: #{joinpoint}"
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# new_string = string.to_s # output: Before trolololo at {:args=>[], :block=>nil, :context=>"trolololo", :method=>:to_s, :bound_method=>#<Method: String(String)#to_s>}
|
131
|
+
# puts new_string # output: "trolololo"
|
132
|
+
#
|
133
|
+
#
|
134
|
+
# == Example usage for all objects that derive from certain classes
|
135
|
+
#
|
136
|
+
# Aspekt::Weaver.before classes: [Array, String], methods: [:initialize, :to_s] do
|
137
|
+
# puts "#{self} is doing something important #{joinpoint}"
|
138
|
+
# end
|
139
|
+
|
140
|
+
def before opts, &block
|
141
|
+
self.weave opts do |joinpoint|
|
142
|
+
joinpoint[:context].instance_exec joinpoint, &block # call block with argument joinpoint
|
143
|
+
joinpoint.proceed # call orginal method
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# Weaves Proc after the method.
|
149
|
+
# @see Aspekt::Weaver.before
|
150
|
+
|
151
|
+
def after opts, &block
|
152
|
+
self.weave opts do |joinpoint|
|
153
|
+
retval = joinpoint.proceed # call orginal method
|
154
|
+
joinpoint.merge! retval: retval # add orginal method retval
|
155
|
+
joinpoint[:context].instance_exec joinpoint, &block # call block with argument joinpoint
|
156
|
+
retval
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# Wheaves Proc for around.
|
162
|
+
#
|
163
|
+
# == Example usage
|
164
|
+
#
|
165
|
+
# hash = {key: :value}
|
166
|
+
# puts "Hash ID is: #{hash.object_id}"
|
167
|
+
#
|
168
|
+
# Aspekt::Weaver.around object: hash, method: :has_key? do |joinpoint|
|
169
|
+
# puts "Enter: #{joinpoint}"
|
170
|
+
# puts "My self ID is: through self #{self.object_id} and through joinpoint[:context] #{joinpoint[:context].object_id}"
|
171
|
+
# result = joinpoint.proceed
|
172
|
+
# puts "Exit with result #{result}: #{joinpoint}"
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# # Calling `hash.has_key?(:key)` outputs:
|
176
|
+
# # Hash ID is: 15441680
|
177
|
+
# # Enter: {:args=>[:key], :block=>nil, :context=>{:key=>:value}, :method=>:has_key?, :bound_method=>#<Method: Hash(Hash)#has_key?>}
|
178
|
+
# # My self ID is: through self 15441680 and through joinpoint[:context] 15441680
|
179
|
+
# # Exit with result true: {:args=>[:key], :block=>nil, :context=>{:key=>:value}, :method=>:has_key?, :bound_method=>#<Method: Hash(Hash)#has_key?>}
|
180
|
+
|
181
|
+
def around opts, &block
|
182
|
+
self.weave opts do |joinpoint|
|
183
|
+
joinpoint[:context].instance_exec joinpoint, &block # call block with argument joinpoint
|
184
|
+
# calling joinpoint.proceed should be done in the block
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
end
|
data/lib/aspekt.rb
CHANGED
@@ -1,137 +1,41 @@
|
|
1
|
+
#$: << '.' unless $:.include?('.')
|
2
|
+
|
1
3
|
require 'is_same' # https://github.com/tione/is_same
|
2
4
|
|
3
5
|
|
4
|
-
|
5
|
-
#
|
6
|
+
|
7
|
+
# *Aspekt* consists of multiple classes, but for the libary user should be important only following methods:
|
6
8
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
+
# * {#before before}
|
10
|
+
# * {#after after}
|
11
|
+
# * {#around around}
|
12
|
+
|
9
13
|
module Aspekt
|
14
|
+
end
|
10
15
|
|
11
16
|
|
12
17
|
|
13
|
-
|
14
|
-
class Object < Hash
|
15
|
-
|
16
|
-
# Creates a new Aspekt::Object with base class Hash and registres self in class :@all.
|
17
|
-
def initialize opts
|
18
|
-
self.merge! opts
|
19
|
-
self.class.all << self
|
20
|
-
end
|
21
|
-
|
22
|
-
# Returns all Objects created.
|
23
|
-
def self.all
|
24
|
-
@all ||= []
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
|
30
|
-
class Pointcut < Aspekt::Object
|
31
|
-
|
32
|
-
# === Example usage
|
33
|
-
# class SomeClass
|
34
|
-
#
|
35
|
-
# def self.some_method
|
36
|
-
# return "some_value_from_singleton"
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# def some_method
|
40
|
-
# return "some_value"
|
41
|
-
# end
|
42
|
-
#
|
43
|
-
# def other_method
|
44
|
-
# return "other_value"
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
# end
|
48
|
-
#
|
49
|
-
# some_object = SomeClass.new
|
50
|
-
#
|
51
|
-
# ==== Pointcuts for instance with types
|
52
|
-
# pointcut = Aspekt::Pointcut.new( class: :SomeClass, method: :some_method )
|
53
|
-
# pointcut = Aspekt::Pointcut.new( class: SomeClass, method: /some/ )
|
54
|
-
# pointcut = Aspekt::Pointcut.new( class: /Some/, method: /some/ )
|
55
|
-
#
|
56
|
-
# ==== Pointcut for class method (singleton method)
|
57
|
-
# pointcut = Aspekt::Pointcut.new( object: SomeClass, method: :some_method )
|
58
|
-
# pointcut = Aspekt::Pointcut.new( object: /Some/, method: /some/)
|
59
|
-
# pointcut = Aspekt::Pointcut.new( class: (class SomeClass; class<<self; self; end; end, method: :some_method )
|
60
|
-
#
|
61
|
-
# ==== Pointcut for only one object
|
62
|
-
# pointcut = Aspekt::Pointcut.new( object: some_object, method: :some_method)
|
63
|
-
#
|
64
|
-
# === Arguments
|
65
|
-
# - object: object, method: :method
|
66
|
-
# - [{object: object, method: :method}, {object: object2, method: :method2
|
67
|
-
# - objects: [object, object2], methods: [:method, :method2]
|
68
|
-
# - class: :SomeClass, methods: [:method, :method2]
|
69
|
-
# - [{objects: [Hash, Array], method: new}, {class: Array, method: :push}, {classes: [Hash, Array, SomeClass], method: /_id/}]
|
70
|
-
#
|
71
|
-
# ==== Limitations
|
72
|
-
# Instances of Regexp, Symbol and Constant are used as finders not thought of as objects.
|
73
|
-
# TODO: Fix. Possibly add a new hash key value?
|
74
|
-
|
75
|
-
def initialize *matchers
|
76
|
-
matchers = matchers.flatten.each do |matcher|
|
77
|
-
matcher[:classes] = [matcher.delete(:class)].flatten if matcher.has_key?(:class)
|
78
|
-
matcher[:objects] = [matcher.delete(:object)].flatten if matcher.has_key?(:object)
|
79
|
-
matcher[:methods] = [matcher.delete(:method)].flatten if matcher.has_key?(:method)
|
80
|
-
end
|
81
|
-
super matchers: matchers
|
82
|
-
end
|
83
|
-
|
84
|
-
# === Example usage
|
85
|
-
# pointcut = Aspekt::Pointcut.new( class: :SomeClass, method: :some_method ).is_matching?( object: SomeClass.new )
|
86
|
-
# pointcut = Aspekt::Pointcut.new( object: :SomeClass, method: :some_method ).is_matching?( object: SomeClass )
|
87
|
-
#
|
88
|
-
# === Arguments
|
89
|
-
# - object: object
|
90
|
-
# - object: object, method: :method
|
91
|
-
def is_matching? opts
|
92
|
-
raise ArgumentError, "has to include keys 'object', may include 'method'." unless opts[:object]
|
93
|
-
|
94
|
-
self[:matchers].each do |matcher|
|
95
|
-
# object matching
|
96
|
-
return true if
|
97
|
-
(matcher.has_key(:objects) and matcher[:objects].select{|object| object.is_same?(opts[:object])}.length > 0) or
|
98
|
-
(matcher.has_key(:classes) and matcher[:classes].select{|klass| klass.is_same?(opts[:object].class.name)}.length > 0)
|
99
|
-
# method matching
|
100
|
-
return true unless opts.has_key?(:methods) or
|
101
|
-
(matcher.has_key(:methods) and matcher[:methods].select{|klass| klass.is_same?(opts[:methods].class.name)}.length > 0)
|
102
|
-
end
|
103
|
-
|
104
|
-
return false
|
105
|
-
end
|
106
|
-
|
107
|
-
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
class Advice < Aspekt::Object
|
112
|
-
|
113
|
-
# == Example usage
|
114
|
-
# advice = Aspekt::Advice.new ( type: :before, pointcut: pointcut ) do |joinpoint|
|
115
|
-
# puts "before #{joinpoint}"
|
116
|
-
# end
|
117
|
-
#
|
118
|
-
# advice = Aspekt::Advice.new ( type: :around, pointcuts: [pointcut1, pointcut2]) do |joinpoint|
|
119
|
-
# puts "around :start for #{joinpoint}"
|
120
|
-
# return_value = joinpoint.proceed
|
121
|
-
# puts "around :end for #{joinpoint}"
|
122
|
-
# end
|
123
|
-
#
|
124
|
-
def initialize opts
|
125
|
-
opts[:pointcuts] = [opts.delete(:pointcut)].flatten if opts[:pointcut]
|
126
|
-
super opts
|
127
|
-
raise ArgumentError, "type has to be Symbol: before, after or around" unless [:before, :after, :around].include?(self[:type])
|
128
|
-
raise ArgumentError, "no pointcut defined" unless self[:pointcuts].length
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
18
|
+
# Core
|
132
19
|
|
20
|
+
require 'aspekt/object'
|
21
|
+
require 'aspekt/joinpoint'
|
22
|
+
require 'aspekt/pointcut'
|
23
|
+
require 'aspekt/advice'
|
24
|
+
require 'aspekt/weaver'
|
133
25
|
|
134
26
|
|
135
|
-
end
|
136
27
|
|
28
|
+
# Shorthand methods
|
29
|
+
|
30
|
+
require 'aspekt/helpers/shorthand_methods.rb'
|
137
31
|
|
32
|
+
include Aspekt::Helpers::ShorthandMethods # introduces aspect(), advice() and pointcut()
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
# Load aspects
|
37
|
+
|
38
|
+
$LOAD_PATH.each do |folder|
|
39
|
+
aspects_folder = "#{folder}/aspects"
|
40
|
+
Dir.glob("#{aspects_folder}/**/*.rb") {|file| require file } if Dir.exists? aspects_folder
|
41
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aspekt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-25 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: is_same
|
16
|
-
requirement: &
|
16
|
+
requirement: &13075360 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *13075360
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &13074900 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *13074900
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
name:
|
38
|
-
requirement: &
|
37
|
+
name: rspec-core
|
38
|
+
requirement: &13074480 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,16 +43,43 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *13074480
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: yard
|
49
|
+
requirement: &13074060 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *13074060
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: yard-rdoc
|
60
|
+
requirement: &13073640 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *13073640
|
47
69
|
description: Before, after and around method calls. Supports Regexp matching.
|
48
70
|
email: margus@tione.eu
|
49
71
|
executables: []
|
50
72
|
extensions: []
|
51
73
|
extra_rdoc_files: []
|
52
74
|
files:
|
53
|
-
- README.rdoc
|
54
75
|
- Gemfile
|
55
76
|
- lib/aspekt.rb
|
77
|
+
- lib/aspekt/object.rb
|
78
|
+
- lib/aspekt/helpers/shorthand_methods.rb
|
79
|
+
- lib/aspekt/joinpoint.rb
|
80
|
+
- lib/aspekt/pointcut.rb
|
81
|
+
- lib/aspekt/weaver.rb
|
82
|
+
- lib/aspekt/advice.rb
|
56
83
|
homepage: https://github.com/tione/aspekt
|
57
84
|
licenses: []
|
58
85
|
post_install_message:
|
@@ -76,5 +103,6 @@ rubyforge_project:
|
|
76
103
|
rubygems_version: 1.8.15
|
77
104
|
signing_key:
|
78
105
|
specification_version: 3
|
79
|
-
summary:
|
106
|
+
summary: AOP libary with simple and capable before(), after() and around().
|
80
107
|
test_files: []
|
108
|
+
has_rdoc:
|
data/README.rdoc
DELETED
@@ -1,19 +0,0 @@
|
|
1
|
-
= IN THE MAKING, NOT USABLE
|
2
|
-
|
3
|
-
== Installation
|
4
|
-
|
5
|
-
Add to your Gemfile:
|
6
|
-
gem 'aspekt'
|
7
|
-
|
8
|
-
And run:
|
9
|
-
bundle install
|
10
|
-
|
11
|
-
Or run:
|
12
|
-
gem install aspekt
|
13
|
-
|
14
|
-
|
15
|
-
== Usage examples
|
16
|
-
|
17
|
-
=== Including before, after and around methods support into Object or Class
|
18
|
-
|
19
|
-
=== Creating aspects
|