local_eval 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ module LocalEval
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,3 @@
1
+ module LocalEval
2
+ VERSION = "0.2.0"
3
+ 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
+