docile 0.9.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 +7 -0
- data/.yardopts +7 -0
- data/Gemfile +4 -0
- data/LICENSE +19 -0
- data/README.md +75 -0
- data/Rakefile +11 -0
- data/docile.gemspec +28 -0
- data/lib/docile.rb +31 -0
- data/lib/docile/fallback_context_proxy.rb +52 -0
- data/lib/docile/version.rb +3 -0
- data/spec/docile_spec.rb +94 -0
- data/spec/spec_helper.rb +10 -0
- metadata +111 -0
data/.gitignore
ADDED
data/.yardopts
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2011 Marc Siegel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Docile
|
2
|
+
|
3
|
+
Definition: *Ready to accept control or instruction; submissive* [[1]]
|
4
|
+
|
5
|
+
Tired of overly complex DSL libraries and hairy meta-programming?
|
6
|
+
Let's make our Ruby DSLs more *docile*...
|
7
|
+
|
8
|
+
[1]: http://www.google.com/search?q=docile+definition "Google"
|
9
|
+
|
10
|
+
## Usage
|
11
|
+
|
12
|
+
Let's treat an Array's methods as its own DSL:
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
Docile.dsl_eval [] do
|
16
|
+
push 1
|
17
|
+
push 2
|
18
|
+
end
|
19
|
+
#=> [1, 3]
|
20
|
+
```
|
21
|
+
|
22
|
+
Mutating (changing) the array is fine, but what you probably really want as your DSL is actually a [Builder Pattern][2].
|
23
|
+
|
24
|
+
For example, if you have a PizzaBuilder class that can build a pizza:
|
25
|
+
|
26
|
+
``` ruby
|
27
|
+
@sauce_level = :extra
|
28
|
+
pizza = PizzaBuilder.new.cheese.pepperoni.bacon.sauce(@sauce_level).build
|
29
|
+
```
|
30
|
+
|
31
|
+
Then you can use this PizzaBuilder class as a DSL:
|
32
|
+
|
33
|
+
``` ruby
|
34
|
+
@sauce_level = :extra
|
35
|
+
pizza = Docile.dsl_eval PizzaBuilder.new do
|
36
|
+
cheese
|
37
|
+
pepperoni
|
38
|
+
bacon
|
39
|
+
sauce @sauce_level
|
40
|
+
end.build
|
41
|
+
```
|
42
|
+
|
43
|
+
[2]: http://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern "Builder Pattern"
|
44
|
+
|
45
|
+
## Features
|
46
|
+
|
47
|
+
1. method lookup falls back from the DSL object to the block's context
|
48
|
+
2. local variable lookup falls back from the DSL object to the block's context
|
49
|
+
3. instance variables are from the block's context only
|
50
|
+
|
51
|
+
## Installation
|
52
|
+
|
53
|
+
``` bash
|
54
|
+
$ gem install docile
|
55
|
+
```
|
56
|
+
|
57
|
+
## Documentation
|
58
|
+
|
59
|
+
Documentation hosted on *rubydoc.info*: [Docile Documentation](http://rubydoc.info/gems/docile)
|
60
|
+
Or, read the code hosted on *github.com*: [Docile Code](https://github.com/ms-ati/docile)
|
61
|
+
|
62
|
+
## Note on Patches/Pull Requests
|
63
|
+
|
64
|
+
* Fork the project.
|
65
|
+
* Setup your development environment with: gem install bundler; bundle install
|
66
|
+
* Make your feature addition or bug fix.
|
67
|
+
* Add tests for it. This is important so I don't break it in a
|
68
|
+
future version unintentionally.
|
69
|
+
* Commit, do not mess with rakefile, version, or history.
|
70
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
71
|
+
* Send me a pull request. Bonus points for topic branches.
|
72
|
+
|
73
|
+
## Copyright
|
74
|
+
|
75
|
+
Copyright (c) 2011 Marc Siegel. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "github/markup"
|
3
|
+
require "redcarpet"
|
4
|
+
require "yard"
|
5
|
+
require "yard/rake/yardoc_task"
|
6
|
+
|
7
|
+
YARD::Rake::YardocTask.new do |t|
|
8
|
+
OTHER_PATHS = %w()
|
9
|
+
t.files = ['lib/**/*.rb', OTHER_PATHS]
|
10
|
+
t.options = %w(--markup-provider=redcarpet --markup=markdown)
|
11
|
+
end
|
data/docile.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "docile/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "docile"
|
7
|
+
s.version = Docile::VERSION
|
8
|
+
s.authors = ["Marc Siegel"]
|
9
|
+
s.email = ["msiegel@usainnov.com"]
|
10
|
+
s.homepage = "http://docile.github.com"
|
11
|
+
s.summary = "Docile keeps your Ruby DSL's tame and well-behaved"
|
12
|
+
s.description = "Docile turns any Ruby object into a DSL. Especially useful with the Builder pattern."
|
13
|
+
|
14
|
+
s.rubyforge_project = "docile"
|
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 "rspec", "~> 2.7.0"
|
22
|
+
|
23
|
+
# Github flavored markdown in YARD documentation
|
24
|
+
# http://blog.nikosd.com/2011/11/github-flavored-markdown-in-yard.html
|
25
|
+
s.add_development_dependency "yard"
|
26
|
+
s.add_development_dependency "redcarpet"
|
27
|
+
s.add_development_dependency "github-markup"
|
28
|
+
end
|
data/lib/docile.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "docile/version"
|
2
|
+
require "docile/fallback_context_proxy"
|
3
|
+
|
4
|
+
module Docile
|
5
|
+
# Executes a block in the context of an object whose interface represents a DSL.
|
6
|
+
#
|
7
|
+
# Example of using an Array as a DSL:
|
8
|
+
# Docile.dsl_eval [] do
|
9
|
+
# push 1
|
10
|
+
# push 2
|
11
|
+
# pop
|
12
|
+
# push 3
|
13
|
+
# end
|
14
|
+
# #=> [1, 3]
|
15
|
+
#
|
16
|
+
# @param dsl [Object] an object whose methods represent a DSL
|
17
|
+
# @param block [Proc] a block to execute in the DSL context
|
18
|
+
# @return [Object] the dsl object, after execution of the block
|
19
|
+
def dsl_eval(dsl, &block)
|
20
|
+
block_context = eval("self", block.binding)
|
21
|
+
proxy_context = FallbackContextProxy.new(dsl, block_context)
|
22
|
+
begin
|
23
|
+
block_context.instance_variables.each { |ivar| proxy_context.instance_variable_set(ivar, block_context.instance_variable_get(ivar)) }
|
24
|
+
proxy_context.instance_eval(&block)
|
25
|
+
ensure
|
26
|
+
block_context.instance_variables.each { |ivar| block_context.instance_variable_set(ivar, proxy_context.instance_variable_get(ivar)) }
|
27
|
+
end
|
28
|
+
dsl
|
29
|
+
end
|
30
|
+
module_function :dsl_eval
|
31
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Docile
|
4
|
+
class FallbackContextProxy
|
5
|
+
NON_PROXIED_METHODS = Set[:object_id, :__send__, :__id__, :==, :equal?, :"!", :"!=", :instance_eval,
|
6
|
+
:instance_variables, :instance_variable_get, :instance_variable_set,
|
7
|
+
:remove_instance_variable]
|
8
|
+
|
9
|
+
NON_PROXIED_INSTANCE_VARIABLES = Set[:@__receiver__, :@__fallback__]
|
10
|
+
|
11
|
+
instance_methods.each do |method|
|
12
|
+
unless NON_PROXIED_METHODS.include?(method.to_sym)
|
13
|
+
undef_method(method)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(receiver, fallback)
|
18
|
+
@__receiver__ = receiver
|
19
|
+
@__fallback__ = fallback
|
20
|
+
end
|
21
|
+
|
22
|
+
def id
|
23
|
+
@__receiver__.__send__(:id)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Special case due to `Kernel#sub`'s existence
|
27
|
+
def sub(*args, &block)
|
28
|
+
__proxy_method__(:sub, *args, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Special case to allow proxy instance variables
|
32
|
+
def instance_variables
|
33
|
+
super - NON_PROXIED_INSTANCE_VARIABLES.to_a
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(method, *args, &block)
|
37
|
+
__proxy_method__(method, *args, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def __proxy_method__(method, *args, &block)
|
41
|
+
begin
|
42
|
+
@__receiver__.__send__(method.to_sym, *args, &block)
|
43
|
+
rescue ::NoMethodError => e
|
44
|
+
begin
|
45
|
+
@__fallback__.__send__(method.to_sym, *args, &block)
|
46
|
+
rescue ::NoMethodError
|
47
|
+
raise(e)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/spec/docile_spec.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Docile do
|
4
|
+
|
5
|
+
context :dsl_eval do
|
6
|
+
|
7
|
+
class InnerDSL
|
8
|
+
def initialize; @b = 'b'; end
|
9
|
+
attr_accessor :b
|
10
|
+
end
|
11
|
+
|
12
|
+
class OuterDSL
|
13
|
+
def initialize; @a = 'a'; end
|
14
|
+
attr_accessor :a
|
15
|
+
def inner(&block)
|
16
|
+
Docile.dsl_eval(InnerDSL.new, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def outer(&block)
|
21
|
+
Docile.dsl_eval(OuterDSL.new, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return the DSL object" do
|
25
|
+
Docile.dsl_eval([]) do
|
26
|
+
push 1
|
27
|
+
push 2
|
28
|
+
pop
|
29
|
+
push 3
|
30
|
+
end.should == [1, 3]
|
31
|
+
end
|
32
|
+
|
33
|
+
context "methods" do
|
34
|
+
it "should find method of outer dsl in outer dsl scope" do
|
35
|
+
outer { a.should == 'a' }
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should find method of inner dsl in inner dsl scope" do
|
39
|
+
outer { inner { b.should == 'b' } }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should find method of outer dsl in inner dsl scope" do
|
43
|
+
outer { inner { a.should == 'a' } }
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should find method of block's context in outer dsl scope" do
|
47
|
+
def c; 'c'; end
|
48
|
+
outer { c.should == 'c' }
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should find method of block's context in inner dsl scope" do
|
52
|
+
def c; 'c'; end
|
53
|
+
outer { inner { c.should == 'c' } }
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should find method of outer dsl in preference to block context" do
|
57
|
+
def a; 'not a'; end
|
58
|
+
outer { a.should == 'a' }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "local variables" do
|
63
|
+
it "should find local variable from block context in outer dsl scope" do
|
64
|
+
foo = 'foo'
|
65
|
+
outer { foo.should == 'foo' }
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should find local variable from block definition in inner dsl scope" do
|
69
|
+
bar = 'bar'
|
70
|
+
outer { inner { bar.should == 'bar' } }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "instance variables" do
|
75
|
+
it "should find instance variable from block definition in outer dsl scope" do
|
76
|
+
@iv1 = 'iv1'; outer { @iv1.should == 'iv1' }
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should write instance variable assigned in block into outer dsl scope" do
|
80
|
+
@iv1 = 'foo'; outer { @iv1 = 'bar' }; @iv1.should == 'bar'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should find instance variable from block definition in inner dsl scope" do
|
84
|
+
@iv2 = 'iv2'; outer { inner { @iv2.should == 'iv2' } }
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should find instance variable from block definition in inner dsl scope" do
|
88
|
+
@iv2 = 'foo'; outer { inner { @iv2 = 'bar' } }; @iv2.should == 'bar'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
test_dir = File.dirname(__FILE__)
|
5
|
+
$LOAD_PATH.unshift test_dir unless $LOAD_PATH.include?(test_dir)
|
6
|
+
|
7
|
+
lib_dir = File.join(File.dirname(test_dir), 'lib')
|
8
|
+
$LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include?(lib_dir)
|
9
|
+
|
10
|
+
require 'docile'
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docile
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.9.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marc Siegel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-12-06 00:00:00 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.7.0
|
24
|
+
type: :development
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: yard
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: redcarpet
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: github-markup
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id004
|
59
|
+
description: Docile turns any Ruby object into a DSL. Especially useful with the Builder pattern.
|
60
|
+
email:
|
61
|
+
- msiegel@usainnov.com
|
62
|
+
executables: []
|
63
|
+
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files: []
|
67
|
+
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .yardopts
|
71
|
+
- Gemfile
|
72
|
+
- LICENSE
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- docile.gemspec
|
76
|
+
- lib/docile.rb
|
77
|
+
- lib/docile/fallback_context_proxy.rb
|
78
|
+
- lib/docile/version.rb
|
79
|
+
- spec/docile_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
homepage: http://docile.github.com
|
82
|
+
licenses: []
|
83
|
+
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: "0"
|
101
|
+
requirements: []
|
102
|
+
|
103
|
+
rubyforge_project: docile
|
104
|
+
rubygems_version: 1.8.11
|
105
|
+
signing_key:
|
106
|
+
specification_version: 3
|
107
|
+
summary: Docile keeps your Ruby DSL's tame and well-behaved
|
108
|
+
test_files:
|
109
|
+
- spec/docile_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
has_rdoc:
|