dsl_maker 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|