teejayvanslyke-gazer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-02-14
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,13 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ lib/gazer.rb
7
+ script/console
8
+ script/destroy
9
+ script/generate
10
+ spec/gazer_spec.rb
11
+ spec/spec.opts
12
+ spec/spec_helper.rb
13
+ tasks/rspec.rake
data/PostInstall.txt ADDED
@@ -0,0 +1,7 @@
1
+
2
+ For more information on gazer, see http://gazer.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
data/README.rdoc ADDED
@@ -0,0 +1,79 @@
1
+ = gazer
2
+
3
+ * http://github.com/teejayvanslyke/gazer
4
+
5
+ == DESCRIPTION:
6
+
7
+ Poor man's aspect-oriented programming for Ruby.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Elegant DSL
12
+ * before & after pointcuts.
13
+ * Rails integration.
14
+
15
+ == ROADMAP:
16
+
17
+ * Locking down Object monkeypatching scheme.
18
+ * More selectors and pointcuts.
19
+ * Regular-expression-based pointcuts.
20
+ * <tt>:only</tt> and <tt>except</tt> options for Rails helper.
21
+
22
+ == SYNOPSIS:
23
+
24
+ Gazer's DSL is accessible to subclasses of Gazer::Aspect::Base.
25
+
26
+ class LoggingAspect < Gazer::Aspect::Base
27
+
28
+ before instances_of(Article) => :create do |point|
29
+ puts "Creating article"
30
+ end
31
+
32
+ after instances_of([Article, Comment]) => :save do |point|
33
+ puts "Saved #{point.object}."
34
+ end
35
+
36
+ end
37
+
38
+ I have also provided a Rails generator for generating aspects for
39
+ your project. Simply execute the following:
40
+
41
+ $ script/generate aspect logging
42
+
43
+ A template will be created at <tt>app/aspects/logging_aspect.rb</tt>
44
+ for your aspect-oriented pleasure.
45
+
46
+ To include an aspect for a given controller, use the aspect class method:
47
+
48
+ class MyController < ApplicationController
49
+ aspect :logging
50
+ end
51
+
52
+ == INSTALL:
53
+
54
+ $ sudo gem install teejayvanslyke-gazer
55
+
56
+ == LICENSE:
57
+
58
+ (The MIT License)
59
+
60
+ Copyright (c) 2009 T.J. VanSlyke
61
+
62
+ Permission is hereby granted, free of charge, to any person obtaining
63
+ a copy of this software and associated documentation files (the
64
+ 'Software'), to deal in the Software without restriction, including
65
+ without limitation the rights to use, copy, modify, merge, publish,
66
+ distribute, sublicense, and/or sell copies of the Software, and to
67
+ permit persons to whom the Software is furnished to do so, subject to
68
+ the following conditions:
69
+
70
+ The above copyright notice and this permission notice shall be
71
+ included in all copies or substantial portions of the Software.
72
+
73
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
74
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
75
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
76
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
77
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
78
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
79
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 1
3
+ :major: 0
4
+ :minor: 0
data/lib/gazer.rb ADDED
@@ -0,0 +1,12 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+
5
+ module Gazer
6
+ VERSION = '0.0.1'
7
+ end
8
+
9
+ require 'gazer/object_extensions'
10
+ require 'gazer/aspect'
11
+ require 'gazer/rails' if defined?(RAILS_ENV)
12
+
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'aspect/join_point'
5
+ require 'aspect/pointcut'
6
+ require 'aspect/base'
7
+ require 'aspect/filter'
8
+ require 'aspect/instance_of'
9
+
@@ -0,0 +1,38 @@
1
+
2
+ module Gazer
3
+ module Aspect
4
+ class Base
5
+
6
+ class << self
7
+
8
+ def add_pointcut(pointcut)
9
+ @pointcuts ||= []
10
+ @pointcuts << pointcut
11
+ end
12
+
13
+ attr_accessor :pointcuts
14
+
15
+ def instances_of(klass)
16
+ InstancesOf.new(klass)
17
+ end
18
+
19
+ def apply!
20
+ pointcuts.each do |pc| pc.apply! end
21
+ end
22
+
23
+ def before(selector, &block)
24
+ add_pointcut BeforePointcut.new(selector, &block)
25
+ end
26
+
27
+ def after(selector, &block)
28
+ add_pointcut AfterPointcut.new(selector, &block)
29
+ end
30
+
31
+ def around(selector, &block)
32
+ add_pointcut AroundPointcut.new(selector, &block)
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ module Gazer
2
+ module Aspect
3
+ class Filter
4
+ def initialize(expr)
5
+ if expr.is_a?(Array)
6
+ @types = expr
7
+ else
8
+ @types = [ expr ]
9
+ end
10
+ end
11
+
12
+ def advise_before(sym, &block)
13
+ @types.each {|t| t.advise_before(sym, &block)}
14
+ end
15
+
16
+ def advise_around(sym, &block)
17
+ @types.each {|t| t.advise_around(sym, &block)}
18
+ end
19
+
20
+ def advise_after(sym, &block)
21
+ @types.each {|t| t.advise_after(sym, &block)}
22
+ end
23
+
24
+ def advise_instances_before(sym, &block)
25
+ @types.each {|t| t.advise_instances_before(sym, &block)}
26
+ end
27
+
28
+ def advise_instances_around(sym, &block)
29
+ @types.each {|t| t.advise_instances_around(sym, &block)}
30
+ end
31
+
32
+ def advise_instances_after(sym, &block)
33
+ @types.each {|t| t.advise_instances_after(sym, &block)}
34
+ end
35
+ end
36
+ end
37
+ end
38
+
@@ -0,0 +1,21 @@
1
+ module Gazer
2
+ module Aspect
3
+ class InstancesOf
4
+ def initialize(filter)
5
+ @filter = Filter.new(filter)
6
+ end
7
+
8
+ def advise_before(sym, &block)
9
+ @filter.advise_instances_before(sym, &block)
10
+ end
11
+
12
+ def advise_around(sym, &block)
13
+ @filter.advise_instances_around(sym, &block)
14
+ end
15
+
16
+ def advise_after(sym, &block)
17
+ @filter.advise_instances_after(sym, &block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,8 @@
1
+ require 'ostruct'
2
+
3
+ module Gazer
4
+ module Aspect
5
+ class JoinPoint < OpenStruct; end
6
+ end
7
+ end
8
+
@@ -0,0 +1,34 @@
1
+ module Gazer
2
+ module Aspect
3
+ class Pointcut
4
+ def initialize(selector, &block)
5
+ @selector = selector
6
+ @block = block
7
+ end
8
+ end
9
+
10
+ class BeforePointcut < Pointcut
11
+ def apply!
12
+ @selector.keys.each do |key|
13
+ Filter.new(key).advise_before(@selector[key], &@block)
14
+ end
15
+ end
16
+ end
17
+
18
+ class AfterPointcut < Pointcut
19
+ def apply!
20
+ @selector.keys.each do |key|
21
+ Filter.new(key).advise_after(@selector[key], &@block)
22
+ end
23
+ end
24
+ end
25
+
26
+ class AroundPointcut < Pointcut
27
+ def apply!
28
+ @selector.keys.each do |key|
29
+ Filter.new(key).advise_around(@selector[key], &@block)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,141 @@
1
+ module Gazer
2
+
3
+ module ObjectExtensions
4
+
5
+ module ClassMethods
6
+ def advise(sym, block)
7
+ @advice ||= {}
8
+ @advice[sym] ||= []
9
+ @advice[sym] << block
10
+ end
11
+
12
+ def advice
13
+ @advice ||= {}
14
+ end
15
+
16
+ def advice_for(sym)
17
+ @advice[sym]
18
+ end
19
+
20
+ def backup_method(sym)
21
+ id = "__#{sym}_#{backup_methods_for(sym).size}__"
22
+ alias_method id, sym # Backup original method
23
+ private id # Make backup private
24
+ @backup_methods_for ||= {}
25
+ @backup_methods_for[sym] ||= []
26
+ @backup_methods_for[sym] << id
27
+ return id
28
+ end
29
+
30
+ def backup_methods_for(sym)
31
+ @backup_methods_for ||= {}
32
+ @backup_methods_for[sym] ||= []
33
+ end
34
+
35
+ def unadvise_all
36
+ advice.each do |sym, arr|
37
+ code = <<-CODE
38
+ class << self
39
+ alias_method #{sym.inspect}, '__#{sym}_0__'
40
+ public #{sym.inspect}
41
+ end
42
+ CODE
43
+ instance_eval(code)
44
+ end
45
+ end
46
+
47
+ def advise_before(sym, &block)
48
+ return unless respond_to?(sym)
49
+ advise(sym, block)
50
+ code = <<-CODE
51
+ class << self
52
+ hook = backup_method(#{sym.inspect})
53
+ define_method #{sym.inspect} do |*args|
54
+ self.advice_for(#{sym.inspect}).last.call(
55
+ Gazer::Aspect::JoinPoint.new(:object => self,
56
+ :method => #{sym.inspect},
57
+ :args => args))
58
+ __send__ hook, *args # Invoke backup
59
+ end
60
+ end
61
+ CODE
62
+ instance_eval(code)
63
+ end
64
+
65
+ def advise_around(sym, &block)
66
+ return unless respond_to?(sym)
67
+ advise(sym, block)
68
+ code = <<-CODE
69
+ class << self
70
+ hook = backup_method(#{sym.inspect})
71
+ define_method #{sym.inspect} do |*args|
72
+ self.advice_for(#{sym.inspect}).last.call(
73
+ Gazer::Aspect::JoinPoint.new(:object => self,
74
+ :method => #{sym.inspect},
75
+ :args => args))
76
+ __send__ hook, *args # Invoke backup
77
+ end
78
+ end
79
+ CODE
80
+ instance_eval(code)
81
+ end
82
+
83
+ def advise_after(sym, &block)
84
+ return unless respond_to?(sym)
85
+ advise(sym, block)
86
+ code = <<-CODE
87
+ class << self
88
+ hook = backup_method(#{sym.inspect})
89
+ define_method #{sym.inspect} do |*args|
90
+ rval = __send__ hook, *args # Invoke backup
91
+ self.advice_for(#{sym.inspect}).last.call(
92
+ Gazer::Aspect::JoinPoint.new(:object => self,
93
+ :method => #{sym.inspect},
94
+ :args => args))
95
+ return rval
96
+ end
97
+ end
98
+ CODE
99
+ instance_eval(code)
100
+ end
101
+
102
+ def advise_instances_before(sym, &block)
103
+ hook = backup_method(sym)
104
+ define_method sym do |*args|
105
+ block.call(
106
+ Gazer::Aspect::JoinPoint.new(:object => self,
107
+ :method => sym,
108
+ :args => args))
109
+ __send__ hook, *args # Invoke backup
110
+ end
111
+ end
112
+
113
+ def advise_instances_around(sym, &block)
114
+ hook = backup_method(sym)
115
+ define_method sym do |*args|
116
+ block.call(
117
+ Gazer::Aspect::JoinPoint.new(:object => self,
118
+ :method => sym,
119
+ :args => args))
120
+ __send__ hook, *args
121
+ end
122
+ end
123
+
124
+ def advise_instances_after(sym, &block)
125
+ hook = backup_method(sym)
126
+ define_method sym do |*args| # Replace method
127
+ rval = __send__ hook, *args # Invoke backup
128
+ block.call(
129
+ Gazer::Aspect::JoinPoint.new(:object => self,
130
+ :method => sym,
131
+ :args => args))
132
+ return rval
133
+ end
134
+ end
135
+ end
136
+
137
+ end
138
+
139
+ end
140
+
141
+ Object.extend(Gazer::ObjectExtensions::ClassMethods)
@@ -0,0 +1,4 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rails/action_controller'
@@ -0,0 +1,19 @@
1
+ module Gazer
2
+ module Rails
3
+ module ActionController
4
+ module ClassMethods
5
+ def aspect(*args)
6
+ args.each do |aspect|
7
+ define_method "__apply_#{aspect}_aspect__" do
8
+ eval("#{aspect.to_s.classify}Aspect.apply!")
9
+ end
10
+
11
+ before_filter "__apply_#{aspect}_aspect__"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ ActionController::Base.extend(Gazer::Rails::ActionController::ClassMethods)
@@ -0,0 +1,181 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+
3
+
4
+ def message(text) end
5
+ describe Gazer do
6
+
7
+ include Gazer
8
+
9
+ class Dog
10
+
11
+ def self.create
12
+ new
13
+ end
14
+
15
+ def bark!
16
+ "woof!"
17
+ end
18
+
19
+ end
20
+
21
+ class Cat
22
+ def hiss!
23
+ end
24
+
25
+ def run_away!
26
+ end
27
+
28
+ def self.get_scared
29
+ end
30
+ end
31
+
32
+ describe "advising instance methods" do
33
+
34
+ describe "before the method call" do
35
+
36
+ it "executes the advice provided before the method is executed" do
37
+
38
+ @cat = Cat.new
39
+
40
+ Dog.advise_instances_before(:bark!) do |point|
41
+ @cat.hiss!
42
+ end
43
+
44
+ @dog = Dog.new
45
+ @cat.should_receive(:hiss!)
46
+ @dog.bark!
47
+
48
+ end
49
+
50
+ end
51
+
52
+ describe "after the method call" do
53
+
54
+ it "executes the advice provided after the method is executed" do
55
+
56
+ @cat = Cat.new
57
+
58
+ Dog.advise_instances_after(:bark!) do |point|
59
+ @cat.run_away!
60
+ end
61
+
62
+ @dog = Dog.new
63
+ @cat.should_receive(:run_away!)
64
+ @dog.bark!
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ describe "advising class methods" do
72
+
73
+ describe "before the method call" do
74
+
75
+ it "executes the advice provided before the method is executed" do
76
+
77
+ Dog.unadvise_all
78
+ Cat.unadvise_all
79
+
80
+ Dog.advise_before(:create) do |point|
81
+ Cat.get_scared
82
+ end
83
+
84
+ Cat.should_receive(:get_scared)
85
+ @dog = Dog.create
86
+
87
+ end
88
+
89
+ end
90
+
91
+ describe "after the method call" do
92
+
93
+ it "executes the advice provided after the method is executed" do
94
+
95
+ Dog.unadvise_all
96
+
97
+ Dog.advise_after(:create) do |point|
98
+ Cat.get_scared
99
+ end
100
+
101
+ Cat.should_receive(:get_scared)
102
+ @dog = Dog.create
103
+
104
+ end
105
+
106
+ end
107
+
108
+ describe "after instantiating an instance already" do
109
+
110
+ before :each do
111
+ Dog.unadvise_all
112
+ Cat.unadvise_all
113
+
114
+ @dog = Dog.new
115
+ @cat = Cat.new
116
+ end
117
+
118
+ it "does not execute the advice if none has been defined" do
119
+ @cat.should_not_receive(:hiss!)
120
+ @dog.bark!
121
+ end
122
+
123
+ it "executes the advice provided on the already-instantiated instance" do
124
+ Dog.advise_instances_after(:bark!) do |point|
125
+ @cat.hiss!
126
+ end
127
+
128
+ @cat.should_receive(:hiss!)
129
+ @dog.bark!
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+
136
+ describe "aspect DSL" do
137
+ class TestAspect < Gazer::Aspect::Base
138
+ before instances_of(Dog) => :bark! do |point|
139
+ Cat.get_scared
140
+ end
141
+
142
+ after instances_of(Cat) => :hiss! do |point|
143
+ Dog.new
144
+ end
145
+
146
+ before Dog => :create do |point|
147
+ message("creating #{point.object.name}")
148
+ end
149
+
150
+ def self.message(text)
151
+ end
152
+ end
153
+
154
+ before :all do
155
+ TestAspect.apply!
156
+ end
157
+
158
+ before :each do
159
+ @dog = Dog.new
160
+ @cat = Cat.new
161
+
162
+ end
163
+
164
+ it "makes the cat run away before the dog barks" do
165
+ Cat.should_receive(:get_scared)
166
+ @dog.bark!
167
+ end
168
+
169
+ it "creates a new dog after the cat hisses" do
170
+ Dog.should_receive(:new)
171
+ @cat.hiss!
172
+ end
173
+
174
+ it "messages that we are creating a new Dog" do
175
+ TestAspect.should_receive(:message).with("creating Dog")
176
+ Dog.create
177
+ end
178
+
179
+ end
180
+
181
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'spec'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ gem 'rspec'
6
+ require 'spec'
7
+ end
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'gazer'
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(__FILE__), "test_generator_helper.rb")
2
+
3
+ require 'rails_generator'
4
+
5
+ class TestAspectGenerator < Test::Unit::TestCase
6
+ include RubiGen::GeneratorTestHelper
7
+
8
+ def setup
9
+ bare_setup
10
+ end
11
+
12
+ def teardown
13
+ bare_teardown
14
+ end
15
+
16
+ # Some generator-related assertions:
17
+ # assert_generated_file(name, &block) # block passed the file contents
18
+ # assert_directory_exists(name)
19
+ # assert_generated_class(name, &block)
20
+ # assert_generated_module(name, &block)
21
+ # assert_generated_test_for(name, &block)
22
+ # The assert_generated_(class|module|test_for) &block is passed the body of the class/module within the file
23
+ # assert_has_method(body, *methods) # check that the body has a list of methods (methods with parentheses not supported yet)
24
+ #
25
+ # Other helper methods are:
26
+ # app_root_files - put this in teardown to show files generated by the test method (e.g. p app_root_files)
27
+ # bare_setup - place this in setup method to create the APP_ROOT folder for each test
28
+ # bare_teardown - place this in teardown method to destroy the TMP_ROOT or APP_ROOT folder after each test
29
+
30
+ def test_generator_without_options
31
+ name = "myapp"
32
+ run_generator('aspect', [name], sources)
33
+ assert_directory_exists "some/directory"
34
+ assert_generated_file "some_file"
35
+ end
36
+
37
+ private
38
+ def sources
39
+ [RubiGen::PathSource.new(:test, File.join(File.dirname(__FILE__),"..", generator_path))
40
+ ]
41
+ end
42
+
43
+ def generator_path
44
+ "rails_generators"
45
+ end
46
+ end
@@ -0,0 +1,29 @@
1
+ begin
2
+ require File.dirname(__FILE__) + '/test_helper'
3
+ rescue LoadError
4
+ require 'test/unit'
5
+ end
6
+ require 'fileutils'
7
+
8
+ # Must set before requiring generator libs.
9
+ TMP_ROOT = File.dirname(__FILE__) + "/tmp" unless defined?(TMP_ROOT)
10
+ PROJECT_NAME = "myproject" unless defined?(PROJECT_NAME)
11
+ app_root = File.join(TMP_ROOT, PROJECT_NAME)
12
+ if defined?(APP_ROOT)
13
+ APP_ROOT.replace(app_root)
14
+ else
15
+ APP_ROOT = app_root
16
+ end
17
+ if defined?(RAILS_ROOT)
18
+ RAILS_ROOT.replace(app_root)
19
+ else
20
+ RAILS_ROOT = app_root
21
+ end
22
+
23
+ begin
24
+ require 'rubigen'
25
+ rescue LoadError
26
+ require 'rubygems'
27
+ require 'rubigen'
28
+ end
29
+ require 'rubigen/helpers/generator_test_helper'
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: teejayvanslyke-gazer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - T.J. VanSlyke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-17 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: poor man's aspect-oriented programming for Ruby
17
+ email: T.J. VanSlyke
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - History.txt
26
+ - Manifest.txt
27
+ - PostInstall.txt
28
+ - README.rdoc
29
+ - VERSION.yml
30
+ - lib/gazer
31
+ - lib/gazer/aspect
32
+ - lib/gazer/aspect/base.rb
33
+ - lib/gazer/aspect/filter.rb
34
+ - lib/gazer/aspect/instance_of.rb
35
+ - lib/gazer/aspect/join_point.rb
36
+ - lib/gazer/aspect/pointcut.rb
37
+ - lib/gazer/aspect.rb
38
+ - lib/gazer/object_extensions.rb
39
+ - lib/gazer/rails
40
+ - lib/gazer/rails/action_controller.rb
41
+ - lib/gazer/rails.rb
42
+ - lib/gazer.rb
43
+ - test/test_aspect_generator.rb
44
+ - test/test_generator_helper.rb
45
+ - spec/gazer_spec.rb
46
+ - spec/spec.opts
47
+ - spec/spec_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/teejayvanslyke/gazer
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --inline-source
53
+ - --charset=UTF-8
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ version:
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ requirements: []
69
+
70
+ rubyforge_project:
71
+ rubygems_version: 1.2.0
72
+ signing_key:
73
+ specification_version: 2
74
+ summary: poor man's aspect-oriented programming for Ruby
75
+ test_files: []
76
+