local_eval 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|