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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cf937996460904dce3266547aab1e117ee498e8d
4
- data.tar.gz: 85796fbba9b301299057952af7b5b72bda58d4cc
3
+ metadata.gz: a60b85736172ba9bfbd06b14f39a8d0163bd6cf1
4
+ data.tar.gz: 32d6433a84322447e8104e921c9a47e354a60f11
5
5
  SHA512:
6
- metadata.gz: 96fce7def349ca81bebe71ea969f6f7af69d242409a4ffc8c39c5b9bc2cfbd0e9952a903191f02ebe154ba4740c801c439948713d11c0c4fb47d540e8c36a835
7
- data.tar.gz: 8673a94d49a4acb702937b099de6a3c4b20d52b896c4e8a25e16ff59fce3f6c7a1fdbb5abcb536cd24bc7f3a62119134f1b890d6ee4335f254ee59d07532f30c
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
 
@@ -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
- # add_entrypoint() will use @accumulator to handle multiple entrypoints.
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
- @accumulator = []
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
- # Returns the binding as needed by parse_dsl() and execute_dsl()
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() and ___set()
93
- # available for your use. These are aliases to instance_variable_get and
94
- # instance_variable_set, respectively. Please read the coercions provided for
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(as_attr, *args, &block)
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 $is_dsl.call(type)
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
- # Without defn_block, there's no way to give back the result of the
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?(name.to_sym)
189
+ if self.respond_to?(symname)
232
190
  raise "'#{name.to_s}' is already an entrypoint"
233
191
  end
234
192
 
235
- if $is_dsl.call(args)
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(name.to_sym) do |*args, &dsl_block|
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.instance_exec(*args, &defn_block)
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[name.to_sym] = dsl_class
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 self.respond_to?(name.to_sym)
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
- # A helper method for handling defaults from args easily.
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
- # @param method_name [String] The name of the attribute being defaulted.
291
- # @param args [Array] The arguments provided to the block.
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
- add_helper(:default) do |method_name, args, position=0|
296
- method = method_name.to_sym
297
- if args.length >= (position + 1) && !self.send(method)
298
- self.send(method, args[position])
299
- end
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
@@ -1,6 +1,6 @@
1
1
  module DSL
2
2
  class Maker
3
3
  # The current version of this library
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.6'
5
5
  end
6
6
  end
@@ -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)
@@ -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.5
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-07-30 00:00:00.000000000 Z
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