forwarder2 0.2.0

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.
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: []