dsl_maker 0.0.1 → 0.0.2
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/Changes +10 -0
- data/README.md +34 -5
- data/lib/dsl/maker/version.rb +1 -1
- data/lib/dsl/maker.rb +198 -176
- data/spec/multi_level_spec.rb +71 -39
- data/spec/multiple_invocation_spec.rb +29 -0
- data/spec/single_level_spec.rb +36 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a86f0dfab03af6d1b25904ef7c7dd0ef4090b33
|
4
|
+
data.tar.gz: f63cd7dd2beaa1850851dba11a42d19ac67300a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb2d7b7e998e65c35b595904fe49fb0b42c549d8796d303a5ac9b5faa366f7b6fb12e4ba3a74fef8517c961eb5a9086b44fde8a1080481a4028e2eb6a9deeffb
|
7
|
+
data.tar.gz: 8a9aafadbc5c3c4a99e071609e49abf2b95f115b8106431e272500b96fad7afa26e913bd465fc762ff5525b1718dec33edcf1c8e83283c89cdf84a3cca43429a
|
data/Changes
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Revision history for DSL::Maker (ordered by revision number).
|
2
|
+
|
3
|
+
0.0.2 Jul 22 2015
|
4
|
+
- Allow add_entrypoint() to take a DSL class instead of args
|
5
|
+
- Added execute_dsl() that takes a block
|
6
|
+
- Several refactorings to improve maintainability
|
7
|
+
- Recursive DSLs are now simpler to define and maintain
|
8
|
+
|
9
|
+
0.0.1 Jul 21 2015
|
10
|
+
- Initial release
|
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# DSL::Maker
|
2
2
|
|
3
|
+
[](https://rubygems.org/gems/dsl_maker)
|
4
|
+
[](https://rubygems.org/gems/dsl_maker)
|
5
|
+
[](http://rubydoc.info/github/robkinyon/ruby-dsl-maker)
|
6
|
+
|
3
7
|
[](https://travis-ci.org/robkinyon/ruby-dsl-maker)
|
4
8
|
[](https://codeclimate.com/github/robkinyon/ruby-dsl-maker)
|
5
9
|
[](https://codecov.io/github/robkinyon/ruby-dsl-maker)
|
@@ -157,7 +161,7 @@ best to explain.
|
|
157
161
|
|
158
162
|
```ruby
|
159
163
|
Person = Struct.new(:name, :age, :mother, :father)
|
160
|
-
class
|
164
|
+
class FamilyTreeDSL < DSL::Maker
|
161
165
|
parent = generate_dsl({
|
162
166
|
:name => String,
|
163
167
|
:age => String,
|
@@ -177,7 +181,7 @@ class FamilyTree < DSL::Maker
|
|
177
181
|
end
|
178
182
|
end
|
179
183
|
|
180
|
-
john_smith =
|
184
|
+
john_smith = FamilyTreeDSL.parse_dsl("
|
181
185
|
person 'John Smith' do
|
182
186
|
age 20
|
183
187
|
mother 'Mary Smith' do
|
@@ -193,6 +197,31 @@ john_smith = FamilyTree.parse_dsl("
|
|
193
197
|
|
194
198
|
The result is exactly the same as before.
|
195
199
|
|
200
|
+
### Recursive DSLs
|
201
|
+
|
202
|
+
We're making an artificial distinction between `person` and `parent` in the
|
203
|
+
`FamilyTreeDSL` example above. Really, we want to say "A person can have a mother
|
204
|
+
and a father and those are also persons." So, let's say that.
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
Person = Struct.new(:name, :age, :mother, :father)
|
208
|
+
|
209
|
+
class FamilyTreeDSL < DSL::Maker
|
210
|
+
person_dsl = add_entrypoint(:person, {
|
211
|
+
:name => String,
|
212
|
+
:age => String,
|
213
|
+
}) do |*args|
|
214
|
+
default(:name, args)
|
215
|
+
Person.new(name, age mother, father)
|
216
|
+
end
|
217
|
+
|
218
|
+
build_dsl_element(person_dsl, :mother, person_dsl)
|
219
|
+
build_dsl_element(person_dsl, :father, person_dsl)
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
Now, we can handle an arbitrarily-deep family tree.
|
224
|
+
|
196
225
|
### Handling multiple items
|
197
226
|
|
198
227
|
Chef's recipe files have many entries of different types in them. It doesn't do
|
@@ -239,7 +268,7 @@ you get back an `Array` with everything in the right order.
|
|
239
268
|
|
240
269
|
### Class Methods
|
241
270
|
|
242
|
-
DSL::Maker provides
|
271
|
+
DSL::Maker provides five class methods - three for constructing your DSL and two
|
243
272
|
for parsing your DSL.
|
244
273
|
|
245
274
|
* `add_entrypoint(Symbol, Hash={}, Block)`
|
@@ -261,13 +290,13 @@ This is normally called by `generate_dsl()` to actually construct the DSL elemen
|
|
261
290
|
It is provided for you so that you can create recursive DSL definitions. Look at
|
262
291
|
the tests in `spec/multi_level_spec.rb` for an example of this.
|
263
292
|
|
264
|
-
* `parse_dsl(String)`
|
293
|
+
* `parse_dsl(String)` / `execute_dsl(&block)`
|
265
294
|
|
266
295
|
You call this on your DSL class when you're ready to invoke your DSL. It will
|
267
296
|
return whatever the block provided `add_entrypoint()` returns.
|
268
297
|
|
269
298
|
In the case of multiple DSL entrypoints (for example, a normal Chef recipe),
|
270
|
-
|
299
|
+
these methods will return an array with all the return values in the order of
|
271
300
|
invocation.
|
272
301
|
|
273
302
|
### Coercions
|
data/lib/dsl/maker/version.rb
CHANGED
data/lib/dsl/maker.rb
CHANGED
@@ -2,206 +2,228 @@ require 'dsl/maker/version'
|
|
2
2
|
|
3
3
|
require 'docile'
|
4
4
|
|
5
|
-
#
|
6
|
-
|
7
|
-
# This is
|
8
|
-
class
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
define_method(name) { result }
|
16
|
-
end
|
5
|
+
# This is the base class we provide.
|
6
|
+
class DSL::Maker
|
7
|
+
# This is a useful class that contains all the Boolean handling we have.
|
8
|
+
class Boolean
|
9
|
+
{
|
10
|
+
:yes => true, :no => false,
|
11
|
+
:on => true, :off => false,
|
12
|
+
}.each do |name, result|
|
13
|
+
define_method(name) { result }
|
14
|
+
end
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
return
|
16
|
+
# 21 character method names are obscene. Make it easier to read.
|
17
|
+
alias :___set :instance_variable_set
|
18
|
+
|
19
|
+
# 21 character method names are obscene. Make it easier to read.
|
20
|
+
alias :___get :instance_variable_get
|
21
|
+
|
22
|
+
# A helper method for handling defaults from args easily.
|
23
|
+
#
|
24
|
+
# @param method_name [String] The name of the attribute being defaulted.
|
25
|
+
# @param args [Array] The arguments provided to the block.
|
26
|
+
# @param position [Integer] The index in args to work with, default 0.
|
27
|
+
#
|
28
|
+
# @return nil
|
29
|
+
def default(method_name, args, position=0)
|
30
|
+
method = method_name.to_sym
|
31
|
+
if args.length >= (position + 1) && !self.send(method)
|
32
|
+
self.send(method, args[position])
|
37
33
|
end
|
34
|
+
return
|
38
35
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# The bang-bang boolean-izes the value. We want this to be lossy.
|
46
|
-
!!value
|
36
|
+
end
|
37
|
+
Yes = On = True = true
|
38
|
+
No = Off = False = false
|
39
|
+
$to_bool = lambda do |value|
|
40
|
+
if value
|
41
|
+
return false if %w(no off false nil).include? value.to_s.downcase
|
47
42
|
end
|
43
|
+
# The bang-bang boolean-izes the value. We want this to be lossy.
|
44
|
+
!!value
|
45
|
+
end
|
48
46
|
|
49
|
-
|
47
|
+
# TODO: Is this safe if the invoker doesn't use parse_dsl()?
|
48
|
+
@@accumulator = []
|
49
|
+
|
50
|
+
# Parse the DSL provided in the parameter.
|
51
|
+
#
|
52
|
+
# @note If the DSL contains multiple entrypoints, then this will return an
|
53
|
+
# Array. This is desirable.
|
54
|
+
#
|
55
|
+
# @param dsl [String] The DSL to be parsed by this class.
|
56
|
+
#
|
57
|
+
# @return [Object] Whatever is returned by the block defined in this class.
|
58
|
+
def self.parse_dsl(dsl)
|
59
|
+
# add_entrypoint() will use @@accumulator to handle multiple entrypoints.
|
60
|
+
# Reset it here so that we're only handling the values from this run.
|
50
61
|
@@accumulator = []
|
62
|
+
eval dsl, self.get_binding
|
63
|
+
if @@accumulator.length <= 1
|
64
|
+
return @@accumulator[0]
|
65
|
+
end
|
66
|
+
return @@accumulator
|
67
|
+
end
|
51
68
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
if @@accumulator.length <= 1
|
66
|
-
return @@accumulator[0]
|
67
|
-
end
|
68
|
-
return @@accumulator
|
69
|
+
# Execute the DSL provided in the block.
|
70
|
+
#
|
71
|
+
# @note If the DSL contains multiple entrypoints, then this will return an
|
72
|
+
# Array. This is desirable.
|
73
|
+
#
|
74
|
+
# @param &block [Block] The DSL to be executed by this class.
|
75
|
+
#
|
76
|
+
# @return [Object] Whatever is returned by &block
|
77
|
+
def self.execute_dsl(&block)
|
78
|
+
@@accumulator = []
|
79
|
+
instance_eval(&block)
|
80
|
+
if @@accumulator.length <= 1
|
81
|
+
return @@accumulator[0]
|
69
82
|
end
|
83
|
+
return @@accumulator
|
84
|
+
end
|
70
85
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
86
|
+
@@dsl_elements = {
|
87
|
+
String => ->(klass, name, type) {
|
88
|
+
as_attr = '@' + name.to_s
|
89
|
+
klass.class_eval do
|
90
|
+
define_method(name.to_sym) do |*args|
|
91
|
+
___set(as_attr, args[0].to_s) unless args.empty?
|
92
|
+
___get(as_attr)
|
79
93
|
end
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
94
|
+
end
|
95
|
+
},
|
96
|
+
Boolean => ->(klass, name, type) {
|
97
|
+
as_attr = '@' + name.to_s
|
98
|
+
klass.class_eval do
|
99
|
+
define_method(name.to_sym) do |*args|
|
100
|
+
___set(as_attr, $to_bool.call(args[0])) unless args.empty?
|
101
|
+
# Ensure that the default nil returns as false.
|
102
|
+
!!___get(as_attr)
|
89
103
|
end
|
90
|
-
|
91
|
-
}
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
___get(as_attr)
|
104
|
+
end
|
105
|
+
},
|
106
|
+
}
|
107
|
+
|
108
|
+
# Add a single element of a DSL to a class representing a level in a DSL.
|
109
|
+
#
|
110
|
+
# Each of the types represents a coercion - a guarantee and check of the value
|
111
|
+
# in that name. The standard coercions are:
|
112
|
+
#
|
113
|
+
# * String - whatever you give is returned.
|
114
|
+
# * Boolean - the truthiness of whatever you give is returned.
|
115
|
+
# * generate_dsl() - this represents a new level of the DSL.
|
116
|
+
#
|
117
|
+
# @param klass [Class] The class representing this level in the DSL.
|
118
|
+
# @param name [String] The name of the element we're working on.
|
119
|
+
# @param type [Class] The type of this element we're working on.
|
120
|
+
# This is the coercion spoken above.
|
121
|
+
#
|
122
|
+
# @return nil
|
123
|
+
def self.build_dsl_element(klass, name, type)
|
124
|
+
if @@dsl_elements.has_key?(type)
|
125
|
+
@@dsl_elements[type].call(klass, name, type)
|
126
|
+
elsif type.is_a?(Class) && type.ancestors.include?(Boolean)
|
127
|
+
as_attr = '@' + name.to_s
|
128
|
+
klass.class_eval do
|
129
|
+
define_method(name.to_sym) do |*args, &dsl_block|
|
130
|
+
unless (args.empty? && !dsl_block)
|
131
|
+
obj = type.new
|
132
|
+
Docile.dsl_eval(obj, &dsl_block) if dsl_block
|
133
|
+
|
134
|
+
# I don't know why this code doesn't work, but it's why __apply().
|
135
|
+
#___set(as_attr, obj.instance_exec(*args, &defn_block))
|
136
|
+
___set(as_attr, obj.__apply(*args))
|
124
137
|
end
|
138
|
+
___get(as_attr)
|
125
139
|
end
|
126
|
-
else
|
127
|
-
raise "Unrecognized element type '#{type}'"
|
128
140
|
end
|
129
|
-
|
130
|
-
|
141
|
+
else
|
142
|
+
raise "Unrecognized element type '#{type}'"
|
131
143
|
end
|
132
144
|
|
133
|
-
|
134
|
-
|
135
|
-
# In order for Docile to parse a DSL, each level must be represented by a
|
136
|
-
# different class. This method creates anonymous classes that each represents
|
137
|
-
# a different level in the DSL's structure.
|
138
|
-
#
|
139
|
-
# The creation of each DSL element is delegated to build_dsl_element.
|
140
|
-
#
|
141
|
-
# @param args [Hash] the elements of the DSL block (passed to generate_dsl)
|
142
|
-
# @param defn_block [Proc] what is executed once the DSL block is parsed.
|
143
|
-
#
|
144
|
-
# @return [Class] The class that implements this level's DSL definition.
|
145
|
-
def self.generate_dsl(args={}, &defn_block)
|
146
|
-
raise 'Block required for generate_dsl' unless block_given?
|
147
|
-
|
148
|
-
# Inherit from the Boolean class to gain access to the useful methods
|
149
|
-
# TODO: Convert DSL::Maker::Boolean into a Role
|
150
|
-
# TODO: Create a DSL::Maker::Base class to inherit from
|
151
|
-
klass = Class.new(Boolean) do
|
152
|
-
# This instance method exists because we cannot seem to inline its work
|
153
|
-
# where we call it. Could it be a problem of incorrect binding?
|
154
|
-
# It has to be defined here because it needs access to &defn_block
|
155
|
-
define_method(:__apply) do |*args|
|
156
|
-
instance_exec(*args, &defn_block)
|
157
|
-
end
|
158
|
-
end
|
145
|
+
return
|
146
|
+
end
|
159
147
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
148
|
+
# Add the meat of a DSL block to some level of this class's DSL.
|
149
|
+
#
|
150
|
+
# In order for Docile to parse a DSL, each level must be represented by a
|
151
|
+
# different class. This method creates anonymous classes that each represents
|
152
|
+
# a different level in the DSL's structure.
|
153
|
+
#
|
154
|
+
# The creation of each DSL element is delegated to build_dsl_element.
|
155
|
+
#
|
156
|
+
# @param args [Hash] the elements of the DSL block (passed to generate_dsl)
|
157
|
+
# @param defn_block [Proc] what is executed once the DSL block is parsed.
|
158
|
+
#
|
159
|
+
# @return [Class] The class that implements this level's DSL definition.
|
160
|
+
def self.generate_dsl(args={}, &defn_block)
|
161
|
+
raise 'Block required for generate_dsl' unless block_given?
|
162
|
+
|
163
|
+
# Inherit from the Boolean class to gain access to the useful methods
|
164
|
+
# TODO: Convert DSL::Maker::Boolean into a Role
|
165
|
+
# TODO: Create a DSL::Maker::Base class to inherit from
|
166
|
+
dsl_class = Class.new(Boolean) do
|
167
|
+
# This instance method exists because we cannot seem to inline its work
|
168
|
+
# where we call it. Could it be a problem of incorrect binding?
|
169
|
+
# It has to be defined here because it needs access to &defn_block
|
170
|
+
define_method(:__apply) do |*args|
|
171
|
+
instance_exec(*args, &defn_block)
|
172
|
+
end
|
173
|
+
end
|
164
174
|
|
165
|
-
|
175
|
+
args.each do |name, type|
|
176
|
+
if dsl_class.new.respond_to? name.to_sym
|
177
|
+
raise "Illegal attribute name '#{name}'"
|
166
178
|
end
|
167
179
|
|
168
|
-
|
180
|
+
build_dsl_element(dsl_class, name, type)
|
169
181
|
end
|
170
182
|
|
171
|
-
|
172
|
-
|
173
|
-
# This delegates to generate_dsl() for the majority of the work.
|
174
|
-
#
|
175
|
-
# @param name [String] the name of the entrypoint
|
176
|
-
# @param args [Hash] the elements of the DSL block (passed to generate_dsl)
|
177
|
-
# @param defn_block [Proc] what is executed once the DSL block is parsed.
|
178
|
-
#
|
179
|
-
# @return nil
|
180
|
-
def self.add_entrypoint(name, args={}, &defn_block)
|
181
|
-
# Without defn_block, there's no way to give back the result of the
|
182
|
-
# DSL parsing. So, raise an error if we don't get one.
|
183
|
-
# TODO: Provide a default block that returns the datastructure as a HoH.
|
184
|
-
raise "Block required for add_entrypoint" unless block_given?
|
185
|
-
|
186
|
-
# Ensure that get_binding() exists in the child class. This is necessary to
|
187
|
-
# provide parse_dsl() so that eval works as expected. We have to do it here
|
188
|
-
# because this is the only place we know for certain will be called.
|
189
|
-
unless self.respond_to? :get_binding
|
190
|
-
define_singleton_method(:get_binding) { binding }
|
191
|
-
end
|
183
|
+
return dsl_class
|
184
|
+
end
|
192
185
|
|
193
|
-
|
194
|
-
|
195
|
-
|
186
|
+
# Add an entrypoint (top-level DSL element) to this class's DSL.
|
187
|
+
#
|
188
|
+
# This delegates to generate_dsl() for the majority of the work.
|
189
|
+
#
|
190
|
+
# @note `args` could be a Hash (to be passed to generate_dsl()) or the result
|
191
|
+
# of a call to generate_dsl().
|
192
|
+
#
|
193
|
+
# @param name [String] the name of the entrypoint
|
194
|
+
# @param args [Hash] the elements of the DSL block (passed to generate_dsl)
|
195
|
+
# @param defn_block [Proc] what is executed once the DSL block is parsed.
|
196
|
+
#
|
197
|
+
# @return [Class] The class that implements this level's DSL definition.
|
198
|
+
def self.add_entrypoint(name, args={}, &defn_block)
|
199
|
+
# Without defn_block, there's no way to give back the result of the
|
200
|
+
# DSL parsing. So, raise an error if we don't get one.
|
201
|
+
# TODO: Provide a default block that returns the datastructure as a HoH.
|
202
|
+
raise "Block required for add_entrypoint" unless block_given?
|
203
|
+
|
204
|
+
# Ensure that get_binding() exists in the child class. This is necessary to
|
205
|
+
# provide parse_dsl() so that eval works as expected. We have to do it here
|
206
|
+
# because this is the only place we know for certain will be called.
|
207
|
+
unless self.respond_to? :get_binding
|
208
|
+
define_singleton_method(:get_binding) { binding }
|
209
|
+
end
|
196
210
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
204
|
-
return
|
211
|
+
# FIXME: This is a wart. Really, we should be pulling out name, then
|
212
|
+
# yielding to generate_dsl() in some fashion.
|
213
|
+
if args.is_a?(Class) && args.ancestors.include?(Boolean)
|
214
|
+
dsl_class = args
|
215
|
+
else
|
216
|
+
dsl_class = generate_dsl(args, &defn_block)
|
205
217
|
end
|
218
|
+
|
219
|
+
define_singleton_method(name.to_sym) do |*args, &dsl_block|
|
220
|
+
obj = dsl_class.new
|
221
|
+
Docile.dsl_eval(obj, &dsl_block) if dsl_block
|
222
|
+
rv = obj.instance_exec(*args, &defn_block)
|
223
|
+
@@accumulator.push(rv)
|
224
|
+
return rv
|
225
|
+
end
|
226
|
+
|
227
|
+
return dsl_class
|
206
228
|
end
|
207
229
|
end
|
data/spec/multi_level_spec.rb
CHANGED
@@ -92,47 +92,43 @@ describe 'A multi-level DSL making family-trees' do
|
|
92
92
|
expect(person.child.child.name).to eq('Judith')
|
93
93
|
end
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
add_entrypoint(:person, {
|
105
|
-
:name => String,
|
106
|
-
:child => person,
|
107
|
-
}) do
|
108
|
-
Person.new(name, child)
|
95
|
+
describe "with recursion" do
|
96
|
+
it "can handle a single axis of recursion" do
|
97
|
+
dsl_class = Class.new(DSL::Maker) do
|
98
|
+
person_dsl = add_entrypoint(:person, {
|
99
|
+
:name => String,
|
100
|
+
}) do
|
101
|
+
Person.new(name, child)
|
102
|
+
end
|
103
|
+
build_dsl_element(person_dsl, :child, person_dsl)
|
109
104
|
end
|
110
|
-
end
|
111
105
|
|
112
|
-
|
113
|
-
|
114
|
-
person
|
115
|
-
|
116
|
-
|
117
|
-
name 'Seth'
|
106
|
+
# This list of names is taken from
|
107
|
+
# https://en.wikipedia.org/wiki/Family_tree_of_the_Bible
|
108
|
+
person = dsl_class.parse_dsl("
|
109
|
+
person {
|
110
|
+
name 'Adam'
|
118
111
|
child {
|
119
|
-
name '
|
112
|
+
name 'Seth'
|
120
113
|
child {
|
121
|
-
name '
|
114
|
+
name 'Enos'
|
122
115
|
child {
|
123
|
-
name '
|
116
|
+
name 'Cainan'
|
124
117
|
child {
|
125
|
-
name '
|
118
|
+
name 'Mahalaleel'
|
126
119
|
child {
|
127
|
-
name '
|
120
|
+
name 'Jared'
|
128
121
|
child {
|
129
|
-
name '
|
122
|
+
name 'Enoch'
|
130
123
|
child {
|
131
|
-
name '
|
124
|
+
name 'Methuselah'
|
132
125
|
child {
|
133
|
-
name '
|
126
|
+
name 'Lamech'
|
134
127
|
child {
|
135
|
-
name '
|
128
|
+
name 'Noah'
|
129
|
+
child {
|
130
|
+
name 'Shem'
|
131
|
+
}
|
136
132
|
}
|
137
133
|
}
|
138
134
|
}
|
@@ -143,16 +139,52 @@ describe 'A multi-level DSL making family-trees' do
|
|
143
139
|
}
|
144
140
|
}
|
145
141
|
}
|
146
|
-
|
147
|
-
|
142
|
+
")
|
143
|
+
|
144
|
+
[
|
145
|
+
'Adam', 'Seth', 'Enos', 'Cainan', 'Mahalaleel', 'Jared',
|
146
|
+
'Enoch', 'Methuselah', 'Lamech', 'Noah', 'Shem',
|
147
|
+
].each do |name|
|
148
|
+
expect(person).to be_instance_of(Person)
|
149
|
+
expect(person.name).to eq(name)
|
150
|
+
person = person.child
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
it "can handle two axes of recursion" do
|
155
|
+
OtherPerson = Struct.new(:name, :mother, :father)
|
156
|
+
dsl_class = Class.new(DSL::Maker) do
|
157
|
+
person_dsl = add_entrypoint(:person, {
|
158
|
+
:name => String,
|
159
|
+
}) do
|
160
|
+
OtherPerson.new(name, mother, father)
|
161
|
+
end
|
162
|
+
build_dsl_element(person_dsl, :mother, person_dsl)
|
163
|
+
build_dsl_element(person_dsl, :father, person_dsl)
|
164
|
+
end
|
165
|
+
|
166
|
+
person = dsl_class.parse_dsl("
|
167
|
+
person {
|
168
|
+
name 'John Smith'
|
169
|
+
mother {
|
170
|
+
name 'Mary Smith'
|
171
|
+
}
|
172
|
+
father {
|
173
|
+
name 'Tom Smith'
|
174
|
+
}
|
175
|
+
}
|
176
|
+
")
|
177
|
+
|
178
|
+
expect(person).to be_instance_of(OtherPerson)
|
179
|
+
expect(person.name).to eq('John Smith')
|
180
|
+
|
181
|
+
mother = person.mother
|
182
|
+
expect(mother).to be_instance_of(OtherPerson)
|
183
|
+
expect(mother.name).to eq('Mary Smith')
|
148
184
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
].each do |name|
|
153
|
-
expect(person).to be_instance_of(Person)
|
154
|
-
expect(person.name).to eq(name)
|
155
|
-
person = person.child
|
185
|
+
father = person.father
|
186
|
+
expect(father).to be_instance_of(OtherPerson)
|
187
|
+
expect(father.name).to eq('Tom Smith')
|
156
188
|
end
|
157
189
|
end
|
158
190
|
end
|
@@ -53,4 +53,33 @@ describe "A DSL describing cars used with multiple invocations" do
|
|
53
53
|
expect(vehicles[2]).to be_instance_of(Truck)
|
54
54
|
expect(vehicles[2].maker).to eq('Toyota')
|
55
55
|
end
|
56
|
+
|
57
|
+
it "does all the same things with execute_dsl" do
|
58
|
+
dsl_class = Class.new(DSL::Maker) do
|
59
|
+
add_entrypoint(:car, {
|
60
|
+
:maker => String,
|
61
|
+
}) do
|
62
|
+
Car.new(maker)
|
63
|
+
end
|
64
|
+
add_entrypoint(:truck, {
|
65
|
+
:maker => String,
|
66
|
+
}) do
|
67
|
+
Truck.new(maker)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
vehicles = dsl_class.execute_dsl do
|
72
|
+
truck { maker 'Ford' }
|
73
|
+
car { maker 'Honda' }
|
74
|
+
truck { maker 'Toyota' }
|
75
|
+
end
|
76
|
+
expect(vehicles).to be_instance_of(Array)
|
77
|
+
expect(vehicles.length).to eq(3)
|
78
|
+
expect(vehicles[0]).to be_instance_of(Truck)
|
79
|
+
expect(vehicles[0].maker).to eq('Ford')
|
80
|
+
expect(vehicles[1]).to be_instance_of(Car)
|
81
|
+
expect(vehicles[1].maker).to eq('Honda')
|
82
|
+
expect(vehicles[2]).to be_instance_of(Truck)
|
83
|
+
expect(vehicles[2].maker).to eq('Toyota')
|
84
|
+
end
|
56
85
|
end
|
data/spec/single_level_spec.rb
CHANGED
@@ -103,12 +103,15 @@ describe 'A single-level DSL for pizza' do
|
|
103
103
|
|
104
104
|
it 'makes a pizza with everything' do
|
105
105
|
dsl_class = Class.new(DSL::Maker) do
|
106
|
-
|
106
|
+
toppings_dsl = generate_dsl({
|
107
107
|
:cheese => DSL::Maker::Boolean,
|
108
108
|
:bacon => DSL::Maker::Boolean,
|
109
109
|
:pepperoni => DSL::Maker::Boolean,
|
110
110
|
:sauce => String,
|
111
|
-
})
|
111
|
+
}) {}
|
112
|
+
|
113
|
+
# This is a wart - this block should be against toppings_dsl, not here.
|
114
|
+
add_entrypoint(:pizza, toppings_dsl) do
|
112
115
|
Pizza.new(cheese, pepperoni, bacon, sauce)
|
113
116
|
end
|
114
117
|
end
|
@@ -128,4 +131,35 @@ describe 'A single-level DSL for pizza' do
|
|
128
131
|
:cheese => true,
|
129
132
|
)
|
130
133
|
end
|
134
|
+
|
135
|
+
it 'can execute the DSL directly' do
|
136
|
+
dsl_class = Class.new(DSL::Maker) do
|
137
|
+
toppings_dsl = generate_dsl({
|
138
|
+
:cheese => DSL::Maker::Boolean,
|
139
|
+
:bacon => DSL::Maker::Boolean,
|
140
|
+
:pepperoni => DSL::Maker::Boolean,
|
141
|
+
:sauce => String,
|
142
|
+
}) {}
|
143
|
+
|
144
|
+
# This is a wart - this block should be against toppings_dsl, not here.
|
145
|
+
add_entrypoint(:pizza, toppings_dsl) do
|
146
|
+
Pizza.new(cheese, pepperoni, bacon, sauce)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
pizza = dsl_class.execute_dsl do
|
151
|
+
pizza {
|
152
|
+
cheese yes
|
153
|
+
pepperoni yes
|
154
|
+
bacon no
|
155
|
+
sauce :extra
|
156
|
+
}
|
157
|
+
end
|
158
|
+
verify_pizza(pizza,
|
159
|
+
:sauce => 'extra',
|
160
|
+
:pepperoni => true,
|
161
|
+
:bacon => false,
|
162
|
+
:cheese => true,
|
163
|
+
)
|
164
|
+
end
|
131
165
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dsl_maker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Kinyon
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-07-
|
11
|
+
date: 2015-07-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: docile
|
@@ -131,6 +131,7 @@ files:
|
|
131
131
|
- ".rspec"
|
132
132
|
- ".travis.yml"
|
133
133
|
- ".yardopts"
|
134
|
+
- Changes
|
134
135
|
- Gemfile
|
135
136
|
- LICENSE
|
136
137
|
- README.md
|