docile 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|