dsl_maker 0.0.7 → 0.0.8

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: 2f42b67736e6299fee74ad059d995f9f0e9e71dc
4
- data.tar.gz: cc54596f31ec4278eb33c5a11d504b1774556634
3
+ metadata.gz: fd8b809906f6bde7d8b1839095dc2ad018148190
4
+ data.tar.gz: 13708d81d5f96de3f5744736c77f4b55ef2d5748
5
5
  SHA512:
6
- metadata.gz: ffe540380885b22fd3dc7a7d118ada0ae1ac958502619cbbea19f2c42b2546ccd60e9147a3475dc58d83c2b69982c87f08a7e6251ea172b141b7bf5bcda6e007
7
- data.tar.gz: a0950aa163fe8b9c8b41b311c85ee369c229bd32099f6ce742a2e7f62f3b954effefcbfc7a90777287dc67ea84a482951dd5672a567cfb3d3ae10ad845dc6556
6
+ metadata.gz: d0c2f6464bc5d08693a7298dae3074c9f89f00c9f2959bb1366b86a5cfdf211bc6fc779c045cb111fd04f0e0e7aff1aa8304ccd895d084fa32119e4fdba7c951
7
+ data.tar.gz: c9ecc30fba1b36b6c6aac878de49a6191df39180ef05dd96996a411e82c5c52e44ef202254ba08736755e95fe9d8df3fba94ad512adc51ddc6d139d96f1a987a
data/Changes CHANGED
@@ -1,5 +1,15 @@
1
1
  Revision history for DSL::Maker (ordered by revision number).
2
2
 
3
+ 0.0.8 Aug 07 2015
4
+ - Make sure to provide a unique value for each type name.
5
+ - Allow 'Object' to be used as an alias for 'Any'
6
+ - Signficant refactorings (Thanks, "Alex Burkhart <saterus@gmail.com>"!!)
7
+ - Test globals are properly segregated
8
+ - Properly separated the control and DSL classes
9
+ - Verifications can be used at every DSL class level
10
+ - Moved build_dsl_element() into private. It can still be used within the
11
+ control class under construction.
12
+
3
13
  0.0.7 Aug 06 2015
4
14
  - Added the 'Any' type coercion.
5
15
  - Added a TL;DR section at the top of the README.
data/lib/dsl/maker.rb CHANGED
@@ -2,6 +2,13 @@ require 'dsl/maker/version'
2
2
 
3
3
  require 'docile'
4
4
 
5
+ # Children of DSL::Maker are the "control class". All of the classes that inherit
6
+ # from DSL::Maker::Base are "dsl classes" - classes that are passed to Docile and
7
+ # which represent levels of the DSL. In order for :parse_dsl/:execute_dsl to
8
+ # return back the accumulated values in the order provided, we need to "pierce the
9
+ # veil" (so to speak) between the control and dsl classes. That's done using the
10
+ # parent_class class attribute in the dsl classes.
11
+
5
12
  # This is the base class we provide.
6
13
  class DSL::Maker
7
14
  # This is the base class for all DSL-parsing classes.
@@ -11,10 +18,32 @@ class DSL::Maker
11
18
 
12
19
  # 21 character method names are obscene. Make it easier to read.
13
20
  alias :___get :instance_variable_get
21
+
22
+ def get_binding
23
+ binding
24
+ end
25
+
26
+ define_singleton_method(:add_verification) do |&block|
27
+ # FIXME: This throws regardless. Is this because of the difference between
28
+ # proc and block?
29
+ #raise "Block required for add_verification" unless block_given?
30
+
31
+ @verifications ||= []
32
+
33
+ # This craziness converts the block provided into a proc that can be called
34
+ # in add_entrypoint(). Taken from http://stackoverflow.com/a/2946734/1732954
35
+ # Note: self is not preserved. This should be okay because the verification
36
+ # should be idempotent relative to the value provided (side-effect-free).
37
+ obj = Object.new
38
+ obj.define_singleton_method(:_, &block)
39
+ @verifications.push(obj.method(:_).to_proc)
40
+
41
+ return
42
+ end
14
43
  end
15
44
 
16
- # Create the DSL::Maker::Any type identifier
17
- Any = nil
45
+ # Create the DSL::Maker::Any type identifier, equivalent to Object.
46
+ Any = Object
18
47
 
19
48
  # This is a useful module that contains all the Boolean handling we need.
20
49
  module Boolean
@@ -44,8 +73,11 @@ class DSL::Maker
44
73
  # @param dsl [String] The DSL to be parsed by this class.
45
74
  #
46
75
  # @return [Object] Whatever is returned by the block defined in this class.
47
- def self.parse_dsl(dsl)
48
- __run_dsl { eval dsl, self.get_binding }
76
+ def self.parse_dsl(dsl=nil)
77
+ raise 'Must call add_entrypoint before parse_dsl' unless @klass
78
+ raise 'String required for parse_dsl' unless dsl.instance_of? String
79
+
80
+ run_dsl() { eval dsl, @klass.new.get_binding }
49
81
  end
50
82
 
51
83
  # Execute the DSL provided in the block.
@@ -57,18 +89,15 @@ class DSL::Maker
57
89
  #
58
90
  # @return [Object] Whatever is returned by &block
59
91
  def self.execute_dsl(&block)
92
+ raise 'Must call add_entrypoint before execute_dsl' unless @klass
60
93
  raise 'Block required for execute_dsl' unless block_given?
61
94
 
62
- __run_dsl { instance_eval(&block) }
95
+ run_dsl() { @klass.new.instance_eval(&block) }
63
96
  end
64
97
 
65
- # FIXME: This may have to be changed when the elements can be altered because
66
- # it is global to the hierarchy. But, that may be desirable.
67
- @@types = {}
68
-
69
98
  # This adds a type coercion that's used when creating the DSL.
70
99
  #
71
- # Note: These type coercions are global to all DSLs.
100
+ # @note These type coercions are global to all DSLs.
72
101
  #
73
102
  # @param type [Object] the name of the helper
