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 +7 -0
- data/LICENSE +20 -0
- data/README.md +493 -0
- data/lib/forwarder.rb +23 -0
- data/lib/forwarder/arguments.rb +184 -0
- data/lib/forwarder/compiler.rb +63 -0
- data/lib/forwarder/evaller.rb +59 -0
- data/lib/forwarder/meta.rb +118 -0
- data/lib/forwarder/params.rb +42 -0
- data/lib/forwarder/version.rb +3 -0
- metadata +84 -0
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
|
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: []
|