dsl_maker 0.0.5 → 0.0.6
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 +6 -0
- data/README.md +10 -1
- data/lib/dsl/maker.rb +116 -68
- data/lib/dsl/maker/version.rb +1 -1
- data/spec/error_spec.rb +24 -0
- data/spec/single_level_spec.rb +6 -8
- data/spec/validation_spec.rb +29 -0
- 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: a60b85736172ba9bfbd06b14f39a8d0163bd6cf1
|
4
|
+
data.tar.gz: 32d6433a84322447e8104e921c9a47e354a60f11
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e99238cb102708130d8ea95967c5acf0764988b9db5123e395f67a9f3213c8cdff94e7807d835884d6e9efb07af931a708bd69fdc9c3d66ae5e384a8543a62cb
|
7
|
+
data.tar.gz: 233cd1b86c6a0cee61a04ade5f947aa2b78ee35a69d14750bf4fb020f3137d5d8854548110d6483650ee9cb4d570f61be4b1999764fe6a84b0888e45f1bb0d43
|
data/Changes
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
Revision history for DSL::Maker (ordered by revision number).
|
2
2
|
|
3
|
+
0.0.6 Aug ?? 2015
|
4
|
+
- Added add_verification(name?) to allow for runtime verifications
|
5
|
+
- Don't require a dummy block when passing a DSL class to add_entrypoint.
|
6
|
+
- Refactored several items into private methods
|
7
|
+
- Pulled the provided types and helpers out of the class definition
|
8
|
+
|
3
9
|
0.0.5 Jul 30 2015
|
4
10
|
- Added missing YaRDOC for new features in 0.0.4
|
5
11
|
- Added add_coercion() to allow the user to define new coercions for use when
|
data/README.md
CHANGED
@@ -353,7 +353,16 @@ $ gem install dsl_maker
|
|
353
353
|
|
354
354
|
## TODO
|
355
355
|
|
356
|
-
* Add support for Arrays
|
356
|
+
* Add support for Arrays (ArrayOf[Type]?)
|
357
|
+
* Add support for generating useful errors (ideally with line numbers ... ?)
|
358
|
+
* Add support for auto-generating documentation
|
359
|
+
* Add default block that returns a Struct-of-Structs named after entrypoints
|
360
|
+
* Add example of binary to execute a DSL
|
361
|
+
* Add example of/link to validation and production of data structure
|
362
|
+
* 3-part DSL handling
|
363
|
+
* DSL for DSL construction
|
364
|
+
* Add "include" helper that loads another file and continues the execution
|
365
|
+
* Should provide useful directory searching
|
357
366
|
|
358
367
|
## Links
|
359
368
|
|
data/lib/dsl/maker.rb
CHANGED
@@ -42,14 +42,7 @@ class DSL::Maker
|
|
42
42
|
#
|
43
43
|
# @return [Object] Whatever is returned by the block defined in this class.
|
44
44
|
def self.parse_dsl(dsl)
|
45
|
-
|
46
|
-
# Reset it here so that we're only handling the values from this run.
|
47
|
-
@accumulator = []
|
48
|
-
eval dsl, self.get_binding
|
49
|
-
if @accumulator.length <= 1
|
50
|
-
return @accumulator[0]
|
51
|
-
end
|
52
|
-
return @accumulator
|
45
|
+
__run_dsl { eval dsl, self.get_binding }
|
53
46
|
end
|
54
47
|
|
55
48
|
# Execute the DSL provided in the block.
|
@@ -61,19 +54,9 @@ class DSL::Maker
|
|
61
54
|
#
|
62
55
|
# @return [Object] Whatever is returned by &block
|
63
56
|
def self.execute_dsl(&block)
|
64
|
-
|
65
|
-
instance_eval(&block)
|
66
|
-
if @accumulator.length <= 1
|
67
|
-
return @accumulator[0]
|
68
|
-
end
|
69
|
-
return @accumulator
|
70
|
-
end
|
57
|
+
raise 'Block required for execute_dsl' unless block_given?
|
71
58
|
|
72
|
-
|
73
|
-
#
|
74
|
-
# @return [Binding] The binding of the invoking class.
|
75
|
-
def self.get_binding
|
76
|
-
binding
|
59
|
+
__run_dsl { instance_eval(&block) }
|
77
60
|
end
|
78
61
|
|
79
62
|
# FIXME: This may have to be changed when the elements can be altered because
|
@@ -88,11 +71,11 @@ class DSL::Maker
|
|
88
71
|
# @param &block [Block] The function to be executed when the coercion is exercised.
|
89
72
|
#
|
90
73
|
# Your block will receive the following signature: |attr, *args| where 'attr' is
|
91
|
-
# the name of the attribute and *args are the arguments passed into your method
|
92
|
-
# You are responsible for acting as a mutator. You have ___get()
|
93
|
-
# available for your use. These are aliases to
|
94
|
-
# instance_variable_set, respectively. Please read the
|
95
|
-
# you in this source file.
|
74
|
+
# the name of the attribute and *args are the arguments passed into your method
|
75
|
+
# within the DSL. You are responsible for acting as a mutator. You have ___get()
|
76
|
+
# and ___set() available for your use. These are aliases to
|
77
|
+
# instance_variable_get and instance_variable_set, respectively. Please read the
|
78
|
+
# coercions provided for you in this source file as examples.
|
96
79
|
#
|
97
80
|
# @return nil
|
98
81
|
def self.add_type(type, &block)
|
@@ -100,10 +83,9 @@ class DSL::Maker
|
|
100
83
|
raise "'#{type}' is already a type coercion" if @@types.has_key? type
|
101
84
|
|
102
85
|
@@types[type] = ->(klass, name, type) {
|
103
|
-
as_attr = '@' + name.to_s
|
104
86
|
klass.class_eval do
|
105
87
|
define_method(name.to_sym) do |*args|
|
106
|
-
instance_exec(
|
88
|
+
instance_exec('@' + name.to_s, *args, &block)
|
107
89
|
end
|
108
90
|
end
|
109
91
|
}
|
@@ -111,24 +93,6 @@ class DSL::Maker
|
|
111
93
|
return
|
112
94
|
end
|
113
95
|
|
114
|
-
add_type(Integer) do |attr, *args|
|
115
|
-
___set(attr, args[0].to_i) unless args.empty?
|
116
|
-
___get(attr)
|
117
|
-
end
|
118
|
-
add_type(String) do |attr, *args|
|
119
|
-
___set(attr, args[0].to_s) unless args.empty?
|
120
|
-
___get(attr)
|
121
|
-
end
|
122
|
-
add_type(Boolean) do |attr, *args|
|
123
|
-
___set(attr, Boolean.coerce(args[0])) unless args.empty?
|
124
|
-
# Ensure that the default nil also returns as false.
|
125
|
-
!!___get(attr)
|
126
|
-
end
|
127
|
-
|
128
|
-
$is_dsl = lambda do |proto|
|
129
|
-
proto.is_a?(Class) && proto.ancestors.include?(DSL::Maker::Base)
|
130
|
-
end
|
131
|
-
|
132
96
|
# Add a single element of a DSL to a class representing a level in a DSL.
|
133
97
|
#
|
134
98
|
# Each of the types represents a coercion - a guarantee and check of the value
|
@@ -148,16 +112,13 @@ class DSL::Maker
|
|
148
112
|
def self.build_dsl_element(klass, name, type)
|
149
113
|
if @@types.has_key?(type)
|
150
114
|
@@types[type].call(klass, name, type)
|
151
|
-
elsif
|
115
|
+
elsif __is_dsl(type)
|
152
116
|
as_attr = '@' + name.to_s
|
153
117
|
klass.class_eval do
|
154
118
|
define_method(name.to_sym) do |*args, &dsl_block|
|
155
119
|
unless (args.empty? && !dsl_block)
|
156
120
|
obj = type.new
|
157
121
|
Docile.dsl_eval(obj, &dsl_block) if dsl_block
|
158
|
-
|
159
|
-
# I don't know why this code doesn't work, but it's why __apply().
|
160
|
-
#___set(as_attr, obj.instance_exec(*args, &defn_block))
|
161
122
|
___set(as_attr, obj.__apply(*args))
|
162
123
|
end
|
163
124
|
___get(as_attr)
|
@@ -223,31 +184,41 @@ class DSL::Maker
|
|
223
184
|
#
|
224
185
|
# @return [Class] The class that implements this level's DSL definition.
|
225
186
|
def self.add_entrypoint(name, args={}, &defn_block)
|
226
|
-
|
227
|
-
# DSL parsing. So, raise an error if we don't get one.
|
228
|
-
# TODO: Provide a default block that returns the datastructure as a HoH.
|
229
|
-
raise "Block required for add_entrypoint" unless block_given?
|
187
|
+
symname = name.to_sym
|
230
188
|
|
231
|
-
if self.respond_to?(
|
189
|
+
if self.respond_to?(symname)
|
232
190
|
raise "'#{name.to_s}' is already an entrypoint"
|
233
191
|
end
|
234
192
|
|
235
|
-
if
|
193
|
+
if __is_dsl(args)
|
236
194
|
dsl_class = args
|
237
195
|
else
|
196
|
+
# Without defn_block, there's no way to give back the result of the
|
197
|
+
# DSL parsing. So, raise an error if we don't get one.
|
198
|
+
# TODO: Provide a default block that returns the datastructure as a HoH.
|
199
|
+
|
200
|
+
raise "Block required for add_entrypoint" unless block_given?
|
238
201
|
dsl_class = generate_dsl(args, &defn_block)
|
239
202
|
end
|
240
203
|
|
241
|
-
define_singleton_method(
|
204
|
+
define_singleton_method(symname) do |*args, &dsl_block|
|
242
205
|
obj = dsl_class.new
|
243
206
|
Docile.dsl_eval(obj, &dsl_block) if dsl_block
|
244
|
-
rv = obj.
|
207
|
+
rv = obj.__apply(*args)
|
208
|
+
|
209
|
+
if @verifications && @verifications.has_key?(symname)
|
210
|
+
@verifications[symname].each do |verify|
|
211
|
+
failure = verify.call(rv)
|
212
|
+
raise failure if failure
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
245
216
|
@accumulator.push(rv)
|
246
217
|
return rv
|
247
218
|
end
|
248
219
|
|
249
220
|
@entrypoints ||= {}
|
250
|
-
return @entrypoints[
|
221
|
+
return @entrypoints[symname] = dsl_class
|
251
222
|
end
|
252
223
|
|
253
224
|
# This returns the DSL corresponding to the entrypoint's name.
|
@@ -256,7 +227,7 @@ class DSL::Maker
|
|
256
227
|
#
|
257
228
|
# @return [Class] The class that implements this name's DSL definition.
|
258
229
|
def self.entrypoint(name)
|
259
|
-
unless
|
230
|
+
unless __is_entrypoint(name)
|
260
231
|
raise "'#{name.to_s}' is not an entrypoint"
|
261
232
|
end
|
262
233
|
|
@@ -285,18 +256,95 @@ class DSL::Maker
|
|
285
256
|
return
|
286
257
|
end
|
287
258
|
|
288
|
-
#
|
259
|
+
# This adds a verification that's executed after the DSL is finished parsing.
|
260
|
+
#
|
261
|
+
# The verification will be called with the value(s) returned by the entrypoint's
|
262
|
+
# execution. If the verification returns a true value (of any kind), then that
|
263
|
+
# will be raised as a runtime exception.
|
289
264
|
#
|
290
|
-
#
|
291
|
-
#
|
292
|
-
# @param position [Integer] The index in args to work with, default 0.
|
265
|
+
# Note: These verifications are specific to the DSL you add them to.
|
266
|
+
# Note: Verifications are called in the order you specify them.
|
293
267
|
#
|
268
|
+
# @param name [String] the name of the entrypoint to add a verification to
|
269
|
+
# @param &block [Block] The function to be executed when verifications execute
|
270
|
+
#
|
294
271
|
# @return nil
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
272
|
+
def self.add_verification(name, &block)
|
273
|
+
raise "Block required for add_verification" unless block_given?
|
274
|
+
raise "'#{name.to_s}' is not an entrypoint for a verification" unless __is_entrypoint(name)
|
275
|
+
|
276
|
+
@verifications ||= {}
|
277
|
+
@verifications[name.to_sym] ||= []
|
278
|
+
|
279
|
+
# This craziness converts the block provided into a proc that can be called
|
280
|
+
# in add_entrypoint(). Taken from http://stackoverflow.com/a/2946734/1732954
|
281
|
+
# Note: self is not preserved. This should be okay because the verification
|
282
|
+
# should only care about the value provided.
|
283
|
+
obj = Object.new
|
284
|
+
obj.define_singleton_method(:_, &block)
|
285
|
+
@verifications[name.to_sym].push(obj.method(:_).to_proc)
|
286
|
+
|
300
287
|
return
|
301
288
|
end
|
289
|
+
|
290
|
+
private
|
291
|
+
|
292
|
+
# Returns the binding as needed by parse_dsl() and execute_dsl()
|
293
|
+
#
|
294
|
+
# @return [Binding] The binding of the invoking class.
|
295
|
+
def self.get_binding
|
296
|
+
binding
|
297
|
+
end
|
298
|
+
|
299
|
+
def self.__run_dsl()
|
300
|
+
# add_entrypoint() will use @accumulator to handle multiple entrypoints.
|
301
|
+
# Reset it here so that we're only handling the values from this run.
|
302
|
+
@accumulator = []
|
303
|
+
|
304
|
+
yield
|
305
|
+
|
306
|
+
if @accumulator.length <= 1
|
307
|
+
return @accumulator[0]
|
308
|
+
end
|
309
|
+
return @accumulator
|
310
|
+
end
|
311
|
+
|
312
|
+
def self.__is_dsl(proto)
|
313
|
+
proto.is_a?(Class) && proto.ancestors.include?(DSL::Maker::Base)
|
314
|
+
end
|
315
|
+
|
316
|
+
def self.__is_entrypoint(name)
|
317
|
+
respond_to?(name.to_sym)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# These are the default setups.
|
322
|
+
|
323
|
+
DSL::Maker.add_type(Integer) do |attr, *args|
|
324
|
+
___set(attr, args[0].to_i) unless args.empty?
|
325
|
+
___get(attr)
|
326
|
+
end
|
327
|
+
DSL::Maker.add_type(String) do |attr, *args|
|
328
|
+
___set(attr, args[0].to_s) unless args.empty?
|
329
|
+
___get(attr)
|
330
|
+
end
|
331
|
+
DSL::Maker.add_type(DSL::Maker::Boolean) do |attr, *args|
|
332
|
+
___set(attr, DSL::Maker::Boolean.coerce(args[0])) unless args.empty?
|
333
|
+
# Ensure that the default nil also returns as false.
|
334
|
+
!!___get(attr)
|
335
|
+
end
|
336
|
+
|
337
|
+
# A helper method for handling defaults from args easily.
|
338
|
+
#
|
339
|
+
# @param method_name [String] The name of the attribute being defaulted.
|
340
|
+
# @param args [Array] The arguments provided to the block.
|
341
|
+
# @param position [Integer] The index in args to work with, default 0.
|
342
|
+
#
|
343
|
+
# @return nil
|
344
|
+
DSL::Maker.add_helper(:default) do |method_name, args, position=0|
|
345
|
+
method = method_name.to_sym
|
346
|
+
if args.length >= (position + 1) && !self.send(method)
|
347
|
+
self.send(method, args[position])
|
348
|
+
end
|
349
|
+
return
|
302
350
|
end
|
data/lib/dsl/maker/version.rb
CHANGED
data/spec/error_spec.rb
CHANGED
@@ -11,6 +11,12 @@ describe "DSL::Maker validation" do
|
|
11
11
|
}.to raise_error('Block required for generate_dsl')
|
12
12
|
end
|
13
13
|
|
14
|
+
it "requires a block for execute_dsl" do
|
15
|
+
expect {
|
16
|
+
Class.new(DSL::Maker).execute_dsl
|
17
|
+
}.to raise_error('Block required for execute_dsl')
|
18
|
+
end
|
19
|
+
|
14
20
|
describe "for attributes" do
|
15
21
|
it "requires a recognized type for attributes" do
|
16
22
|
expect {
|
@@ -66,6 +72,24 @@ describe "DSL::Maker validation" do
|
|
66
72
|
end
|
67
73
|
end
|
68
74
|
|
75
|
+
describe "for verifications" do
|
76
|
+
it "requires a block for :add_verification" do
|
77
|
+
expect {
|
78
|
+
Class.new(DSL::Maker) do
|
79
|
+
add_verification(:pizza)
|
80
|
+
end
|
81
|
+
}.to raise_error('Block required for add_verification')
|
82
|
+
end
|
83
|
+
|
84
|
+
it "rejects an verification name that doesn't exist in entrypoint()" do
|
85
|
+
dsl_class = Class.new(DSL::Maker)
|
86
|
+
|
87
|
+
expect {
|
88
|
+
dsl_class.add_verification(:x) {}
|
89
|
+
}.to raise_error("'x' is not an entrypoint for a verification")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
69
93
|
describe "for helpers" do
|
70
94
|
it "rejects a helper without a block" do
|
71
95
|
dsl_class = Class.new(DSL::Maker)
|
data/spec/single_level_spec.rb
CHANGED
@@ -108,12 +108,11 @@ describe 'A single-level DSL for pizza' do
|
|
108
108
|
:bacon => DSL::Maker::Boolean,
|
109
109
|
:pepperoni => DSL::Maker::Boolean,
|
110
110
|
:sauce => String,
|
111
|
-
})
|
112
|
-
|
113
|
-
# This is a wart - this block should be against toppings_dsl, not here.
|
114
|
-
add_entrypoint(:pizza, toppings_dsl) do
|
111
|
+
}) do
|
115
112
|
$Pizza.new(cheese, pepperoni, bacon, sauce)
|
116
113
|
end
|
114
|
+
|
115
|
+
add_entrypoint(:pizza, toppings_dsl)
|
117
116
|
end
|
118
117
|
|
119
118
|
pizza = dsl_class.parse_dsl("
|
@@ -139,12 +138,11 @@ describe 'A single-level DSL for pizza' do
|
|
139
138
|
:bacon => DSL::Maker::Boolean,
|
140
139
|
:pepperoni => DSL::Maker::Boolean,
|
141
140
|
: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
|
141
|
+
}) do
|
146
142
|
$Pizza.new(cheese, pepperoni, bacon, sauce)
|
147
143
|
end
|
144
|
+
|
145
|
+
add_entrypoint(:pizza, toppings_dsl)
|
148
146
|
end
|
149
147
|
|
150
148
|
pizza = dsl_class.execute_dsl do
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# These are tests about the validation process for DSL::Maker
|
2
|
+
|
3
|
+
describe "Validations" do
|
4
|
+
it "validates entrypoint-specific items" do
|
5
|
+
dsl_class = Class.new(DSL::Maker) do
|
6
|
+
toppings_dsl = generate_dsl({
|
7
|
+
:cheese => DSL::Maker::Boolean,
|
8
|
+
:bacon => DSL::Maker::Boolean,
|
9
|
+
:pepperoni => DSL::Maker::Boolean,
|
10
|
+
:sauce => String,
|
11
|
+
}) do
|
12
|
+
$Pizza.new(cheese, pepperoni, bacon, sauce)
|
13
|
+
end
|
14
|
+
|
15
|
+
add_entrypoint(:pizza, toppings_dsl)
|
16
|
+
add_verification(:pizza) do |item|
|
17
|
+
return "Pizza must have sauce" unless item.sauce
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
expect {
|
22
|
+
dsl_class.parse_dsl("pizza {}")
|
23
|
+
}.to raise_error("Pizza must have sauce")
|
24
|
+
|
25
|
+
expect {
|
26
|
+
dsl_class.parse_dsl("pizza { sauce :extra }")
|
27
|
+
}.to_not raise_error
|
28
|
+
end
|
29
|
+
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.6
|
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-
|
11
|
+
date: 2015-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: docile
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- spec/multiple_invocation_spec.rb
|
163
163
|
- spec/single_level_spec.rb
|
164
164
|
- spec/spec_helper.rb
|
165
|
+
- spec/validation_spec.rb
|
165
166
|
homepage: https://github.com/robkinyon/ruby-dsl-maker
|
166
167
|
licenses:
|
167
168
|
- GPL2
|