74
103
  # @param &block [Block] The function to be executed when the coercion is exercised.
@@ -96,44 +125,6 @@ class DSL::Maker
96
125
  return
97
126
  end
98
127
 
99
- # Add a single element of a DSL to a class representing a level in a DSL.
100
- #
101
- # Each of the types represents a coercion - a guarantee and check of the value
102
- # in that name. The standard type coercions are:
103
- #
104
- # * String - whatever you give is returned.
105
- # * Integer - the integer value of whatever you give is returned.
106
- # * Boolean - the truthiness of whatever you give is returned.
107
- # * generate_dsl() - this represents a new level of the DSL.
108
- #
109
- # @param klass [Class] The class representing this level in the DSL.
110
- # @param name [String] The name of the element we're working on.
111
- # @param type [Class] The type of this element we're working on.
112
- # This is the type coercion spoken above.
113
- #
114
- # @return nil
115
- def self.build_dsl_element(klass, name, type)
116
- if @@types.has_key?(type)
117
- @@types[type].call(klass, name, type)
118
- elsif __is_dsl(type)
119
- as_attr = '@' + name.to_s
120
- klass.class_eval do
121
- define_method(name.to_sym) do |*args, &dsl_block|
122
- unless (args.empty? && !dsl_block)
123
- obj = type.new
124
- Docile.dsl_eval(obj, &dsl_block) if dsl_block
125
- ___set(as_attr, obj.__apply(*args))
126
- end
127
- ___get(as_attr)
128
- end
129
- end
130
- else
131
- raise "Unrecognized element type '#{type}'"
132
- end
133
-
134
- return
135
- end
136
-
137
128
  # Add the meat of a DSL block to some level of this class's DSL.
138
129
  #
139
130
  # In order for Docile to parse a DSL, each level must be represented by a
@@ -149,15 +140,13 @@ class DSL::Maker
149
140
  def self.generate_dsl(args={}, &defn_block)
150
141
  raise 'Block required for generate_dsl' unless block_given?
151
142
 
152
- # Inherit from the Boolean class to gain access to the useful methods
153
- # TODO: Convert DSL::Maker::Boolean into a Role
154
- # TODO: Create a DSL::Maker::Base class to inherit from
155
143
  dsl_class = Class.new(DSL::Maker::Base) do
156
144
  include DSL::Maker::Boolean
157
145
 
158
- # This instance method exists because we cannot seem to inline its work
159
- # where we call it. Could it be a problem of incorrect binding?
160
- # It has to be defined here because it needs access to &defn_block
146
+ class << self
147
+ attr_accessor :parent_class, :verifications
148
+ end
149
+
161
150
  define_method(:__apply) do |*args|
162
151
  instance_exec(*args, &defn_block)
163
152
  end
@@ -189,11 +178,11 @@ class DSL::Maker
189
178
  def self.add_entrypoint(name, args={}, &defn_block)
190
179
  symname = name.to_sym
191
180
 
192
- if self.respond_to?(symname)
181
+ if is_entrypoint(symname)
193
182
  raise "'#{name.to_s}' is already an entrypoint"
194
183
  end
195
184
 
196
- if __is_dsl(args)
185
+ if is_dsl(args)
197
186
  dsl_class = args
198
187
  else
199
188
  # Without defn_block, there's no way to give back the result of the
@@ -203,21 +192,20 @@ class DSL::Maker
203
192
  raise "Block required for add_entrypoint" unless block_given?
204
193
  dsl_class = generate_dsl(args, &defn_block)
205
194
  end
206
-
207
- define_singleton_method(symname) do |*args, &dsl_block|
208
- obj = dsl_class.new
209
- Docile.dsl_eval(obj, &dsl_block) if dsl_block
210
- rv = obj.__apply(*args)
211
-
212
- if @verifications && @verifications.has_key?(symname)
213
- @verifications[symname].each do |verify|
214
- failure = verify.call(rv)
215
- raise failure if failure
216
- end
217
- end
218
-
219
- @accumulator.push(rv)
220
- return rv
195
+
196
+ if @klass
197
+ build_dsl_element(@klass, symname, dsl_class)
198
+ else
199
+ # FIXME: We shouldn't need the blank block here ...
200
+ # This blank block is representative of the implicit (and missing) outermost
201
+ # block around the DSL that we are not putting into place in :parse_dsl or
202
+ # :execute_dsl.
203
+ @klass = generate_dsl({
204
+ symname => dsl_class
205
+ }) {}
206
+
207
+ # This marks @klass as the root DSL class.
208
+ @klass.parent_class = self
221
209
  end
222
210
 
223
211
  @entrypoints ||= {}
@@ -230,7 +218,7 @@ class DSL::Maker
230
218
  #
231
219
  # @return [Class] The class that implements this name's DSL definition.
232
220
  def self.entrypoint(name)
233
- unless __is_entrypoint(name)
221
+ unless is_entrypoint(name)
234
222
  raise "'#{name.to_s}' is not an entrypoint"
235
223
  end
236
224
 
@@ -265,8 +253,13 @@ class DSL::Maker
265
253
  # execution. If the verification returns a true value (of any kind), then that
266
254
  # will be raised as a runtime exception.
267
255
  #
268
- # Note: These verifications are specific to the DSL you add them to.
269
- # Note: Verifications are called in the order you specify them.
256
+ # You can also call add_verification on the return values from generate_dsl() or
257
+ # add_entrypoint(). In those cases, omit the :name because you have already
258
+ # chosen the DSL layer you're adding the verification to.
259
+ #
260
+ # @note These verifications are specific to the DSL you add them to.
261
+ #
262
+ # @note Verifications are called in the order you specify them.
270
263
  #
271
264
  # @param name [String] the name of the entrypoint to add a verification to
272
265
  # @param &block [Block] The function to be executed when verifications execute
@@ -274,34 +267,79 @@ class DSL::Maker
274
267
  # @return nil
275
268
  def self.add_verification(name, &block)
276
269
  raise "Block required for add_verification" unless block_given?
