docile 1.0.5 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/HISTORY.md +4 -0
- data/README.md +62 -21
- data/lib/docile.rb +46 -18
- data/lib/docile/chaining_fallback_context_proxy.rb +20 -0
- data/lib/docile/execution.rb +36 -0
- data/lib/docile/fallback_context_proxy.rb +6 -0
- data/lib/docile/version.rb +1 -1
- data/spec/docile_spec.rb +204 -116
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57d90aa5d94c06fab36bcf2ad9c058e8b3c7ec3b
|
4
|
+
data.tar.gz: c73f0d3af5a1d87fcc3ae562e2c90bdd7e06b724
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ed37d882ad062a8ae1dc02963582c57ed51383b7916176964a55ad4833ee34e93a98743382d527113554f8cef5774ef96e8e681396de00b11bfdcb4bda80bef
|
7
|
+
data.tar.gz: a000461e5ffe9649801875b2d0228d1a5d61510813713a90fe5cfb4aa594abaaa52e7f2554ec57113eb0b679712f39be469664ccbe82f0ed9afbb9c1f1324d8b
|
data/.rspec
ADDED
data/HISTORY.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# HISTORY
|
2
2
|
|
3
|
+
## [v1.1.0 (Jul 29, 2013)](http://github.com/ms-ati/docile/compare/v1.0.5...v1.1.0)
|
4
|
+
|
5
|
+
- add functional-style DSL objects via `Docile#dsl_eval_immutable`
|
6
|
+
|
3
7
|
## [v1.0.5 (Jul 28, 2013)](http://github.com/ms-ati/docile/compare/v1.0.4...v1.0.5)
|
4
8
|
|
5
9
|
- achieve 100% yard docs coverage
|
data/README.md
CHANGED
@@ -1,19 +1,21 @@
|
|
1
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
|
-
|
7
|
-
Let's make our Ruby DSLs more docile...
|
8
|
-
|
9
|
-
[1]: http://www.google.com/search?q=docile+definition "Google"
|
10
|
-
|
11
2
|
[![Gem Version](https://badge.fury.io/rb/docile.png)](http://badge.fury.io/rb/docile)
|
12
3
|
[![Build Status](https://travis-ci.org/ms-ati/docile.png)](https://travis-ci.org/ms-ati/docile)
|
13
4
|
[![Dependency Status](https://gemnasium.com/ms-ati/docile.png)](https://gemnasium.com/ms-ati/docile)
|
14
5
|
[![Code Climate](https://codeclimate.com/github/ms-ati/docile.png)](https://codeclimate.com/github/ms-ati/docile)
|
15
6
|
[![Coverage Status](https://coveralls.io/repos/ms-ati/docile/badge.png)](https://coveralls.io/r/ms-ati/docile)
|
16
7
|
|
8
|
+
Ruby makes it possible to create very expressive **Domain Specific
|
9
|
+
Languages**, or **DSL**'s for short. However, it requires some deep knowledge and
|
10
|
+
somewhat hairy meta-programming to get the interface just right.
|
11
|
+
|
12
|
+
"Docile" means *Ready to accept control or instruction; submissive* [[1]]
|
13
|
+
|
14
|
+
Instead of each Ruby project reinventing this wheel, let's make our Ruby DSL
|
15
|
+
coding a bit more docile...
|
16
|
+
|
17
|
+
[1]: http://www.google.com/search?q=docile+definition "Google"
|
18
|
+
|
17
19
|
## Basic Usage
|
18
20
|
|
19
21
|
Let's say that we want to make a DSL for modifying Array objects.
|
@@ -26,12 +28,12 @@ with_array([]) do
|
|
26
28
|
pop
|
27
29
|
push 3
|
28
30
|
end
|
29
|
-
|
31
|
+
#=> [1, 3]
|
30
32
|
```
|
31
33
|
|
32
34
|
No problem, just define the method `with_array` like this:
|
33
35
|
|
34
|
-
```
|
36
|
+
```ruby
|
35
37
|
def with_array(arr=[], &block)
|
36
38
|
Docile.dsl_eval(arr, &block)
|
37
39
|
end
|
@@ -53,7 +55,7 @@ pizza do
|
|
53
55
|
pepperoni
|
54
56
|
sauce @sauce_level
|
55
57
|
end
|
56
|
-
|
58
|
+
#=> #<Pizza:0x00001009dc398 @cheese=true, @pepperoni=true, @bacon=false, @sauce=:extra>
|
57
59
|
```
|
58
60
|
|
59
61
|
And let's say we have a PizzaBuilder, which builds a Pizza like this:
|
@@ -83,7 +85,7 @@ def pizza(&block)
|
|
83
85
|
end
|
84
86
|
```
|
85
87
|
|
86
|
-
It's just that easy!
|
88
|
+
It's just that easy!
|
87
89
|
|
88
90
|
[2]: http://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern "Builder Pattern"
|
89
91
|
|
@@ -147,12 +149,49 @@ end
|
|
147
149
|
|
148
150
|
[3]: http://www.sinatrarb.com "Sinatra"
|
149
151
|
|
152
|
+
## Functional-Style DSL Objects
|
153
|
+
|
154
|
+
Sometimes, you want to use an object as a DSL, but it doesn't quite fit the
|
155
|
+
[imperative](http://en.wikipedia.org/wiki/Imperative_programming) pattern shown
|
156
|
+
above.
|
157
|
+
|
158
|
+
Instead of methods like
|
159
|
+
[Array#push](http://www.ruby-doc.org/core-2.0/Array.html#method-i-push), which
|
160
|
+
modifies the object at hand, it has methods like
|
161
|
+
[String#reverse](http://www.ruby-doc.org/core-2.0/String.html#method-i-reverse),
|
162
|
+
which returns a new object without touching the original. Perhaps it's even
|
163
|
+
[frozen](http://www.ruby-doc.org/core-2.0/Object.html#method-i-freeze) in
|
164
|
+
order to enforce [immutability](http://en.wikipedia.org/wiki/Immutable_object).
|
165
|
+
|
166
|
+
Wouldn't it be great if we could just treat these methods as a DSL as well?
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
with_immutable_string("I'm immutable!".freeze) do
|
170
|
+
reverse
|
171
|
+
upcase
|
172
|
+
end
|
173
|
+
#=> "!ELBATUMMI M'I"
|
174
|
+
```
|
175
|
+
|
176
|
+
No problem, just define the method `with_immutable_string` like this:
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
def with_immutable_string(str="", &block)
|
180
|
+
Docile.dsl_eval_immutable(str, &block)
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
All set!
|
185
|
+
|
150
186
|
## Features
|
151
187
|
|
152
|
-
1.
|
153
|
-
2.
|
154
|
-
|
155
|
-
|
188
|
+
1. Method lookup falls back from the DSL object to the block's context
|
189
|
+
2. Local variable lookup falls back from the DSL object to the block's
|
190
|
+
context
|
191
|
+
3. Instance variables are from the block's context only
|
192
|
+
4. Nested DSL evaluation, correctly chaining method and variable handling
|
193
|
+
from the inner to the outer DSL scopes
|
194
|
+
5. Alternatives for both imperative and functional styles of DSL objects
|
156
195
|
|
157
196
|
## Installation
|
158
197
|
|
@@ -173,12 +212,14 @@ Version 1.0.x works on [all ruby versions since 1.8.7](https://github.com/ms-ati
|
|
173
212
|
## Note on Patches/Pull Requests
|
174
213
|
|
175
214
|
* Fork the project.
|
176
|
-
* Setup your development environment with:
|
215
|
+
* Setup your development environment with:
|
216
|
+
`gem install bundler; bundle install`
|
177
217
|
* Make your feature addition or bug fix.
|
178
|
-
* Add tests for it. This is important so I don't break it in a
|
179
|
-
|
218
|
+
* Add tests for it. This is important so I don't break it in a future version
|
219
|
+
unintentionally.
|
180
220
|
* Commit, do not mess with rakefile, version, or history.
|
181
|
-
|
221
|
+
(if you want to have your own version, that is fine but bump version in a
|
222
|
+
commit by itself I can ignore when I pull)
|
182
223
|
* Send me a pull request. Bonus points for topic branches.
|
183
224
|
|
184
225
|
## Copyright
|
data/lib/docile.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require "docile/version"
|
2
|
+
require "docile/execution"
|
2
3
|
require "docile/fallback_context_proxy"
|
4
|
+
require "docile/chaining_fallback_context_proxy"
|
3
5
|
|
4
|
-
# Docile keeps your Ruby DSLs tame and well-behaved
|
5
|
-
# @see http://ms-ati.github.com/docile/
|
6
|
+
# Docile keeps your Ruby DSLs tame and well-behaved.
|
6
7
|
module Docile
|
8
|
+
extend Execution
|
9
|
+
|
7
10
|
# Execute a block in the context of an object whose methods represent the
|
8
11
|
# commands in a DSL.
|
9
12
|
#
|
@@ -11,8 +14,9 @@ module Docile
|
|
11
14
|
#
|
12
15
|
# Use this method to execute an *imperative* DSL, which means that:
|
13
16
|
#
|
14
|
-
# 1.
|
15
|
-
# 2.
|
17
|
+
# 1. Each command mutates the state of the DSL context object
|
18
|
+
# 2. The return value of each command is ignored
|
19
|
+
# 3. The final return value is the original context object
|
16
20
|
#
|
17
21
|
# @example Use a String as a DSL
|
18
22
|
# Docile.dsl_eval("Hello, world!") do
|
@@ -36,21 +40,45 @@ module Docile
|
|
36
40
|
# `dsl` context object
|
37
41
|
# @return [Object] the `dsl` context object after executing the block
|
38
42
|
def dsl_eval(dsl, *args, &block)
|
39
|
-
|
40
|
-
proxy_context = FallbackContextProxy.new(dsl, block_context)
|
41
|
-
begin
|
42
|
-
block_context.instance_variables.each do |ivar|
|
43
|
-
value_from_block = block_context.instance_variable_get(ivar)
|
44
|
-
proxy_context.instance_variable_set(ivar, value_from_block)
|
45
|
-
end
|
46
|
-
proxy_context.instance_exec(*args, &block)
|
47
|
-
ensure
|
48
|
-
block_context.instance_variables.each do |ivar|
|
49
|
-
value_from_dsl_proxy = proxy_context.instance_variable_get(ivar)
|
50
|
-
block_context.instance_variable_set(ivar, value_from_dsl_proxy)
|
51
|
-
end
|
52
|
-
end
|
43
|
+
exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
|
53
44
|
dsl
|
54
45
|
end
|
55
46
|
module_function :dsl_eval
|
47
|
+
|
48
|
+
# Execute a block in the context of an immutable object whose methods,
|
49
|
+
# and the methods of their return values, represent the commands in a DSL.
|
50
|
+
#
|
51
|
+
# @note Use with a *functional* DSL (commands return successor
|
52
|
+
# context objects)
|
53
|
+
#
|
54
|
+
# Use this method to execute a *functional* DSL, which means that:
|
55
|
+
#
|
56
|
+
# 1. The original DSL context object is never mutated
|
57
|
+
# 2. Each command returns the next DSL context object
|
58
|
+
# 3. The final return value is the value returned by the last command
|
59
|
+
#
|
60
|
+
# @example Use a frozen String as a DSL
|
61
|
+
# Docile.dsl_eval_immutable("I'm immutable!".freeze) do
|
62
|
+
# reverse
|
63
|
+
# upcase
|
64
|
+
# end
|
65
|
+
# #=> "!ELBATUMMI M'I"
|
66
|
+
#
|
67
|
+
# @example Use a Float as a DSL
|
68
|
+
# Docile.dsl_eval_immutable(84.5) do
|
69
|
+
# fdiv(2)
|
70
|
+
# floor
|
71
|
+
# end
|
72
|
+
# #=> 42
|
73
|
+
#
|
74
|
+
# @param dsl [Object] immutable context object whose methods make up the
|
75
|
+
# initial DSL
|
76
|
+
# @param args [Array] arguments to be passed to the block
|
77
|
+
# @yield the block of DSL commands to be executed against the
|
78
|
+
# `dsl` context object and successor return values
|
79
|
+
# @return [Object] the return value of the final command in the block
|
80
|
+
def dsl_eval_immutable(dsl, *args, &block)
|
81
|
+
exec_in_proxy_context(dsl, ChainingFallbackContextProxy, *args, &block)
|
82
|
+
end
|
83
|
+
module_function :dsl_eval_immutable
|
56
84
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "docile/fallback_context_proxy"
|
2
|
+
|
3
|
+
module Docile
|
4
|
+
# @api private
|
5
|
+
#
|
6
|
+
# Operates in the same manner as {FallbackContextProxy}, but replacing
|
7
|
+
# the primary `receiver` object with the result of each proxied method.
|
8
|
+
#
|
9
|
+
# This is useful for implementing DSL evaluation for immutable context
|
10
|
+
# objects.
|
11
|
+
#
|
12
|
+
# @see Docile#dsl_eval_immutable
|
13
|
+
class ChainingFallbackContextProxy < FallbackContextProxy
|
14
|
+
# Proxy methods as in {FallbackContextProxy#method_missing}, replacing
|
15
|
+
# `receiver` with the returned value.
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
@__receiver__ = super(method, *args, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Docile
|
2
|
+
# @api private
|
3
|
+
#
|
4
|
+
# A namespace for functions relating to the execution of a block agsinst a
|
5
|
+
# proxy object.
|
6
|
+
module Execution
|
7
|
+
# Execute a block in the context of an object whose methods represent the
|
8
|
+
# commands in a DSL, using a specific proxy class.
|
9
|
+
#
|
10
|
+
# @param dsl [Object] context object whose methods make up the
|
11
|
+
# (initial) DSL
|
12
|
+
# @param proxy_type [FallbackContextProxy,
|
13
|
+
# ChainingFallbackContextProxy]
|
14
|
+
# class to instantiate as the proxy context
|
15
|
+
# @param args [Array] arguments to be passed to the block
|
16
|
+
# @yield the block of DSL commands to be executed
|
17
|
+
# @return [Object] the return value of the block
|
18
|
+
def exec_in_proxy_context(dsl, proxy_type, *args, &block)
|
19
|
+
block_context = eval("self", block.binding)
|
20
|
+
proxy_context = proxy_type.new(dsl, proxy_type.new(dsl, block_context))
|
21
|
+
begin
|
22
|
+
block_context.instance_variables.each do |ivar|
|
23
|
+
value_from_block = block_context.instance_variable_get(ivar)
|
24
|
+
proxy_context.instance_variable_set(ivar, value_from_block)
|
25
|
+
end
|
26
|
+
proxy_context.instance_exec(*args, &block)
|
27
|
+
ensure
|
28
|
+
block_context.instance_variables.each do |ivar|
|
29
|
+
value_from_dsl_proxy = proxy_context.instance_variable_get(ivar)
|
30
|
+
block_context.instance_variable_set(ivar, value_from_dsl_proxy)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
module_function :exec_in_proxy_context
|
35
|
+
end
|
36
|
+
end
|
@@ -1,12 +1,18 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
3
|
module Docile
|
4
|
+
# @api private
|
5
|
+
#
|
4
6
|
# A proxy object with a primary receiver as well as a secondary
|
5
7
|
# fallback receiver.
|
6
8
|
#
|
7
9
|
# Will attempt to forward all method calls first to the primary receiver,
|
8
10
|
# and then to the fallback receiver if the primary does not handle that
|
9
11
|
# method.
|
12
|
+
#
|
13
|
+
# This is useful for implementing DSL evaluation in the context of an object.
|
14
|
+
#
|
15
|
+
# @see Docile#dsl_eval
|
10
16
|
class FallbackContextProxy
|
11
17
|
# The set of methods which will **not** be proxied, but instead answered
|
12
18
|
# by this object directly.
|
data/lib/docile/version.rb
CHANGED
data/spec/docile_spec.rb
CHANGED
@@ -2,24 +2,36 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
describe Docile do
|
4
4
|
|
5
|
-
|
5
|
+
describe "#dsl_eval" do
|
6
|
+
|
7
|
+
context "when DSL context object is an Array" do
|
8
|
+
let(:array) { [] }
|
9
|
+
let!(:result) { execute_dsl_against_array }
|
10
|
+
|
11
|
+
def execute_dsl_against_array
|
12
|
+
Docile.dsl_eval(array) do
|
13
|
+
push 1
|
14
|
+
push 2
|
15
|
+
pop
|
16
|
+
push 3
|
17
|
+
end
|
18
|
+
end
|
6
19
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
push 2
|
11
|
-
pop
|
12
|
-
push 3
|
13
|
-
end.should == [1, 3]
|
14
|
-
end
|
20
|
+
it "executes the block against the DSL context object" do
|
21
|
+
array.should == [1, 3]
|
22
|
+
end
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
24
|
+
it "returns the DSL object after executing block against it" do
|
25
|
+
result.should == array
|
26
|
+
end
|
27
|
+
|
28
|
+
it "doesn't proxy #__id__" do
|
29
|
+
Docile.dsl_eval(array) { __id__.should_not == array.__id__ }
|
30
|
+
end
|
20
31
|
|
21
|
-
|
22
|
-
|
32
|
+
it "raises NoMethodError if the DSL object doesn't implement the method" do
|
33
|
+
expect { Docile.dsl_eval(array) { no_such_method } }.to raise_error(NoMethodError)
|
34
|
+
end
|
23
35
|
end
|
24
36
|
|
25
37
|
Pizza = Struct.new(:cheese, :pepperoni, :bacon, :sauce)
|
@@ -34,13 +46,22 @@ describe Docile do
|
|
34
46
|
end
|
35
47
|
end
|
36
48
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
sauce
|
43
|
-
|
49
|
+
context "when DSL context object is a Builder pattern" do
|
50
|
+
let(:builder) { PizzaBuilder.new }
|
51
|
+
let(:result) { execute_dsl_against_builder_and_call_build }
|
52
|
+
|
53
|
+
def execute_dsl_against_builder_and_call_build
|
54
|
+
@sauce = :extra
|
55
|
+
Docile.dsl_eval(builder) do
|
56
|
+
bacon
|
57
|
+
cheese
|
58
|
+
sauce @sauce
|
59
|
+
end.build
|
60
|
+
end
|
61
|
+
|
62
|
+
it "returns correctly built object" do
|
63
|
+
result.should == Pizza.new(true, false, true, :extra)
|
64
|
+
end
|
44
65
|
end
|
45
66
|
|
46
67
|
class InnerDSL
|
@@ -65,12 +86,12 @@ describe Docile do
|
|
65
86
|
Docile.dsl_eval(OuterDSL.new, &block)
|
66
87
|
end
|
67
88
|
|
68
|
-
|
69
|
-
|
70
|
-
|
89
|
+
context "when given parameters for the DSL block" do
|
90
|
+
def parameterized(*args, &block)
|
91
|
+
Docile.dsl_eval(OuterDSL.new, *args, &block)
|
92
|
+
end
|
71
93
|
|
72
|
-
|
73
|
-
it "should pass parameters to the block" do
|
94
|
+
it "passes parameters to the block" do
|
74
95
|
parameterized(1,2,3) do |x,y,z|
|
75
96
|
x.should == 1
|
76
97
|
y.should == 2
|
@@ -78,11 +99,11 @@ describe Docile do
|
|
78
99
|
end
|
79
100
|
end
|
80
101
|
|
81
|
-
it "
|
102
|
+
it "finds parameters before methods" do
|
82
103
|
parameterized(1) { |a| a.should == 1 }
|
83
104
|
end
|
84
105
|
|
85
|
-
it "
|
106
|
+
it "find outer dsl parameters in inner dsl scope" do
|
86
107
|
parameterized(1,2,3) do |a,b,c|
|
87
108
|
inner_with_params(c) do |d,e|
|
88
109
|
a.should == 1
|
@@ -95,140 +116,207 @@ describe Docile do
|
|
95
116
|
end
|
96
117
|
end
|
97
118
|
|
98
|
-
context "
|
99
|
-
it "should find method of outer dsl in outer dsl scope" do
|
100
|
-
outer { a.should == 'a' }
|
101
|
-
end
|
119
|
+
context "when DSL blocks are nested" do
|
102
120
|
|
103
|
-
|
104
|
-
|
105
|
-
|
121
|
+
context "method lookup" do
|
122
|
+
it "finds method of outer dsl in outer dsl scope" do
|
123
|
+
outer { a.should == 'a' }
|
124
|
+
end
|
106
125
|
|
107
|
-
|
108
|
-
|
109
|
-
|
126
|
+
it "finds method of inner dsl in inner dsl scope" do
|
127
|
+
outer { inner { b.should == 'b' } }
|
128
|
+
end
|
110
129
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
130
|
+
it "finds method of outer dsl in inner dsl scope" do
|
131
|
+
outer { inner { a.should == 'a' } }
|
132
|
+
end
|
115
133
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
134
|
+
it "finds method of block's context in outer dsl scope" do
|
135
|
+
def c; 'c'; end
|
136
|
+
outer { c.should == 'c' }
|
137
|
+
end
|
138
|
+
|
139
|
+
it "finds method of block's context in inner dsl scope" do
|
140
|
+
def c; 'c'; end
|
141
|
+
outer { inner { c.should == 'c' } }
|
142
|
+
end
|
120
143
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
144
|
+
it "finds method of outer dsl in preference to block context" do
|
145
|
+
def a; 'not a'; end
|
146
|
+
outer { a.should == 'a' }
|
147
|
+
outer { inner { a.should == 'a' } }
|
148
|
+
end
|
125
149
|
end
|
126
|
-
end
|
127
150
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
151
|
+
context "local variable lookup" do
|
152
|
+
it "finds local variable from block context in outer dsl scope" do
|
153
|
+
foo = 'foo'
|
154
|
+
outer { foo.should == 'foo' }
|
155
|
+
end
|
156
|
+
|
157
|
+
it "finds local variable from block definition in inner dsl scope" do
|
158
|
+
bar = 'bar'
|
159
|
+
outer { inner { bar.should == 'bar' } }
|
160
|
+
end
|
132
161
|
end
|
133
162
|
|
134
|
-
|
135
|
-
|
136
|
-
|
163
|
+
context "instance variable lookup" do
|
164
|
+
it "finds instance variable from block definition in outer dsl scope" do
|
165
|
+
@iv1 = 'iv1'; outer { @iv1.should == 'iv1' }
|
166
|
+
end
|
167
|
+
|
168
|
+
it "proxies instance variable assignments in block in outer dsl scope back into block's context" do
|
169
|
+
@iv1 = 'foo'; outer { @iv1 = 'bar' }; @iv1.should == 'bar'
|
170
|
+
end
|
171
|
+
|
172
|
+
it "finds instance variable from block definition in inner dsl scope" do
|
173
|
+
@iv2 = 'iv2'; outer { inner { @iv2.should == 'iv2' } }
|
174
|
+
end
|
175
|
+
|
176
|
+
it "proxies instance variable assignments in block in inner dsl scope back into block's context" do
|
177
|
+
@iv2 = 'foo'; outer { inner { @iv2 = 'bar' } }; @iv2.should == 'bar'
|
178
|
+
end
|
137
179
|
end
|
180
|
+
|
138
181
|
end
|
139
182
|
|
140
|
-
context "
|
141
|
-
|
142
|
-
|
183
|
+
context "when DSL context object is a Dispatch pattern" do
|
184
|
+
class DispatchScope
|
185
|
+
def params
|
186
|
+
{ :a => 1, :b => 2, :c => 3 }
|
187
|
+
end
|
143
188
|
end
|
144
189
|
|
145
|
-
|
146
|
-
|
147
|
-
|
190
|
+
class MessageDispatch
|
191
|
+
include Singleton
|
192
|
+
|
193
|
+
def initialize
|
194
|
+
@responders = {}
|
195
|
+
end
|
196
|
+
|
197
|
+
def add_responder path, &block
|
198
|
+
@responders[path] = block
|
199
|
+
end
|
148
200
|
|
149
|
-
|
150
|
-
|
201
|
+
def dispatch path, request
|
202
|
+
Docile.dsl_eval(DispatchScope.new, request, &@responders[path])
|
203
|
+
end
|
151
204
|
end
|
152
205
|
|
153
|
-
|
154
|
-
|
206
|
+
def respond(path, &block)
|
207
|
+
MessageDispatch.instance.add_responder(path, &block)
|
155
208
|
end
|
156
|
-
end
|
157
209
|
|
158
|
-
|
159
|
-
|
160
|
-
{ :a => 1, :b => 2, :c => 3 }
|
210
|
+
def send_request(path, request)
|
211
|
+
MessageDispatch.instance.dispatch(path, request)
|
161
212
|
end
|
162
|
-
end
|
163
213
|
|
164
|
-
|
165
|
-
|
214
|
+
it "dispatches correctly" do
|
215
|
+
@first = @second = nil
|
166
216
|
|
167
|
-
|
168
|
-
|
169
|
-
|
217
|
+
respond '/path' do |request|
|
218
|
+
@first = request
|
219
|
+
end
|
170
220
|
|
171
|
-
|
172
|
-
|
173
|
-
|
221
|
+
respond '/new_bike' do |bike|
|
222
|
+
@second = "Got a new #{bike}"
|
223
|
+
end
|
224
|
+
|
225
|
+
def x(y) ; "Got a #{y}"; end
|
226
|
+
respond '/third' do |third|
|
227
|
+
x(third).should == 'Got a third thing'
|
228
|
+
end
|
229
|
+
|
230
|
+
fourth = nil
|
231
|
+
respond '/params' do |arg|
|
232
|
+
fourth = params[arg]
|
233
|
+
end
|
234
|
+
|
235
|
+
send_request '/path', 1
|
236
|
+
send_request '/new_bike', 'ten speed'
|
237
|
+
send_request '/third', 'third thing'
|
238
|
+
send_request '/params', :b
|
174
239
|
|
175
|
-
|
176
|
-
|
240
|
+
@first.should == 1
|
241
|
+
@second.should == 'Got a new ten speed'
|
242
|
+
fourth.should == 2
|
177
243
|
end
|
178
|
-
end
|
179
244
|
|
180
|
-
def respond path, &block
|
181
|
-
MessageDispatch.instance.add_responder path, &block
|
182
245
|
end
|
183
246
|
|
184
|
-
|
185
|
-
MessageDispatch.instance.dispatch path, request
|
186
|
-
end
|
247
|
+
end
|
187
248
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
249
|
+
describe "#dsl_eval_immutable" do
|
250
|
+
|
251
|
+
context "when DSL context object is a frozen String" do
|
252
|
+
let(:original) { "I'm immutable!".freeze }
|
253
|
+
let!(:result) { execute_non_mutating_dsl_against_string }
|
193
254
|
|
194
|
-
|
195
|
-
|
255
|
+
def execute_non_mutating_dsl_against_string
|
256
|
+
Docile.dsl_eval_immutable(original) do
|
257
|
+
reverse
|
258
|
+
upcase
|
259
|
+
end
|
196
260
|
end
|
197
261
|
|
198
|
-
|
199
|
-
|
200
|
-
x(third).should == 'Got a third thing'
|
262
|
+
it "doesn't modify the original string" do
|
263
|
+
original.should == "I'm immutable!"
|
201
264
|
end
|
202
265
|
|
203
|
-
|
204
|
-
|
205
|
-
fourth = params[arg]
|
266
|
+
it "chains the commands in the block against the DSL context object" do
|
267
|
+
result.should == "!ELBATUMMI M'I"
|
206
268
|
end
|
269
|
+
end
|
207
270
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
send_request '/params', :b
|
271
|
+
context "when DSL context object is a number" do
|
272
|
+
let(:original) { 84.5 }
|
273
|
+
let!(:result) { execute_non_mutating_dsl_against_number }
|
212
274
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
275
|
+
def execute_non_mutating_dsl_against_number
|
276
|
+
Docile.dsl_eval_immutable(original) do
|
277
|
+
fdiv(2)
|
278
|
+
floor
|
279
|
+
end
|
280
|
+
end
|
217
281
|
|
282
|
+
it "chains the commands in the block against the DSL context object" do
|
283
|
+
result.should == 42
|
284
|
+
end
|
285
|
+
end
|
218
286
|
end
|
219
287
|
|
220
|
-
|
288
|
+
end
|
221
289
|
|
222
|
-
|
223
|
-
|
224
|
-
|
290
|
+
describe Docile::FallbackContextProxy do
|
291
|
+
|
292
|
+
describe "#instance_variables" do
|
293
|
+
subject { create_fcp_and_set_one_instance_variable.instance_variables }
|
294
|
+
let(:expected_type_of_names) { type_of_ivar_names_on_this_ruby }
|
295
|
+
let(:actual_type_of_names) { subject.first.class }
|
296
|
+
let(:excluded) { Docile::FallbackContextProxy::NON_PROXIED_INSTANCE_VARIABLES }
|
225
297
|
|
298
|
+
def create_fcp_and_set_one_instance_variable
|
226
299
|
fcp = Docile::FallbackContextProxy.new(nil, nil)
|
227
300
|
fcp.instance_variable_set(:@foo, "foo")
|
301
|
+
fcp
|
302
|
+
end
|
228
303
|
|
229
|
-
|
304
|
+
def type_of_ivar_names_on_this_ruby
|
305
|
+
@a = 1
|
306
|
+
instance_variables.first.class
|
230
307
|
end
|
231
308
|
|
309
|
+
it "returns proxied instance variables" do
|
310
|
+
subject.map(&:to_sym).should include(:@foo)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "doesn't return non-proxied instance variables" do
|
314
|
+
subject.map(&:to_sym).should_not include(*excluded)
|
315
|
+
end
|
316
|
+
|
317
|
+
it "preserves the type (String or Symbol) of names on this ruby version" do
|
318
|
+
actual_type_of_names.should == expected_type_of_names
|
319
|
+
end
|
232
320
|
end
|
233
321
|
|
234
322
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: docile
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Marc Siegel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-07-
|
11
|
+
date: 2013-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -104,6 +104,7 @@ extra_rdoc_files: []
|
|
104
104
|
files:
|
105
105
|
- .coveralls.yml
|
106
106
|
- .gitignore
|
107
|
+
- .rspec
|
107
108
|
- .ruby-gemset
|
108
109
|
- .ruby-version
|
109
110
|
- .travis.yml
|
@@ -115,6 +116,8 @@ files:
|
|
115
116
|
- Rakefile
|
116
117
|
- docile.gemspec
|
117
118
|
- lib/docile.rb
|
119
|
+
- lib/docile/chaining_fallback_context_proxy.rb
|
120
|
+
- lib/docile/execution.rb
|
118
121
|
- lib/docile/fallback_context_proxy.rb
|
119
122
|
- lib/docile/version.rb
|
120
123
|
- spec/docile_spec.rb
|