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 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
+