277
- raise "'#{name.to_s}' is not an entrypoint for a verification" unless __is_entrypoint(name)
270
+ raise "'#{name.to_s}' is not an entrypoint for a verification" unless is_entrypoint(name)
278
271
 
279
- @verifications ||= {}
280
- @verifications[name.to_sym] ||= []
281
-
282
- # This craziness converts the block provided into a proc that can be called
283
- # in add_entrypoint(). Taken from http://stackoverflow.com/a/2946734/1732954
284
- # Note: self is not preserved. This should be okay because the verification
285
- # should only care about the value provided.
286
- obj = Object.new
287
- obj.define_singleton_method(:_, &block)
288
- @verifications[name.to_sym].push(obj.method(:_).to_proc)
289
-
290
- return
272
+ @entrypoints[name.to_sym].add_verification(&block)
291
273
  end
292
274
 
293
275
  private
294
276
 
295
- # Returns the binding as needed by parse_dsl() and execute_dsl()
277
+ # This is deliberately global to the hierarchy in order for DSL::Maker to add
278
+ # the generic types. While this has the potential to cause userspace collisions,
279
+ # it's highly unlikely that DSLs with divergent types will coexist in the same
280
+ # Ruby process.
281
+ @@types = {}
282
+
283
+ # Add a single element of a DSL to a class representing a level in a DSL.
284
+ #
285
+ # Each of the types represents a coercion - a guarantee and check of the value
286
+ # in that name. The standard type coercions are:
296
287
  #
297
- # @return [Binding] The binding of the invoking class.
298
- def self.get_binding
299
- binding
288
+ # * Any - whatever you give is returned.
289
+ # * String - the string value of whatever you give is returned.
290
+ # * Integer - the integer value of whatever you give is returned.
291
+ # * Boolean - the truthiness of whatever you give is returned.
292
+ # * generate_dsl() - this represents a new level of the DSL.
293
+ #
294
+ # @param klass [Class] The class representing this level in the DSL.
295
+ # @param name [String] The name of the element we're working on.
296
+ # @param type [Class] The type of this element we're working on.
297
+ # This is the type coercion spoken above.
298
+ #
299
+ # @return nil
300
+ def self.build_dsl_element(klass, name, type)
301
+ if @@types.has_key?(type)
302
+ @@types[type].call(klass, name, type)
303
+ elsif is_dsl(type)
304
+ as_attr = '@' + name.to_s
305
+ klass.class_eval do
306
+ define_method(name.to_sym) do |*args, &dsl_block|
307
+ if (!args.empty? || dsl_block)
308
+ obj = type.new
309
+ Docile.dsl_eval(obj, &dsl_block) if dsl_block
310
+ rv = obj.__apply(*args)
311
+
312
+ if v = type.instance_variable_get(:@verifications)
313
+ v.each do |verify|
314
+ failure = verify.call(rv)
315
+ raise failure if failure
316
+ end
317
+ end
318
+
319
+ # This is the one place where we pull out the entrypoint results and
320
+ # put them into the control class.
321
+ if klass.parent_class
322
+ # Use the full instance_variable_get() in order to avoid having to
323
+ # create accessors that could be misused outside this class.
324
+ klass.parent_class.instance_variable_get(:@accumulator).push(rv)
325
+ end
326
+
327
+ ___set(as_attr, rv)
328
+ end
329
+ ___get(as_attr)
330
+ end
331
+ end
332
+ else
333
+ raise "Unrecognized element type '#{type}'"
334
+ end
335
+
336
+ return
300
337
  end
301
338
 
302
- def self.__run_dsl()
303
- # add_entrypoint() will use @accumulator to handle multiple entrypoints.
304
- # Reset it here so that we're only handling the values from this run.
339
+ def self.run_dsl()
340
+ # build_dsl_element() will use @accumulator to handle multiple entrypoints if
341
+ # the class in question is a root DSL class. Reset it here so that we're only
342
+ # handling the values from this run.
305
343
  @accumulator = []
306
344
 
307
345
  yield
@@ -312,16 +350,17 @@ class DSL::Maker
312
350
  return @accumulator
313
351
  end
314
352
 
315
- def self.__is_dsl(proto)
353
+ def self.is_dsl(proto)
316
354
  proto.is_a?(Class) && proto.ancestors.include?(DSL::Maker::Base)
317
355
  end
318
356
 
319
- def self.__is_entrypoint(name)
320
- respond_to?(name.to_sym)
357
+ def self.is_entrypoint(name)
358
+ @entrypoints && @entrypoints.has_key?(name.to_sym)
359
+ #@klass && @klass.new.respond_to?(name.to_sym)
321
360
  end
322
361
  end
323
362
 
324
- # These are the default setups.
363
+ # These are the default setups
325
364
 
326
365
  DSL::Maker.add_type(DSL::Maker::Any) do |attr, *args|
327
366
  ___set(attr, args[0]) unless args.empty?
@@ -1,6 +1,6 @@
1
1
  module DSL
2
2
  class Maker
3
3
  # The current version of this library
4
- VERSION = '0.0.7'
4
+ VERSION = '0.0.8'
5
5
  end
6
6
  end
data/spec/args_spec.rb CHANGED
@@ -1,24 +1,23 @@
1
1
  # This will use a DSL that defines fruit
2
2
 
3
3
  describe "A DSL with argument handling describing fruit" do
4
- $Color = Struct.new(:name)
5
- $Fruit = Struct.new(:name, :color)
6
-
7
4
  describe "with one argument in add_entrypoint" do
8
- dsl_class = Class.new(DSL::Maker) do
9
- add_entrypoint(:fruit, {
10
- :name => String,
11
- }) do |*args|
12
- default(:name, args, 0)
13
- $Fruit.new(name, nil)
5
+ let(:dsl_class) {
6
+ Class.new(DSL::Maker) do
7
+ add_entrypoint(:fruit, {
8
+ :name => String,
9
+ }) do |*args|
10
+ default(:name, args, 0)
11
+ Structs::Fruit.new(name, nil)
12
+ end
14
13
  end
15
- end
14
+ }
16
15
 
