bindable_block 0.0.5.1 → 0.0.7
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/README.md +42 -2
- data/README.mountain_berry_fields.md +44 -2
- data/bindable_block.gemspec +5 -1
- data/lib/bindable_block.rb +49 -48
- data/lib/bindable_block/arg_aligner.rb +30 -0
- data/lib/bindable_block/bound_block.rb +61 -0
- data/lib/bindable_block/instance_exec_b.rb +16 -0
- data/lib/bindable_block/version.rb +2 -2
- data/spec/bindable_block_spec.rb +364 -64
- metadata +35 -14
- data/Rakefile +0 -2
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b1c8b49ff2472deebf4c6d9e551166d037b1b8a
|
4
|
+
data.tar.gz: df827455f3cd67f49550b331849fcda641ca48d3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fe81548ae86dc946d2dbe42c3f21ecf9401877eabbec57a1da4183456dc46bc6d7a629f14b3be2c76bfeb47df1e253f39f49a163f388434157173612e8c4a653
|
7
|
+
data.tar.gz: 30b47e2b53ab657136bead810544a145b1d57b25235ff31256b7fe9458068319a01bd2821a47576de54b8ae79cad29f2fbd47532e8c2c37c0bf098b55b9a0112
|
data/README.md
CHANGED
@@ -1,16 +1,56 @@
|
|
1
1
|
# BindableBlock
|
2
2
|
|
3
|
-
`instance_exec` can't take block arguments. Get around that with BindableProc
|
3
|
+
`instance_exec` can't take block arguments. Get around that with BindableProc (also provides optional `BasicObject#instance_exec_b`)
|
4
|
+
|
5
|
+
If you understand that previous statement, then I probably can't dissuade you from using this.
|
6
|
+
If you don't, then a word of warning: This is probably the wrong solution.
|
7
|
+
`instance_exec` is probably the wrong solution too.
|
8
|
+
Metaprogramming is almost never merited.
|
9
|
+
It's lighting your way by setting yourself on fire.
|
10
|
+
Your problem will be simpler if you don't use it.
|
11
|
+
It will be more comprehensible.
|
12
|
+
You'll waste less time later trying to figure out how to get it to do what you want.
|
13
|
+
95% of the times I've used metaprogramming, I've later regretted it.
|
14
|
+
The whole use-case that led to the creation of this gem was wrong,
|
15
|
+
it was unnecessary, it added tremendous complexity to the implementation.
|
16
|
+
Think Rails moving from `find_by_name(name)` to `find_by(name: name)`,
|
17
|
+
and how much better that was.
|
18
|
+
|
19
|
+
BUT! If I still can't convince you, then read on:
|
20
|
+
|
4
21
|
|
5
22
|
```ruby
|
6
23
|
require 'bindable_block'
|
7
24
|
|
8
25
|
User = Struct.new :name
|
9
|
-
greeter = BindableBlock.new
|
26
|
+
greeter = BindableBlock.new { "Welcome, #{name}" }
|
10
27
|
greeter.bind(User.new "Josh").call # => "Welcome, Josh"
|
11
28
|
```
|
12
29
|
|
30
|
+
```ruby
|
31
|
+
require 'bindable_block/instance_exec_b'
|
32
|
+
|
33
|
+
# http://rdoc.info/stdlib/core/BasicObject:instance_exec
|
34
|
+
class KlassWithSecret
|
35
|
+
def initialize
|
36
|
+
@secret = 99
|
37
|
+
end
|
38
|
+
end
|
39
|
+
k = KlassWithSecret.new
|
40
|
+
sum = 10
|
41
|
+
sum_updater = lambda { |to_add| sum += to_add }
|
42
|
+
result = k.instance_exec_b(5, sum_updater) { |x, &b| b.call(@secret+x) } # => 114
|
43
|
+
sum # => 114
|
44
|
+
```
|
45
|
+
|
13
46
|
[Here](https://github.com/JoshCheek/surrogate/blob/eb1d7f98a148c032f6d3ef1d8df8b703386f286d/lib/surrogate/options.rb#L32-34) is an example.
|
47
|
+
Note that it is the old-style where users had to submit the class that could be bound to.
|
48
|
+
|
49
|
+
## Possible advances in usefulness
|
50
|
+
|
51
|
+
It just occurred to me that if we bound it to BasicObject
|
52
|
+
then it could be used on any class without the user needing to
|
53
|
+
specify which class they want to bind it to.
|
14
54
|
|
15
55
|
## Where the abstraction leaks
|
16
56
|
|
@@ -1,18 +1,60 @@
|
|
1
1
|
# BindableBlock
|
2
2
|
|
3
|
-
`instance_exec` can't take block arguments. Get around that with BindableProc
|
3
|
+
`instance_exec` can't take block arguments. Get around that with BindableProc (also provides optional `BasicObject#instance_exec_b`)
|
4
|
+
|
5
|
+
If you understand that previous statement, then I probably can't dissuade you from using this.
|
6
|
+
If you don't, then a word of warning: This is probably the wrong solution.
|
7
|
+
`instance_exec` is probably the wrong solution too.
|
8
|
+
Metaprogramming is almost never merited.
|
9
|
+
It's lighting your way by setting yourself on fire.
|
10
|
+
Your problem will be simpler if you don't use it.
|
11
|
+
It will be more comprehensible.
|
12
|
+
You'll waste less time later trying to figure out how to get it to do what you want.
|
13
|
+
95% of the times I've used metaprogramming, I've later regretted it.
|
14
|
+
The whole use-case that led to the creation of this gem was wrong,
|
15
|
+
it was unnecessary, it added tremendous complexity to the implementation.
|
16
|
+
Think Rails moving from `find_by_name(name)` to `find_by(name: name)`,
|
17
|
+
and how much better that was.
|
18
|
+
|
19
|
+
BUT! If I still can't convince you, then read on:
|
20
|
+
|
4
21
|
|
5
22
|
```ruby
|
6
23
|
<% test 'example', with: :magic_comments do %>
|
7
24
|
require 'bindable_block'
|
8
25
|
|
9
26
|
User = Struct.new :name
|
10
|
-
greeter = BindableBlock.new
|
27
|
+
greeter = BindableBlock.new { "Welcome, #{name}" }
|
11
28
|
greeter.bind(User.new "Josh").call # => "Welcome, Josh"
|
12
29
|
<% end %>
|
13
30
|
```
|
14
31
|
|
32
|
+
```ruby
|
33
|
+
<% test 'example', with :magic_comments do %>
|
34
|
+
require 'bindable_block/instance_exec_b'
|
35
|
+
|
36
|
+
# http://rdoc.info/stdlib/core/BasicObject:instance_exec
|
37
|
+
class KlassWithSecret
|
38
|
+
def initialize
|
39
|
+
@secret = 99
|
40
|
+
end
|
41
|
+
end
|
42
|
+
k = KlassWithSecret.new
|
43
|
+
sum = 10
|
44
|
+
sum_updater = lambda { |to_add| sum += to_add }
|
45
|
+
result = k.instance_exec_b(5, sum_updater) { |x, &b| b.call(@secret+x) } # => 114
|
46
|
+
sum # => 114
|
47
|
+
<% end %>
|
48
|
+
```
|
49
|
+
|
15
50
|
[Here](https://github.com/JoshCheek/surrogate/blob/eb1d7f98a148c032f6d3ef1d8df8b703386f286d/lib/surrogate/options.rb#L32-34) is an example.
|
51
|
+
Note that it is the old-style where users had to submit the class that could be bound to.
|
52
|
+
|
53
|
+
## Possible advances in usefulness
|
54
|
+
|
55
|
+
It just occurred to me that if we bound it to BasicObject
|
56
|
+
then it could be used on any class without the user needing to
|
57
|
+
specify which class they want to bind it to.
|
16
58
|
|
17
59
|
## Where the abstraction leaks
|
18
60
|
|
data/bindable_block.gemspec
CHANGED
@@ -4,7 +4,7 @@ require File.expand_path('../lib/bindable_block/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Josh Cheek"]
|
6
6
|
gem.email = ["josh.cheek@gmail.com"]
|
7
|
-
gem.description = %q{instance_exec can't pass block arguments through. Use a bindable block instead.}
|
7
|
+
gem.description = %q{instance_exec can't pass block arguments through. Use a bindable block instead (there is also an optional core extension instance_exec_b, which will mimic the instance_exec interface, but also allow you to pass the block).}
|
8
8
|
gem.summary = %q{Allows you to bind procs to instances of classes}
|
9
9
|
|
10
10
|
gem.homepage = "https://github.com/JoshCheek/bindable_block"
|
@@ -15,4 +15,8 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.name = "bindable_block"
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
gem.version = BindableBlock::VERSION
|
18
|
+
|
19
|
+
gem.required_ruby_version = '~> 2.1'
|
20
|
+
|
21
|
+
gem.add_development_dependency 'rspec', '>= 3.0.0', '~> 3.0'
|
18
22
|
end
|
data/lib/bindable_block.rb
CHANGED
@@ -1,69 +1,70 @@
|
|
1
1
|
require "bindable_block/version"
|
2
|
+
require 'bindable_block/bound_block'
|
2
3
|
|
3
|
-
class BindableBlock
|
4
|
+
class BindableBlock < Proc
|
4
5
|
|
5
6
|
# match args to arity, since instance_method has lambda properties
|
6
|
-
class ArgAligner
|
7
|
-
def initialize(args, instance_method)
|
8
|
-
@result, @args, @parameters = [], args, instance_method.parameters.map(&:first)
|
9
|
-
take num :req
|
10
|
-
take [num(:opt), args.size].min
|
11
|
-
take args.size if has? :rest
|
12
|
-
end
|
13
|
-
|
14
|
-
def call
|
15
|
-
result
|
16
|
-
end
|
17
7
|
|
18
|
-
|
19
|
-
|
20
|
-
attr_reader :args, :parameters, :result
|
21
|
-
|
22
|
-
def has?(type)
|
23
|
-
parameters.any? { |param| param == type }
|
24
|
-
end
|
25
|
-
|
26
|
-
def num(type)
|
27
|
-
parameters.count { |param| param == type }
|
28
|
-
end
|
29
|
-
|
30
|
-
def take(n)
|
31
|
-
n.times { result << args.shift }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
def initialize(klass, &block)
|
8
|
+
def initialize(klass=BasicObject, &block)
|
9
|
+
@klass = klass
|
37
10
|
@original_block = block
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
11
|
+
if curried_values = block.instance_variable_get(:@curried_values)
|
12
|
+
@original_block = curried_values[:original_block]
|
13
|
+
@curried_args = curried_values[:curried_args]
|
14
|
+
@uncurried_size = curried_values[:uncurried_size]
|
15
|
+
end
|
16
|
+
@instance_method = block_to_method klass, @original_block
|
42
17
|
end
|
43
18
|
|
44
|
-
attr_reader :instance_method, :original_block
|
45
|
-
|
46
19
|
def bind(target)
|
47
|
-
|
48
|
-
|
20
|
+
bound = BoundBlock.new @original_block, &@instance_method.bind(target)
|
21
|
+
if @curried_args then bound.curry(@uncurried_size)[*@curried_args]
|
22
|
+
else bound
|
49
23
|
end
|
50
24
|
end
|
51
25
|
|
52
26
|
def call(*args, &block)
|
53
|
-
original_block.call(*args, &block)
|
27
|
+
@original_block.call(*args, &block)
|
54
28
|
end
|
55
29
|
|
56
|
-
def
|
57
|
-
|
30
|
+
def arity
|
31
|
+
@original_block.arity
|
58
32
|
end
|
59
33
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
34
|
+
def curry(arity=nil)
|
35
|
+
arity ||= @instance_method.parameters.count { |type, _| type == :req }
|
36
|
+
original_block = @original_block # can't use the imeth, because it's unbound, so no curry
|
37
|
+
bindable_blk_class = self.class
|
38
|
+
klass = @klass
|
39
|
+
|
40
|
+
proc_maker = lambda do |curried_args, uncurried_size|
|
41
|
+
p = Proc.new do |*args, &block|
|
42
|
+
actual_args = curried_args + args
|
43
|
+
if uncurried_size <= actual_args.size
|
44
|
+
original_block.call(*actual_args, &block)
|
45
|
+
else
|
46
|
+
curried = proc_maker.call actual_args, uncurried_size
|
47
|
+
bindable_blk_class.new klass, &curried
|
48
|
+
end
|
49
|
+
end
|
50
|
+
p.instance_variable_set :@curried_values, {
|
51
|
+
original_block: original_block,
|
52
|
+
curried_args: curried_args,
|
53
|
+
uncurried_size: arity,
|
54
|
+
}
|
55
|
+
p
|
56
|
+
end
|
57
|
+
curried = proc_maker.call [], arity
|
58
|
+
BindableBlock.new klass, &curried
|
64
59
|
end
|
65
60
|
|
66
|
-
|
67
|
-
|
61
|
+
private
|
62
|
+
|
63
|
+
def block_to_method(klass, block)
|
64
|
+
temp_method_name = "bindable_block_#{Time.now.to_i}_#{$$}_#{rand 1_000_0000}"
|
65
|
+
@klass.__send__(:define_method, temp_method_name, &block)
|
66
|
+
@klass.instance_method(temp_method_name)
|
67
|
+
ensure
|
68
|
+
@klass.__send__(:remove_method, temp_method_name)
|
68
69
|
end
|
69
70
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class BindableBlock < Proc
|
2
|
+
class ArgAligner
|
3
|
+
def initialize(args, instance_method)
|
4
|
+
@result, @args, @parameters = [], args, instance_method.parameters.map(&:first)
|
5
|
+
take num :req
|
6
|
+
take [num(:opt), args.size].min
|
7
|
+
take args.size if has? :rest
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
result
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :args, :parameters, :result
|
17
|
+
|
18
|
+
def has?(type)
|
19
|
+
parameters.any? { |param| param == type }
|
20
|
+
end
|
21
|
+
|
22
|
+
def num(type)
|
23
|
+
parameters.count { |param| param == type }
|
24
|
+
end
|
25
|
+
|
26
|
+
def take(n)
|
27
|
+
n.times { result << args.shift }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'bindable_block/arg_aligner'
|
2
|
+
|
3
|
+
class BindableBlock < Proc
|
4
|
+
class BoundBlock < Proc
|
5
|
+
def initialize(original_block, &method)
|
6
|
+
f, ln, * = caller[2].split(':')
|
7
|
+
self.bound_file = f
|
8
|
+
self.bound_line_number = ln.to_i
|
9
|
+
self.original_block = original_block
|
10
|
+
self.method = method
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(*args, &block)
|
14
|
+
method.call(*align(args), &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def lambda?
|
18
|
+
original_block.lambda?
|
19
|
+
end
|
20
|
+
|
21
|
+
def bound_location
|
22
|
+
[bound_file.dup, bound_line_number]
|
23
|
+
end
|
24
|
+
|
25
|
+
def parameters
|
26
|
+
original_block.parameters
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
original_block.to_s.gsub(/^#<\w*/, "#<#{self.class.name}")
|
31
|
+
end
|
32
|
+
alias to_s inspect
|
33
|
+
|
34
|
+
|
35
|
+
def binding
|
36
|
+
raise NotImplementedError, <<-SADFACE.gsub(/^\s*/, '')
|
37
|
+
I legit tried to figure this out, and can't :(
|
38
|
+
|
39
|
+
* Can't just ask, it's private on most objects
|
40
|
+
* Can't use `__send__` b/c it's like "the binding at the top of the callstack", which would not be the binding of the obj you're invoking it on
|
41
|
+
* Can't `instance_eval { binding }`, because BasicObject doesn't have a binding method
|
42
|
+
* Can't `Kernel.instance_method(:binding).bind(obj).instance_eval { binding }` because if obj is a BasicObject, then Kernel isn't an ancestor and thus won't bind to it
|
43
|
+
* Tried all manner of adding temp methods, subclassing the singleton class, etc. In the end, I think it's just not doable.
|
44
|
+
|
45
|
+
This is your friendly reminder that whatever you're using this method for is probably bullshit.
|
46
|
+
SADFACE
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
attr_accessor :bound_file, :bound_line_number, :original_block, :method
|
52
|
+
|
53
|
+
def align(args)
|
54
|
+
if original_block.lambda?
|
55
|
+
args
|
56
|
+
else
|
57
|
+
ArgAligner.new(args, method).call
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bindable_block'
|
2
|
+
|
3
|
+
class BasicObject
|
4
|
+
def instance_exec_b(*args, argument_block, &instance_block)
|
5
|
+
if argument_block.nil?
|
6
|
+
instance_exec(*args, &instance_block)
|
7
|
+
elsif argument_block.respond_to?(:call) && argument_block.respond_to?(:to_proc)
|
8
|
+
::BindableBlock
|
9
|
+
.new(self.singleton_class, &instance_block)
|
10
|
+
.bind(self)
|
11
|
+
.call(*args, &argument_block)
|
12
|
+
else
|
13
|
+
::Kernel.raise ::ArgumentError, "Last argument to instance_exec_b must be blocky (respond to #call and #to_proc), or nil"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
class BindableBlock
|
2
|
-
VERSION = "0.0.
|
1
|
+
class BindableBlock < Proc
|
2
|
+
VERSION = "0.0.7"
|
3
3
|
end
|
data/spec/bindable_block_spec.rb
CHANGED
@@ -1,101 +1,401 @@
|
|
1
1
|
require 'bindable_block'
|
2
2
|
|
3
|
+
# TODO: when created with a lambda, args should match like lambda args
|
4
|
+
|
3
5
|
describe BindableBlock do
|
6
|
+
let(:name) { 'Unbound Name' }
|
4
7
|
let(:default_name) { "Carmen" }
|
5
8
|
let(:klass) { Struct.new :name }
|
6
9
|
let(:instance) { klass.new default_name }
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
+
# minitest style, b/c otherwise the levels of things being passed around gets to be a bit much
|
12
|
+
def assert_equal(a, b)
|
13
|
+
expect(b).to eq a
|
11
14
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def assert_same(a, b)
|
16
|
+
expect(b).to equal a
|
17
|
+
end
|
18
|
+
def assert(val)
|
19
|
+
expect(val).to be_truthy
|
17
20
|
end
|
21
|
+
def refute(val)
|
22
|
+
expect(val).to_not be_truthy
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
describe 'binding' do
|
27
|
+
it 'can be bound to any object' do
|
28
|
+
block = BindableBlock.new { self }
|
29
|
+
assert_same block.bind(instance).call, instance
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can be bound to a BasicObject' do
|
33
|
+
o = BasicObject.new
|
34
|
+
o.instance_eval { @a = 1 }
|
35
|
+
assert_equal 1, BindableBlock.new { @a }.bind(o).call
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can be rebound' do
|
39
|
+
block = BindableBlock.new { name }
|
40
|
+
assert_equal 'Josh', block.bind(klass.new 'Josh').call
|
41
|
+
assert_equal 'Mei', block.bind(klass.new 'Mei').call
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'can be rebound to instances of different classes' do
|
45
|
+
klass1 = Class.new { def m; 1; end }
|
46
|
+
klass2 = Class.new { def m; 2; end }
|
47
|
+
block = BindableBlock.new { m }
|
48
|
+
assert_equal 1, block.bind(klass1.new).call
|
49
|
+
assert_equal 2, block.bind(klass2.new).call
|
50
|
+
end
|
18
51
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
52
|
+
it 'can scope the class of objects it can be bound to' do
|
53
|
+
klass1 = Class.new
|
54
|
+
klass2 = Class.new
|
55
|
+
block = BindableBlock.new(klass1) {}
|
56
|
+
block.bind(klass1.new).call
|
57
|
+
expect { block.bind(klass2.new).call }.to raise_error TypeError, /instance of/
|
58
|
+
end
|
24
59
|
end
|
25
60
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
61
|
+
describe 'as a block' do
|
62
|
+
it 'can be passed in the block slot to methods and shit' do
|
63
|
+
doubler = lambda { |&block| block.call + '!' }
|
64
|
+
assert_equal 'Unbound Name!', doubler.call(&BindableBlock.new { name })
|
65
|
+
assert_equal 'Carmen!', doubler.call(&BindableBlock.new { name }.bind(instance))
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'can be invoked without being bound, in which case self is that of the scope it was defined in' do
|
69
|
+
def self.a() 1 end
|
70
|
+
@b = 2
|
71
|
+
c = 3
|
72
|
+
block = BindableBlock.new { |d, &e| a + @b + c + d + e.call }
|
73
|
+
assert_equal 15, block.call(4){5}
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'retains it\'s bindability after being passed through a method' do
|
77
|
+
block = BindableBlock.new { name }
|
78
|
+
def m(&bindable_block)
|
79
|
+
bindable_block.bind(instance).call
|
80
|
+
end
|
81
|
+
assert_equal default_name, m(&block)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'has a #bound_location to complement #source_location' do
|
85
|
+
unbound = BindableBlock.new { }
|
86
|
+
bound = unbound.bind(instance)
|
87
|
+
assert_equal bound.bound_location, [__FILE__, __LINE__-1]
|
88
|
+
end
|
89
|
+
|
90
|
+
specify '#bound_location isn\'t susceptible to mutation of returned value' do
|
91
|
+
bound = BindableBlock.new { }.bind(instance)
|
92
|
+
bound.bound_location.first.replace "" # mutate the string
|
93
|
+
bound.bound_location.replace [] # mutate the array
|
94
|
+
assert_equal bound.bound_location, [__FILE__, __LINE__-3]
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'Proc instance methods' do
|
98
|
+
let(:args_and_name) { BindableBlock.new { |arg| [arg, name] } }
|
99
|
+
|
100
|
+
example '#[]' do
|
101
|
+
assert_equal [1, 'Unbound Name'], args_and_name[1]
|
102
|
+
assert_equal [1, 'Carmen'], args_and_name.bind(instance)[1]
|
103
|
+
end
|
104
|
+
|
105
|
+
example '#===' do
|
106
|
+
assert_equal [1, 'Unbound Name'], args_and_name === 1
|
107
|
+
assert_equal [1, 'Carmen'], args_and_name.bind(instance) === 1
|
108
|
+
end
|
109
|
+
|
110
|
+
example '#yield' do
|
111
|
+
assert_equal [1, 'Unbound Name'], args_and_name.yield(1)
|
112
|
+
assert_equal [1, 'Carmen'], args_and_name.bind(instance).yield(1)
|
113
|
+
end
|
114
|
+
|
115
|
+
example '#arity' do
|
116
|
+
p = Proc.new { |a, b, c=1, d=2, *e, f| [a,b,c,d,e,f] }
|
117
|
+
b = BindableBlock.new(&p)
|
118
|
+
assert_equal p.arity, b.arity
|
119
|
+
assert_equal p.arity, b.bind(instance).arity
|
120
|
+
|
121
|
+
l = Proc.new { |a, b, c=1, d=2, *e, f| [a,b,c,d,e,f] }
|
122
|
+
b = BindableBlock.new(&l)
|
123
|
+
assert_equal l.arity, b.arity
|
124
|
+
assert_equal l.arity, b.bind(instance).arity
|
125
|
+
end
|
126
|
+
|
127
|
+
example '#binding' do
|
128
|
+
a = 1
|
129
|
+
b = BindableBlock.new { }
|
130
|
+
|
131
|
+
assert_equal 1, b.binding.eval('a')
|
132
|
+
assert_equal self, b.binding.eval('self')
|
133
|
+
|
134
|
+
pending "If anyone can solve this, I'll be so impressed"
|
135
|
+
assert_equal 1, b.bind(instance).binding.eval('a')
|
136
|
+
assert_equal instance, b.bind(instance).binding.eval('self')
|
137
|
+
end
|
138
|
+
|
139
|
+
example '#clone' do
|
140
|
+
assert_equal [1, 'Unbound Name'], args_and_name.clone.call(1)
|
141
|
+
assert_equal [1, 'Carmen'], args_and_name.bind(instance).clone.call(1)
|
142
|
+
end
|
143
|
+
|
144
|
+
example '#dup' do
|
145
|
+
assert_equal [1, 'Unbound Name'], args_and_name.dup.call(1)
|
146
|
+
assert_equal [1, 'Carmen'], args_and_name.bind(instance).dup.call(1)
|
147
|
+
end
|
148
|
+
|
149
|
+
example '#curry without being bound' do
|
150
|
+
b = BindableBlock.new { |a, b, c, &d| [a, b, c, d.call, name] }
|
151
|
+
four = lambda { 4 }
|
152
|
+
|
153
|
+
assert_equal [1, 2, 3, 4, 'Unbound Name'], b.curry[1, 2, 3, &four]
|
154
|
+
assert_equal [1, 2, 3, 4, 'Unbound Name'], b.curry[1][2][3, &four]
|
155
|
+
assert_equal [1, 2, 3, 4, 'Unbound Name'], b.curry[1, 2][3, &four]
|
156
|
+
end
|
157
|
+
|
158
|
+
example '#curry after being bound' do
|
159
|
+
b = BindableBlock.new { |a, b, c, &d| [a, b, c, d.call, name] }.bind(instance)
|
160
|
+
four = lambda { 4 }
|
161
|
+
assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1][2][3, &four]
|
162
|
+
assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1, 2][3, &four]
|
163
|
+
end
|
164
|
+
|
165
|
+
example '#curry before being bound' do
|
166
|
+
b = BindableBlock.new { |a, b, c, &d| [a, b, c, d.call, name] }
|
167
|
+
four = lambda { 4 }
|
168
|
+
assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1].bind(instance).curry[2][3, &four]
|
169
|
+
assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1, 2].bind(instance).curry[3, &four]
|
170
|
+
assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1][2].bind(instance).curry[3, &four]
|
171
|
+
assert_equal [1, 2, 3, 4, 'Carmen'], b.curry[1][2].bind(instance).curry[3, &four]
|
172
|
+
end
|
173
|
+
|
174
|
+
example '#hash' do
|
175
|
+
raise pending 'punting on this, b/c I really don\'t know what it should do here'
|
176
|
+
end
|
177
|
+
|
178
|
+
example '#lambda?' do
|
179
|
+
refute BindableBlock.new {}.lambda?
|
180
|
+
refute BindableBlock.new {}.bind(instance).lambda?
|
181
|
+
|
182
|
+
assert BindableBlock.new(&lambda {}).lambda?
|
183
|
+
assert BindableBlock.new(&lambda {}).bind(instance).lambda?
|
184
|
+
end
|
185
|
+
|
186
|
+
example '#parameters' do
|
187
|
+
b = BindableBlock.new { |a, b=1, *c, d, e:, **f, &g| }
|
188
|
+
assert_equal [[:opt, :a], [:opt, :b], [:rest, :c], [:opt, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.parameters
|
189
|
+
assert_equal [[:opt, :a], [:opt, :b], [:rest, :c], [:opt, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.bind(instance).parameters
|
190
|
+
|
191
|
+
b = BindableBlock.new(&lambda { |a, b=1, *c, d, e:, **f, &g| })
|
192
|
+
assert_equal [[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.parameters
|
193
|
+
assert_equal [[:req, :a], [:opt, :b], [:rest, :c], [:req, :d], [:keyreq, :e], [:keyrest, :f], [:block, :g]], b.bind(instance).parameters
|
194
|
+
end
|
195
|
+
|
196
|
+
example '#source_location' do
|
197
|
+
b, f, l = BindableBlock.new { }, __FILE__, __LINE__
|
198
|
+
assert_equal [f, l], b.source_location
|
199
|
+
assert_equal [f, l], b.bind(instance).source_location
|
200
|
+
end
|
201
|
+
|
202
|
+
example '#to_proc' do
|
203
|
+
unbound = BindableBlock.new {}
|
204
|
+
assert_same unbound, unbound.to_proc
|
205
|
+
|
206
|
+
bound = unbound.bind(instance)
|
207
|
+
assert_same bound, bound.to_proc
|
208
|
+
end
|
209
|
+
|
210
|
+
example '#to_s and #inspect have the source file and line' do
|
211
|
+
# proc
|
212
|
+
unbound, f, l = BindableBlock.new {}, __FILE__, __LINE__
|
213
|
+
expect(unbound.to_s).to include "#{f}:#{l}"
|
214
|
+
expect(unbound.to_s).to include "BindableBlock"
|
215
|
+
|
216
|
+
bound = unbound.bind(instance)
|
217
|
+
expect(bound.to_s).to include "#{f}:#{l}"
|
218
|
+
expect(bound.to_s).to include "BindableBlock::BoundBlock"
|
219
|
+
|
220
|
+
# lambda
|
221
|
+
unbound, f, l = BindableBlock.new(&lambda {}), __FILE__, __LINE__
|
222
|
+
expect(unbound.to_s).to include "#{f}:#{l}"
|
223
|
+
expect(unbound.to_s).to include "BindableBlock"
|
224
|
+
|
225
|
+
bound = unbound.bind(instance)
|
226
|
+
expect(bound.to_s).to include "#{f}:#{l}"
|
227
|
+
expect(bound.to_s).to include "BindableBlock::BoundBlock"
|
228
|
+
end
|
229
|
+
|
230
|
+
specify '#to_s and #inspect should not have lambda in them' do
|
231
|
+
# proc
|
232
|
+
unbound = BindableBlock.new {}
|
233
|
+
expect(unbound.to_s).to_not include 'lambda'
|
234
|
+
expect(unbound.inspect).to_not include 'lambda'
|
235
|
+
|
236
|
+
bound = unbound.bind instance
|
237
|
+
expect(bound.to_s).to_not include 'lambda'
|
238
|
+
expect(bound.inspect).to_not include 'lambda'
|
239
|
+
|
240
|
+
# lambda
|
241
|
+
unbound = BindableBlock.new(&lambda {})
|
242
|
+
expect(unbound.to_s).to include 'lambda'
|
243
|
+
expect(unbound.inspect).to include 'lambda'
|
244
|
+
|
245
|
+
bound = unbound.bind instance
|
246
|
+
expect(bound.to_s).to include 'lambda'
|
247
|
+
expect(bound.inspect).to include 'lambda'
|
248
|
+
end
|
249
|
+
end
|
39
250
|
end
|
40
251
|
|
252
|
+
|
41
253
|
describe 'arguments' do
|
254
|
+
def assert_same_error(b1, b2)
|
255
|
+
e1 = b1.call rescue $!
|
256
|
+
e2 = b2.call rescue $!
|
257
|
+
expect(e1).to be_an Exception
|
258
|
+
expect(e2).to be_an Exception
|
259
|
+
assert_equal e1.class, e2.class
|
260
|
+
assert_equal e1.message, e2.message
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'matches them lambda-style if initialized with a lambda' do
|
264
|
+
l = lambda { |a| a }
|
265
|
+
block = BindableBlock.new(&l)
|
266
|
+
assert_equal 1, block.call(1)
|
267
|
+
assert_equal 1, block.bind(instance).call(1)
|
268
|
+
assert_same_error lambda { block.call }, lambda { l.call }
|
269
|
+
assert_same_error lambda { block.call 1, 2 }, lambda { l.call 1, 2 }
|
270
|
+
assert_same_error lambda { block.bind(instance).call }, lambda { l.call }
|
271
|
+
assert_same_error lambda { block.bind(instance).call 1, 2 }, lambda { l.call 1, 2 }
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'matches them proc-style if initialized with a proc' do
|
275
|
+
p = Proc.new { |a| a }
|
276
|
+
block = BindableBlock.new(&p)
|
277
|
+
assert_equal 1, block.call(1)
|
278
|
+
assert_equal p.call, block.call
|
279
|
+
assert_equal p.call(1), block.call(1)
|
280
|
+
assert_equal p.call(1, 2), block.call(1, 2)
|
281
|
+
|
282
|
+
assert_equal 1, block.bind(instance).call(1)
|
283
|
+
assert_equal p.call, block.bind(instance).call
|
284
|
+
assert_equal p.call(1), block.bind(instance).call(1)
|
285
|
+
assert_equal p.call(1, 2), block.bind(instance).call(1, 2)
|
286
|
+
end
|
287
|
+
|
42
288
|
it 'can take a block' do
|
43
|
-
block = BindableBlock.new
|
44
|
-
|
45
|
-
block.call(1){2}
|
289
|
+
block = BindableBlock.new { |a, &b| [a, (b&&b.call)] }.bind(instance)
|
290
|
+
assert_equal [1, nil], block.call(1)
|
291
|
+
assert_equal [1, 2], block.call(1){2}
|
46
292
|
end
|
47
293
|
|
48
294
|
specify "when given ordinal arguments at the start, it doesn't care about arity" do
|
49
|
-
block = BindableBlock.new
|
50
|
-
|
51
|
-
block.call(1)
|
52
|
-
block.call(1, 2)
|
295
|
+
block = BindableBlock.new { |a| [a] }.bind(instance)
|
296
|
+
assert_equal [nil], block.call
|
297
|
+
assert_equal [1], block.call(1)
|
298
|
+
assert_equal [1], block.call(1, 2)
|
53
299
|
end
|
54
300
|
|
55
301
|
specify 'when given optional args, it matches them up correctly' do
|
56
|
-
block = BindableBlock.new
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
302
|
+
block = BindableBlock.new { |a, b=1, c=2| [a, b, c] }.bind(instance)
|
303
|
+
assert_equal [nil, 1, 2] , block.call
|
304
|
+
assert_equal [:a, 1, 2] , block.call(:a)
|
305
|
+
assert_equal [:a, :b, 2] , block.call(:a, :b)
|
306
|
+
assert_equal [:a, :b, :c] , block.call(:a, :b, :c)
|
307
|
+
assert_equal [:a, :b, :c] , block.call(:a, :b, :c, :d)
|
62
308
|
end
|
63
309
|
|
64
310
|
specify 'splat acts as a catch all' do
|
65
|
-
block = BindableBlock.new
|
66
|
-
|
67
|
-
|
68
|
-
block.call(1, 2)
|
69
|
-
|
311
|
+
block = BindableBlock.new { |a, *rest| [a, rest] }.bind(instance)
|
312
|
+
assert_equal [nil, []] , block.call
|
313
|
+
assert_equal [1, []] , block.call(1)
|
314
|
+
assert_equal [1, [2]] , block.call(1, 2)
|
315
|
+
assert_equal [1, [2, 3]] , block.call(1, 2, 3)
|
70
316
|
end
|
71
317
|
|
72
318
|
specify "when given ordinal arguments at the end, it doesn't care about arity" do
|
73
|
-
block = BindableBlock.new
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
319
|
+
block = BindableBlock.new { |*a, b, &c| [a, b] }.bind(instance)
|
320
|
+
assert_equal [[], nil] , block.call
|
321
|
+
assert_equal [[], 1] , block.call(1)
|
322
|
+
assert_equal [[1], 2] , block.call(1,2)
|
323
|
+
assert_equal [[1, 2], 3] , block.call(1,2,3)
|
78
324
|
|
79
|
-
block = BindableBlock.new
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
325
|
+
block = BindableBlock.new { |a=:a, b, c| [a, b, c] }.bind(instance)
|
326
|
+
assert_equal [:a, nil, nil] , block.call
|
327
|
+
assert_equal [:a, 1, nil] , block.call(1)
|
328
|
+
assert_equal [:a, 1, 2] , block.call(1, 2)
|
329
|
+
assert_equal [1, 2, 3] , block.call(1, 2, 3)
|
330
|
+
assert_equal [1, 2, 3] , block.call(1, 2, 3, 4)
|
85
331
|
end
|
86
332
|
|
87
333
|
|
88
334
|
specify "when given complex arguments, it matches that shit up right" do
|
89
335
|
proc = Proc.new { |a, b, c=1, d=2, *e, f| [a,b,c,d,e,f] }
|
90
|
-
block = BindableBlock.new(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
336
|
+
block = BindableBlock.new(&proc).bind(instance)
|
337
|
+
assert_equal proc.call , block.call
|
338
|
+
assert_equal proc.call(:a) , block.call(:a)
|
339
|
+
assert_equal proc.call(:a,:b) , block.call(:a,:b)
|
340
|
+
assert_equal proc.call(:a,:b,:c) , block.call(:a,:b,:c)
|
341
|
+
assert_equal proc.call(:a,:b,:c,:d) , block.call(:a,:b,:c,:d)
|
342
|
+
assert_equal proc.call(:a,:b,:c,:d,:e) , block.call(:a,:b,:c,:d,:e)
|
343
|
+
assert_equal proc.call(:a,:b,:c,:d,:e,:f) , block.call(:a,:b,:c,:d,:e,:f)
|
344
|
+
assert_equal proc.call(:a,:b,:c,:d,:e,:f,:g) , block.call(:a,:b,:c,:d,:e,:f,:g)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe 'instance_exec_b' do
|
349
|
+
it 'is only available if you require the file explicitly, since it monkey patches BasicObject' do
|
350
|
+
expect { method :instance_exec_b }.to raise_error NameError
|
351
|
+
require 'bindable_block/instance_exec_b'
|
352
|
+
method :instance_exec_b
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'is on wherever instance_exec is on (BasicObject)' do
|
356
|
+
expect(method(:instance_exec_b).owner).to equal method(:instance_exec).owner
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'provides an instance_exec like method, whose last argument is a block' do
|
360
|
+
a, b = 1, 2
|
361
|
+
result = instance.instance_exec_b(a, lambda { b }) { |ordinal, &block| "#{name}#{ordinal}#{block.call}" }
|
362
|
+
assert_equal "Carmen12", result
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'passes no block if the last param is nil' do
|
366
|
+
a, b = 1, 2
|
367
|
+
result = instance.instance_exec_b(a, nil) { |ordinal, &block| "#{name}#{ordinal}#{!!block}" }
|
368
|
+
assert_equal "Carmen1false", result
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'the block can be blocky things (can be called and put into the block slot), otherwise raises an ArgumentError' do
|
372
|
+
expect { instance.instance_exec_b(1) { |*| } }.to raise_error ArgumentError, /block/
|
373
|
+
expect { instance.instance_exec_b(1, :abc) { |*| } }.to raise_error ArgumentError, /block/ # has to_proc, but not call
|
374
|
+
o = Object.new
|
375
|
+
def o.call(*)
|
376
|
+
2
|
377
|
+
end
|
378
|
+
expect { instance.instance_exec_b(1, o) { |*| } }.to raise_error ArgumentError, /block/ # has call, but not to_proc
|
379
|
+
def o.to_proc
|
380
|
+
Proc.new { 3 }
|
381
|
+
end
|
382
|
+
assert_equal 4, instance.instance_exec_b(1, o) { |n, &b| n + b.call }
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'doesn\'t need ordinal args' do
|
386
|
+
assert instance.instance_exec_b(lambda {}) { |&block| !!block }
|
387
|
+
refute instance.instance_exec_b(nil) { |&block| !!block }
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'Doesn\'t define methods on the class' do
|
391
|
+
class BasicObject
|
392
|
+
old_method_added = singleton_class.method(:method_added)
|
393
|
+
def self.method_added(method_name)
|
394
|
+
raise "METHOD ADDED: #{method_name}"
|
395
|
+
end
|
396
|
+
instance_exec_b(lambda {}) { }
|
397
|
+
define_singleton_method(:method_added, &old_method_added)
|
398
|
+
end
|
99
399
|
end
|
100
400
|
end
|
101
401
|
end
|
metadata
CHANGED
@@ -1,57 +1,78 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bindable_block
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.7
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Josh Cheek
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
11
|
+
date: 2014-07-10 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: 3.0.0
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.0
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
14
33
|
description: instance_exec can't pass block arguments through. Use a bindable block
|
15
|
-
instead
|
34
|
+
instead (there is also an optional core extension instance_exec_b, which will mimic
|
35
|
+
the instance_exec interface, but also allow you to pass the block).
|
16
36
|
email:
|
17
37
|
- josh.cheek@gmail.com
|
18
38
|
executables: []
|
19
39
|
extensions: []
|
20
40
|
extra_rdoc_files: []
|
21
41
|
files:
|
22
|
-
- .gitignore
|
42
|
+
- ".gitignore"
|
23
43
|
- Gemfile
|
24
44
|
- LICENSE
|
25
45
|
- README.md
|
26
46
|
- README.mountain_berry_fields.md
|
27
|
-
- Rakefile
|
28
47
|
- bindable_block.gemspec
|
29
48
|
- lib/bindable_block.rb
|
49
|
+
- lib/bindable_block/arg_aligner.rb
|
50
|
+
- lib/bindable_block/bound_block.rb
|
51
|
+
- lib/bindable_block/instance_exec_b.rb
|
30
52
|
- lib/bindable_block/version.rb
|
31
53
|
- spec/bindable_block_spec.rb
|
32
54
|
homepage: https://github.com/JoshCheek/bindable_block
|
33
55
|
licenses: []
|
56
|
+
metadata: {}
|
34
57
|
post_install_message:
|
35
58
|
rdoc_options: []
|
36
59
|
require_paths:
|
37
60
|
- lib
|
38
61
|
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
-
none: false
|
40
62
|
requirements:
|
41
|
-
- -
|
63
|
+
- - "~>"
|
42
64
|
- !ruby/object:Gem::Version
|
43
|
-
version: '
|
65
|
+
version: '2.1'
|
44
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
-
none: false
|
46
67
|
requirements:
|
47
|
-
- -
|
68
|
+
- - ">="
|
48
69
|
- !ruby/object:Gem::Version
|
49
70
|
version: '0'
|
50
71
|
requirements: []
|
51
72
|
rubyforge_project:
|
52
|
-
rubygems_version:
|
73
|
+
rubygems_version: 2.2.2
|
53
74
|
signing_key:
|
54
|
-
specification_version:
|
75
|
+
specification_version: 4
|
55
76
|
summary: Allows you to bind procs to instances of classes
|
56
77
|
test_files:
|
57
78
|
- spec/bindable_block_spec.rb
|
data/Rakefile
DELETED