dsl_maker 0.0.7 → 0.0.8

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