17
16
  it "can handle nil" do
18
17
  fruit = dsl_class.parse_dsl("
19
- fruit
18
+ fruit {}
20
19
  ")
21
- expect(fruit).to be_instance_of($Fruit)
20
+ expect(fruit).to be_instance_of(Structs::Fruit)
22
21
  expect(fruit.name).to be_nil
23
22
  end
24
23
 
@@ -26,7 +25,7 @@ describe "A DSL with argument handling describing fruit" do
26
25
  fruit = dsl_class.parse_dsl("
27
26
  fruit { name 'banana' }
28
27
  ")
29
- expect(fruit).to be_instance_of($Fruit)
28
+ expect(fruit).to be_instance_of(Structs::Fruit)
30
29
  expect(fruit.name).to eq('banana')
31
30
  end
32
31
 
@@ -34,7 +33,7 @@ describe "A DSL with argument handling describing fruit" do
34
33
  fruit = dsl_class.parse_dsl("
35
34
  fruit 'banana'
36
35
  ")
37
- expect(fruit).to be_instance_of($Fruit)
36
+ expect(fruit).to be_instance_of(Structs::Fruit)
38
37
  expect(fruit.name).to eq('banana')
39
38
  end
40
39
 
@@ -45,29 +44,31 @@ describe "A DSL with argument handling describing fruit" do
45
44
  name 'banana'
46
45
  end
47
46
  ")
48
- expect(fruit).to be_instance_of($Fruit)
47
+ expect(fruit).to be_instance_of(Structs::Fruit)
49
48
  expect(fruit.name).to eq('banana')
50
49
  end
51
50
  end
52
51
 
53
52
  describe "with two arguments in add_entrypoint" do
54
- dsl_class = Class.new(DSL::Maker) do
55
- add_entrypoint(:fruit, {
56
- :name => String,
57
- :color => String,
58
- }) do |*args|
59
- default('name', args)
60
- default('color', args, 1)
53
+ let(:dsl_class) {
54
+ Class.new(DSL::Maker) do
55
+ add_entrypoint(:fruit, {
56
+ :name => String,
57
+ :color => String,
58
+ }) do |*args|
59
+ default('name', args)
60
+ default('color', args, 1)
61
61
 
62
- $Fruit.new(name, color)
62
+ Structs::Fruit.new(name, color)
63
+ end
63
64
  end
64
- end
65
+ }
65
66
 
66
67
  it "can handle no arguments" do
67
68
  fruit = dsl_class.parse_dsl("
68
- fruit
69
+ fruit {}
69
70
  ")
70
- expect(fruit).to be_instance_of($Fruit)
71
+ expect(fruit).to be_instance_of(Structs::Fruit)
71
72
  expect(fruit.name).to be_nil
72
73
  expect(fruit.color).to be_nil
73
74
  end
@@ -79,7 +80,7 @@ describe "A DSL with argument handling describing fruit" do
79
80
  color 'yellow'
80
81
  }
81
82
  ")
82
- expect(fruit).to be_instance_of($Fruit)
83
+ expect(fruit).to be_instance_of(Structs::Fruit)
83
84
  expect(fruit.name).to eq('banana')
84
85
  expect(fruit.color).to eq('yellow')
85
86
 
@@ -89,7 +90,7 @@ describe "A DSL with argument handling describing fruit" do
89
90
  color 'green'
90
91
  end
91
92
  ")
92
- expect(fruit).to be_instance_of($Fruit)
93
+ expect(fruit).to be_instance_of(Structs::Fruit)
93
94
  expect(fruit.name).to eq('plantain')
94
95
  expect(fruit.color).to eq('green')
95
96
  end
@@ -98,27 +99,29 @@ describe "A DSL with argument handling describing fruit" do
98
99
  fruit = dsl_class.parse_dsl("
99
100
  fruit 'banana', 'yellow'
100
101
  ")
101
- expect(fruit).to be_instance_of($Fruit)
102
+ expect(fruit).to be_instance_of(Structs::Fruit)
102
103
  expect(fruit.name).to eq('banana')
103
104
  expect(fruit.color).to eq('yellow')
104
105
  end
105
106
  end
106
107
 
107
108
  describe "with one argument in generate_dsl" do
108
- dsl_class = Class.new(DSL::Maker) do
109
- add_entrypoint(:fruit, {
110
- :name => String,
111
- :color => generate_dsl({
109
+ let (:dsl_class) {
110
+ Class.new(DSL::Maker) do
111
+ add_entrypoint(:fruit, {
112
112
  :name => String,
113
- }) { |*args|
113
+ :color => generate_dsl({
114
+ :name => String,
115
+ }) { |*args|
116
+ default('name', args, 0)
117
+ Structs::Color.new(name)
118
+ }
119
+ }) do |*args|
114
120
  default('name', args, 0)
115
- $Color.new(name)
116
- }
117
- }) do |*args|
118
- default('name', args, 0)
119
- $Fruit.new(name, color)
121
+ Structs::Fruit.new(name, color)
122
+ end
120
123
  end
121
- end
124
+ }
122
125
 
123
126
  it "can handle arguments for fruit, but attribute for color" do
124
127
  fruit = dsl_class.parse_dsl("
@@ -128,9 +131,9 @@ describe "A DSL with argument handling describing fruit" do
128
131
  }
129
132
  end
130
133
  ")
131
- expect(fruit).to be_instance_of($Fruit)
134
+ expect(fruit).to be_instance_of(Structs::Fruit)
132
135
  expect(fruit.name).to eq('banana')
133
- expect(fruit.color).to be_instance_of($Color)
136
+ expect(fruit.color).to be_instance_of(Structs::Color)
134
137
  expect(fruit.color.name).to eq('yellow')
135
138
  end
136
139
 
@@ -140,9 +143,9 @@ describe "A DSL with argument handling describing fruit" do
140
143
  color 'yellow'
141
144
  end
142
145
  ")
143
- expect(fruit).to be_instance_of($Fruit)
146
+ expect(fruit).to be_instance_of(Structs::Fruit)
144
147
  expect(fruit.name).to eq('banana')
145
- expect(fruit.color).to be_instance_of($Color)
148
+ expect(fruit.color).to be_instance_of(Structs::Color)
146
149
  expect(fruit.color.name).to eq('yellow')
147
150
  end
148
151
 
@@ -154,9 +157,9 @@ describe "A DSL with argument handling describing fruit" do
154
157
  end
155
158
  end
156
159
  ")
157
- expect(fruit).to be_instance_of($Fruit)
160
+ expect(fruit).to be_instance_of(Structs::Fruit)
158
161
  expect(fruit.name).to eq('banana')
159
- expect(fruit.color).to be_instance_of($Color)
162
+ expect(fruit.color).to be_instance_of(Structs::Color)
160
163
  expect(fruit.color.name).to eq('green')
161
164
  end
162
165
  end
@@ -2,9 +2,6 @@
2
2
  # does the right thing.
3
3
 
4
4
  describe "Passing a class into generate_dsl" do
5
- $Car = Struct.new(:maker, :wheel)
6
- $Wheel = Struct.new(:maker, :size)
7
-
8
5
  it "can do it" do
9
6
  wheel_dsl = Class.new(DSL::Maker) do
10
7
  add_entrypoint(:wheel, {
@@ -12,7 +9,7 @@ describe "Passing a class into generate_dsl" do
12
9
  :maker => String,
13
10
  }) do |*args|
14
11
  default(:maker, args, 0)
15
- $Wheel.new(maker, size)
12
+ Structs::Wheel.new(maker, size)
16
13
  end
17
14
  end
18
15
 
@@ -22,7 +19,7 @@ describe "Passing a class into generate_dsl" do
22
19
  :wheel => wheel_dsl.entrypoint(:wheel),
23
20
  }) do |*args|
24
21
  default(:maker, args, 0)
25
- $Car.new(maker, wheel)
22
+ Structs::Car.new(maker, wheel)
26
23
  end
27
24
  end
28
25
 
@@ -33,9 +30,9 @@ describe "Passing a class into generate_dsl" do
33
30
  end
34
31
  end
35
32
  end
36
- expect(car).to be_instance_of($Car)
33
+ expect(car).to be_instance_of(Structs::Car)
37
34
  expect(car.maker).to eq('honda')
38
- expect(car.wheel).to be_instance_of($Wheel)
35
+ expect(car.wheel).to be_instance_of(Structs::Wheel)
39
36
  expect(car.wheel.maker).to eq('goodyear')
40
37
  expect(car.wheel.size).to eq(26)
41
38
  end
@@ -49,7 +46,7 @@ describe "Passing a class into generate_dsl" do
49
46
  :maker => String,
50
47
  }) do |*args|
