docile 1.0.5 → 1.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.
- 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
|
[](http://badge.fury.io/rb/docile)
|
12
3
|
[](https://travis-ci.org/ms-ati/docile)
|
13
4
|
[](https://gemnasium.com/ms-ati/docile)
|
14
5
|
[](https://codeclimate.com/github/ms-ati/docile)
|
15
6
|
[](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
|