chainable 0.3.1 → 0.3.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/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
|