51
48
  default(:maker, args, 0)
52
- $Wheel.new(maker, size)
49
+ Structs::Wheel.new(maker, size)
53
50
  end
54
51
  end
55
52
 
@@ -63,7 +60,7 @@ describe "Passing a class into generate_dsl" do
63
60
  :wheel => wheel_dsl.entrypoint(:wheel),
64
61
  }) do |*args|
65
62
  default(:maker, args, 0)
66
- $Car.new(maker, wheel)
63
+ Structs::Car.new(maker, wheel)
67
64
  end
68
65
  end
69
66
 
@@ -74,9 +71,9 @@ describe "Passing a class into generate_dsl" do
74
71
  end
75
72
  end
76
73
  end
77
- expect(car).to be_instance_of($Car)
74
+ expect(car).to be_instance_of(Structs::Car)
78
75
  expect(car.maker).to eq('honda')
79
- expect(car.wheel).to be_instance_of($Wheel)
76
+ expect(car.wheel).to be_instance_of(Structs::Wheel)
80
77
  expect(car.wheel.maker).to eq('goodyear')
81
78
  expect(car.wheel.size).to eq(26)
82
79
  end
data/spec/error_spec.rb CHANGED
@@ -11,10 +11,44 @@ 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')
14
+ describe "for :parse_dsl" do
15
+ it "requires an entrypoint" do
16
+ expect {
17
+ Class.new(DSL::Maker).parse_dsl("")
18
+ }.to raise_error('Must call add_entrypoint before parse_dsl')
19
+ end
20
+
21
+ it "requires a string (check nil)" do
22
+ expect {
23
+ kls = Class.new(DSL::Maker)
24
+ kls.add_entrypoint(:x) {}
25
+ kls.parse_dsl
26
+ }.to raise_error('String required for parse_dsl')
27
+ end
28
+
29
+ it "requires a string (check number)" do
30
+ expect {
31
+ kls = Class.new(DSL::Maker)
32
+ kls.add_entrypoint(:x) {}
33
+ kls.parse_dsl(1)
34
+ }.to raise_error('String required for parse_dsl')
35
+ end
36
+ end
37
+
38
+ describe "for :execute_dsl" do
39
+ it "requires an entrypoint" do
40
+ expect {
41
+ Class.new(DSL::Maker).execute_dsl {}
42
+ }.to raise_error('Must call add_entrypoint before execute_dsl')
43
+ end
44
+
45
+ it "requires a block" do
46
+ expect {
47
+ kls = Class.new(DSL::Maker)
48
+ kls.add_entrypoint(:x) {}
49
+ kls.execute_dsl
50
+ }.to raise_error('Block required for execute_dsl')
51
+ end
18
52
  end
19
53
 
20
54
  describe "for attributes" do
data/spec/helper_spec.rb CHANGED
@@ -1,15 +1,12 @@
1
1
  # This uses a DSL that also provides a set of useful helpers.
2
2
  #
3
3
  describe "A DSL with helpers" do
4
- $Car = Struct.new(:maker, :wheel)
5
- $Wheel = Struct.new(:maker, :size)
6
-
7
4
  it "can add a helper that's useful" do
8
5
  dsl_class = Class.new(DSL::Maker) do
9
6
  add_entrypoint(:car, {
10
7
  :maker => String,
11
8
  }) do
