mixit 0.1.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/.gitignore +6 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/README.md +64 -0
- data/Rakefile +9 -0
- data/lib/mixit/core_ext/object.rb +3 -0
- data/lib/mixit/version.rb +3 -0
- data/lib/mixit.rb +60 -0
- data/mixit.gemspec +23 -0
- data/test/setup.rb +7 -0
- data/test/test_mixit.rb +109 -0
- metadata +88 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
| Project | Mixit
|
2
|
+
|:----------------|:--------------------------------------------------
|
3
|
+
| Homepage | https://github.com/robgleeson/Mixit
|
4
|
+
| Documentation | http://rubydoc.info/gems/Mixit/frames
|
5
|
+
| Author | Rob Gleeson
|
6
|
+
|
7
|
+
|
8
|
+
__DESCRIPTION__
|
9
|
+
|
10
|
+
Mixit can be used to temporarily extend the calling scope of a block or object with instance methods from a module.
|
11
|
+
It is written to help Domain Specific Language(DSL) writers provide a DSL with their own methods
|
12
|
+
without losing the calling scope of the caller.
|
13
|
+
|
14
|
+
__WHY?__
|
15
|
+
|
16
|
+
Some Ruby DSL approaches already exist(maybe one suits you):
|
17
|
+
|
18
|
+
* `instance_eval`
|
19
|
+
The calling scope is lost, and internal state is exposed to the user of the DSL.
|
20
|
+
|
21
|
+
* `instance_eval` via "EmptyContext"
|
22
|
+
The calling scope is lost, but the object is void of any state.
|
23
|
+
|
24
|
+
* Pass `self` onto the calling block.
|
25
|
+
The cleanest approach but some of the beauty of the DSL can be lost.
|
26
|
+
|
27
|
+
All these approaches(but the last) lose the calling scope, and that's why I created 'Mixit'.
|
28
|
+
I wanted the user of the DSL to have access to the calling scope, but have access to my DSL as
|
29
|
+
implicit receivers of 'self' as well.
|
30
|
+
|
31
|
+
This approach is by no means 'perfect', or even 'good'.
|
32
|
+
If you use it wisely, though, it can be a cool way to build nice DSLs.
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
__EXAMPLES__
|
37
|
+
|
38
|
+
Mix 'Foo' onto the 'self' referenced by _block_:
|
39
|
+
|
40
|
+
module Foo
|
41
|
+
def bar
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def DSL(&block)
|
46
|
+
Mixit.mix(Foo, block)
|
47
|
+
end
|
48
|
+
|
49
|
+
DSL do
|
50
|
+
p respond_to?(:bar) # => true
|
51
|
+
end
|
52
|
+
|
53
|
+
p respond_to?(:bar) # => false
|
54
|
+
|
55
|
+
__INSTALL__
|
56
|
+
|
57
|
+
gem install mixit
|
58
|
+
|
59
|
+
__LICENSE__
|
60
|
+
|
61
|
+
|
62
|
+
See LICENSE.txt
|
63
|
+
|
64
|
+
|
data/Rakefile
ADDED
data/lib/mixit.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require "mixit/core_ext/object"
|
2
|
+
require "mixit/version"
|
3
|
+
|
4
|
+
class Mixit
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def mix(*args, &block)
|
9
|
+
case args.last
|
10
|
+
when Proc
|
11
|
+
scope = args.pop
|
12
|
+
receiver = scope.binding.eval("self")
|
13
|
+
modules = args
|
14
|
+
else
|
15
|
+
receiver = args.pop
|
16
|
+
modules = args
|
17
|
+
end
|
18
|
+
|
19
|
+
new(modules).mix_onto(receiver, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(modules)
|
24
|
+
@modules = modules.map(&:clone)
|
25
|
+
end
|
26
|
+
|
27
|
+
def mix_onto(receiver, &block)
|
28
|
+
if block.arity == 1
|
29
|
+
block.call extend!(receiver, true)
|
30
|
+
else
|
31
|
+
ivars = receiver.instance_variables
|
32
|
+
extend!(receiver, false).instance_eval(&block)
|
33
|
+
|
34
|
+
# Remove instance variables added by @modules, or &block from 'receiver'.
|
35
|
+
# We want to restore the calling scope to its previous state.
|
36
|
+
(receiver.instance_variables - ivars).each do |ivar|
|
37
|
+
receiver.send(:remove_instance_variable, ivar)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Remove methods added by @modules from 'receiver'.
|
41
|
+
# We want to restore the calling scope to its previous state.
|
42
|
+
@modules.each do |mod|
|
43
|
+
mod.instance_methods(false).each do |meth|
|
44
|
+
mod.send(:remove_method, meth)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def extend!(receiver, to_be_cloned=false)
|
51
|
+
if to_be_cloned
|
52
|
+
receiver = receiver.clone
|
53
|
+
end
|
54
|
+
|
55
|
+
@modules.inject(receiver) { |obj, mod| obj.extend(mod) }
|
56
|
+
end
|
57
|
+
private :extend!
|
58
|
+
|
59
|
+
end
|
60
|
+
|
data/mixit.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "mixit/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "mixit"
|
7
|
+
s.version = Mixit::VERSION
|
8
|
+
s.authors = ["Rob Gleeson"]
|
9
|
+
s.email = ["rob@flowof.info"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Temporarily extend the calling scope of a block with instance methods from a module.}
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "mixit"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rake" , "~> 0.9.2"
|
22
|
+
s.add_development_dependency "minitest" , "~> 2.5"
|
23
|
+
end
|
data/test/setup.rb
ADDED
data/test/test_mixit.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
context Mixit do
|
2
|
+
|
3
|
+
context 'mix' do
|
4
|
+
context 'When receiving a block.' do
|
5
|
+
before do
|
6
|
+
@module = Module.new do
|
7
|
+
def callme
|
8
|
+
:ok
|
9
|
+
end
|
10
|
+
|
11
|
+
def i
|
12
|
+
@i = 'asdf'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'When extending the calling scope of a Proc.' do
|
18
|
+
context 'Without cloning the receiver.' do
|
19
|
+
it 'must provide access to mixed in module methoss' do
|
20
|
+
block = proc { }
|
21
|
+
|
22
|
+
Mixit.mix(@module, block) do
|
23
|
+
callme.must_equal(:ok)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'must provide access to surrounding locals.' do
|
28
|
+
block = proc { }
|
29
|
+
local = :ok
|
30
|
+
|
31
|
+
Mixit.mix(@module, block) do
|
32
|
+
local.must_equal(:ok)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'must provide access to surrounding methods.' do
|
37
|
+
def callme_() :ok end
|
38
|
+
block = proc { }
|
39
|
+
|
40
|
+
Mixit.mix(@module, block) do
|
41
|
+
callme_.must_equal(:ok)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'must remove instance variables added by the passed block.' do
|
46
|
+
block = proc { }
|
47
|
+
|
48
|
+
Mixit.mix(@module, block) do
|
49
|
+
@ok = :no
|
50
|
+
end
|
51
|
+
|
52
|
+
instance_variable_defined?(:@ok).must_equal(false)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'must remove instance variables added by the mixed in modules.' do
|
56
|
+
block = proc { }
|
57
|
+
|
58
|
+
Mixit.mix(@module, block) do
|
59
|
+
i
|
60
|
+
instance_variable_defined?(:@i).must_equal(true)
|
61
|
+
end
|
62
|
+
|
63
|
+
instance_variable_defined?(:@i).must_equal(false)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'must not remove instance variables set before the scope is extended.' do
|
67
|
+
block = proc {}
|
68
|
+
@ok = :ok
|
69
|
+
|
70
|
+
Mixit.mix(@module, block) do
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
instance_variable_defined?(:@ok).must_equal(true)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'must remove methods added by mixed in modules.' do
|
78
|
+
block = proc {}
|
79
|
+
|
80
|
+
Mixit.mix(@module, block) do
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
respond_to?(:callme).must_equal(false)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'With the receiver cloned.' do
|
90
|
+
it 'must provide access to mixed in module methods.' do
|
91
|
+
block = proc {}
|
92
|
+
|
93
|
+
Mixit.mix(@module, block) do |receiver|
|
94
|
+
receiver.callme.must_equal(:ok)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'must not extending the original receiver with mixed in module methods.' do
|
99
|
+
block = proc {}
|
100
|
+
|
101
|
+
Mixit.mix(@module, block) do |receiver|
|
102
|
+
respond_to?(:callme).must_equal(false)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mixit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rob Gleeson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-19 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70294493576860 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.9.2
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70294493576860
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: minitest
|
27
|
+
requirement: &70294493576360 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.5'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70294493576360
|
36
|
+
description: Temporarily extend the calling scope of a block with instance methods
|
37
|
+
from a module.
|
38
|
+
email:
|
39
|
+
- rob@flowof.info
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- .gitignore
|
45
|
+
- .travis.yml
|
46
|
+
- Gemfile
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- lib/mixit.rb
|
50
|
+
- lib/mixit/core_ext/object.rb
|
51
|
+
- lib/mixit/version.rb
|
52
|
+
- mixit.gemspec
|
53
|
+
- test/setup.rb
|
54
|
+
- test/test_mixit.rb
|
55
|
+
homepage: ''
|
56
|
+
licenses: []
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
hash: 4457976507324133179
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
hash: 4457976507324133179
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project: mixit
|
81
|
+
rubygems_version: 1.8.11
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Temporarily extend the calling scope of a block with instance methods from
|
85
|
+
a module.
|
86
|
+
test_files:
|
87
|
+
- test/setup.rb
|
88
|
+
- test/test_mixit.rb
|