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 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 from zenspider.
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
- Add github gems, if you haven't already:
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
- Install gem:
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({}) { |a, b| a.merge names.delete(b) }
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 = Module.new
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
- def auto_chain
42
- class << self
43
- chain_method :method_added do |name|
44
- Chainable.skip_chain { chain_method name }
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
- class << self
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
- def self.sexp_walk(sexp, forbidden_locals = [], &block)
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
- define_method(name) { |*a, &b| m.bind(self).call(*a, &b) }
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
@@ -2,7 +2,7 @@ require "lib/chainable"
2
2
 
3
3
  describe "Chainable#chain_method" do
4
4
 
5
- before :each do
5
+ before do
6
6
  @a_class = Class.new do
7
7
  def foo
8
8
  :foo
@@ -2,7 +2,7 @@ require "lib/chainable"
2
2
 
3
3
  describe "Chainable#chain_method(..., :try_merge => true)" do
4
4
 
5
- before :each do
5
+ before do
6
6
  @a_class = Class.new do
7
7
  def foo
8
8
  :foo
@@ -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 { |block| block.should raise_error(ArgumentError) }
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.1
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