12
- $Car.new(maker)
9
+ Structs::Car.new(maker)
13
10
  end
14
11
 
15
12
  add_helper(:transform) do |name|
@@ -22,7 +19,7 @@ describe "A DSL with helpers" do
22
19
  maker transform('Honda')
23
20
  }
24
21
  ")
25
- expect(car).to be_instance_of($Car)
22
+ expect(car).to be_instance_of(Structs::Car)
26
23
  expect(car.maker).to eq('HONDA')
27
24
  end
28
25
 
@@ -36,10 +33,10 @@ describe "A DSL with helpers" do
36
33
  :wheel => generate_dsl({
37
34
  :maker => String,
38
35
  }) do
39
- $Wheel.new(maker)
36
+ Structs::Wheel.new(maker)
40
37
  end
41
38
  }) do
42
- $Car.new(maker, wheel)
39
+ Structs::Car.new(maker, wheel)
43
40
  end
44
41
 
45
42
  add_helper(:transform2) do |name|
@@ -55,9 +52,9 @@ describe "A DSL with helpers" do
55
52
  }
56
53
  }
57
54
  ")
58
- expect(car).to be_instance_of($Car)
55
+ expect(car).to be_instance_of(Structs::Car)
59
56
  expect(car.maker).to eq('Honda')
60
- expect(car.wheel).to be_instance_of($Wheel)
57
+ expect(car.wheel).to be_instance_of(Structs::Wheel)
61
58
  expect(car.wheel.maker).to eq('GOODYEAR')
62
59
  end
63
60
  end
@@ -4,8 +4,6 @@
4
4
  # 1. Because we're creating classes on the fly, we must fully-qualify the Boolean
5
5
  # class name. If we created real classes, the context would be provided for us.
6
6
  describe 'A multi-level DSL making family-trees' do
7
- $Person = Struct.new(:name, :child)
8
-
9
7
  it "can handle a simple single-level parse of a two-level DSL" do
10
8
  dsl_class = Class.new(DSL::Maker) do
11
9
  add_entrypoint(:person, {
@@ -13,15 +11,15 @@ describe 'A multi-level DSL making family-trees' do
13
11
  :child => generate_dsl({
14
12
  :name => String,
15
13
  }) {
16
- $Person.new(name)
14
+ Structs::Person.new(name)
17
15
  },
18
16
  }) do
19
- $Person.new(name, child)
17
+ Structs::Person.new(name, child)
20
18
  end
21
19
  end
22
20
 
23
21
  person = dsl_class.parse_dsl('person { name "Tom" }')
24
- expect(person).to be_instance_of($Person)
22
+ expect(person).to be_instance_of(Structs::Person)
25
23
  expect(person.name).to eq('Tom')
26
24
  expect(person.child).to be_nil
27
25
  end
@@ -33,10 +31,10 @@ describe 'A multi-level DSL making family-trees' do
33
31
  :child => generate_dsl({
34
32
  :name => String,
35
33
  }) {
36
- $Person.new(name, nil)
34
+ Structs::Person.new(name, nil)
37
35
  },
38
36
  }) do
39
- $Person.new(name, child)
37
+ Structs::Person.new(name, child)
40
38
  end
41
39
  end
42
40
 
@@ -48,9 +46,9 @@ describe 'A multi-level DSL making family-trees' do
48
46
  }
49
47
  }
50
48
  ")
51
- expect(person).to be_instance_of($Person)
49
+ expect(person).to be_instance_of(Structs::Person)
52
50
  expect(person.name).to eq('Tom')
53
- expect(person.child).to be_instance_of($Person)
51
+ expect(person.child).to be_instance_of(Structs::Person)
54
52
  expect(person.child.name).to eq('Bill')
55
53
  end
56
54
 
@@ -63,13 +61,13 @@ describe 'A multi-level DSL making family-trees' do
63
61
  :child => generate_dsl({
64
62
  :name => String,
65
63
  }) {
66
- $Person.new(name, nil)
64
+ Structs::Person.new(name, nil)
67
65
  },
68
66
  }) {
69
- $Person.new(name, child)
67
+ Structs::Person.new(name, child)
70
68
  },
71
69
  }) do
72
- $Person.new(name, child)
70
+ Structs::Person.new(name, child)
73
71
  end
74
72
  end
75
73
 
@@ -84,11 +82,11 @@ describe 'A multi-level DSL making family-trees' do
84
82
  }
85
83
  }
86
84
  ")
87
- expect(person).to be_instance_of($Person)
85
+ expect(person).to be_instance_of(Structs::Person)
88
86
  expect(person.name).to eq('Tom')
89
- expect(person.child).to be_instance_of($Person)
87
+ expect(person.child).to be_instance_of(Structs::Person)
90
88
  expect(person.child.name).to eq('Bill')
91
- expect(person.child.child).to be_instance_of($Person)
89
+ expect(person.child.child).to be_instance_of(Structs::Person)
92
90
  expect(person.child.child.name).to eq('Judith')
93
91
  end
94
92
 
@@ -98,7 +96,7 @@ describe 'A multi-level DSL making family-trees' do
98
96
  person_dsl = add_entrypoint(:person, {
99
97
  :name => String,
100
98
  }) do
101
- $Person.new(name, child)
99
+ Structs::Person.new(name, child)
102
100
  end
103
101
  build_dsl_element(person_dsl, :child, person_dsl)
104
102
  end
@@ -145,19 +143,18 @@ describe 'A multi-level DSL making family-trees' do
145
143
  'Adam', 'Seth', 'Enos', 'Cainan', 'Mahalaleel', 'Jared',
146
144
  'Enoch', 'Methuselah', 'Lamech', 'Noah', 'Shem',
147
145
  ].each do |name|
148
- expect(person).to be_instance_of($Person)
146
+ expect(person).to be_instance_of(Structs::Person)
149
147
  expect(person.name).to eq(name)
150
148
  person = person.child
151
149
  end
152
150
  end
153
151
 
