ripar 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f44f23505f92820e31ac7f4b67ede9e07e209983
4
+ data.tar.gz: 6994a3df9208181c7613edeba903268fe1624df3
5
+ SHA512:
6
+ metadata.gz: c7387c6606f90ee3ea131064e2b9f0123d76bc7c7e8427a0a26f9d3f5ec7e3aea06ae64262f7adc1796b8615562346ca1e980b60a6dbc3b85e682cc16112546b
7
+ data.tar.gz: 24a3048c49f28abaa233ebeb22b3aba41b085f5711f65faf52e42101832da60fe35d545385058b15b3420f11ee5dfae0379a19067edd3e3ab606159ee0455e67
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 2.1.1@ripar --create
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ # needs bundler-1.3.4, and 1.5 is in gemspec
4
+ # and the project uses 2.0 features
5
+ #- 1.9.3
6
+ - 2.0.0
7
+ # not supported by travis. yet?
8
+ #- 2.1.1
9
+ script: bundle exec rspec spec
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
+ [![Gem Version](https://badge.fury.io/rb/ripar.png)](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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Ripar
2
+ VERSION = "0.0.1"
3
+ 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
@@ -0,0 +1,6 @@
1
+ # must be before coverage'd code is loaded
2
+ require 'simplecov'
3
+
4
+ SimpleCov.start do
5
+ add_filter '/spec/'
6
+ end
@@ -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