ripar 0.0.1
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/.gitignore +17 -0
- data/.rvmrc +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +128 -0
- data/Rakefile +1 -0
- data/lib/ripar.rb +18 -0
- data/lib/ripar/combinder.rb +107 -0
- data/lib/ripar/roller.rb +112 -0
- data/lib/ripar/version.rb +3 -0
- data/ripar.gemspec +28 -0
- data/spec/binding_wrapper_spec.rb +40 -0
- data/spec/combinder_spec.rb +163 -0
- data/spec/cov.rb +6 -0
- data/spec/roller_spec.rb +259 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f44f23505f92820e31ac7f4b67ede9e07e209983
|
4
|
+
data.tar.gz: 6994a3df9208181c7613edeba903268fe1624df3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c7387c6606f90ee3ea131064e2b9f0123d76bc7c7e8427a0a26f9d3f5ec7e3aea06ae64262f7adc1796b8615562346ca1e980b60a6dbc3b85e682cc16112546b
|
7
|
+
data.tar.gz: 24a3048c49f28abaa233ebeb22b3aba41b085f5711f65faf52e42101832da60fe35d545385058b15b3420f11ee5dfae0379a19067edd3e3ab606159ee0455e67
|
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 2.1.1@ripar --create
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
def from_gemrc
|
2
|
+
# auto-load from ~/.gemrc
|
3
|
+
home_gemrc = Pathname('~/.gemrc').expand_path
|
4
|
+
|
5
|
+
if home_gemrc.exist?
|
6
|
+
require 'yaml'
|
7
|
+
# use all the sources specified in .gemrc
|
8
|
+
YAML.load_file(home_gemrc)[:sources]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# use the gemrc source if defined or CANON is truthy in ENV
|
13
|
+
# otherwise just use the default
|
14
|
+
def preferred_sources
|
15
|
+
rv = from_gemrc unless eval(ENV['CANON']||'')
|
16
|
+
rv ||= []
|
17
|
+
rv << 'http://rubygems.org' if rv.empty?
|
18
|
+
rv
|
19
|
+
end
|
20
|
+
|
21
|
+
preferred_sources.each{|src| source src}
|
22
|
+
|
23
|
+
# Specify your gem's dependencies in ripar.gemspec
|
24
|
+
gemspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 John Anderson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
[](http://badge.fury.io/rb/ripar)
|
2
|
+
|
3
|
+
# Ripar
|
4
|
+
|
5
|
+
Think riparian. Think old man river, he jus' keep on rollin'. Think
|
6
|
+
[rive](http://etymonline.com/index.php?search=rive). Also river, reaver,
|
7
|
+
repair, reaper.
|
8
|
+
|
9
|
+
Tear chained method calls apart, put them in a block, and return the block value. eg
|
10
|
+
|
11
|
+
Before:
|
12
|
+
``` ruby
|
13
|
+
result = values.select{|x| x.name =~ /Jo/}.map{|x| x.count}.inject(0){|s,x| s + x}
|
14
|
+
```
|
15
|
+
|
16
|
+
After:
|
17
|
+
``` ruby
|
18
|
+
result = values.rive do
|
19
|
+
select{|x| x.name =~ /Jo/}
|
20
|
+
map{|x| x.count}
|
21
|
+
inject(0){|s,x| s + x}
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
This is also a little different to instance_eval, because the following will work:
|
26
|
+
|
27
|
+
``` ruby
|
28
|
+
outside_block_regex = /Wahoody-hey/
|
29
|
+
|
30
|
+
result = values.rive do
|
31
|
+
select{|x| x.name =~ outside_block_regex}
|
32
|
+
map{|x| x.count}
|
33
|
+
inject(0){|s,x| s + x}
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
**Warning** this can have some rare but weird side effects:
|
38
|
+
- will probably break on classes that have defined method_missing, but not respond_to_missing
|
39
|
+
- an outside variable with the same name as an inside method
|
40
|
+
taking in-place hash argument will cause a syntax error.
|
41
|
+
|
42
|
+
But you can obviate all of that by just using the safe syntax:
|
43
|
+
|
44
|
+
``` ruby
|
45
|
+
outside_block_regex = /Wahoody-hey/
|
46
|
+
|
47
|
+
result = values.rive do |vs|
|
48
|
+
vs.select{|x| x.name =~ outside_block_regex}
|
49
|
+
vs.map{|x| x.count}
|
50
|
+
vs.inject(0){|s,x| s + x}
|
51
|
+
end
|
52
|
+
```
|
53
|
+
|
54
|
+
Or using the magic disambiguaters:
|
55
|
+
|
56
|
+
``` ruby
|
57
|
+
select = /Wahoody-hey/
|
58
|
+
|
59
|
+
result = values.rive do
|
60
|
+
__inside__.select{|x| x.name =~ __outside__.select}
|
61
|
+
map{|x| x.count}
|
62
|
+
inject(0){|s,x| s + x}
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
## Installation
|
67
|
+
|
68
|
+
Add this line to your application's Gemfile:
|
69
|
+
|
70
|
+
gem 'ripar'
|
71
|
+
|
72
|
+
And then execute:
|
73
|
+
|
74
|
+
$ bundle
|
75
|
+
|
76
|
+
Or install it yourself as:
|
77
|
+
|
78
|
+
$ gem install ripar
|
79
|
+
|
80
|
+
## Usage
|
81
|
+
|
82
|
+
In your class
|
83
|
+
``` ruby
|
84
|
+
class YourChainableThing
|
85
|
+
include Ripar
|
86
|
+
end
|
87
|
+
|
88
|
+
yct = YourChainableThing.new.rive do
|
89
|
+
# operations
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
In a singleton
|
94
|
+
``` ruby
|
95
|
+
o = Object.new
|
96
|
+
class << o
|
97
|
+
include Ripar
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
Monkey-patch
|
102
|
+
``` ruby
|
103
|
+
class Object
|
104
|
+
include Ripar
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
The standard github pull request dance:
|
111
|
+
|
112
|
+
1. Fork it ( http://github.com/<my-github-username>/ripar/fork )
|
113
|
+
1. Create your feature branch (`git checkout -b my-new-feature`)
|
114
|
+
1. Commit your changes (`git commit -am 'Add some feature'`)
|
115
|
+
1. Push to the branch (`git push origin my-new-feature`)
|
116
|
+
1. Create new Pull Request
|
117
|
+
|
118
|
+
## PS
|
119
|
+
```ruby
|
120
|
+
Thing = Struct.new :name, :count
|
121
|
+
values = [
|
122
|
+
Thing.new('John', 20),
|
123
|
+
Thing.new('Joe', 7),
|
124
|
+
Thing.new('Paul', 3),
|
125
|
+
Thing.new('James', 3),
|
126
|
+
Thing.new('Wahoody-heydi-dude', 3.141527),
|
127
|
+
]
|
128
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/ripar.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "ripar/version"
|
2
|
+
require "ripar/roller"
|
3
|
+
|
4
|
+
# include this in a class/instance and put the calls that
|
5
|
+
# would have been chained in the block.
|
6
|
+
module Ripar
|
7
|
+
# return the final value
|
8
|
+
# short, unique, unlikely to clash with other methods in Object
|
9
|
+
# if you want to monkey-patch.
|
10
|
+
def rive( &block )
|
11
|
+
Roller.rive self, &block
|
12
|
+
end
|
13
|
+
|
14
|
+
# return the roller object containing the final value
|
15
|
+
def roller( &block )
|
16
|
+
Roller.new self, &block
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# This is the part that lets an instance_eval-style block access
|
2
|
+
# variables defined outside of it. Arguably that's a horrible idea, really.
|
3
|
+
#
|
4
|
+
# Implements method_missing to figure out which of binding.self and obj can handle the missing method.
|
5
|
+
# Can have somewhat weird side effect of making variables assigned to lambdas (or anything
|
6
|
+
# implementing call actually) directly callable.
|
7
|
+
#
|
8
|
+
# TODO @binding.eval('self') might define method_missing, so we may actually have to
|
9
|
+
# attempt to call the method to see if it's missing.
|
10
|
+
class Ripar::Combinder < BasicObject
|
11
|
+
# This should be a refinement
|
12
|
+
module BindingNiceness
|
13
|
+
def self
|
14
|
+
eval('self')
|
15
|
+
end
|
16
|
+
|
17
|
+
def local_variables
|
18
|
+
eval('local_variables')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize( obj, saved_binding )
|
23
|
+
@obj, @binding = obj, saved_binding
|
24
|
+
@binding.extend BindingNiceness
|
25
|
+
end
|
26
|
+
|
27
|
+
class AmbiguousMethod < ::RuntimeError; end
|
28
|
+
|
29
|
+
# long method, but we want to keep BasicObject really empty.
|
30
|
+
# TODO could split into private __methods
|
31
|
+
def method_missing( meth, *args, &blk )
|
32
|
+
if @obj.respond_to?( meth ) && (@binding.self.methods - ::Object.instance_methods).include?( meth )
|
33
|
+
begin
|
34
|
+
return @obj.__ambiguous_method__( @binding.self, meth, *args, &blk )
|
35
|
+
rescue ::NoMethodError => ex
|
36
|
+
::Kernel.raise AmbiguousMethod, "method :#{meth} exists on both #{@binding.self.inspect} (outside) and #{@obj.inspect} (inside) #{ex.message}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if @binding.local_variables.include?( meth )
|
41
|
+
# This branch is only necessary to do lambda calls with (),
|
42
|
+
# because variables in the binding are already, well, part of the binding.
|
43
|
+
# So they are picked up before the code ever calls method_missing
|
44
|
+
bound_value = @binding.eval meth.to_s
|
45
|
+
|
46
|
+
if bound_value.respond_to?( :call )
|
47
|
+
# It's a local variable, but it's been forced to come here by (). So call it.
|
48
|
+
bound_value.call(*args)
|
49
|
+
else
|
50
|
+
# assume that the user really wants to call the object's method
|
51
|
+
# rather than access the outside variable.
|
52
|
+
@obj.send meth, *args, &blk
|
53
|
+
end
|
54
|
+
elsif @binding.self.respond_to?( meth )
|
55
|
+
@binding.self.send meth, *args, &blk
|
56
|
+
else
|
57
|
+
@obj.send meth, *args, &blk
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def respond_to?( meth, include_all = false )
|
62
|
+
# ::Kernel.puts "Combinder#respond_to #{meth}"
|
63
|
+
# ::Kernel.puts "Combinder local variables #{@binding.local_variables}"
|
64
|
+
return @binding.local_variables.include?( meth ) || @binding.self.respond_to?( meth, include_all ) || @obj.respond_to?( meth, include_all )
|
65
|
+
end
|
66
|
+
|
67
|
+
# for disambiguating outside variables vs method calls, otherwise
|
68
|
+
# Ruby interpreter will find the outside variable name, and
|
69
|
+
# use that instead of the method call. You could also
|
70
|
+
# force the method call with (), but that is sometimes ugly.
|
71
|
+
def __inside__
|
72
|
+
@obj
|
73
|
+
end
|
74
|
+
|
75
|
+
# Forward method calls to bound variables
|
76
|
+
# Could probably just use Combinder here.
|
77
|
+
# TODO this should be accessing bound_self, right?
|
78
|
+
# Variables are accessed anyway by Ruby interpreter because
|
79
|
+
# They're in the binding. Not sure.
|
80
|
+
class BindingWrapper
|
81
|
+
def initialize( wrapped_binding )
|
82
|
+
@binding = wrapped_binding
|
83
|
+
@binding.extend BindingNiceness
|
84
|
+
end
|
85
|
+
|
86
|
+
def method_missing(meth, *args, &blk)
|
87
|
+
::Kernel.raise "outside variables can't take arguments" if args.size != 0
|
88
|
+
|
89
|
+
if @binding.local_variables.include?( meth )
|
90
|
+
@binding.eval meth.to_s
|
91
|
+
elsif @binding.self.respond_to? meth
|
92
|
+
@binding.self.send meth, *args, &blk
|
93
|
+
else
|
94
|
+
::Kernel.raise NoMethodError, "No such outside variable #{meth}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def respond_to_missing?( meth, include_all = false )
|
99
|
+
@binding.local_variables.include?(meth) || @binding.self.respond_to?(meth, include_all)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# access the outside of the block, ie its binding.
|
104
|
+
def __outside__
|
105
|
+
BindingWrapper.new @binding
|
106
|
+
end
|
107
|
+
end
|
data/lib/ripar/roller.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require "ripar/version"
|
2
|
+
require 'ripar/combinder.rb'
|
3
|
+
|
4
|
+
# Pass in the original chainable object
|
5
|
+
# method calls in block will be applied
|
6
|
+
# resulting value will be in riven
|
7
|
+
class Ripar::Roller < BasicObject
|
8
|
+
def initialize( original, &block )
|
9
|
+
@original = original
|
10
|
+
# clone is for protection or original - not strictly necessary?
|
11
|
+
@riven = original.clone
|
12
|
+
roll_block( &block ) if block
|
13
|
+
end
|
14
|
+
|
15
|
+
# Callback from Combinder.
|
16
|
+
#
|
17
|
+
# This happens when the outside self is_a?(original.class) already,
|
18
|
+
# and inside is the roller, which will both respond to the same set
|
19
|
+
# of methods. So we want the method calls to go to the roller inside the
|
20
|
+
# roller block, otherwise the current value of riven in the roller
|
21
|
+
# will end up being constantly reset to original. Which
|
22
|
+
# is exactly not the point.
|
23
|
+
def __ambiguous_method__( binding_self, meth, *args, &blk )
|
24
|
+
# TODO maybe this should just always default to the roller? I don't see much point
|
25
|
+
# in being fancier than that.
|
26
|
+
# ::Kernel.puts "__ambiguous_method__ #{meth} for #{binding_self.inspect} and #{@obj.inspect}"
|
27
|
+
if binding_self == @original
|
28
|
+
# send it to the roller
|
29
|
+
send meth, *args, &blk
|
30
|
+
else
|
31
|
+
# don't know what to do with it, so let Combinder raise an AmbiguousMethod
|
32
|
+
# TODO too much coupling because this class knows about Combinder internals.
|
33
|
+
raise ::NoMethodError, meth
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
attr_accessor :riven, :original
|
38
|
+
|
39
|
+
# this is so that we a roller is returned from a
|
40
|
+
# call, you can call roller on it again, and keep
|
41
|
+
# things rolling along.
|
42
|
+
def roller( &block )
|
43
|
+
if block
|
44
|
+
roll_block &block
|
45
|
+
else
|
46
|
+
self
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# instantiate the roller, pass it the block and
|
51
|
+
# immediately return the modified copy
|
52
|
+
def self.rive( original, &block )
|
53
|
+
new( original, &block ).riven
|
54
|
+
end
|
55
|
+
|
56
|
+
# Forward to riven, if the method exists.
|
57
|
+
def method_missing(meth, *args, &block)
|
58
|
+
if @riven.respond_to? meth
|
59
|
+
interim = @riven.send( meth, *args, &block )
|
60
|
+
# ::Kernel.puts "sending #{meth}(#{args.inspect}) to #{@riven.inspect}: #{interim}"
|
61
|
+
@riven = interim
|
62
|
+
else
|
63
|
+
super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# used by combinder, so must be defined, otherwise it perturbs method_missing
|
68
|
+
def send( meth, *args, &block )
|
69
|
+
method_missing( meth, *args, &block )
|
70
|
+
end
|
71
|
+
|
72
|
+
# used by combinder, so must be defined, otherwise it perturbs method_missing
|
73
|
+
# no point in using respond_to_missing?, because that's part of Object#respond_to,
|
74
|
+
# not BasicObject
|
75
|
+
def respond_to?( meth, include_all = false )
|
76
|
+
@riven.respond_to?(meth, include_all) || __methods__.include?(meth)
|
77
|
+
end
|
78
|
+
|
79
|
+
# make sure this BasicObject plays nicely in pry
|
80
|
+
def inspect
|
81
|
+
%Q{#<#{self.__class__} original: #{@original.inspect}, riven: #{@riven.inspect}>}
|
82
|
+
end
|
83
|
+
|
84
|
+
def pretty_inspect
|
85
|
+
inspect
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_s; inspect; end
|
89
|
+
|
90
|
+
# include useful methods from Kernel, but rename
|
91
|
+
define_method :__class__, ::Kernel.instance_method(:class)
|
92
|
+
define_method :__object_id__, ::Kernel.instance_method(:object_id)
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def roll_block( &block )
|
97
|
+
case block.arity
|
98
|
+
when 0
|
99
|
+
::Ripar::Combinder.new( self, block.binding ).instance_eval &block
|
100
|
+
when 1
|
101
|
+
yield self
|
102
|
+
else
|
103
|
+
::Kernel.raise "Don't know how to handle arity #{block.arity}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def __methods__
|
110
|
+
self.__class__.instance_methods
|
111
|
+
end
|
112
|
+
end
|
data/ripar.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ripar/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ripar"
|
8
|
+
spec.version = Ripar::VERSION
|
9
|
+
spec.authors = ["John Anderson"]
|
10
|
+
spec.email = ["panic@semiosix.com"]
|
11
|
+
spec.summary = %q{Chained methods cam be clunky. Use a block instead.}
|
12
|
+
spec.description = %q{Convert series of chained methods from . syntax to block syntax. Like instance_eval but with access to external scope.}
|
13
|
+
spec.homepage = "http://github.com/djellemah/ripar"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "pry"
|
25
|
+
spec.add_development_dependency "pry-debundle"
|
26
|
+
spec.add_development_dependency "pry-debugger"
|
27
|
+
spec.add_development_dependency "simplecov"
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require Pathname(__dir__) + '../lib/ripar.rb'
|
4
|
+
|
5
|
+
describe Ripar::Combinder::BindingWrapper do
|
6
|
+
describe '#method_missing' do
|
7
|
+
def flying_dutchman
|
8
|
+
'BigBoat'
|
9
|
+
end
|
10
|
+
|
11
|
+
subject do
|
12
|
+
unknown_soldier = 'SomeGuy'
|
13
|
+
Ripar::Combinder::BindingWrapper.new(binding)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'fails for args' do
|
17
|
+
->{subject.unknown_soldier('hello')}.should raise_error(/can't take arguments/)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'finds local variables' do
|
21
|
+
subject.respond_to?(:unknown_soldier).should be_true
|
22
|
+
subject.unknown_soldier.should == 'SomeGuy'
|
23
|
+
meth = subject.method(:unknown_soldier)
|
24
|
+
meth.call.should == 'SomeGuy'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'finds self methods' do
|
28
|
+
subject.respond_to?(:flying_dutchman).should be_true
|
29
|
+
subject.flying_dutchman.should == 'BigBoat'
|
30
|
+
meth = subject.method(:flying_dutchman)
|
31
|
+
meth.call.should == 'BigBoat'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'error for not found method/variable' do
|
35
|
+
subject.respond_to?(:marie_celeste).should be_false
|
36
|
+
->{subject.marie_celeste}.should raise_error(NoMethodError, /outside/)
|
37
|
+
->{subject.method(:marie_celeste)}.should raise_error(NameError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require Pathname(__dir__) + '../lib/ripar.rb'
|
4
|
+
|
5
|
+
describe Ripar::Combinder do
|
6
|
+
def an_object
|
7
|
+
@an_object ||= Object.new.tap do |obj|
|
8
|
+
class << obj
|
9
|
+
def flying_dutchman
|
10
|
+
'BigBoat'
|
11
|
+
end
|
12
|
+
|
13
|
+
def oops
|
14
|
+
'dead mudokons'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# otherwise rspec tries to create a Combinder and fails
|
21
|
+
subject{}
|
22
|
+
|
23
|
+
# Yes, you really have to set up combinder in example,
|
24
|
+
# so you can get the binding
|
25
|
+
|
26
|
+
describe 'ambiguous method' do
|
27
|
+
def oops
|
28
|
+
'more dead mudokons'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'raise on ambiguous method' do
|
32
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
33
|
+
->{combinder.instance_eval{ oops }}.should raise_error(Ripar::Combinder::AmbiguousMethod, /exists on both/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'callback on ambiguous method' do
|
37
|
+
an_object.stub :__ambiguous_method__ do |binding_self, meth, *args|
|
38
|
+
binding_self.object_id.should == self.object_id
|
39
|
+
meth.should == :oops
|
40
|
+
args.should == []
|
41
|
+
end
|
42
|
+
|
43
|
+
Ripar::Combinder.new(an_object, binding).instance_eval{oops}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'local variables' do
|
48
|
+
it 'gets value' do
|
49
|
+
unknown_soldier = 'SomeGuy'
|
50
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
51
|
+
combinder.instance_eval{ unknown_soldier }.should == 'SomeGuy'
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'respond_to as methods' do
|
55
|
+
unknown_soldier = 'SomeGuy'
|
56
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
57
|
+
combinder.instance_eval{ respond_to? :unknown_soldier }.should be_true
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'respond_to local variables as methods' do
|
61
|
+
unknown_soldier = 'SomeGuy'
|
62
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
63
|
+
|
64
|
+
combinder.instance_eval do
|
65
|
+
respond_to?(:unknown_soldier)
|
66
|
+
end.should be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'use respond_to_missing via method()' do
|
70
|
+
unknown_soldier = 'SomeGuy'
|
71
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
72
|
+
|
73
|
+
combinder.instance_eval do
|
74
|
+
meth = __outside__.method(:unknown_soldier)
|
75
|
+
meth.call
|
76
|
+
end.should == 'SomeGuy'
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'force to inside method call with ()' do
|
80
|
+
flying_dutchman ='LovesCheese'
|
81
|
+
|
82
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
83
|
+
|
84
|
+
# this is the local variable, above
|
85
|
+
combinder.instance_eval{ flying_dutchman }.should == 'LovesCheese'
|
86
|
+
|
87
|
+
# this is the method call on an_object
|
88
|
+
combinder.instance_eval{ flying_dutchman() }.should == 'BigBoat'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'self methods' do
|
93
|
+
it 'finds them' do
|
94
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
95
|
+
combinder.instance_eval{ respond_to?(:flying_dutchman) }.should be_true
|
96
|
+
combinder.instance_eval{ flying_dutchman }.should == 'BigBoat'
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'method() works' do
|
100
|
+
pending "this is hard to support. not sure if it's even necessary"
|
101
|
+
combinder.instance_eval{ method(:flying_dutchman) }.call.should == 'BigBoat'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe 'binding self methods' do
|
106
|
+
def hero
|
107
|
+
'missing, presumed dead'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'finds them' do
|
111
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
112
|
+
combinder.instance_eval{ respond_to?(:hero) }.should be_true
|
113
|
+
combinder.instance_eval{ hero }.should == 'missing, presumed dead'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe 'non-existent variables/methods' do
|
118
|
+
it 'error for not found method/variable' do
|
119
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
120
|
+
combinder.instance_eval{subject.respond_to?(:marie_celeste)}.should be_false
|
121
|
+
|
122
|
+
lambda do
|
123
|
+
combinder.instance_eval{marie_celeste}
|
124
|
+
end.should raise_error(NoMethodError)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'method for not found method/variable' do
|
128
|
+
pending 'ditto'
|
129
|
+
combinder.instance_eval do
|
130
|
+
->{method(:marie_celeste)}.should raise_error(NameError)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe '() for .call' do
|
136
|
+
it 'calls lambda' do
|
137
|
+
fn = ->(val){"#{val}, where are you?"}
|
138
|
+
|
139
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
140
|
+
combinder.instance_eval{fn(oops)}.should == 'dead mudokons, where are you?'
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'calls #call' do
|
144
|
+
obj = Object.new
|
145
|
+
class << obj
|
146
|
+
def call( finagle )
|
147
|
+
finagle * 2
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
152
|
+
combinder.instance_eval{obj(oops)}.should == 'dead mudokonsdead mudokons'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe '#__inside__' do
|
157
|
+
it 'allows access to inside method' do
|
158
|
+
combinder = Ripar::Combinder.new(an_object, binding)
|
159
|
+
combinder.instance_eval{__inside__}.should == an_object
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
data/spec/cov.rb
ADDED
data/spec/roller_spec.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require Pathname(__dir__) + '../lib/ripar.rb'
|
4
|
+
|
5
|
+
describe Ripar::Roller do
|
6
|
+
def collection
|
7
|
+
@collection ||= [1,2,3,4,5,6,7,8].tap do |ry|
|
8
|
+
class << ry
|
9
|
+
def multiply( factor: 2 )
|
10
|
+
map{|x| x * factor}
|
11
|
+
end
|
12
|
+
|
13
|
+
include Ripar
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# this deliberately doesn't have a ?
|
19
|
+
def even( arg )
|
20
|
+
arg % 2 == 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def seven
|
24
|
+
7
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns an instance immediately' do
|
28
|
+
rvs = collection.rive do
|
29
|
+
reverse
|
30
|
+
end
|
31
|
+
|
32
|
+
rvs.should_not be_respond_to(:__class__)
|
33
|
+
rvs.should be_a(Array)
|
34
|
+
collection.should == rvs.reverse
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'riven returns an instance' do
|
38
|
+
rlr = collection.roller
|
39
|
+
rlr.riven.should_not be_respond_to(:__class__)
|
40
|
+
rlr.riven.should be_a(collection.class)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'pretends to be the original class' do
|
44
|
+
rlr = collection.roller
|
45
|
+
rlr.should be_a(Array)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'takes a 1 arg block' do
|
49
|
+
rlr = collection.roller do |rlr|
|
50
|
+
rlr.original.should be_a(Array)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'applies a 1 arg block' do
|
55
|
+
evens = collection.rive do |vs|
|
56
|
+
vs.select{|x| even(x)}
|
57
|
+
end
|
58
|
+
|
59
|
+
evens.should be_a(Array)
|
60
|
+
evens.should == [2,4,6,8]
|
61
|
+
end
|
62
|
+
|
63
|
+
# And this is the really important one
|
64
|
+
it 'takes a 0 arg block' do
|
65
|
+
method(:even).should be_a(Method)
|
66
|
+
|
67
|
+
rlr = collection.roller do
|
68
|
+
select{|x| even(x)}
|
69
|
+
end
|
70
|
+
|
71
|
+
rlr.riven.should be_a(Array)
|
72
|
+
rlr.riven.should == [2,4,6,8]
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'fails for -1 arity' do
|
76
|
+
->{ collection.roller {|*args|} }.should raise_error(/arity -1/)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'fails for 2 arity' do
|
80
|
+
->{ collection.roller {|arg1,arg2|} }.should raise_error(/arity 2/)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'disambiguates outside variable and inside method call' do
|
84
|
+
multiply = 'go forth and'
|
85
|
+
rlr = collection.roller do
|
86
|
+
__inside__.multiply factor: 3
|
87
|
+
end
|
88
|
+
rlr.riven.should == collection.multiply( factor: 3 )
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'access outside variable when name clashes' do
|
92
|
+
reverse = 2
|
93
|
+
rlr = collection.roller do
|
94
|
+
select{|x| x < reverse }
|
95
|
+
end
|
96
|
+
rlr.riven.should == [1]
|
97
|
+
end
|
98
|
+
|
99
|
+
it '(...) will force inside method call and ignore outside local variable' do
|
100
|
+
delete = 'doggone'
|
101
|
+
rlr = collection.roller do
|
102
|
+
delete(2)
|
103
|
+
end
|
104
|
+
rlr.riven.should == 2
|
105
|
+
end
|
106
|
+
|
107
|
+
it '() will force inside method call' do
|
108
|
+
reverse = 'sdrawkcab'
|
109
|
+
|
110
|
+
rlr = collection.roller do
|
111
|
+
reverse()
|
112
|
+
end
|
113
|
+
rlr.riven.should == collection.reverse
|
114
|
+
|
115
|
+
rlr = collection.roller do
|
116
|
+
reverse
|
117
|
+
end
|
118
|
+
|
119
|
+
rlr.riven.should_not == collection.reverse
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'syntax error for outside variable and inside method call in-place hash syntax' do
|
123
|
+
multiply = 'go forth and'
|
124
|
+
rlr = collection.roller do
|
125
|
+
pending "Not possible for in-place hash syntax"
|
126
|
+
# This will cause a syntax error because the interpreter
|
127
|
+
# finds the outside variable, and we're trying to call a method.
|
128
|
+
# but only if we're using the in-place hash syntax
|
129
|
+
# multiply factor: 3
|
130
|
+
end
|
131
|
+
rlr.riven.should == [3,6,9,12,15,18,21,24]
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'in-place hash syntax with name clash ok with (...)' do
|
135
|
+
multiply = 'go forth and'
|
136
|
+
rlr = collection.roller do
|
137
|
+
multiply( factor: 3 )
|
138
|
+
end
|
139
|
+
rlr.riven.should == [3,6,9,12,15,18,21,24]
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'allows explicit access to outside variables' do
|
143
|
+
selection = 8
|
144
|
+
rlr = collection.roller do
|
145
|
+
select{|x| x == __outside__.selection}
|
146
|
+
end
|
147
|
+
rlr.riven.should == [8]
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'allows explicit access to outside methods' do
|
151
|
+
rlr = collection.roller do
|
152
|
+
select{|x| x == __outside__.seven}
|
153
|
+
end
|
154
|
+
rlr.riven.should == [7]
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'allows variable assignment inside' do
|
158
|
+
filter = 'hello'
|
159
|
+
rlr = collection.roller do
|
160
|
+
irl = __inside__
|
161
|
+
irl.map{|x| x-1}
|
162
|
+
end
|
163
|
+
rlr.riven.should == [0,1,2,3,4,5,6,7]
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'can directly call lambdas' do
|
167
|
+
fn = ->(x){x + 4}
|
168
|
+
rlr = collection.roller do
|
169
|
+
select{|x| x < fn(1)}
|
170
|
+
end
|
171
|
+
rlr.riven.should == [1,2,3,4]
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'can directly call outside callables' do
|
175
|
+
fn = Object.new.tap do |obj|
|
176
|
+
class << obj
|
177
|
+
def call(ignored)
|
178
|
+
3
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
rlr = collection.roller do
|
184
|
+
select{|x| x < fn(1)}
|
185
|
+
end
|
186
|
+
rlr.riven.should == [1,2]
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'can access outside lambdas' do
|
190
|
+
fn = ->(x){x.odd?}
|
191
|
+
rlr = collection.roller do
|
192
|
+
select &fn
|
193
|
+
end
|
194
|
+
rlr.riven.should == [1,3,5,7]
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'ambiguous method' do
|
198
|
+
def to_be_duplicated
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'handles non-nested ambiguity' do
|
202
|
+
class << collection
|
203
|
+
def to_be_duplicated
|
204
|
+
require 'pry'; binding.pry
|
205
|
+
flat_map{|x| [x,x]}
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
->{collection.roller{ to_be_duplicated }}.should raise_error(Ripar::Combinder::AmbiguousMethod)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'handles nested rollers' do
|
213
|
+
class << collection
|
214
|
+
def square_evens
|
215
|
+
roller do
|
216
|
+
select &:even?
|
217
|
+
map{|x| x ** 2}
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
collection.square_evens.riven.should == [4,16,36,64]
|
223
|
+
|
224
|
+
squares_doubled = collection.roller do
|
225
|
+
square_evens
|
226
|
+
self * 2
|
227
|
+
end
|
228
|
+
|
229
|
+
squares_doubled.inspect.should =~ /Roller/
|
230
|
+
squares_doubled.riven.should == [4,16,36,64] * 2
|
231
|
+
squares_doubled.roller{count}.should == 8
|
232
|
+
|
233
|
+
# TODO this should really be elsewhere
|
234
|
+
squares_doubled.riven.should == 8
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
describe '#roller' do
|
239
|
+
it 'returns self with no block' do
|
240
|
+
rlr = collection.roller
|
241
|
+
rlr.__class__.should == Ripar::Roller
|
242
|
+
rlr.__object_id__.should == rlr.roller.__object_id__
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe '#inspect' do
|
247
|
+
it 'has class name' do
|
248
|
+
collection.roller.inspect.should =~ /Ripar::Roller/
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'has original' do
|
252
|
+
collection.roller.original.should == collection
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'has riven' do
|
256
|
+
collection.roller.riven.object_id.should_not == collection.object_id
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ripar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Anderson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-debundle
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry-debugger
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Convert series of chained methods from . syntax to block syntax. Like
|
112
|
+
instance_eval but with access to external scope.
|
113
|
+
email:
|
114
|
+
- panic@semiosix.com
|
115
|
+
executables: []
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- ".gitignore"
|
120
|
+
- ".rvmrc"
|
121
|
+
- ".travis.yml"
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- lib/ripar.rb
|
127
|
+
- lib/ripar/combinder.rb
|
128
|
+
- lib/ripar/roller.rb
|
129
|
+
- lib/ripar/version.rb
|
130
|
+
- ripar.gemspec
|
131
|
+
- spec/binding_wrapper_spec.rb
|
132
|
+
- spec/combinder_spec.rb
|
133
|
+
- spec/cov.rb
|
134
|
+
- spec/roller_spec.rb
|
135
|
+
homepage: http://github.com/djellemah/ripar
|
136
|
+
licenses:
|
137
|
+
- MIT
|
138
|
+
metadata: {}
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
requirements: []
|
154
|
+
rubyforge_project:
|
155
|
+
rubygems_version: 2.2.2
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: Chained methods cam be clunky. Use a block instead.
|
159
|
+
test_files:
|
160
|
+
- spec/binding_wrapper_spec.rb
|
161
|
+
- spec/combinder_spec.rb
|
162
|
+
- spec/cov.rb
|
163
|
+
- spec/roller_spec.rb
|