local_eval 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +0 -0
- data/README.markdown +78 -0
- data/Rakefile +58 -0
- data/lib/local_eval/version.rb +3 -0
- data/lib/local_eval/version_flymake.rb +3 -0
- data/lib/local_eval.rb +71 -0
- data/test/helper1.rb +73 -0
- data/test/test.rb +142 -0
- metadata +100 -0
data/CHANGELOG
ADDED
File without changes
|
data/README.markdown
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
LocalEval
|
2
|
+
=============
|
3
|
+
|
4
|
+
(C) John Mair (banisterfiend) 2010
|
5
|
+
|
6
|
+
_instance\_eval without changing self_
|
7
|
+
|
8
|
+
Using `local_eval` you get most of the benefits of `instance_eval`
|
9
|
+
with very few of the drawbacks: local instance variables are
|
10
|
+
accessible within the block and method lookups are directed to the
|
11
|
+
correct receiver. Unlike `instance_eval` the `self` of the
|
12
|
+
block is not changed.
|
13
|
+
|
14
|
+
LocalEval provides the `local_eval` and `local_eval_with` methods.
|
15
|
+
|
16
|
+
* Install the [gem](https://rubygems.org/gems/local_eval): `gem install local_eval`
|
17
|
+
* Read the [documentation](http://rdoc.info/github/banister/local_eval/master/file/README.markdown)
|
18
|
+
* See the [source code](http://github.com/banister/local_eval)
|
19
|
+
|
20
|
+
example: local_eval
|
21
|
+
--------------------------
|
22
|
+
|
23
|
+
Using `local_eval` we can add the functionality of the receiver to the
|
24
|
+
block:
|
25
|
+
|
26
|
+
|
27
|
+
class C
|
28
|
+
def hello(name)
|
29
|
+
"hello #{name}!"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
o = C.new
|
34
|
+
|
35
|
+
@name = "John"
|
36
|
+
o.local_eval { hello(@name) } #=> "hello John!"
|
37
|
+
|
38
|
+
example: capture
|
39
|
+
--------------------
|
40
|
+
|
41
|
+
`gen_extend` lets us mix objects into objects:
|
42
|
+
|
43
|
+
o = Object.new
|
44
|
+
class << o
|
45
|
+
def bye
|
46
|
+
:bye
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
n = Object.new
|
51
|
+
n.gen_extend o
|
52
|
+
n.bye #=> :bye
|
53
|
+
|
54
|
+
How it works
|
55
|
+
--------------
|
56
|
+
|
57
|
+
Object2module simply removes the check for `T_MODULE` from `rb_include_module()`
|
58
|
+
|
59
|
+
Companion Libraries
|
60
|
+
--------------------
|
61
|
+
|
62
|
+
Remix is one of a series of experimental libraries that mess with
|
63
|
+
the internals of Ruby to bring new and interesting functionality to
|
64
|
+
the language, see also:
|
65
|
+
|
66
|
+
* [Remix](http://github.com/banister/remix) - Makes ancestor chains read/write
|
67
|
+
* [Include Complete](http://github.com/banister/include_complete) - Brings in
|
68
|
+
module singleton classes during an include. No more ugly ClassMethods and included() hook hacks.
|
69
|
+
* [Prepend](http://github.com/banister/prepend) - Prepends modules in front of a class; so method lookup starts with the module
|
70
|
+
* [GenEval](http://github.com/banister/gen_eval) - A strange new breed of instance_eval
|
71
|
+
|
72
|
+
Contact
|
73
|
+
-------
|
74
|
+
|
75
|
+
Problems or questions contact me at [github](http://github.com/banister)
|
76
|
+
|
77
|
+
|
78
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
dlext = Config::CONFIG['DLEXT']
|
2
|
+
direc = File.dirname(__FILE__)
|
3
|
+
|
4
|
+
require 'rake/clean'
|
5
|
+
require 'rake/gempackagetask'
|
6
|
+
require "#{direc}/lib/local_eval/version"
|
7
|
+
|
8
|
+
CLOBBER.include("**/*.#{dlext}", "**/*~", "**/*#*", "**/*.log", "**/*.o")
|
9
|
+
CLEAN.include("ext/**/*.#{dlext}", "ext/**/*.log", "ext/**/*.o",
|
10
|
+
"ext/**/*~", "ext/**/*#*", "ext/**/*.obj", "ext/**/*.def", "ext/**/*.pdb")
|
11
|
+
|
12
|
+
def apply_spec_defaults(s)
|
13
|
+
s.name = "local_eval"
|
14
|
+
s.summary = "instance_eval without changing self"
|
15
|
+
s.version = LocalEval::VERSION
|
16
|
+
s.date = Time.now.strftime '%Y-%m-%d'
|
17
|
+
s.author = "John Mair (banisterfiend)"
|
18
|
+
s.email = 'jrmair@gmail.com'
|
19
|
+
s.description = s.summary
|
20
|
+
s.require_path = 'lib'
|
21
|
+
s.add_dependency("remix",">=0.4.8")
|
22
|
+
s.add_dependency("object2module",">=0.4.3")
|
23
|
+
s.homepage = "http://banisterfiend.wordpress.com"
|
24
|
+
s.has_rdoc = 'yard'
|
25
|
+
s.files = Dir["ext/**/extconf.rb", "ext/**/*.h", "ext/**/*.c", "lib/**/*.rb",
|
26
|
+
"test/*.rb", "CHANGELOG", "README.markdown", "Rakefile"]
|
27
|
+
end
|
28
|
+
|
29
|
+
task :test do
|
30
|
+
sh "bacon -k #{direc}/test/test.rb"
|
31
|
+
end
|
32
|
+
|
33
|
+
namespace :ruby do
|
34
|
+
spec = Gem::Specification.new do |s|
|
35
|
+
apply_spec_defaults(s)
|
36
|
+
s.platform = Gem::Platform::RUBY
|
37
|
+
end
|
38
|
+
|
39
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
40
|
+
pkg.need_zip = false
|
41
|
+
pkg.need_tar = false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
desc "build all platform gems at once"
|
46
|
+
task :gems => [:rmgems, "ruby:gem"]
|
47
|
+
|
48
|
+
desc "remove all platform gems"
|
49
|
+
task :rmgems => ["ruby:clobber_package"]
|
50
|
+
|
51
|
+
desc "build and push latest gems"
|
52
|
+
task :pushgems => :gems do
|
53
|
+
chdir("#{direc}/pkg") do
|
54
|
+
Dir["*.gem"].each do |gemfile|
|
55
|
+
sh "gem push #{gemfile}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/local_eval.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
direc = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require "#{direc}/local_eval/version"
|
4
|
+
require 'remix'
|
5
|
+
require 'object2module'
|
6
|
+
|
7
|
+
module LocalEval
|
8
|
+
|
9
|
+
# Thread-local name for the hidden self used by `capture`
|
10
|
+
# @return [String] The name of the hidden self used by `capture`
|
11
|
+
def self.context_self_name
|
12
|
+
"@__self__#{Thread.current.object_id}"
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassExtensions
|
16
|
+
|
17
|
+
# Find the instance associated with the singleton class
|
18
|
+
# @return [Object] Instance associated with the singleton class
|
19
|
+
def __attached__
|
20
|
+
ObjectSpace.each_object(self).first
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ObjectExtensions
|
25
|
+
|
26
|
+
def local_eval(*objs, &block)
|
27
|
+
objs = Array(self) if objs.empty?
|
28
|
+
context = eval('self', block.binding)
|
29
|
+
|
30
|
+
# if the receiver is the same as the block context then don't
|
31
|
+
# mix in anything, as functionality is already present.
|
32
|
+
if objs.include?(context)
|
33
|
+
objs.delete(context)
|
34
|
+
return yield if objs.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
# add functionality to anonymous module to ease mixing and unmixing
|
38
|
+
functionality = Module.new.gen_include *objs.map { |o| o.is_a?(Module) ? o.singleton_class : o }
|
39
|
+
|
40
|
+
# mix the anonymous module into the block context
|
41
|
+
context.temp_extend functionality, &block
|
42
|
+
end
|
43
|
+
alias_method :local_eval_with, :local_eval
|
44
|
+
|
45
|
+
# This form is meant to be called without a receiver
|
46
|
+
private :local_eval_with
|
47
|
+
|
48
|
+
def capture(&block)
|
49
|
+
|
50
|
+
# 1. Get name of enclosing method (method that invoked
|
51
|
+
# `capture ` block)
|
52
|
+
# 2. Find owner of enclosing method (owner is guaranteed to be
|
53
|
+
# a singleton class)
|
54
|
+
# 4. Find associated object (attached object) of the singleton class
|
55
|
+
# 5. This object will be the receiver of the method call, so
|
56
|
+
# instance_eval on it.
|
57
|
+
method_name = eval('__method__', block.binding)
|
58
|
+
method_owner = method(method_name).owner
|
59
|
+
attached_object = method_owner.__attached__
|
60
|
+
attached_object.instance_eval &block
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Class
|
66
|
+
include LocalEval::ClassExtensions
|
67
|
+
end
|
68
|
+
|
69
|
+
class Object
|
70
|
+
include LocalEval::ObjectExtensions
|
71
|
+
end
|
data/test/helper1.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
class Module
|
2
|
+
public :remove_const, :include, :remove_method
|
3
|
+
end
|
4
|
+
|
5
|
+
Reset = proc do
|
6
|
+
O = Object.new
|
7
|
+
O2 = Object.new
|
8
|
+
|
9
|
+
class A
|
10
|
+
def self.a
|
11
|
+
:a
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class B
|
16
|
+
def self.b
|
17
|
+
:b
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class << O
|
22
|
+
def hello
|
23
|
+
:o
|
24
|
+
end
|
25
|
+
|
26
|
+
def ivar_set(var, val)
|
27
|
+
instance_variable_set(var, val)
|
28
|
+
end
|
29
|
+
|
30
|
+
def receiver_ivar_set
|
31
|
+
capture {
|
32
|
+
@receiver_ivar = :receiver
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
class << O2
|
39
|
+
def receiver_ivar_set2
|
40
|
+
capture {
|
41
|
+
@receiver_ivar2 = :receiver2
|
42
|
+
}
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class C
|
47
|
+
def self.build_proc
|
48
|
+
proc { love }
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.hello
|
52
|
+
:c
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.c
|
56
|
+
:c
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.ivar(v)
|
60
|
+
v
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module M
|
65
|
+
def self.hello
|
66
|
+
:m
|
67
|
+
end
|
68
|
+
|
69
|
+
def m
|
70
|
+
:m
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/test/test.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
direc = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require "#{direc}/../lib/local_eval"
|
5
|
+
require "#{direc}/helper1"
|
6
|
+
|
7
|
+
puts "Testing LocalEval version #{LocalEval::VERSION}..."
|
8
|
+
puts "With Remix version #{Remix::VERSION} and Object2module version #{Object2module::VERSION}"
|
9
|
+
puts "Ruby version #{RUBY_VERSION}"
|
10
|
+
|
11
|
+
describe LocalEval do
|
12
|
+
before do
|
13
|
+
Reset.call
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
[:A, :B, :C, :M, :O, :O2].each do |c|
|
18
|
+
Object.remove_const(c)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'mixing in an object' do
|
23
|
+
it 'should mix in and mixout the object and make functionality available to block' do
|
24
|
+
lambda { hello }.should.raise NameError
|
25
|
+
O.local_eval { hello }.should == :o
|
26
|
+
lambda { hello }.should.raise NameError
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not error when receiver is the same as block context' do
|
30
|
+
lambda { local_eval { :hello } }.should.not.raise ArgumentError
|
31
|
+
local_eval { :hello }.should == :hello
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should mix implicit self into context of block' do
|
35
|
+
def self.love; :love; end
|
36
|
+
local_eval(&C.build_proc).should == :love
|
37
|
+
self.singleton_class.remove_method(:love)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'local_eval_with' do
|
42
|
+
it 'should mix in multiple objects and make functionality available to the block' do
|
43
|
+
lambda { a }.should.raise NameError
|
44
|
+
lambda { b }.should.raise NameError
|
45
|
+
lambda { local_eval_with(A, B) { a; b; } }.should.not.raise NameError
|
46
|
+
lambda { a }.should.raise NameError
|
47
|
+
lambda { b }.should.raise NameError
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should not error when mixing in multiple objects that include the context of the block' do
|
51
|
+
lambda { local_eval_with(self, A, B) { a; b } }.should.not.raise NameError
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'mixing in a class' do
|
56
|
+
it 'should mix in and mixout the class' do
|
57
|
+
lambda { hello }.should.raise NameError
|
58
|
+
C.local_eval { c }.should == :c
|
59
|
+
lambda { hello }.should.raise NameError
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should mixin and mixout a class/module chain' do
|
63
|
+
C.extend M
|
64
|
+
lambda { c }.should.raise NameError
|
65
|
+
lambda { m }.should.raise NameError
|
66
|
+
C.local_eval { c.should == :c; m.should == :m }
|
67
|
+
lambda { c }.should.raise NameError
|
68
|
+
lambda { m }.should.raise NameError
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe 'ivars in the block' do
|
73
|
+
it 'should make ivars accessible to, and modifiable by, block' do
|
74
|
+
O.local_eval { @x = 5 }
|
75
|
+
@x.should == 5
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should make the method set a local ivar' do
|
79
|
+
instance_variable_defined?(:@v).should == false
|
80
|
+
lambda { ivar_set }.should.raise NameError
|
81
|
+
O.local_eval { ivar_set(:@v, 10) }
|
82
|
+
lambda { ivar_set }.should.raise NameError
|
83
|
+
@v.should == 10
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should make local ivars accessible to mixed in methods' do
|
87
|
+
@y = 10
|
88
|
+
lambda { ivar(@y) }.should.raise NameError
|
89
|
+
C.local_eval { ivar(@y) }.should == 10
|
90
|
+
@y.should == 10
|
91
|
+
lambda { ivar(@y) }.should.raise NameError
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'capture block' do
|
96
|
+
it 'should make capture evaluate the method in receiver context' do
|
97
|
+
instance_variable_defined?(:@receiver_ivar).should == false
|
98
|
+
lambda { receiver_ivar_set }.should.raise NameError
|
99
|
+
O.local_eval { receiver_ivar_set }
|
100
|
+
instance_variable_defined?(:@receiver_ivar).should == false
|
101
|
+
O.instance_variable_get(:@receiver_ivar).should == :receiver
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should redirect methods to appropriate receivers' do
|
105
|
+
O.instance_variable_defined?(:@receiver_ivar2).should == false
|
106
|
+
O2.instance_variable_defined?(:@receiver_ivar2).should == false
|
107
|
+
instance_variable_defined?(:@receiver_ivar).should == false
|
108
|
+
instance_variable_defined?(:@receiver_ivar2).should == false
|
109
|
+
lambda { receiver_ivar_set; receiver_ivar_set2 }.should.raise NameError
|
110
|
+
local_eval_with(O, O2) { receiver_ivar_set; receiver_ivar_set2 }
|
111
|
+
instance_variable_defined?(:@receiver_ivar).should == false
|
112
|
+
instance_variable_defined?(:@receiver_ivar2).should == false
|
113
|
+
O.instance_variable_get(:@receiver_ivar).should == :receiver
|
114
|
+
O2.instance_variable_get(:@receiver_ivar2).should == :receiver2
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not prevent method lookup on capture-methods on objects that are not involved in the local_eval' do
|
118
|
+
O2.instance_variable_defined?(:@receiver_ivar2).should == false
|
119
|
+
O.local_eval { O2.receiver_ivar_set2.should == :receiver2 }
|
120
|
+
O2.instance_variable_get(:@receiver_ivar2).should == :receiver2
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should work properly with nested local_evals' do
|
124
|
+
O.local_eval do
|
125
|
+
O2.local_eval { receiver_ivar_set2 }
|
126
|
+
lambda { receiver_ivar_set2 }.should.raise NameError
|
127
|
+
receiver_ivar_set
|
128
|
+
end
|
129
|
+
|
130
|
+
O2.instance_variable_get(:@receiver_ivar2).should == :receiver2
|
131
|
+
O.instance_variable_get(:@receiver_ivar).should == :receiver
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe 'mixing in a module' do
|
136
|
+
it 'should mix in and mixout the module' do
|
137
|
+
lambda { hello }.should.raise NameError
|
138
|
+
M.local_eval { hello }.should == :m
|
139
|
+
lambda { hello }.should.raise NameError
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: local_eval
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- John Mair (banisterfiend)
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-11-15 00:00:00 +13:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: remix
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 4
|
31
|
+
- 8
|
32
|
+
version: 0.4.8
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: object2module
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
- 4
|
46
|
+
- 3
|
47
|
+
version: 0.4.3
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
description: instance_eval without changing self
|
51
|
+
email: jrmair@gmail.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- lib/local_eval/version.rb
|
60
|
+
- lib/local_eval/version_flymake.rb
|
61
|
+
- lib/local_eval.rb
|
62
|
+
- test/helper1.rb
|
63
|
+
- test/test.rb
|
64
|
+
- CHANGELOG
|
65
|
+
- README.markdown
|
66
|
+
- Rakefile
|
67
|
+
has_rdoc: yard
|
68
|
+
homepage: http://banisterfiend.wordpress.com
|
69
|
+
licenses: []
|
70
|
+
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
version: "0"
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
requirements: []
|
93
|
+
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.3.7
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: instance_eval without changing self
|
99
|
+
test_files: []
|
100
|
+
|