cuts 0.0.1
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/CHANGES +16 -0
- data/COPYING +674 -0
- data/NOTES +6 -0
- data/README +51 -0
- data/Rakefile +2 -0
- data/lib/cuts.rb +2 -0
- data/lib/cuts/aop.rb +205 -0
- data/lib/cuts/cut.rb +190 -0
- data/meta/project.yaml +23 -0
- data/meta/unixname +1 -0
- data/meta/version +1 -0
- data/setup.rb +1467 -0
- data/test/template.rb +16 -0
- metadata +72 -0
data/NOTES
ADDED
data/README
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= Cuts
|
2
|
+
|
3
|
+
http://cuts.rubyforge.org
|
4
|
+
|
5
|
+
|
6
|
+
== INTRODUCTION
|
7
|
+
|
8
|
+
Cuts is an expiremental implementation of cut-based AOP for Ruby.
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
== RELEASE NOTES
|
13
|
+
|
14
|
+
Please see NOTES file.
|
15
|
+
|
16
|
+
|
17
|
+
== RECENT CHANGES
|
18
|
+
|
19
|
+
Please see CHANGES file.
|
20
|
+
|
21
|
+
|
22
|
+
== HOW TO INSTALL
|
23
|
+
|
24
|
+
Describe your installation procedure here.
|
25
|
+
|
26
|
+
To install with RubyGems simply open a console and type:
|
27
|
+
|
28
|
+
gem install cuts
|
29
|
+
|
30
|
+
To manually installation, download the tgz package and type:
|
31
|
+
|
32
|
+
tar -xvzf cuts-x.y.z.tgz
|
33
|
+
cd cuts-x.y.z.tgz
|
34
|
+
rake config
|
35
|
+
rake setup
|
36
|
+
rake install
|
37
|
+
|
38
|
+
|
39
|
+
== USAGE
|
40
|
+
|
41
|
+
Describe how to use your library or application here.
|
42
|
+
|
43
|
+
|
44
|
+
== LICENSE
|
45
|
+
|
46
|
+
Copyright (c) 2008
|
47
|
+
|
48
|
+
This program is ditributed unser the terms of the MIT license.
|
49
|
+
|
50
|
+
Please see COPYING file.
|
51
|
+
|
data/Rakefile
ADDED
data/lib/cuts.rb
ADDED
data/lib/cuts/aop.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# TITLE:
|
2
|
+
#
|
3
|
+
# Aspect Oriented Programming for Ruby
|
4
|
+
#
|
5
|
+
# SUMMARY:
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# AUTHORS:
|
9
|
+
#
|
10
|
+
# - Thomas Sawyer
|
11
|
+
#
|
12
|
+
# NOTES:
|
13
|
+
#
|
14
|
+
# - Can JointPoint and Target be the same class?
|
15
|
+
|
16
|
+
require 'facets/kernel/object'
|
17
|
+
require 'facets/module/methods'
|
18
|
+
require 'facets/cut'
|
19
|
+
|
20
|
+
#
|
21
|
+
|
22
|
+
class Aspect < Module
|
23
|
+
|
24
|
+
def initialize(&block)
|
25
|
+
instance_eval(&block)
|
26
|
+
extend self
|
27
|
+
end
|
28
|
+
|
29
|
+
def points
|
30
|
+
@points ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
# TODO Should this accept pattern matches as an alternative to the block too?
|
34
|
+
# Eg. join(name, pattern=nil, &block)
|
35
|
+
def join(name, &block)
|
36
|
+
(points[name] ||= []) << block
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
|
43
|
+
class Joinpoint
|
44
|
+
def initialize(object, base, method, *args, &block)
|
45
|
+
@object = object
|
46
|
+
@base = base
|
47
|
+
@method = method
|
48
|
+
@args = args
|
49
|
+
@block = block
|
50
|
+
end
|
51
|
+
|
52
|
+
def ===(match)
|
53
|
+
case match
|
54
|
+
when Proc
|
55
|
+
match.call(self)
|
56
|
+
else # Pattern matches (not supported presently)
|
57
|
+
match.to_sym == @method.to_sym
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ==(sym)
|
62
|
+
sym.to_sym == @method.to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
|
67
|
+
def super
|
68
|
+
anc = @object.class.ancestors.find{ |anc| anc.method_defined?(@method) }
|
69
|
+
anc.instance_method(@method).bind(@object).call(*@args, &@block)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
# module LogAspect
|
75
|
+
# extend self
|
76
|
+
#
|
77
|
+
# join :log do |jp|
|
78
|
+
# jp.name == :x
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# def log(target)
|
82
|
+
# r = target.super
|
83
|
+
# ...
|
84
|
+
# return r
|
85
|
+
# end
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# class X
|
89
|
+
|
90
|
+
|
91
|
+
class Target
|
92
|
+
|
93
|
+
def initialize(aspect, advice, *target, &block)
|
94
|
+
@aspect = aspect
|
95
|
+
@advice = advice
|
96
|
+
@target = target
|
97
|
+
@block = block
|
98
|
+
end
|
99
|
+
|
100
|
+
def super
|
101
|
+
@aspect.send(@advice, *@target, &@block)
|
102
|
+
end
|
103
|
+
|
104
|
+
alias_method :call, :super
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
def cross_cut(klass)
|
109
|
+
Cut.new(klass) do
|
110
|
+
define_method :__base__ do klass end
|
111
|
+
|
112
|
+
all_instance_methods.each do |meth|
|
113
|
+
undef_method(meth) unless meth.to_s =~ /(^__|initialize$|p$|class$|inspect$)/
|
114
|
+
end
|
115
|
+
|
116
|
+
#def initialize(base, *args, &block)
|
117
|
+
# @base = base
|
118
|
+
# @delegate = base.__new(*args, &block)
|
119
|
+
# @advices = {}
|
120
|
+
#end
|
121
|
+
|
122
|
+
def method_missing(sym, *args, &blk)
|
123
|
+
# p "METHOD MISSING: #{sym}" #if DEBUG
|
124
|
+
|
125
|
+
@advices ||= {}
|
126
|
+
|
127
|
+
base = __base__
|
128
|
+
|
129
|
+
jp = Joinpoint.new(self, base, sym, *args, &blk)
|
130
|
+
|
131
|
+
# calculate advices on first use.
|
132
|
+
unless @advices[sym]
|
133
|
+
@advices[sym] = []
|
134
|
+
base.aspects.each do |aspect|
|
135
|
+
aspect.points.each do |advice, matches|
|
136
|
+
matches.each do |match|
|
137
|
+
if jp === match
|
138
|
+
@advices[sym] << [aspect, advice]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
target = jp #Target.new(self, sym, *args, &blk) # Target == JoinPoint ?
|
146
|
+
|
147
|
+
@advices[sym].each do |(aspect, advice)|
|
148
|
+
target = Target.new(aspect, advice, target)
|
149
|
+
end
|
150
|
+
|
151
|
+
target.super
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
#
|
158
|
+
|
159
|
+
class Class
|
160
|
+
#def cut; @cut; end
|
161
|
+
def aspects; @aspects ||= []; end
|
162
|
+
|
163
|
+
def apply(aspect)
|
164
|
+
if aspects.empty?
|
165
|
+
cross_cut(self)
|
166
|
+
#(class << self;self;end).class_eval do
|
167
|
+
# alias_method :__new, :new
|
168
|
+
# def new(*args, &block)
|
169
|
+
# CrossConcerns.new(self,*args, &block)
|
170
|
+
# end
|
171
|
+
#end
|
172
|
+
end
|
173
|
+
aspects.unshift(aspect)
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
=begin demo
|
180
|
+
|
181
|
+
class X
|
182
|
+
def x; "x"; end
|
183
|
+
def y; "y"; end
|
184
|
+
def q; "<" + x + ">"; end
|
185
|
+
end
|
186
|
+
|
187
|
+
Xa = Aspect.new do
|
188
|
+
join :x do |jp|
|
189
|
+
jp == :x
|
190
|
+
end
|
191
|
+
|
192
|
+
def x(target); '{' + target.super + '}'; end
|
193
|
+
end
|
194
|
+
|
195
|
+
X.apply(Xa)
|
196
|
+
|
197
|
+
x1 = X.new
|
198
|
+
#print 'X == ' ; p x1
|
199
|
+
print 'X == ' ; p x1.class
|
200
|
+
print '["q", "y", "x"] == ' ; p x1.public_methods(false)
|
201
|
+
print '"{x}" == ' ; p x1.x
|
202
|
+
print '"y" == ' ; p x1.y
|
203
|
+
print '"<{x}>" == ' ; p x1.q
|
204
|
+
|
205
|
+
=end
|
data/lib/cuts/cut.rb
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
# TITLE:
|
2
|
+
#
|
3
|
+
# Cut
|
4
|
+
#
|
5
|
+
# SUMMARY:
|
6
|
+
#
|
7
|
+
# By definition, a Cut is a *transparent* subclass.
|
8
|
+
# Thay serve as the basis of Cut-based AOP, by providing
|
9
|
+
# a clean wrapping mechinism.
|
10
|
+
#
|
11
|
+
# COPYRIGHT:
|
12
|
+
#
|
13
|
+
# Copyright (c) 2005 Thomas Sawyer
|
14
|
+
#
|
15
|
+
# LICENSE:
|
16
|
+
#
|
17
|
+
# Ruby License
|
18
|
+
#
|
19
|
+
# This module is free software. You may use, modify, and/or redistribute this
|
20
|
+
# software under the same terms as Ruby.
|
21
|
+
#
|
22
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT
|
23
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
24
|
+
# FOR A PARTICULAR PURPOSE.
|
25
|
+
#
|
26
|
+
# AUTHORS:
|
27
|
+
#
|
28
|
+
# - Thomas Sawyer
|
29
|
+
|
30
|
+
require 'facets/module/name'
|
31
|
+
require 'facets/kernel/silence'
|
32
|
+
|
33
|
+
# = Cut
|
34
|
+
#
|
35
|
+
# Cuts are transparent subclasses. Thay are the basis of
|
36
|
+
# Cut-based AOP. The general idea of Cut-based AOP is that
|
37
|
+
# the Cut can serve a clean container for customized advice on
|
38
|
+
# top of which more sophisticated AOP systems can be built.
|
39
|
+
#
|
40
|
+
# == Examples
|
41
|
+
#
|
42
|
+
# The basic usage is:
|
43
|
+
#
|
44
|
+
# class X
|
45
|
+
# def x; "x"; end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# cut :C < X do
|
49
|
+
# def x; '{' + super + '}'; end
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# X.new.x #=> "{x}"
|
53
|
+
#
|
54
|
+
# To use this in an AOP fashion you can define an Aspect, as a class
|
55
|
+
# or function module, and tie it together with the Cut.
|
56
|
+
#
|
57
|
+
# module LogAspect
|
58
|
+
# extend self
|
59
|
+
# def log(meth, result)
|
60
|
+
# ...
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# cut :C < X do
|
65
|
+
# def x
|
66
|
+
# LogAspect.log(:x, r = super)
|
67
|
+
# return r
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# == Implementation
|
72
|
+
#
|
73
|
+
# Cuts act as a "pre-class". Which depictively is:
|
74
|
+
#
|
75
|
+
# ACut < AClass < ASuperClass
|
76
|
+
#
|
77
|
+
# Instantiating AClass effecively instantiates ACut instead,
|
78
|
+
# but that action is effectively transparent.
|
79
|
+
#
|
80
|
+
# This is the basic model of this particluar implementation:
|
81
|
+
#
|
82
|
+
# class Klass
|
83
|
+
# def x; "x"; end
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
# cut KlassCut < Klass
|
87
|
+
# def x; '{' + super + '}'; end
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# We cut it like so:
|
91
|
+
#
|
92
|
+
# Klass = KlassCut
|
93
|
+
#
|
94
|
+
# p Klass.new.x
|
95
|
+
#
|
96
|
+
# This is simple and relatvely robust, but not 100% transparent.
|
97
|
+
# So we add some redirection methods to the cut to improve the
|
98
|
+
# transparency.
|
99
|
+
#
|
100
|
+
# Due to limitation in meta-programming Ruby as this level, the
|
101
|
+
# transparency isn't perfect, but it's fairly close, and we continue
|
102
|
+
# to improve it.
|
103
|
+
|
104
|
+
class Cut
|
105
|
+
|
106
|
+
def self.new(klass, &block)
|
107
|
+
cut = Class.new(klass, &block) # <-- This is the actual cut.
|
108
|
+
|
109
|
+
#cut.class_eval(&block)
|
110
|
+
|
111
|
+
cut.send(:include, Transparency)
|
112
|
+
cut.extend MetaTransparency
|
113
|
+
|
114
|
+
# TODO Shutdown warning
|
115
|
+
silence_warnings do
|
116
|
+
klass.modspace::const_set(klass.basename, cut)
|
117
|
+
end
|
118
|
+
|
119
|
+
return cut
|
120
|
+
end
|
121
|
+
|
122
|
+
# These methods are needed to emulate full transparancy as
|
123
|
+
# closely as possible.
|
124
|
+
|
125
|
+
module Transparency
|
126
|
+
def methods(all=true)
|
127
|
+
self.class.superclass.instance_methods(all)
|
128
|
+
end
|
129
|
+
def public_methods(all=true)
|
130
|
+
self.class.superclass.public_instance_methods(all)
|
131
|
+
end
|
132
|
+
def private_methods(all=true)
|
133
|
+
self.class.superclass.private_instance_methods(all)
|
134
|
+
end
|
135
|
+
def protected_methods(all=true)
|
136
|
+
self.class.superclass.protected_instance_methods(all)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# These methods are needed to emulate full transparancy as
|
141
|
+
# closely as possible.
|
142
|
+
|
143
|
+
module MetaTransparency
|
144
|
+
#def instance_method(name) ; p "XXXX"; superclass.instance_method(name) ; end
|
145
|
+
def define_method(*a,&b) ; superclass.define_method(*a,&b) ; end
|
146
|
+
def module_eval(*a,&b) ; superclass.module_eval(*a,&b) ; end
|
147
|
+
def class_eval(*a,&b) ; superclass.class_eval(*a,&b) ; end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
class Symbol
|
154
|
+
#alias :_op_lt_without_cuts :<
|
155
|
+
|
156
|
+
# A little tick to simulate subclassing literal syntax.
|
157
|
+
|
158
|
+
def <(klass)
|
159
|
+
if Class === klass
|
160
|
+
[self,klass]
|
161
|
+
else
|
162
|
+
raise NoMethodError, "undefined method `<' for :#{self}:Symbol"
|
163
|
+
#_op_lt_without_cuts(cut_class)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
module Kernel
|
170
|
+
# Cut convienence method.
|
171
|
+
|
172
|
+
def cut(klass, &block)
|
173
|
+
case klass
|
174
|
+
when Array
|
175
|
+
name, klass = *klass
|
176
|
+
else
|
177
|
+
name = nil
|
178
|
+
end
|
179
|
+
|
180
|
+
cut = Cut.new(klass, &block)
|
181
|
+
|
182
|
+
# How to handle main, but not other instance spaces?
|
183
|
+
#klass.modspace::const_set(klass.basename, cut)
|
184
|
+
mod = (Module === self ? self : Object)
|
185
|
+
mod.const_set(cutname, cut) # <<- this is what we don't have in Cut.new
|
186
|
+
|
187
|
+
return cut
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|