dsl_compose 2.15.3 → 2.15.5

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
  SHA256:
3
- metadata.gz: 2fd1fd8c1e9fa1f20c3a46fa9d27bd9aca1cf5f4529e8faa2e86ba5b972ef211
4
- data.tar.gz: ca41bb7b1ef3f699b0d96baf77dee95fb27a7fcf0d26b7dcc37838b3a575dbff
3
+ metadata.gz: e0750c158e424591df6b08e2ac3786b689116b5850152b6f6fe51fc39092895a
4
+ data.tar.gz: 7e818d88dd0dde898b80faa4eb24f18eaa6ca8bd64a4c83fc974621ccee71e3e
5
5
  SHA512:
6
- metadata.gz: 53328f870487ad66a45bf7d179a5085d39049a0eb02eefacaa7e38a386bdd2fa043cd9fb7a887888ed53156d6b6cdf9fe828e19c53fd0c768f8e81d3b666ec8d
7
- data.tar.gz: d904bb0b9c0188755031e678099fa8644f5712433eea0471b0f581ea1247d6f29725766f638383d412bfb0fa7a187d1bcb5bba51ec61fd725ab171446cc87e7b
6
+ metadata.gz: a374a44e85a232cc6681c98034a1196cc7ccc077689a3f9bb75354aa54e0e02be8758a527de68944af95c81f04157b07df4bcaccab20877cfee48f2164eddad2
7
+ data.tar.gz: 73fd1c43e1677b42c2389a4919e0ced10feda5e35ca564d1d025376fbb357282f5d8bf7411980d3d9d744c46b6d50cbc2fa370314ad0c4de58093eec569fc53d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.15.5](https://github.com/craigulliott/dsl_compose/compare/v2.15.4...v2.15.5) (2023-10-06)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * added a first_use_only option to the parser so that only the most recent execution of a DSL will be used ([717293d](https://github.com/craigulliott/dsl_compose/commit/717293d906a183d8a13ffa1c4ae47e901de428a0))
9
+ * specs and fixes for `first_use_only` option on parser ([a7dc087](https://github.com/craigulliott/dsl_compose/commit/a7dc0878ffef9a104c25d4f43d5796f939cb7567))
10
+
11
+ ## [2.15.4](https://github.com/craigulliott/dsl_compose/compare/v2.15.3...v2.15.4) (2023-10-04)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * passing context along with where DSL's were defined, and then including this context in error messages ([5238a3d](https://github.com/craigulliott/dsl_compose/commit/5238a3daa8c1a32e77783d56c7c429b6335995bd))
17
+
3
18
  ## [2.15.3](https://github.com/craigulliott/dsl_compose/compare/v2.15.2...v2.15.3) (2023-10-03)
4
19
 
5
20
 
@@ -48,9 +48,11 @@ module DSLCompose
48
48
 
49
49
  # add a singleton method with the name of this new DSL onto our class, this is how our new DSL will be accessed
50
50
  define_singleton_method name do |*args, &block|
51
+ called_from = caller(1..1).first
52
+
51
53
  # when it is called, we process this new dynamic DSL with the interpreter
52
54
  # `self` here is the class in which the dsl is being used, not the class in which the DSL was defined
53
- interpreter.execute_dsl self, dsl, *args, &block
55
+ interpreter.execute_dsl self, dsl, called_from, *args, &block
54
56
  end
55
57
 
56
58
  end
@@ -127,7 +127,7 @@ module DSLCompose
127
127
  Interpreter.new(self).instance_eval(&block)
128
128
  end
129
129
  rescue => e
130
- raise e, "Error defining argument #{name}: #{e.message}", e.backtrace
130
+ raise e, "Error while defining argument #{name}\n#{e.message}", e.backtrace
131
131
  end
132
132
 
133
133
  # Set the description for this Argument to the provided value.
@@ -60,7 +60,7 @@ module DSLCompose
60
60
  Interpreter.new(self).instance_eval(&block)
61
61
  end
62
62
  rescue => e
63
- raise e, "Error defining method #{name}: #{e.message}", e.backtrace
63
+ raise e, "Error while defining method #{name}\n#{e.message}", e.backtrace
64
64
  end
65
65
 
66
66
  # Set the description for this DSLMethod to the provided value.
@@ -83,8 +83,6 @@ module DSLCompose
83
83
  else
84
84
  raise NoBlockProvidedError, "No block was provided for this DSL"
85
85
  end
86
- rescue => e
87
- raise e, "Error defining DSL #{@name} on #{@klass}: #{e.message}", e.backtrace
88
86
  end
89
87
 
90
88
  # Set the description for this DSL to the provided value.
@@ -4,25 +4,27 @@ module DSLCompose
4
4
  class Interpreter
5
5
  class Execution
6
6
  class Arguments
7
- class MissingRequiredArgumentsError < StandardError
7
+ class MissingRequiredArgumentsError < InterpreterError
8
8
  end
9
9
 
10
- class TooManyArgumentsError < StandardError
10
+ class TooManyArgumentsError < InterpreterError
11
11
  end
12
12
 
13
- class OptionalArgumentsShouldBeHashError < StandardError
13
+ class OptionalArgumentsShouldBeHashError < InterpreterError
14
14
  end
15
15
 
16
- class InvalidArgumentTypeError < StandardError
16
+ class InvalidArgumentTypeError < InterpreterError
17
17
  end
18
18
 
19
- class ArrayNotValidError < StandardError
19
+ class ArrayNotValidError < InterpreterError
20
20
  end
21
21
 
22
22
  attr_reader :arguments
23
+ attr_reader :called_from
23
24
 
24
- def initialize arguments, *args
25
+ def initialize arguments, called_from, *args
25
26
  @arguments = {}
27
+ @called_from = called_from
26
28
 
27
29
  required_argument_count = arguments.required_arguments.count
28
30
  required_non_kwarg_argument_count = arguments.required_arguments.count { |a| !a.kwarg }
@@ -40,7 +42,7 @@ module DSLCompose
40
42
  required_kwargs = arguments.required_arguments.filter { |a| a.kwarg }
41
43
 
42
44
  if required_kwargs.any? && !all_kwargs.is_a?(Hash)
43
- raise MissingRequiredArgumentsError, "This has required keyword arguments, but no keyword arguments were provided"
45
+ raise MissingRequiredArgumentsError.new("This has required keyword arguments, but no keyword arguments were provided", called_from)
44
46
  end
45
47
 
46
48
  required_kwargs.each do |required_kwarg|
@@ -51,7 +53,7 @@ module DSLCompose
51
53
  # left with only the optional args
52
54
  all_kwargs.delete required_kwarg.name
53
55
  else
54
- raise MissingRequiredArgumentsError, "The required kwarg `#{required_kwarg.name}` was not provided"
56
+ raise MissingRequiredArgumentsError.new("The required kwarg `#{required_kwarg.name}` was not provided", called_from)
55
57
  end
56
58
  end
57
59
 
@@ -61,12 +63,12 @@ module DSLCompose
61
63
 
62
64
  # assert that a value is provided for every required argument
63
65
  unless required_argument_count == required_args.count
64
- raise MissingRequiredArgumentsError, "This requires #{required_non_kwarg_argument_count} arguments, but only #{required_args.count} were provided"
66
+ raise MissingRequiredArgumentsError.new("This requires #{required_non_kwarg_argument_count} arguments, but only #{required_args.count} were provided", called_from)
65
67
  end
66
68
 
67
69
  # assert that too many arguments have not been provided
68
70
  if args.count > required_argument_count + (has_optional_arguments ? 1 : 0)
69
- raise TooManyArgumentsError, "Too many arguments provided"
71
+ raise TooManyArgumentsError.new("Too many arguments provided", called_from)
70
72
  end
71
73
 
72
74
  # Assume all optonal arguments are their defaults (except booleans, which default to false).
@@ -88,7 +90,7 @@ module DSLCompose
88
90
  # asset that, if provided, then the optional argument (always the last one) is a Hash
89
91
  if has_optional_arguments && !optional_arg.nil?
90
92
  unless optional_arg.is_a? Hash
91
- raise OptionalArgumentsShouldBeHashError, "If provided, then the optional arguments must be last, and be represented as a Hash"
93
+ raise OptionalArgumentsShouldBeHashError.new("If provided, then the optional arguments must be last, and be represented as a Hash", called_from)
92
94
  end
93
95
 
94
96
  # assert the each provided optional argument is valid
@@ -110,7 +112,7 @@ module DSLCompose
110
112
  end
111
113
 
112
114
  if optional_arg_value.is_a?(Array) && !optional_argument.array
113
- raise ArrayNotValidError, "An array was provided to an argument which does not accept an array of values"
115
+ raise ArrayNotValidError.new("An array was provided to an argument which does not accept an array of values", called_from)
114
116
  end
115
117
 
116
118
  # to simplify the code, we always process the reset of the validations as an array, even
@@ -121,38 +123,38 @@ module DSLCompose
121
123
  case optional_argument.type
122
124
  when :integer
123
125
  unless value.is_a? Integer
124
- raise InvalidArgumentTypeError, "`#{value}` is not an Integer"
126
+ raise InvalidArgumentTypeError.new("`#{value}` is not an Integer", called_from)
125
127
  end
126
128
  optional_argument.validate_integer! value
127
129
 
128
130
  when :float
129
131
  # float allows either floats or integers
130
132
  unless value.is_a?(Float) || value.is_a?(Integer)
131
- raise InvalidArgumentTypeError, "`#{value}` is not an Float or Integer"
133
+ raise InvalidArgumentTypeError.new("`#{value}` is not an Float or Integer", called_from)
132
134
  end
133
135
  optional_argument.validate_float! value
134
136
 
135
137
  when :symbol
136
138
  unless value.is_a? Symbol
137
- raise InvalidArgumentTypeError, "`#{value}` is not a Symbol"
139
+ raise InvalidArgumentTypeError.new("`#{value}` is not a Symbol", called_from)
138
140
  end
139
141
  optional_argument.validate_symbol! value
140
142
 
141
143
  when :string
142
144
  unless value.is_a? String
143
- raise InvalidArgumentTypeError, "`#{value}` is not a String"
145
+ raise InvalidArgumentTypeError.new("`#{value}` is not a String", called_from)
144
146
  end
145
147
  optional_argument.validate_string! value
146
148
 
147
149
  when :boolean
148
150
  unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
149
- raise InvalidArgumentTypeError, "`#{value}` is not a boolean"
151
+ raise InvalidArgumentTypeError.new("`#{value}` is not a boolean", called_from)
150
152
  end
151
153
  optional_argument.validate_boolean! value
152
154
 
153
155
  when :class
154
156
  unless value.is_a?(ClassCoerce)
155
- raise InvalidArgumentTypeError, "`#{value}` is not a class coerce (String)"
157
+ raise InvalidArgumentTypeError.new("`#{value}` is not a class coerce (String)", called_from)
156
158
  end
157
159
  optional_argument.validate_class! value
158
160
 
@@ -160,7 +162,7 @@ module DSLCompose
160
162
  optional_argument.validate_object! value
161
163
 
162
164
  else
163
- raise InvalidArgumentTypeError, "The argument value `#{value}` is not a supported type"
165
+ raise InvalidArgumentTypeError.new("The argument value `#{value}` is not a supported type", called_from)
164
166
  end
165
167
  end
166
168
 
@@ -174,7 +176,7 @@ module DSLCompose
174
176
  optional_arg_value
175
177
  end
176
178
  rescue => e
177
- raise e, "Error processing optional argument #{optional_argument_name}: #{e.message}", e.backtrace
179
+ raise e, "Error processing optional argument #{optional_argument_name}\n#{e.message}", e.backtrace
178
180
  end
179
181
 
180
182
  end
@@ -200,7 +202,7 @@ module DSLCompose
200
202
  end
201
203
 
202
204
  if required_arg_value.is_a?(Array) && !required_argument.array
203
- raise ArrayNotValidError, "An array was provided to an argument which does not accept an array of values"
205
+ raise ArrayNotValidError.new("An array was provided to an argument which does not accept an array of values", called_from)
204
206
  end
205
207
 
206
208
  # to simplify the code, we always process the reset of the validations as an array, even
@@ -211,38 +213,38 @@ module DSLCompose
211
213
  case required_argument.type
212
214
  when :integer
213
215
  unless value.is_a? Integer
214
- raise InvalidArgumentTypeError, "`#{value}` is not an Integer"
216
+ raise InvalidArgumentTypeError.new("`#{value}` is not an Integer", called_from)
215
217
  end
216
218
  required_argument.validate_integer! value
217
219
 
218
220
  when :float
219
221
  # float allows either floats or integers
220
222
  unless value.is_a?(Float) || value.is_a?(Integer)
221
- raise InvalidArgumentTypeError, "`#{value}` is not an Float or Integer"
223
+ raise InvalidArgumentTypeError.new("`#{value}` is not an Float or Integer", called_from)
222
224
  end
223
225
  required_argument.validate_float! value
224
226
 
225
227
  when :symbol
226
228
  unless value.is_a? Symbol
227
- raise InvalidArgumentTypeError, "`#{value}` is not a Symbol"
229
+ raise InvalidArgumentTypeError.new("`#{value}` is not a Symbol", called_from)
228
230
  end
229
231
  required_argument.validate_symbol! value
230
232
 
231
233
  when :string
232
234
  unless value.is_a? String
233
- raise InvalidArgumentTypeError, "`#{value}` is not a String"
235
+ raise InvalidArgumentTypeError.new("`#{value}` is not a String", called_from)
234
236
  end
235
237
  required_argument.validate_string! value
236
238
 
237
239
  when :boolean
238
240
  unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
239
- raise InvalidArgumentTypeError, "`#{value}` is not a boolean"
241
+ raise InvalidArgumentTypeError.new("`#{value}` is not a boolean", called_from)
240
242
  end
241
243
  required_argument.validate_boolean! value
242
244
 
243
245
  when :class
244
246
  unless value.is_a?(ClassCoerce)
245
- raise InvalidArgumentTypeError, "`#{value}` is not a class coerce (String)"
247
+ raise InvalidArgumentTypeError.new("`#{value}` is not a class coerce (String)", called_from)
246
248
  end
247
249
  required_argument.validate_class! value
248
250
 
@@ -250,7 +252,7 @@ module DSLCompose
250
252
  required_argument.validate_object! value
251
253
 
252
254
  else
253
- raise InvalidArgumentTypeError, "The argument `#{value}` is not a supported type"
255
+ raise InvalidArgumentTypeError.new("The argument `#{value}` is not a supported type", called_from)
254
256
  end
255
257
  end
256
258
 
@@ -264,7 +266,7 @@ module DSLCompose
264
266
  required_arg_value
265
267
  end
266
268
  rescue => e
267
- raise e, "Error processing required argument #{argument_name}: #{e.message}", e.backtrace
269
+ raise e, "Error processing required argument #{argument_name}\n#{e.message}", e.backtrace
268
270
  end
269
271
  end
270
272
 
@@ -5,15 +5,17 @@ module DSLCompose
5
5
  class Execution
6
6
  class MethodCalls
7
7
  class MethodCall
8
- class InvalidDescriptionError < StandardError
8
+ class InvalidDescriptionError < InterpreterError
9
9
  end
10
10
 
11
11
  attr_reader :dsl_method
12
+ attr_reader :called_from
12
13
  attr_reader :arguments
13
14
 
14
- def initialize dsl_method, *args, &block
15
+ def initialize dsl_method, called_from, *args, &block
15
16
  @dsl_method = dsl_method
16
- @arguments = Arguments.new(dsl_method.arguments, *args)
17
+ @called_from = called_from
18
+ @arguments = Arguments.new(dsl_method.arguments, called_from, *args)
17
19
  end
18
20
 
19
21
  def method_name
@@ -30,7 +32,7 @@ module DSLCompose
30
32
  # generate documentation
31
33
  def add_parser_usage_note note
32
34
  unless note.is_a?(String) && note.strip.length > 0
33
- raise InvalidDescriptionError, "The parser usage description `#{note}` is invalid, it must be of type string and have length greater than 0"
35
+ raise InvalidDescriptionError.new("The parser usage description `#{note}` is invalid, it must be of type string and have length greater than 0", @called_from)
34
36
  end
35
37
 
36
38
  @parser_usage_notes ||= []
@@ -14,16 +14,18 @@ module DSLCompose
14
14
  @method_calls.filter { |mc| mc.method_name == method_name }.any?
15
15
  end
16
16
 
17
- def add_method_call(dsl_method, ...)
17
+ def add_method_call(dsl_method, called_from, ...)
18
18
  # make sure we always have a variable which can be used in the exception message
19
+ # set to nil first, so that if we get an exception while setting them
20
+ # it wont break the error message generation
19
21
  dsl_method_name = nil
20
22
  dsl_method_name = dsl_method.name
21
23
 
22
- method_call = MethodCall.new(dsl_method, ...)
24
+ method_call = MethodCall.new(dsl_method, called_from, ...)
23
25
  @method_calls << method_call
24
26
  method_call
25
27
  rescue => e
26
- raise e, "Error while executing method #{dsl_method_name}: #{e.message}", e.backtrace
28
+ raise e, "Error while executing method #{dsl_method_name}\n#{e.message}", e.backtrace
27
29
  end
28
30
 
29
31
  def method_calls_by_name method_name
@@ -3,26 +3,28 @@
3
3
  module DSLCompose
4
4
  class Interpreter
5
5
  class Execution
6
- class MethodIsUniqueError < StandardError
6
+ class MethodIsUniqueError < InterpreterError
7
7
  end
8
8
 
9
- class RequiredMethodNotCalledError < StandardError
9
+ class RequiredMethodNotCalledError < InterpreterError
10
10
  end
11
11
 
12
- class InvalidDescriptionError < StandardError
12
+ class InvalidDescriptionError < InterpreterError
13
13
  end
14
14
 
15
15
  attr_reader :dsl
16
+ attr_reader :called_from
16
17
  attr_reader :klass
17
18
  attr_reader :method_calls
18
19
  attr_reader :arguments
19
20
 
20
21
  # execute/process a dynamically defined DSL
21
- def initialize klass, dsl, *args, &block
22
+ def initialize klass, dsl, called_from, *args, &block
22
23
  @klass = klass
23
24
  @dsl = dsl
25
+ @called_from = called_from
24
26
  @method_calls = MethodCalls.new
25
- @arguments = Arguments.new(dsl.arguments, *args)
27
+ @arguments = Arguments.new(dsl.arguments, called_from, *args)
26
28
 
27
29
  # dynamically process the DSL by calling the provided block
28
30
  # all methods executions will be caught and processed by the method_missing method below
@@ -34,7 +36,7 @@ module DSLCompose
34
36
  dsl.required_dsl_methods.each do |dsl_method|
35
37
  unless @method_calls.method_called? dsl_method.name
36
38
  dsl_method_name = dsl_method&.name
37
- raise RequiredMethodNotCalledError, "The method #{dsl_method_name} is required, but was not called within this DSL"
39
+ raise RequiredMethodNotCalledError.new("The method #{dsl_method_name} is required, but was not called within this DSL", called_from)
38
40
  end
39
41
  end
40
42
  end
@@ -43,7 +45,7 @@ module DSLCompose
43
45
  # generate documentation
44
46
  def add_parser_usage_note note
45
47
  unless note.is_a?(String) && note.strip.length > 0
46
- raise InvalidDescriptionError, "The parser usage description `#{note}` is invalid, it must be of type string and have length greater than 0"
48
+ raise InvalidDescriptionError.new("The parser usage description `#{note}` is invalid, it must be of type string and have length greater than 0", called_from)
47
49
  end
48
50
 
49
51
  @parser_usage_notes ||= []
@@ -60,15 +62,17 @@ module DSLCompose
60
62
 
61
63
  # catch and process any method calls within the DSL block
62
64
  def method_missing(method_name, ...)
65
+ called_from = caller(1..1).first
66
+
63
67
  # if the method does not exist, then this will raise a MethodDoesNotExistError
64
68
  dsl_method = @dsl.dsl_method method_name
65
69
 
66
70
  # if the method is unique, then it can only be called once per DSL
67
71
  if dsl_method.unique? && @method_calls.method_called?(method_name)
68
- raise MethodIsUniqueError, "This method `#{method_name}` is unique and can only be called once within this DSL"
72
+ raise MethodIsUniqueError.new("This method `#{method_name}` is unique and can only be called once within this DSL", called_from)
69
73
  end
70
74
 
71
- @method_calls.add_method_call(dsl_method, ...)
75
+ @method_calls.add_method_call(dsl_method, called_from, ...)
72
76
  end
73
77
 
74
78
  def respond_to_missing?(method_name, *args)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class Interpreter
5
+ class InterpreterError < StandardError
6
+ attr_reader :original_context
7
+
8
+ def initialize(message, original_context)
9
+ super(message)
10
+ @original_context = original_context
11
+ end
12
+
13
+ def to_s
14
+ "#{super}\ndsl source: #{@original_context}"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -4,10 +4,7 @@ module DSLCompose
4
4
  # The class is reponsible for parsing and executing a dynamic DSL (dynamic DSLs are
5
5
  # created using the DSLCompose::DSL class).
6
6
  class Interpreter
7
- class DSLExecutionNotFoundError < StandardError
8
- end
9
-
10
- class InvalidDescriptionError < StandardError
7
+ class InvalidDescriptionError < InterpreterError
11
8
  end
12
9
 
13
10
  # A dynamic DSL can be used multiple times on the same class, each time the DSL is used
@@ -23,7 +20,7 @@ module DSLCompose
23
20
  # generate documentation
24
21
  def add_parser_usage_note child_class, note
25
22
  unless note.is_a?(String) && note.strip.length > 0
26
- raise InvalidDescriptionError, "The parser usage description `#{note}` is invalid, it must be of type string and have length greater than 0"
23
+ raise InvalidDescriptionError.new("The parser usage description `#{note}` is invalid, it must be of type string and have length greater than 0", called_from)
27
24
  end
28
25
  @parser_usage_notes ||= {}
29
26
  @parser_usage_notes[child_class] ||= []
@@ -40,20 +37,23 @@ module DSLCompose
40
37
  # Execute/process a dynamically defined DSL on a class.
41
38
  # `klass` is the class in which the DSL is being used, not
42
39
  # the class in which the DSL was defined.
43
- def execute_dsl(klass, dsl, ...)
40
+ def execute_dsl(klass, dsl, called_from, ...)
44
41
  # make sure we have these variables for the exception message below
42
+ # set to nil first, so that if we get an exception while setting them
43
+ # it wont break the error message generation
45
44
  class_name = nil
46
- class_name = klass.name
47
45
  dsl_name = nil
46
+ class_name = klass.name
48
47
  dsl_name = dsl.name
49
48
 
50
- execution = Execution.new(klass, dsl, ...)
49
+ execution = Execution.new(klass, dsl, called_from, ...)
51
50
  @executions << execution
52
51
  execution
53
52
  rescue => e
54
- raise e, "Error processing dsl #{dsl_name} for class #{class_name}: #{e.message}", e.backtrace
53
+ raise e, "Error while defining DSL #{dsl_name} for class #{class_name}:\n#{e.message}", e.backtrace
55
54
  end
56
55
 
56
+ #
57
57
  # Returns an array of all executions for a given class.
58
58
  def class_executions klass
59
59
  @executions.filter { |e| e.klass == klass }
@@ -66,8 +66,15 @@ module DSLCompose
66
66
 
67
67
  # Returns an array of all executions for a given name and class. This includes
68
68
  # any ancestors of the provided class
69
- def class_dsl_executions klass, dsl_name, on_current_class, on_ancestor_class
70
- @executions.filter { |e| e.dsl.name == dsl_name && ((on_current_class && e.klass == klass) || (on_ancestor_class && klass < e.klass)) }
69
+ def class_dsl_executions klass, dsl_name, on_current_class, on_ancestor_class, first_use_only
70
+ filtered_executions = @executions.filter { |e| e.dsl.name == dsl_name && ((on_current_class && e.klass == klass) || (on_ancestor_class && klass < e.klass)) }
71
+ # Because the classes were evaluated in order, we can just return the
72
+ # last execution
73
+ if first_use_only && filtered_executions.length > 0
74
+ [filtered_executions.last]
75
+ else
76
+ filtered_executions
77
+ end
71
78
  end
72
79
 
73
80
  # returns the most recent, closest single execution of a dsl with the
@@ -83,7 +90,7 @@ module DSLCompose
83
90
  # note that this method does not need to do any special sorting, the required
84
91
  # order for getting the most recent execution is already guaranteed because
85
92
  # parent classes in ruby always have to be evaluated before their descendants
86
- class_dsl_executions(klass, dsl_name, true, true).last
93
+ class_dsl_executions(klass, dsl_name, true, true, false).last
87
94
  end
88
95
 
89
96
  # removes all executions from the interpreter, and any parser_usage_notes
@@ -28,6 +28,8 @@ module DSLCompose
28
28
  @dsl_execution = dsl_execution
29
29
  @method_names = method_names
30
30
 
31
+ dsl_name = dsl_execution.dsl.name
32
+
31
33
  # assert that a block was provided
32
34
  unless block
33
35
  raise NoBlockProvided
@@ -100,6 +102,14 @@ module DSLCompose
100
102
 
101
103
  # yeild the block in the context of this class
102
104
  instance_exec(**args, &block)
105
+ rescue => e
106
+ # if this is an InterpreterError, then it already has the called_from metadata
107
+ # just continue raising the original error
108
+ if e.is_a? Interpreter::InterpreterError
109
+ raise
110
+ end
111
+ # otherwise, decorate the error with where the DSL was defined
112
+ raise e, "#{e.message}\nparsing class: #{child_class.name}\ndsl name: #{dsl_name}\ndsl method name: #{method_name}\ndsl source: #{method_call.called_from}", e.backtrace
103
113
  end
104
114
  end
105
115
  end
@@ -20,7 +20,7 @@ module DSLCompose
20
20
  # of the provided name is used by the child class.
21
21
  #
22
22
  # base_class and child_class are set from the ForChildrenOfParser
23
- def initialize base_class, child_class, dsl_names, on_current_class, on_ancestor_class, &block
23
+ def initialize base_class, child_class, dsl_names, on_current_class, on_ancestor_class, first_use_only, &block
24
24
  @base_class = base_class
25
25
  @child_class = child_class
26
26
  @dsl_names = dsl_names
@@ -62,7 +62,7 @@ module DSLCompose
62
62
  dsl_names.each do |dsl_name|
63
63
  # a dsl can be execued multiple times on a class, so we find all of the executions
64
64
  # here and then yield the block once for each execution
65
- base_class.dsls.class_dsl_executions(child_class, dsl_name, on_current_class, on_ancestor_class).each do |dsl_execution|
65
+ base_class.dsls.class_dsl_executions(child_class, dsl_name, on_current_class, on_ancestor_class, first_use_only).each do |dsl_execution|
66
66
  # we only provide the requested arguments to the block, this allows
67
67
  # us to use keyword arguments to force a naming convention on these arguments
68
68
  # and to validate their use
@@ -108,6 +108,14 @@ module DSLCompose
108
108
  @dsl_execution = dsl_execution
109
109
  # yield the block in the context of this class
110
110
  instance_exec(**args, &block)
111
+ rescue => e
112
+ # if this is an InterpreterError, then it already has the called_from metadata
113
+ # just continue raising the original error
114
+ if e.is_a? Interpreter::InterpreterError
115
+ raise
116
+ end
117
+ # otherwise, decorate the error with where the DSL was defined
118
+ raise e, "#{e.message}\nparsing class: #{child_class.name}\ndsl name: #{dsl_name}\ndsl source: #{dsl_execution.called_from}", e.backtrace
111
119
  end
112
120
  end
113
121
  end
@@ -90,36 +90,42 @@ module DSLCompose
90
90
  # end
91
91
  #
92
92
  # If `on_current_class` is true, then the block will be yielded to for each DSL
93
- # which was used directly on the current class. If `oncurrent_class` is false,
94
- # then the block will not be yielded to for any DSL which was used directly on.
93
+ # which was used directly on the current class. If `on_current_class` is false,
94
+ # then the block will not be yielded to for any DSL which was used directly on it.
95
95
  # If `on_ancestor_class` is true, then the block will be yielded to for each DSL
96
96
  # which was used on any class in the current classes ancestry. If `on_ancestor_class`
97
97
  # is false, then the block will not be yielded to for any DSL which was used on
98
98
  # any class in the current classes ancestry.
99
- def for_dsl dsl_names, on_current_class: true, on_ancestor_class: false, &block
99
+ #
100
+ # If `first_use_only` is true, then this block will only yeild once for each subject class
101
+ # which directly uses or inherits use of the provided DSL. The DSL execution which occurs
102
+ # first in the class will be selected, and if the class does not use the DSL then each of
103
+ # the classes ancestors will be tested until an execution is found (only the current class
104
+ # will be tested if skip_inherited_dsls has been set to true).
105
+ def for_dsl dsl_names, on_current_class: true, on_ancestor_class: false, first_use_only: false, &block
100
106
  child_class = @child_class
101
107
 
102
108
  unless child_class
103
109
  raise NoChildClassError, "No child_class was found, please call this method from within a `for_children_of` block"
104
110
  end
105
111
 
106
- ForDSLParser.new(@base_class, child_class, dsl_names, on_current_class, on_ancestor_class, &block)
112
+ ForDSLParser.new(@base_class, child_class, dsl_names, on_current_class, on_ancestor_class, first_use_only, &block)
107
113
  end
108
114
 
109
115
  # this is a wrapper for the `for_dsl` method, but it provides a value of true
110
116
  # for the `on_ancestor_class` argument and a value of false for the `on_current_class`
111
117
  # argument. This will cause the parser to only yeild for dsls which were used on
112
118
  # a class which is in the current classes ancestry, but not on the current class
113
- def for_inherited_dsl dsl_names, &block
114
- for_dsl dsl_names, on_current_class: false, on_ancestor_class: true, &block
119
+ def for_inherited_dsl dsl_names, first_use_only: false, &block
120
+ for_dsl dsl_names, on_current_class: false, on_ancestor_class: true, first_use_only: first_use_only, &block
115
121
  end
116
122
 
117
123
  # this is a wrapper for the `for_dsl` method, but it provides a value of true
118
124
  # for the `on_ancestor_class` argument and a value of true for the `on_current_class`
119
125
  # argument. This will cause the parser to yeild for dsls which were used on either
120
126
  # the current class or any class in its ancestry
121
- def for_dsl_or_inherited_dsl dsl_names, &block
122
- for_dsl dsl_names, on_current_class: true, on_ancestor_class: true, &block
127
+ def for_dsl_or_inherited_dsl dsl_names, first_use_only: false, &block
128
+ for_dsl dsl_names, on_current_class: true, on_ancestor_class: true, first_use_only: first_use_only, &block
123
129
  end
124
130
 
125
131
  # takes a description of what this parser does and stores it against the DSL definition
@@ -89,7 +89,7 @@ module DSLCompose
89
89
  # Returns an array of ExecutionReaders to represent each time the DSL was used
90
90
  # on the provided class.
91
91
  def executions
92
- @dsl_defining_class.dsls.class_dsl_executions(@klass, @dsl_name, true, false).map do |execution|
92
+ @dsl_defining_class.dsls.class_dsl_executions(@klass, @dsl_name, true, false, false).map do |execution|
93
93
  ExecutionReader.new execution
94
94
  end
95
95
  end
@@ -100,7 +100,7 @@ module DSLCompose
100
100
  # earliest ancestor first and if the DSL was used more than once on a class then
101
101
  # the order they were used.
102
102
  def ancestor_executions
103
- @dsl_defining_class.dsls.class_dsl_executions(@klass, @dsl_name, false, true).map do |execution|
103
+ @dsl_defining_class.dsls.class_dsl_executions(@klass, @dsl_name, false, true, false).map do |execution|
104
104
  ExecutionReader.new execution
105
105
  end
106
106
  end
@@ -110,7 +110,7 @@ module DSLCompose
110
110
  # be returned in the order they were executed, which is the earliest ancestor first
111
111
  # and if the DSL was used more than once on a class then the order they were used.
112
112
  def all_executions
113
- @dsl_defining_class.dsls.class_dsl_executions(@klass, @dsl_name, true, true).map do |execution|
113
+ @dsl_defining_class.dsls.class_dsl_executions(@klass, @dsl_name, true, true, false).map do |execution|
114
114
  ExecutionReader.new execution
115
115
  end
116
116
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSLCompose
4
- VERSION = "2.15.3"
4
+ VERSION = "2.15.5"
5
5
  end
data/lib/dsl_compose.rb CHANGED
@@ -34,6 +34,7 @@ require "dsl_compose/reader/execution_reader/arguments_reader"
34
34
 
35
35
  require "dsl_compose/reader_base"
36
36
 
37
+ require "dsl_compose/interpreter/interpreter_error"
37
38
  require "dsl_compose/interpreter/execution/method_calls/method_call"
38
39
  require "dsl_compose/interpreter/execution/method_calls"
39
40
  require "dsl_compose/interpreter/execution/arguments"
@@ -4,23 +4,24 @@ module DSLCompose
4
4
  class Execution
5
5
  class Arguments
6
6
  attr_reader arguments: Hash[untyped, untyped]
7
- def initialize: (untyped arguments, *untyped args) -> void
7
+ attr_reader called_from: String
8
+
9
+ def initialize: (untyped arguments, String called_from, *untyped args) -> void
8
10
  def to_h: -> Hash[untyped, untyped]
9
11
 
10
- class MissingRequiredArgumentsError < StandardError
11
- def initialize: (untyped required_count, untyped provided_count) -> void
12
+ class MissingRequiredArgumentsError < InterpreterError
12
13
  end
13
14
 
14
- class TooManyArgumentsError < StandardError
15
+ class TooManyArgumentsError < InterpreterError
15
16
  end
16
17
 
17
- class OptionalArgumentsShouldBeHashError < StandardError
18
+ class OptionalArgumentsShouldBeHashError < InterpreterError
18
19
  end
19
20
 
20
- class InvalidArgumentTypeError < StandardError
21
+ class InvalidArgumentTypeError < InterpreterError
21
22
  end
22
23
 
23
- class ArrayNotValidError < StandardError
24
+ class ArrayNotValidError < InterpreterError
24
25
  end
25
26
  end
26
27
  end
@@ -4,21 +4,22 @@ module DSLCompose
4
4
  class Execution
5
5
  class MethodCalls
6
6
  class MethodCall
7
- class InvalidDescriptionError < StandardError
8
- end
9
-
10
7
  @method_call: MethodCall
11
8
  @parser_usage_notes: Array[String]
12
9
 
13
10
  attr_reader dsl_method: DSL::DSLMethod
11
+ attr_reader called_from: String
14
12
  attr_reader arguments: Arguments
15
13
 
16
- def initialize: (DSL::DSLMethod dsl_method, *untyped args) -> void
14
+ def initialize: (DSL::DSLMethod dsl_method, String called_from, *untyped args) -> void
17
15
  def method_name: -> Symbol
18
16
  def add_parser_usage_note: (String note) -> void
19
17
  def parser_usage_notes: () -> Array[String]
20
18
 
21
19
  def to_h: -> Hash[untyped, untyped]
20
+
21
+ class InvalidDescriptionError < InterpreterError
22
+ end
22
23
  end
23
24
  end
24
25
  end
@@ -9,7 +9,7 @@ module DSLCompose
9
9
  def method_calls: () -> Array[MethodCall]
10
10
  def method_called?: (Symbol method_name) -> bool
11
11
  def method_calls_by_name: (Symbol method_name) -> Array[MethodCall]
12
- def add_method_call: (DSL::DSLMethod dsl_method, *untyped args) -> MethodCall
12
+ def add_method_call: (DSL::DSLMethod dsl_method, String called_from, *untyped args) -> MethodCall
13
13
  end
14
14
  end
15
15
  end
@@ -5,11 +5,12 @@ module DSLCompose
5
5
  @parser_usage_notes: Array[String]
6
6
 
7
7
  attr_reader dsl: DSL
8
+ attr_reader called_from: String
8
9
  attr_reader klass: Object
9
10
  attr_reader method_calls: MethodCalls
10
11
  attr_reader arguments: Arguments
11
12
 
12
- def initialize: (Object klass, DSL dsl, *untyped args) -> void
13
+ def initialize: (Object klass, DSL dsl, String called_from, *untyped args) -> void
13
14
  def instance_eval: () -> void
14
15
  def add_parser_usage_note: (String note) -> void
15
16
  def parser_usage_notes: () -> Array[String]
@@ -18,11 +19,15 @@ module DSLCompose
18
19
  def method_missing: (Symbol method_name, *untyped args) -> untyped
19
20
  def respond_to_missing?: (Symbol method_name, *untyped args) -> untyped
20
21
 
21
- class MethodIsUniqueError < StandardError
22
+ class MethodIsUniqueError < InterpreterError
22
23
  end
23
24
 
24
- class RequiredMethodNotCalledError < StandardError
25
+ class RequiredMethodNotCalledError < InterpreterError
25
26
  end
27
+
28
+ class InvalidDescriptionError < InterpreterError
29
+ end
30
+
26
31
  end
27
32
  end
28
33
  end
@@ -0,0 +1,11 @@
1
+
2
+ # Classes
3
+ module DSLCompose
4
+ class Interpreter
5
+ class InterpreterError < StandardError
6
+ attr_reader original_context: String
7
+ def initialize: (String message, String original_context) -> void
8
+ def to_s: -> String
9
+ end
10
+ end
11
+ end
@@ -12,10 +12,7 @@ module DSLCompose
12
12
  def add_parser_usage_note: (singleton(Object) klass, String note) -> void
13
13
  def parser_usage_notes: (singleton(Object) klass) -> Array[String]
14
14
 
15
- class InvalidDescriptionError < StandardError
16
- end
17
-
18
- class DSLExecutionNotFoundError < StandardError
15
+ class InvalidDescriptionError < InterpreterError
19
16
  end
20
17
  end
21
18
  end
@@ -8,7 +8,7 @@ module DSLCompose
8
8
  @dsl_names: Array[Symbol]
9
9
  @dsl_execution: Interpreter::Execution
10
10
 
11
- def initialize: (singleton(Object) base_class, singleton(Object) child_class, Array[Symbol] dsl_names, bool on_current_class, bool on_ancestor_class) -> void
11
+ def initialize: (singleton(Object) base_class, singleton(Object) child_class, Array[Symbol] dsl_names, bool on_current_class, bool on_ancestor_class, bool first_use_only) -> void
12
12
 
13
13
  # overriding this method because steep doesn't
14
14
  # correctly infer that a block is being passed to this method
@@ -14,10 +14,12 @@ module DSLCompose
14
14
  ) -> void
15
15
 
16
16
  private
17
- def for_dsl: (Array[Symbol] dsl_names, ?on_current_class: bool, ?on_ancestor_class: bool) -> untyped
17
+ def for_dsl: (Array[Symbol] dsl_names, ?on_current_class: bool, ?on_ancestor_class: bool, ?first_use_only: bool) -> untyped
18
+ def for_inherited_dsl: (Array[Symbol] dsl_names, ?first_use_only: bool) -> untyped
19
+ def for_dsl_or_inherited_dsl: (Array[Symbol] dsl_names, ?first_use_only: bool) -> untyped
18
20
 
19
21
  class AllBlockParametersMustBeKeywordParametersError < StandardError
20
- end
22
+ end
21
23
 
22
24
  class ClassDoesNotUseDSLComposeError < StandardError
23
25
  end
@@ -26,7 +28,7 @@ module DSLCompose
26
28
  end
27
29
 
28
30
  class NoChildClassError < StandardError
29
- end
31
+ end
30
32
  end
31
33
  end
32
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dsl_compose
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.3
4
+ version: 2.15.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Craig Ulliott
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-03 00:00:00.000000000 Z
11
+ date: 2023-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: class_spec_helper
@@ -67,6 +67,7 @@ files:
67
67
  - lib/dsl_compose/interpreter/execution/arguments.rb
68
68
  - lib/dsl_compose/interpreter/execution/method_calls.rb
69
69
  - lib/dsl_compose/interpreter/execution/method_calls/method_call.rb
70
+ - lib/dsl_compose/interpreter/interpreter_error.rb
70
71
  - lib/dsl_compose/parser.rb
71
72
  - lib/dsl_compose/parser/block_arguments.rb
72
73
  - lib/dsl_compose/parser/for_children_of_parser.rb
@@ -109,6 +110,7 @@ files:
109
110
  - sig/dsl_compose/interpreter/execution/arguments.rbs
110
111
  - sig/dsl_compose/interpreter/execution/method_calls.rbs
111
112
  - sig/dsl_compose/interpreter/execution/method_calls/method_call.rbs
113
+ - sig/dsl_compose/interpreter/interpreter_error.rbs
112
114
  - sig/dsl_compose/parser.rbs
113
115
  - sig/dsl_compose/parser/block_arguments.rbs
114
116
  - sig/dsl_compose/parser/for_children_of_parser.rbs