154
152
  it "can handle two axes of recursion" do
155
- OtherPerson = Struct.new(:name, :mother, :father)
156
153
  dsl_class = Class.new(DSL::Maker) do
157
154
  person_dsl = add_entrypoint(:person, {
158
155
  :name => String,
159
156
  }) do
160
- OtherPerson.new(name, mother, father)
157
+ Structs::OtherPerson.new(name, mother, father)
161
158
  end
162
159
  build_dsl_element(person_dsl, :mother, person_dsl)
163
160
  build_dsl_element(person_dsl, :father, person_dsl)
@@ -175,15 +172,15 @@ describe 'A multi-level DSL making family-trees' do
175
172
  }
176
173
  ")
177
174
 
178
- expect(person).to be_instance_of(OtherPerson)
175
+ expect(person).to be_instance_of(Structs::OtherPerson)
179
176
  expect(person.name).to eq('John Smith')
180
177
 
181
178
  mother = person.mother
182
- expect(mother).to be_instance_of(OtherPerson)
179
+ expect(mother).to be_instance_of(Structs::OtherPerson)
183
180
  expect(mother.name).to eq('Mary Smith')
184
181
 
185
182
  father = person.father
186
- expect(father).to be_instance_of(OtherPerson)
183
+ expect(father).to be_instance_of(Structs::OtherPerson)
187
184
  expect(father.name).to eq('Tom Smith')
188
185
  end
189
186
  end
@@ -1,15 +1,12 @@
1
- # This will use a DSL that defines $Cars
1
+ # This will use a DSL that defines Structs::Cars
2
2
 
3
3
  describe "A DSL describing cars used with multiple invocations" do
4
- $Car = Struct.new(:maker, :wheel)
5
- $Truck = Struct.new(:maker, :wheel)
6
-
7
4
  it "returns two items in the right order" do
8
5
  dsl_class = Class.new(DSL::Maker) do
9
6
  add_entrypoint(:car, {
10
- :maker => String,
7
+ :maker => DSL::Maker::Any,
11
8
  }) do
12
- $Car.new(maker)
9
+ Structs::Car.new(maker)
13
10
  end
14
11
  end
15
12
 
@@ -19,9 +16,9 @@ describe "A DSL describing cars used with multiple invocations" do
19
16
  ")
20
17
  expect(cars).to be_instance_of(Array)
21
18
  expect(cars.length).to eq(2)
22
- expect(cars[0]).to be_instance_of($Car)
19
+ expect(cars[0]).to be_instance_of(Structs::Car)
23
20
  expect(cars[0].maker).to eq('Honda')
24
- expect(cars[1]).to be_instance_of($Car)
21
+ expect(cars[1]).to be_instance_of(Structs::Car)
25
22
  expect(cars[1].maker).to eq('Acura')
26
23
  end
27
24
 
@@ -30,12 +27,12 @@ describe "A DSL describing cars used with multiple invocations" do
30
27
  add_entrypoint(:car, {
31
28
  :maker => String,
32
29
  }) do
33
- $Car.new(maker)
30
+ Structs::Car.new(maker)
34
31
  end
35
32
  add_entrypoint(:truck, {
36
33
  :maker => String,
37
34
  }) do
38
- $Truck.new(maker)
35
+ Structs::Truck.new(maker)
39
36
  end
40
37
  end
41
38
 
@@ -46,11 +43,11 @@ describe "A DSL describing cars used with multiple invocations" do
46
43
  ")
47
44
  expect(vehicles).to be_instance_of(Array)
48
45
  expect(vehicles.length).to eq(3)
49
- expect(vehicles[0]).to be_instance_of($Truck)
46
+ expect(vehicles[0]).to be_instance_of(Structs::Truck)
50
47
  expect(vehicles[0].maker).to eq('Ford')
51
- expect(vehicles[1]).to be_instance_of($Car)
48
+ expect(vehicles[1]).to be_instance_of(Structs::Car)
52
49
  expect(vehicles[1].maker).to eq('Honda')
53
- expect(vehicles[2]).to be_instance_of($Truck)
50
+ expect(vehicles[2]).to be_instance_of(Structs::Truck)
54
51
  expect(vehicles[2].maker).to eq('Toyota')
55
52
  end
56
53
 
@@ -59,12 +56,12 @@ describe "A DSL describing cars used with multiple invocations" do
59
56
  add_entrypoint(:car, {
60
57
  :maker => String,
61
58
  }) do
62
- $Car.new(maker)
59
+ Structs::Car.new(maker)
63
60
  end
64
61
  add_entrypoint(:truck, {
65
62
  :maker => String,
66
63
  }) do
67
- $Truck.new(maker)
64
+ Structs::Truck.new(maker)
68
65
  end
69
66
  end
70
67
 
@@ -75,11 +72,11 @@ describe "A DSL describing cars used with multiple invocations" do
75
72
  end
76
73
  expect(vehicles).to be_instance_of(Array)
77
74
  expect(vehicles.length).to eq(3)
78
- expect(vehicles[0]).to be_instance_of($Truck)
75
+ expect(vehicles[0]).to be_instance_of(Structs::Truck)
79
76
  expect(vehicles[0].maker).to eq('Ford')
80
- expect(vehicles[1]).to be_instance_of($Car)
77
+ expect(vehicles[1]).to be_instance_of(Structs::Car)
81
78
  expect(vehicles[1].maker).to eq('Honda')
82
- expect(vehicles[2]).to be_instance_of($Truck)
79
+ expect(vehicles[2]).to be_instance_of(Structs::Truck)
83
80
  expect(vehicles[2].maker).to eq('Toyota')
84
81
  end
85
82
  end
@@ -11,22 +11,46 @@
11
11
  # 1. Because we're creating classes on the fly, we must fully-qualify the Boolean
12
12
  # class name. If we created real classes, the context would be provided for us.
13
13
  describe 'A single-level DSL for pizza' do
14
- $toppings = [:cheese, :pepperoni, :bacon, :sauce]
15
- $Pizza = Struct.new(*$toppings)
16
-
14
+ # This uses $toppings defined in spec/spec_helper.rb
17
15
  def verify_pizza(pizza, values={})
