forwarder2 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2321eef417bac1725fd34fb39d99d5a8ce187abd
4
+ data.tar.gz: 5b4e950db43bd7a004750bbb5987746a56a9e6d3
5
+ SHA512:
6
+ metadata.gz: dd4327158ec4ab363e588e7661bb87629a32275dfbc20eb1e3ab9d407f35fc2f394594d26d7c8e717cd24a2b3c0b369195feec998c5f4f21d40647ba9d679d31
7
+ data.tar.gz: 6d2754e5d8fee19b6356197c3a448a9e92cd18496dc25b246e7b622cab941b63796ebdcfd1a831a3ded3ce7bf460060c16ac4dfa267f06a347faf2378190e4cf
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Robert Dober
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,493 @@
1
+ # Forwarder2
2
+
3
+ This implementation is for, and needs, Ruby 2.
4
+ For Ruby 1.9 please see https://github.com/RobertDober/Forwarder19.
5
+ For Ruby 1.8.7 please see https://github.com/RobertDober/Forwarder.
6
+
7
+ ## Abstract
8
+
9
+ Ruby's core Forwardable gets the job done(barely), but produces most unreadable code.
10
+
11
+ This is a nonintrusive (as is Forwardable) module that allows to delegate methods to instance variables,
12
+ objects returned by instance\_methods, other methods of the same receiver, the receiver itself, a chain of messages or
13
+ an arbitrary object. Paramters can be provided in the forwarding definition (parially or totally.
14
+
15
+ It also defines after and before filters. and some more sophisticated use cases}
16
+
17
+ ## License
18
+
19
+ This software is released under the very liberal MIT license as indicated in the attached file LICENSE.
20
+ If you do not have the LICENSE file delivered the terms of the license are referred to here:
21
+ http://www.opensource.org/licenses/mit-license.html
22
+
23
+ ## Performance
24
+
25
+ Execution time is that of 85~95% of Forwardable by evalling strings whenever possible.
26
+
27
+
28
+ ## Simple Delegation As In Forwardable
29
+
30
+ ```ruby
31
+
32
+ forward <a_message>, to: <target>
33
+ forward <a_message>, to: <target>, as: <translation>
34
+ ```
35
+
36
+ These two forms of the `forward` method, (and *only* these two forms) are directly implemented with
37
+ `def_delegator` method of `Forwardable`, as follows:
38
+
39
+ ```ruby
40
+ def_delegator <target> <a_message>
41
+ def_delegator <translation> <a_message>
42
+ ```
43
+
44
+ Furthermore the `forward_all` method is translated to the `def_delegators` method in the following form,
45
+ thusly
46
+
47
+ ```ruby
48
+ forward_all msg1, msg2, msg3, ..., to: target
49
+ ```
50
+ is implemented as
51
+
52
+ ```ruby
53
+ def_delegators target, msg1, msg2, msg3, ...
54
+ ```
55
+
56
+ ### Additional Features
57
+
58
+ * Parameters (partial or total application)
59
+ * Custom And Chained Targets
60
+ * AOP Filters
61
+ * Helpers
62
+
63
+
64
+ ## Parameters
65
+
66
+
67
+ ### Passing One Parameter
68
+
69
+ Assuming a class `ArrayWrapper` and that their instances wrap the array object via the instance variable
70
+ `@ary` the Smalltalk method `second` can be implemented as follows.
71
+
72
+ ```ruby
73
+ require 'forwarder'
74
+ class ArrayWrapper
75
+ extend Forwarder
76
+ forward :second, to: :@ary, as: :[], with: 1
77
+ ...
78
+ end
79
+ ```
80
+
81
+ The `with` keyword paramter is thus used to provide the first slice of arguments that will be provided
82
+ to the forwarded invocation. This slice will be extended by the actual parameters of the invocation
83
+ of the proxy method (e.g. the instance method defined by the `forward` method itself).
84
+
85
+ ### Passing More Parameters
86
+
87
+ If `with:` is passed an array, it is splatted into the invocation, thus allowing us to pass more than
88
+ one parameter. This becomes clearer with an example.
89
+
90
+ ```ruby
91
+
92
+ forward :add_whitespace_to_punctuation,
93
+ to: :name,
94
+ as: :gsub!,
95
+ with: [ /[,.]\b/, '\& ' ]
96
+ ```
97
+
98
+ ### A Useful Shorthand
99
+
100
+ As I found myself using the following idioms all the time
101
+
102
+ ```ruby
103
+
104
+ forward :some_method, to: :@some_hash, as: :[], with: :some_method
105
+ forward :other_method, to: :hash, as: :[], with: :other_key
106
+
107
+ ```
108
+
109
+ I conceived the `to_hash` shortcut for these. Strictly spoken (and not striktly spoken
110
+ too) this is a gross generalisation of the usecase as if they target had to be a `Hash`
111
+ all the time. This is not the case of course, we are just forwarding a message with a
112
+ parameter...
113
+
114
+ Here is how the above idioms can be expressed by means of the `to_hash` target:
115
+
116
+ ```ruby
117
+
118
+ forward :some_method, to_hash: :@some_hash
119
+ forward :other_method, to_hash: :hash, as: :other_key
120
+
121
+ ```
122
+
123
+ Concerning jargon we are doing something a little bit confusing here. In all cases we have
124
+ an implicit translation (which is `:[]` of course). In the second case we have an explicit
125
+ translation (being `:other_key`) too. The explicit translation is transformed into the first,
126
+ and only argument, as we do not allow explicit arguments for `to_hash:` targets.
127
+
128
+ However you still can use the `forward_all` version and a `to_hash:` chain target, here is
129
+ an example:
130
+
131
+ ```ruby
132
+
133
+ class Params
134
+ extend Forwarder
135
+ forward_all :count, :limit, to_hash: [:@params, :mandatory]
136
+ forward :pretend?, to_hash: [:@params, :optional], as: :dry_run
137
+ end
138
+
139
+ ```
140
+
141
+ AOP is not supported for `to_hash:` targets in this version, this might change in the future
142
+ as use cases are imaginable (e.g. an after filter for the `:pretend?` method, applying !! to
143
+ the result).
144
+
145
+ ### Partial Application
146
+
147
+ This example gives us the oppurtunity to look at a use case for partial applications. Let us assume that
148
+ we do not always use whitespaces, than we can leave the second paramter to be provided by the invocation
149
+ of the defined forwarder proxy.
150
+
151
+ ```ruby
152
+ forward :add_something_to_punctuation,
153
+ to: :name,
154
+ as: :gsub!,
155
+ with: /([,.])\b/
156
+ ```
157
+
158
+ We can achieve the same as above with the following invocation
159
+
160
+ ```ruby
161
+ o = Name.new( "the,quick, fox." )
162
+ o.add_something_to_punctuation( '\1 ' )
163
+ # name: "the, quick, fox." )
164
+ ```
165
+
166
+ but we can also add a hyphen after interpunctations with this invocation
167
+
168
+ ```ruby
169
+ o = Name.new( "the,quick, fox." )
170
+ o.add_something_to_punctuation( '\1- ' )
171
+ # name: "the,- quick,- fox." )
172
+ ```
173
+
174
+ But more importantely we can forward to the partial application, thus using the
175
+ partial application as a mean of composition
176
+
177
+ ```ruby
178
+ forward :add_ws_to_punctuation,
179
+ to_object: :self,
180
+ as: :add_something_to_punctuation,
181
+ with: '\1 '
182
+
183
+ forward :add_hypen_to_punctuation,
184
+ to_object: self,
185
+ as: :add_something_to_punctuation,
186
+ with_block: ->(*grps){ "#{grps.first}- " }
187
+
188
+ ```
189
+
190
+ ### Passing One Array
191
+
192
+ If a real array shall be passed in as one parameter it can be wrapped into an array of one element,
193
+ or the `with_ary:` keyword parameter can be used.
194
+
195
+ Example:
196
+
197
+ ```ruby
198
+ forward :append_suffix, to: :@ary, as: :concat, with: [%w{ my suffix }]
199
+ forward :append_suffix, to: :@ary, as: :concat, with_ary: %w{ my suffix }
200
+ ```
201
+
202
+ ### Passing A Block
203
+
204
+ In case of the necessity to provide a block to the forwarded invocation, it can be specified as the
205
+ block parameter of the `forward` invocation itself.
206
+
207
+ The following example uses inject to compute a sum of elements
208
+
209
+ ```ruby
210
+ forward :sum, to: :elements, as: :inject do |s,e| s+e end
211
+ ```
212
+
213
+ Please note however that common patterns like this one can benefit of the provided
214
+ helpers, in our case it is Integer.sum.
215
+
216
+ ```ruby
217
+ require 'forwarder/helpers/integer/sum'
218
+ ...
219
+ forward :sum, to: :elements, as: :inject &Integer.sum
220
+ # or
221
+ forward :sum, to: :elements, as: :inject, with_block: Integer.sum
222
+ ...
223
+ ```
224
+
225
+ Accounting for different tastes a block can be provided as a block parameter or
226
+ as a `lambda` to the `with_block:` keyword parameter. The later is taking preference
227
+ over the former, which no defined usage of the block in this case (at least for
228
+ the time being).
229
+
230
+ ### Selective Helpers
231
+
232
+ As we do not want to be intrusive the helpers
233
+ have to be requested explicitly.
234
+
235
+ This can be done in three levels of granularity:
236
+
237
+ * Per helper
238
+
239
+ `require 'forwarder/helpers/integer/sum'`
240
+
241
+ * All helpers
242
+
243
+ `require 'forwarder/helpers'`
244
+
245
+ * Per monkey patched class
246
+
247
+ `require 'forwarder/helpers/integer'`
248
+
249
+ ## Custom And Chained Targets
250
+
251
+ So far the `to:` keyword was followed by a symbol or string denoting a _symbolic receiver_, that is
252
+ an instance_variable or method with the denoted name. Custom and Chain Targets are implementing a
253
+ different story.
254
+
255
+ ### Chain Targets
256
+
257
+ Chain Targets are also expressed with the `to:` keyword parameter, but by passing an array of _symbolic receivers_.
258
+ This array will resolve to the final target by sending each message to the result of the preceding message.
259
+ The following example should make this clearer:
260
+
261
+
262
+ ```ruby
263
+ forward :size, to: %w{@content children}
264
+ ```
265
+
266
+ which could have been implemented by hand as follows:
267
+
268
+ ```ruby
269
+ def size
270
+ @content.children.size
271
+ end
272
+ ```
273
+
274
+ ### Custom Targets
275
+
276
+ Allow the user to define a target that cannot be expressed as a _symbolic receiver_.
277
+
278
+ Custom targets are expressed by the means of the `to_object:` keyword parameter.
279
+
280
+
281
+ I want to give two examples here, the first
282
+ using `self`, which evaluates to the module in which `forward` is invoked of course, and might
283
+ thus be used to forward to class instance methods, as in the following example:
284
+
285
+ ```ruby
286
+ class Callback
287
+ def self.instances; @__instances__ ||= [] end
288
+
289
+ def self.register an_instance
290
+ instances << an_instance
291
+ end
292
+
293
+ extend Forwarder
294
+ forward :register, to_object: self
295
+
296
+ def initialize
297
+ register self
298
+ end
299
+ end
300
+ ```
301
+
302
+ But when looking closely one can see that the `self.register` method is just another delegation, thus the whole code
303
+ can be rewritten even more concesily as:
304
+
305
+ ```ruby
306
+ class Callback
307
+ class << self
308
+ extend Forwarder
309
+ forward :<<, to: :instances
310
+
311
+ def instances; @__instances__ ||= [] end
312
+ end
313
+
314
+ extend Forwarder
315
+ forward :register, to_object: self, as: :<<
316
+ end
317
+ ```
318
+
319
+ The second example is a forward to the instance itself, for that purpose the symbol :self
320
+ can be used. The followin is, again, an implementation of Smalltalk's `second` method. But
321
+ here we are defining it on `Array` itself, not a wrapper.
322
+
323
+
324
+ ```ruby
325
+ class Array
326
+ extend Forwarder
327
+ forward :second, to_object: :self, as: :[], with: 1
328
+ ```
329
+
330
+ However the same could be accomplished by using the object/identity helper and the default
331
+ target implementation.
332
+
333
+ ```ruby
334
+ require 'forwarder/helpers/object/identity'
335
+ class Array
336
+ extend Forwarder
337
+ forward :second, to: :identity, as: :[], with: 1
338
+ ```
339
+
340
+ #### Custom Targets And Closures
341
+
342
+ Another application of custom targets would be to hide a enclosed object, but as in the first
343
+ example above, such an object cannot be defined on instance level, but only on class level.
344
+ Assuming that the class itself does not need access to the object enclosed by the closure, one
345
+ could easily implement an instance count for a class as follows:
346
+
347
+
348
+ ```ruby
349
+
350
+ container = []
351
+ forward :register, to_object: container, as: :<<, with: :sentinel
352
+ forward :instance_count, to_object: container, as: :size
353
+
354
+ ```
355
+
356
+ ## AOP Filters
357
+
358
+ Before and After filters are implemented in this version.
359
+
360
+ The respective `before:` and
361
+ `after:` keyword parameters expect lambdas as paramters, but by specifying the `:use_block`
362
+ value the block parameter of the `forward` method can be _abused_ for this purpose.
363
+
364
+ The following examples all operate on a class wrapping a hash instance via the `hash` attribute
365
+ reader. Our first goal is to implement a `max_value` method, that will return the maxium value
366
+ of all values for given keys.
367
+
368
+ ### After Filter
369
+
370
+ The lambda provided by `after:` is applied to the return value of the forwarded invocation.
371
+ The following three examples all implement the `max_value` method correctly.
372
+
373
+
374
+ ```ruby
375
+ forward :max_value, to: :hash, as: :values_at, after: lambda{ |x| x.max }
376
+ forward :max_value, to: :hash, as: :values_at, after: :use_block do | x |
377
+ x.max
378
+ end
379
+ require 'forwarder/helpers/kernel/sendmsg'
380
+ forward :max_value, to: :hash, as: :values_at, after: sendmsg( :max )
381
+ ```
382
+
383
+ N.B. The `Kernel#sendmsg` method is my reply to the hated - by me that is at least - `Symbol#to_proc` kludge and its
384
+ limitations, I will talk about it more in the Helpers section.
385
+
386
+ ### Before Filter.
387
+
388
+ Our next goal is to implement a method `value_of_max` that returns the value for the greatest of
389
+ all provided keys.
390
+
391
+ For this we will use a before filter, its lambda is applied to the arguments
392
+ of the implemented forwarder and the result will be passed in to the forwarded invocation. The pass in
393
+ will use a splash if appropriate.
394
+
395
+
396
+ ```ruby
397
+ forward :value_of_max, to: :hash, as: :[], before: lambda{ |*args| args.max }
398
+ require 'forwarder/helpers/kernel/sendmsg'
399
+ forward :value_of_max, to: :hash, as: :[], before: sendmsg( :max )
400
+ ```
401
+
402
+ ## Helpers
403
+
404
+ *N.B.* These are no longer part of Forwarder19, but have been moved into the gemdependency lab419_core.
405
+
406
+ Helpers define two type of methods. Firstly methods that return lambdas for frequently used
407
+ block patterns, e.g. `Integer.sum`. Secondly methods that are convenient to use inside `forward`
408
+ invocations, but not necessarily only there, e.g. `Kernel#sendmsg` or `Object#identity`.
409
+
410
+ ### Functional Helpers
411
+
412
+ I see this second group, as small as it is, as an important enhancement for the functional
413
+ programming style. The possibilty to nullify a block that is necessarily used in a chain
414
+ of functional calls by passing in `{|x| x.identity}`, `sendmsg(:identity)` or even the
415
+ hated `&:identity` is a recurring pattern.
416
+
417
+ **Warning:** I will become evangelic now.
418
+
419
+ I do not like the `Symbol#to_proc` kludge, and that for two reasons. The first is pragamatic.
420
+ You cannot pass parameters, and that sucks. Why can I express `map(&:succ)` but not `map(&:+, 2)`.
421
+ Well the answer is clear, Ruby's syntax does not support it.
422
+
423
+ The second reason is on philosophical grounds. It feels wrong that Symbol shall be responsable
424
+ of transforming itself into a lambda.
425
+
426
+ Thus I created a helper in Kernel that takes the responsability, and doing so
427
+ with a clear name, expressing intent. This helper is `Kernel#sendmsg`.
428
+
429
+
430
+ ```ruby
431
+ map do |ele|
432
+ ele.hello "World"
433
+ end
434
+ ```
435
+
436
+ is the same as
437
+
438
+
439
+ ```ruby
440
+ map( &sendmsg( :hello, "World") )
441
+ ```
442
+
443
+ Furthermore it might be usuful to keep the returned `lambda` around, please compare
444
+
445
+ ```ruby
446
+ adder = sendmsg( :+ )
447
+ ```
448
+ versus
449
+
450
+ ```ruby
451
+ adder = :+.to_proc
452
+ ```
453
+
454
+
455
+ Mapping with a `Symbol` might not only be conveniently expressed as sending a message to each
456
+ element, sometimes a different meaning might be appropriate as in the example below:
457
+
458
+
459
+ ```ruby
460
+ map do | ele |
461
+ some_method ele
462
+ end
463
+ ```
464
+
465
+ A different helper can do this job without any ambiguity:
466
+
467
+
468
+ ```ruby
469
+ map( &applying( :some_method ) )
470
+ ```
471
+
472
+ ### Commonly Used Pattern Helpers
473
+
474
+ This group of helpers is just to avoid to rewrite lambdas you/one/whoever/I have written zillions of times. Here is a short list of examples
475
+ the API doc should give you enough information if you look for something specific.
476
+
477
+ #### Integer.sum
478
+
479
+ ```ruby
480
+ class Integer
481
+ def self.sum
482
+ ->(a, b){ a + b }
483
+ end
484
+ end
485
+ ```
486
+
487
+ #### Integer#inc
488
+
489
+ ```ruby
490
+ class Integer
491
+ alias_method :inc, :succ # should have used forward ;)
492
+ end
493
+ ```
data/lib/forwarder.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'forwardable'
2
+ require 'forwarder/params'
3
+
4
+ module Forwarder
5
+
6
+ # How forward works:
7
+ # The parameters are analyzied by the Params object by means of the `prepare_forward`
8
+ # method. The `prepare_forward` method makes have use of the Argument object which
9
+ # implements a query API for what the given arguments allow the forwarder to do.
10
+ # And eventually the `forward!` method realises the delegation
11
+ def forward *args, &blk
12
+ params = Forwarder::Params.new self
13
+ params.prepare_forward( *args, &blk )
14
+ params.forward!
15
+ end
16
+
17
+ def forward_all *args, &blk
18
+ opts = args.pop
19
+ args.each do | arg |
20
+ forward( arg, opts, &blk)
21
+ end
22
+ end
23
+ end # module Forwarder
@@ -0,0 +1,184 @@
1
+ require 'forwarder/evaller'
2
+
3
+ module Forwarder
4
+ class Arguments
5
+ attr_reader :args, :message, :target
6
+
7
+ def all?
8
+ !args? && !lambda? && @__all__
9
+ end
10
+
11
+ def after
12
+ @after ||= @params[:after]
13
+ end
14
+
15
+ def after?
16
+ after
17
+ end
18
+
19
+ def aop?
20
+ @__aop__ ||= !aop_values.empty?
21
+ end
22
+
23
+ def args?
24
+ !!args
25
+ end
26
+
27
+ def before
28
+ @before ||= @params[:before] || @params[:before_with_block]
29
+ end
30
+
31
+ def before?
32
+ before
33
+ end
34
+
35
+ def before_with_block?
36
+ !!@params[:before_with_block]
37
+ end
38
+
39
+
40
+ def chain?
41
+ @params[ :to_chain ]
42
+ end
43
+
44
+ def complete_args *args
45
+ (self.args || []) + args
46
+ end
47
+
48
+ def custom_target?
49
+ @params[:to_object]
50
+ end
51
+
52
+ # def delegatable?
53
+ # !aop? && !custom_target? && !all? && !chain? && !args && !lambda?
54
+ # end
55
+
56
+ def evaluable?
57
+ !lambda? &&
58
+ !aop? &&
59
+ ( !args || args.all?{|a| Evaller.evaluable? a } ) &&
60
+ ( !custom_target? || Evaller.evaluable?( custom_target? ) )
61
+ end
62
+
63
+ def lambda default=nil
64
+ lambda? || default
65
+ end
66
+
67
+ def lambda?
68
+ @lambda
69
+ end
70
+
71
+ def must_not_compile?
72
+ lambda? || aop? || custom_target?
73
+ end
74
+
75
+ # This is always nil unless we are a custom_target, in which case
76
+ # default is returned if target is :self, else target is returned
77
+ def object_target default
78
+ return unless custom_target?
79
+ target == :self ? default : target
80
+ end
81
+
82
+ def serialized_params
83
+ Evaller.serialize args
84
+ end
85
+
86
+ def to_hash?
87
+ @__to_hash__ ||= @params[ :to_hash ]
88
+ end
89
+
90
+ def translation alternative=nil, &blk
91
+ @params[ :as ].tap do | tltion |
92
+ break alternative unless tltion
93
+ break tltion unless blk
94
+ blk.( tltion )
95
+ end
96
+ end
97
+
98
+ private
99
+ def aop_values
100
+ @__aop_values__ ||= @params.values_at( :after, :before, :before_with_block ).compact
101
+ end
102
+
103
+ def check_for_incompatibilities!
104
+ raise ArgumentError, "cannot provide translations for forward_all" if @__all__ && translation
105
+ raise ArgumentError, "cannot provide arguments for forward_all" if @__all__ && args?
106
+ end
107
+
108
+ def initialize *args, &blk
109
+ @message = args.shift
110
+ raise ArgumentError, "need one message and a hash of kwd params, plus an optional block" unless args.size == 1 && args.first.is_a?( Hash )
111
+ @params = args.first
112
+ set_message
113
+ set_target
114
+ set_args blk
115
+ check_for_incompatibilities!
116
+ translate_to_hash
117
+ end
118
+
119
+ def set_args blk
120
+ set_lambda blk
121
+ hw = @params.has_key? :with
122
+ ha = @params.has_key? :with_ary
123
+ raise ArgumentError, "cannot use :with and :with_ary parameter" if hw && ha
124
+ unless to_hash?
125
+ set_args_normal if hw
126
+ set_args_ary if ha
127
+ end
128
+ end
129
+
130
+ def set_args_ary
131
+ @args = [ @params[:with_ary] ]
132
+ raise ArgumentError, ":with_ary needs an array parameter" unless Array === @args.first
133
+ end
134
+
135
+ def set_args_normal
136
+ case arg = @params[:with]
137
+ when Array
138
+ @args = arg.dup rescue arg
139
+ else
140
+ @args = [ (arg.dup rescue arg) ]
141
+ end
142
+ end
143
+
144
+ def set_message
145
+ case @message
146
+ when Array
147
+ @__all__ = true
148
+ end
149
+ end
150
+
151
+ def set_lambda blk
152
+
153
+ if use_block?
154
+ @after = blk if @params[:after] == :use_block
155
+ @before = blk if @params[:before] == :use_block || @params[:before_with_block] == :use_block
156
+ @lambda = @params[:with_block]
157
+ else
158
+ raise ArgumentError, "cannot use :with_block and a block" if
159
+ @params[:with_block] && blk
160
+
161
+ @lambda = @params.fetch :with_block, blk
162
+ end
163
+ end
164
+
165
+ def set_target
166
+ [:to, :to_chain, :to_object, :to_hash].each do | tgt_kwd |
167
+ tgt = @params[ tgt_kwd ]
168
+ next unless tgt
169
+
170
+ raise ArgumentError, "more than one target specified." if @target
171
+ @target = tgt
172
+ end
173
+ raise ArgumentError, "no target specified." unless @target
174
+ end
175
+
176
+ def translate_to_hash
177
+ return unless @params[:to_hash]
178
+ raise ArgumentError, "cannot provide arguments for to_hash:" if @params.has_key?( :with ) || @params.has_key?( :with_ary )
179
+ end
180
+ def use_block?
181
+ aop_values.include?( :use_block )
182
+ end
183
+ end # class Arguments
184
+ end # module Forwarder
@@ -0,0 +1,63 @@
1
+ require 'forwarder/evaller'
2
+ module Forwarder
3
+ # The compiler's responsability is to create a string representation of
4
+ # the delegation method, or to nil if no such string representation exists
5
+ # and delegation has to be done dynamically.
6
+ class Compiler
7
+ attr_reader :arguments
8
+
9
+
10
+ def compile
11
+ # Cannot compile because of intrinsic uncompilable traits of arguments
12
+ return if arguments.must_not_compile?
13
+
14
+ # To Hash can always compile
15
+ return compile_to_hash if arguments.to_hash?
16
+
17
+ # Cannot compile only because the arguments.args cannot be compiled
18
+ @compiled_args = Evaller.serialize arguments.args
19
+ return unless @compiled_args
20
+
21
+ # Can compile :)))
22
+ return compile_to_all if arguments.message.is_a? Array
23
+ compile_one
24
+ end
25
+
26
+ private
27
+
28
+ def compile_one
29
+ tltion = arguments.translation arguments.message
30
+ "def #{arguments.message} *args, &blk; " +
31
+ "#{self.class.compile_target arguments.target}.#{tltion}( " +
32
+ @compiled_args +
33
+ "*args, &blk ) end"
34
+ end
35
+
36
+ def compile_to_all
37
+ arguments.message.map{ |msg|
38
+ "def #{msg} *args, &blk; #{arguments.target}.#{msg}( *args, &blk ) end"
39
+ }.join("\n")
40
+ end
41
+
42
+ def compile_to_hash
43
+ target = arguments.to_hash?
44
+ target = target.join(".") if Array === target
45
+ [arguments.message]
46
+ .flatten
47
+ .map do | msg |
48
+ # N.B. that the expression between [] is always a Symbol
49
+ "def #{msg}; #{target}[ #{(arguments.translation||msg).to_sym.inspect} ] end"
50
+ end.join("\n")#.tap do |x| debugger end
51
+ end
52
+
53
+ def initialize args
54
+ @arguments = args
55
+ end
56
+
57
+ class << self
58
+ def compile_target target
59
+ [ target ].flatten.join( "." )
60
+ end
61
+ end
62
+ end # class Compiler
63
+ end # module Forwarder
@@ -0,0 +1,59 @@
1
+ module Forwarder
2
+ module Evaller extend self
3
+
4
+ NotSerializable = Class.new RuntimeError
5
+
6
+ def evaluable? an_object, cache={}
7
+ !!_serialize_one( an_object, cache )
8
+ rescue NotSerializable
9
+ false
10
+ end
11
+
12
+ def serialize args
13
+ return "" if args.nil? || args.empty?
14
+ serialize_without_arg_suffix( args ).join(", ") + ", "
15
+ rescue NotSerializable
16
+ nil
17
+ end
18
+
19
+ def serialize_without_arg_suffix args, cache={}
20
+ args.map{ | arg |
21
+ _serialize_one arg, cache
22
+ }
23
+ end
24
+
25
+ private
26
+ def _serialize_hash hsh, cache
27
+ hsh.inject [] do | r, (k, v) |
28
+ k = _serialize_one k, cache
29
+ v = _serialize_one v, cache
30
+ r << "#{k} => #{v}"
31
+ end.join( ", ")
32
+ end
33
+
34
+ def _serialize_one arg, cache
35
+ case arg
36
+ when String
37
+ "'" + arg + "'"
38
+ when Symbol, Fixnum, NilClass, FalseClass, TrueClass
39
+ arg.inspect
40
+ else
41
+ _serialize_object arg, cache
42
+ end
43
+ end
44
+
45
+ def _serialize_object arg, cache
46
+ oid = arg.object_id
47
+ raise NotSerializable if cache[oid]
48
+ cache[oid]=true
49
+ case arg
50
+ when Array
51
+ ["[ ", serialize_without_arg_suffix( arg, cache ).join(", "), " ]"].join
52
+ when Hash
53
+ [ "{ ", _serialize_hash( arg, cache ), " }" ].join
54
+ else
55
+ raise NotSerializable
56
+ end
57
+ end
58
+ end # module Evaller
59
+ end # module Forwarder
@@ -0,0 +1,118 @@
1
+ module Forwarder
2
+ # I am the workerbee defining the methods and stuff
3
+ class Meta
4
+
5
+ attr_reader :arguments, :forwardee
6
+
7
+ # TODO: Break AOP out of this so that we do not check @ runtime
8
+ def forward
9
+ if arguments.before_with_block?
10
+ forward_with_before_with_block
11
+ elsif arguments.aop?
12
+ forward_with_aop
13
+ else
14
+ forward_without_aop
15
+ end
16
+ end
17
+
18
+ def forward_chain
19
+ a = arguments
20
+ sr = symbolic_receiver
21
+ forwardee.module_eval do
22
+ define_method a.message do |*args, &blk|
23
+ if a.before_with_block?
24
+ args = Array(a.before.(*args,&blk)) if a.before_with_block?
25
+ blk = args.pop
26
+ elsif a.before?
27
+ args = instance_exec( *args, &a.before )
28
+ end
29
+
30
+ tgt = a.target.inject( self ){ |r, sym| sr.( r, sym ) }
31
+ tgt.send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) ).tap do | result |
32
+ break a.after.( result ) if a.after?
33
+ end
34
+ #.send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) )
35
+ end
36
+ end
37
+ end
38
+
39
+ def forward_object
40
+ a = arguments
41
+ forwardee.module_eval do
42
+ define_method a.message do |*args, &blk|
43
+ args = instance_exec( *args, &a.before ) if a.before?
44
+
45
+ a.object_target( self )
46
+ .send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) ).tap do | result |
47
+ break instance_exec( result, &a.after ) if a.after?
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ private
55
+
56
+ def forward_with_aop
57
+ a = arguments
58
+ sr = symbolic_receiver
59
+ forwardee.module_eval do
60
+ define_method a.message do |*args, &blk|
61
+ args = instance_exec( *args, &a.before ) if a.before?
62
+ sr
63
+ .( self, a.target )
64
+ .send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) ).tap do | result |
65
+ break instance_exec( result, &a.after ) if a.after?
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def forward_without_aop
72
+ a = arguments
73
+ sr = symbolic_receiver
74
+ forwardee.module_eval do
75
+ define_method a.message do |*args, &blk|
76
+ sr
77
+ .( self, a.target )
78
+ .send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( blk ) )
79
+ end
80
+ end
81
+ end
82
+
83
+ def forward_with_before_with_block
84
+ a = arguments
85
+ sr = symbolic_receiver
86
+ forwardee.module_eval do
87
+ define_method a.message do |*args, &blk|
88
+ args = Array(a.before.(*args, &blk))
89
+ new_blk = args.pop
90
+
91
+ sr
92
+ .( self, a.target )
93
+ .send( a.translation( a.message ), *a.complete_args(*args), &a.lambda( new_blk ) ).tap do | result |
94
+ break instance_exec( result, &a.after ) if a.after?
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ def initialize forwardee, arguments
102
+ @forwardee = forwardee
103
+ @arguments = arguments
104
+ end
105
+
106
+ def symbolic_receiver
107
+ @__symbolic_receiver__ = ->(rec, sym) do
108
+ case "#{sym}"
109
+ when /\A@/
110
+ rec.instance_variable_get sym
111
+ else
112
+ rec.send sym
113
+ end
114
+ end
115
+ end
116
+
117
+ end # class Meta
118
+ end # module Forwarder
@@ -0,0 +1,42 @@
1
+ require 'forwarder/arguments'
2
+ require 'forwarder/compiler'
3
+ require 'forwarder/meta'
4
+
5
+ module Forwarder
6
+ class Params
7
+ attr_reader :forwardee, :arguments
8
+ def forward!
9
+ compiled = compile_forward
10
+ if compiled
11
+ forwardee.module_eval compiled, __FILE__, __LINE__
12
+ else
13
+ general_delegate
14
+ end
15
+ end
16
+
17
+ def prepare_forward *args, &blk
18
+ @arguments = Arguments.new( *args, &blk )
19
+ end
20
+
21
+ private
22
+
23
+ def compile_forward
24
+ compiler = Compiler.new arguments
25
+ compiler.compile
26
+ end
27
+
28
+ def initialize forwardee
29
+ @forwardee = forwardee
30
+ end
31
+
32
+ def general_delegate
33
+ if arguments.chain?
34
+ Meta.new( forwardee, arguments ).forward_chain
35
+ elsif arguments.custom_target?
36
+ Meta.new( forwardee, arguments ).forward_object
37
+ else
38
+ Meta.new( forwardee, arguments ).forward
39
+ end
40
+ end
41
+ end # class Params
42
+ end # module Forwarder
@@ -0,0 +1,3 @@
1
+ module Forwarder
2
+ Version = '0.2.0'
3
+ end # module Forwarder
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forwarder2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Robert Dober
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ description: "Ruby's core Forwardable gets the job done(barely) and produces most
42
+ unreadable code. \n\n Forwarder2 not only is more readable, much more feature rich,
43
+ but also slightly faster, meaning you can use it without performance penalty.\n\n
44
+ \ Additional features include: providing arguments, (partially if needed), AOP and
45
+ custom forwarding to hashes\n "
46
+ email: robert.dober@gmail.com
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - lib/forwarder.rb
52
+ - lib/forwarder/arguments.rb
53
+ - lib/forwarder/evaller.rb
54
+ - lib/forwarder/compiler.rb
55
+ - lib/forwarder/version.rb
56
+ - lib/forwarder/meta.rb
57
+ - lib/forwarder/params.rb
58
+ - LICENSE
59
+ - README.md
60
+ homepage: https://github.com/RobertDober/Forwarder2
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: 2.0.0
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.1.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Delegation And AOP Filters For It
84
+ test_files: []