chainable 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +9 -4
- data/lib/chainable.rb +74 -12
- data/spec/chainable/chain_method_spec.rb +1 -1
- data/spec/chainable/chain_method_try_merge_spec.rb +1 -1
- data/spec/chainable/merge_method_spec.rb +8 -1
- metadata +1 -3
- data/sexp_stuff.rb +0 -27
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
<b>A word of warning:</b>
|
2
2
|
|
3
|
-
This is heavy ruby abuse. It even got the Evil of the Day Award
|
3
|
+
This is heavy ruby abuse. It even got the <b>Evil of the Day Award™</b> from zenspider.
|
4
4
|
|
5
5
|
Forks are welcome!
|
6
6
|
|
@@ -170,8 +170,13 @@ chain_method tends do produce slightly faster methods than alias_method_chain:
|
|
170
170
|
alias_method_chain (define_method) 3.470000 0.590000 4.060000 ( 4.096245)
|
171
171
|
|
172
172
|
== Installation
|
173
|
-
|
173
|
+
From RubyForge:
|
174
|
+
gem install chainable
|
175
|
+
|
176
|
+
From GitHub:
|
174
177
|
gem sources -a http://gems.github.com
|
178
|
+
gem install rkh-chainable
|
179
|
+
|
180
|
+
== Running test
|
175
181
|
|
176
|
-
|
177
|
-
gem install rkh-chainable
|
182
|
+
The specs should work with rspec, mspec and bacon.
|
data/lib/chainable.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "ruby2ruby"
|
2
2
|
|
3
|
+
# This mixin will be included in Module.
|
3
4
|
module Chainable
|
4
5
|
|
5
6
|
# This will "chain" a method (read: push it to a module and include it).
|
@@ -7,13 +8,25 @@ module Chainable
|
|
7
8
|
# Maybe that is not what you want, as methods defined by def tend to be
|
8
9
|
# faster. If that is the case, simply don't pass the block and call def
|
9
10
|
# after chain_method instead.
|
11
|
+
#
|
12
|
+
# It takes the following options:
|
13
|
+
#
|
14
|
+
# === try_merge
|
15
|
+
# try_merge will try merge_method for every method given and only chain if
|
16
|
+
# that fails. Default is false.
|
17
|
+
#
|
18
|
+
# === module_reuse
|
19
|
+
# Will try to reuse the last mixin to keep the inheritance chain short.
|
20
|
+
# Default is true.
|
10
21
|
def chain_method(*names, &block)
|
11
|
-
options = names.grep(Hash).inject(
|
22
|
+
options = names.grep(Hash).inject(Chainable.default_options) do |a, b|
|
23
|
+
a.merge names.delete(b)
|
24
|
+
end
|
12
25
|
names = Chainable.try_merge(self, *names, &block) if options[:try_merge]
|
13
26
|
names.each do |name|
|
14
27
|
name = name.to_s
|
15
28
|
if instance_methods(false).include? name
|
16
|
-
mod =
|
29
|
+
mod = Chainable.mixin_for(self, name, options[:module_reuse])
|
17
30
|
Chainable.copy_method(self, mod, name)
|
18
31
|
include mod
|
19
32
|
end
|
@@ -27,6 +40,7 @@ module Chainable
|
|
27
40
|
# chain_method(:some_method, :try_merge => true) { ... }
|
28
41
|
# instead, which will fall back to chain_method if merge fails.
|
29
42
|
def merge_method(*names, &block)
|
43
|
+
raise ArgumentError, "no block given" unless block
|
30
44
|
names.each do |name|
|
31
45
|
name = name.to_s
|
32
46
|
raise ArgumentError, "cannot merge #{name}" unless instance_methods(false).include? name
|
@@ -38,19 +52,38 @@ module Chainable
|
|
38
52
|
# will be called on that method right after it has been defined. This will
|
39
53
|
# only affect methods defined for the class (or module) auto_chain has been
|
40
54
|
# send to. See README.rdoc or spec/chainable/auto_chain_spec.rb for examples.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
55
|
+
#
|
56
|
+
# auto_chain takes a hash of options, just like chain_method does.
|
57
|
+
def auto_chain options = {}
|
58
|
+
eigenclass = (class << self; self; end)
|
59
|
+
eigenclass.class_eval do
|
60
|
+
chain_method :method_added, :try_merge => false do |name|
|
61
|
+
Chainable.skip_chain { chain_method name, options }
|
45
62
|
end
|
46
63
|
end
|
47
64
|
result = yield
|
48
|
-
|
49
|
-
remove_method :method_added
|
50
|
-
end
|
65
|
+
eigenclass.class_eval { remove_method :method_added }
|
51
66
|
result
|
52
67
|
end
|
53
68
|
|
69
|
+
# Default options for auto_chain and chain_method.
|
70
|
+
#
|
71
|
+
# Example usage:
|
72
|
+
# Chainable.default_options[:try_merge] = true
|
73
|
+
def self.default_options
|
74
|
+
@default_options ||= { :try_merge => false, :module_reuse => true }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Creates mixin used by chain_method.
|
78
|
+
def self.mixin_for(klass, name, reuse = true)
|
79
|
+
@last_mixin ||= {}
|
80
|
+
if reuse and klass.ancestors[1] == @last_mixin[klass]
|
81
|
+
im = @last_mixin[klass].instance_methods(false)
|
82
|
+
return @last_mixin[klass] unless im.include?(name.to_s)
|
83
|
+
end
|
84
|
+
@last_mixin[klass] = Module.new
|
85
|
+
end
|
86
|
+
|
54
87
|
# Used internally. See source of Chainbale#auto_chain.
|
55
88
|
def self.skip_chain
|
56
89
|
return if @auto_chain
|
@@ -73,6 +106,8 @@ module Chainable
|
|
73
106
|
end
|
74
107
|
end
|
75
108
|
|
109
|
+
# The sexp part of wrapped_source. Note: In rubinius, we could use this directly
|
110
|
+
# rather than generating the source again.
|
76
111
|
def self.wrapped_sexp(klass, name, wrapper)
|
77
112
|
inner = unified sexp_for(klass, name)
|
78
113
|
outer = unified sexp_for(wrapper)
|
@@ -82,7 +117,9 @@ module Chainable
|
|
82
117
|
s(:defn, name, s(:args), s(:scope, s(:block, outer[3])))
|
83
118
|
end
|
84
119
|
|
85
|
-
|
120
|
+
# Traveling a methods sexp tree. Block will be called for every super.
|
121
|
+
def self.sexp_walk(sexp, forbidden_locals = [], &block) # :yield: sexp
|
122
|
+
# TODO: Refactor me, I'm ugly!
|
86
123
|
return [] unless sexp.is_a? Sexp
|
87
124
|
local = nil
|
88
125
|
case sexp[0]
|
@@ -101,10 +138,12 @@ module Chainable
|
|
101
138
|
sexp.inject(locals) { |l, e| l + sexp_walk(e, forbidden_locals, &block) }
|
102
139
|
end
|
103
140
|
|
141
|
+
# Unify sexp.
|
104
142
|
def self.unified sexp
|
105
143
|
unifier.process sexp
|
106
144
|
end
|
107
145
|
|
146
|
+
# Give a proc, class and method, string or sexp and get a sexp.
|
108
147
|
def self.sexp_for a, b = nil
|
109
148
|
require "parse_tree"
|
110
149
|
case a
|
@@ -115,10 +154,10 @@ module Chainable
|
|
115
154
|
end
|
116
155
|
end
|
117
156
|
|
157
|
+
# Unifier with modifications for Ruby2Ruby. (Stolen from Ruby2Ruby.)
|
118
158
|
def self.unifier
|
119
159
|
return @unifier if @unifier
|
120
160
|
@unifier = Unifier.new
|
121
|
-
# HACK (stolen from ruby2ruby)
|
122
161
|
@unifier.processors.each { |p| p.unsupported.delete :cfunc }
|
123
162
|
@unifier
|
124
163
|
end
|
@@ -136,14 +175,37 @@ module Chainable
|
|
136
175
|
end
|
137
176
|
end
|
138
177
|
|
178
|
+
def self.store_method(block)
|
179
|
+
@method_store ||= []
|
180
|
+
@method_store << block
|
181
|
+
@method_store.length - 1
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.call_stored_method(method_id, owner, *args, &block)
|
185
|
+
@method_store[method_id].bind(owner).call(*args, &block)
|
186
|
+
end
|
187
|
+
|
139
188
|
# Copies a method from one module to another.
|
189
|
+
# TODO: This could be solved totally different in Rubinius.
|
140
190
|
def self.copy_method(source_class, target_class, name)
|
141
191
|
begin
|
142
192
|
target_class.class_eval Ruby2Ruby.translate(source_class, name)
|
143
193
|
rescue NameError
|
194
|
+
# If we get here, the method is written in C or something. So let's do
|
195
|
+
# some evil magic.
|
144
196
|
m = source_class.instance_method name
|
145
197
|
target_class.class_eval do
|
146
|
-
|
198
|
+
begin
|
199
|
+
eval "define_method(name) { |*a, &b| m.bind(self).call(*a, &b) }"
|
200
|
+
rescue SyntaxError # Ruby < 1.8.7, JRuby
|
201
|
+
# Really really evil, have to change it.
|
202
|
+
method_id = Chainable.store_method(m)
|
203
|
+
eval %[
|
204
|
+
def #{name}(*a, &b)
|
205
|
+
Chainable.call_stored_method(#{method_id}, self, *a, &b)
|
206
|
+
end
|
207
|
+
]
|
208
|
+
end
|
147
209
|
end
|
148
210
|
end
|
149
211
|
end
|
@@ -46,7 +46,14 @@ describe "Chainable#merge_method" do
|
|
46
46
|
superclass = Class.new { define_method(:inherited_method) { } }
|
47
47
|
Class.new(superclass) { merge_method(:inherited_method) { } }
|
48
48
|
end
|
49
|
-
forbidden.each
|
49
|
+
forbidden.each do |block|
|
50
|
+
# just for fun
|
51
|
+
if defined? Bacon
|
52
|
+
block.should.raise(ArgumentError)
|
53
|
+
else
|
54
|
+
block.should raise_error(ArgumentError)
|
55
|
+
end
|
56
|
+
end
|
50
57
|
end
|
51
58
|
|
52
59
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chainable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Konstantin Haase
|
@@ -37,7 +37,6 @@ extra_rdoc_files:
|
|
37
37
|
- spec/chainable/chain_method_spec.rb
|
38
38
|
- spec/chainable/chain_method_try_merge_spec.rb
|
39
39
|
- benchmark/chainable.rb
|
40
|
-
- sexp_stuff.rb
|
41
40
|
files:
|
42
41
|
- LICENSE
|
43
42
|
- Rakefile
|
@@ -48,7 +47,6 @@ files:
|
|
48
47
|
- spec/chainable/chain_method_spec.rb
|
49
48
|
- spec/chainable/chain_method_try_merge_spec.rb
|
50
49
|
- benchmark/chainable.rb
|
51
|
-
- sexp_stuff.rb
|
52
50
|
has_rdoc: true
|
53
51
|
homepage: http://rkh.github.com/chainable
|
54
52
|
post_install_message:
|
data/sexp_stuff.rb
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require "parse_tree"
|
2
|
-
require "ruby2ruby"
|
3
|
-
|
4
|
-
module FastSexp
|
5
|
-
|
6
|
-
def r2r(a, b = nil)
|
7
|
-
return Ruby2Ruby.new.process a if a.is_a? Sexp
|
8
|
-
r2r u(a, b = nil)
|
9
|
-
end
|
10
|
-
|
11
|
-
def u(a, b = nil)
|
12
|
-
unless @unifier
|
13
|
-
@unifier = Unifier.new
|
14
|
-
@unifier.processors.each { |p| p.unsupported.delete :cfunc }
|
15
|
-
end
|
16
|
-
case a
|
17
|
-
when Sexp then sexp = a
|
18
|
-
when String, Class then sexp = ParseTree.translate(a, b)
|
19
|
-
when Proc then sexp = ParseTree.new.parse_tree_for_proc(a)
|
20
|
-
else raise ArgumentError, "dunno how to handle #{a.inspect}"
|
21
|
-
end
|
22
|
-
@unifier.process sexp
|
23
|
-
end
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
include FastSexp
|