18
- expect(pizza).to be_instance_of($Pizza)
16
+ expect(pizza).to be_instance_of(Structs::Pizza)
19
17
  $toppings.each do |topping|
20
18
  expect(pizza.send(topping)).to eq(values[topping])
21
19
  end
22
20
  end
23
21
 
22
+ describe 'handles an empty DSL' do
23
+ it 'with :parse_dsl' do
24
+ dsl_class = Class.new(DSL::Maker) do
25
+ add_entrypoint(:pizza) {
26
+ Structs::Pizza.new
27
+ }
28
+ end
29
+
30
+ pizza = dsl_class.parse_dsl('')
31
+ expect(pizza).to be(nil)
32
+ end
33
+
34
+ it 'with :execute_dsl' do
35
+ dsl_class = Class.new(DSL::Maker) do
36
+ add_entrypoint(:pizza) {
37
+ Structs::Pizza.new
38
+ }
39
+ end
40
+
41
+ pizza = dsl_class.execute_dsl {}
42
+ expect(pizza).to be(nil)
43
+ end
44
+ end
45
+
24
46
  it 'makes a blank pizza' do
25
47
  dsl_class = Class.new(DSL::Maker) do
26
- add_entrypoint(:pizza) { $Pizza.new }
48
+ add_entrypoint(:pizza) {
49
+ Structs::Pizza.new
50
+ }
27
51
  end
28
52
 
29
- pizza = dsl_class.parse_dsl('pizza')
53
+ pizza = dsl_class.parse_dsl('pizza {}')
30
54
  verify_pizza(pizza)
31
55
  end
32
56
 
@@ -36,7 +60,7 @@ describe 'A single-level DSL for pizza' do
36
60
  add_entrypoint(:pizza, {
37
61
  :cheese => DSL::Maker::Boolean,
38
62
  }) do
39
- $Pizza.new(cheese, nil, nil, nil)
63
+ Structs::Pizza.new(cheese, nil, nil, nil)
40
64
  end
41
65
  end
42
66
 
@@ -84,7 +108,7 @@ describe 'A single-level DSL for pizza' do
84
108
  add_entrypoint(:pizza, {
85
109
  :sauce => String,
86
110
  }) do
87
- $Pizza.new(nil, nil, nil, sauce)
111
+ Structs::Pizza.new(nil, nil, nil, sauce)
88
112
  end
89
113
  end
90
114
 
@@ -109,7 +133,7 @@ describe 'A single-level DSL for pizza' do
109
133
  :pepperoni => DSL::Maker::Boolean,
110
134
  :sauce => String,
111
135
  }) do
112
- $Pizza.new(cheese, pepperoni, bacon, sauce)
136
+ Structs::Pizza.new(cheese, pepperoni, bacon, sauce)
113
137
  end
114
138
 
115
139
  add_entrypoint(:pizza, toppings_dsl)
@@ -139,7 +163,7 @@ describe 'A single-level DSL for pizza' do
139
163
  :pepperoni => DSL::Maker::Boolean,
140
164
  :sauce => String,
141
165
  }) do
142
- $Pizza.new(cheese, pepperoni, bacon, sauce)
166
+ Structs::Pizza.new(cheese, pepperoni, bacon, sauce)
143
167
  end
144
168
 
145
169
  add_entrypoint(:pizza, toppings_dsl)
data/spec/spec_helper.rb CHANGED
@@ -29,3 +29,19 @@ unless on_1_8?
29
29
  end
30
30
 
31
31
  require 'dsl/maker'
32
+
33
+ module Structs
34
+ Car = Struct.new(:maker, :wheel)
35
+ Truck = Struct.new(:maker, :wheel)
36
+ Wheel = Struct.new(:maker, :size)
37
+
38
+ Person = Struct.new(:name, :child)
39
+ OtherPerson = Struct.new(:name, :mother, :father)
40
+
41
+ $toppings = [:cheese, :pepperoni, :bacon, :sauce]
42
+ Pizza = Struct.new(*$toppings)
43
+
44
+ Color = Struct.new(:name)
45
+ Fruit = Struct.new(:name, :color)
46
+ end
47
+
@@ -9,7 +9,7 @@ describe "Validations" do
9
9
  :pepperoni => DSL::Maker::Boolean,
10
10
  :sauce => String,
11
11
  }) do
12
- $Pizza.new(cheese, pepperoni, bacon, sauce)
12
+ Structs::Pizza.new(cheese, pepperoni, bacon, sauce)
13
13
  end
14
14
 
15
15
  add_entrypoint(:pizza, toppings_dsl)
@@ -26,4 +26,30 @@ describe "Validations" do
26
26
  dsl_class.parse_dsl("pizza { sauce :extra }")
27
27
  }.to_not raise_error
28
28
  end
29
+
30
+ it "validates at the dsl_class level" do
31
+ dsl_class = Class.new(DSL::Maker) do
32
+ toppings_dsl = generate_dsl({
33
+ :cheese => DSL::Maker::Boolean,
34
+ :bacon => DSL::Maker::Boolean,
35
+ :pepperoni => DSL::Maker::Boolean,
36
+ :sauce => String,
37
+ }) do
38
+ Structs::Pizza.new(cheese, pepperoni, bacon, sauce)
39
+ end
40
+ toppings_dsl.add_verification do |item|
41
+ return "Pizza must have sauce" unless item.sauce
42
+ end
43
+
44
+ add_entrypoint(:pizza, toppings_dsl)
45
+ end
46
+
47
+ expect {
48
+ dsl_class.parse_dsl("pizza {}")
49
+ }.to raise_error("Pizza must have sauce")
50
+
51
+ expect {
52
+ dsl_class.parse_dsl("pizza { sauce :extra }")
53
+ }.to_not raise_error
54
+ end
29
55
  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.7
4
+ version: 0.0.8
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-08-06 00:00:00.000000000 Z
11
+ date: 2015-08-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: docile