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 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