dsl_compose 1.0.0 → 1.2.0

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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +300 -3
  4. data/lib/dsl_compose/composer.rb +74 -0
  5. data/lib/dsl_compose/dsl/arguments/argument/equal_to_validation.rb +25 -0
  6. data/lib/dsl_compose/dsl/arguments/argument/format_validation.rb +25 -0
  7. data/lib/dsl_compose/dsl/arguments/argument/greater_than_or_equal_to_validation.rb +35 -0
  8. data/lib/dsl_compose/dsl/arguments/argument/greater_than_validation.rb +35 -0
  9. data/lib/dsl_compose/dsl/arguments/argument/in_validation.rb +35 -0
  10. data/lib/dsl_compose/dsl/arguments/argument/interpreter.rb +86 -0
  11. data/lib/dsl_compose/dsl/arguments/argument/length_validation.rb +42 -0
  12. data/lib/dsl_compose/dsl/arguments/argument/less_than_or_equal_to_validation.rb +35 -0
  13. data/lib/dsl_compose/dsl/arguments/argument/less_than_validation.rb +35 -0
  14. data/lib/dsl_compose/dsl/arguments/argument/not_in_validation.rb +35 -0
  15. data/lib/dsl_compose/dsl/arguments/argument.rb +299 -0
  16. data/lib/dsl_compose/dsl/arguments.rb +113 -0
  17. data/lib/dsl_compose/dsl/dsl_method/interpreter.rb +57 -0
  18. data/lib/dsl_compose/dsl/dsl_method.rb +143 -0
  19. data/lib/dsl_compose/dsl/interpreter.rb +72 -0
  20. data/lib/dsl_compose/dsl.rb +152 -0
  21. data/lib/dsl_compose/dsls.rb +80 -0
  22. data/lib/dsl_compose/interpreter/execution/arguments.rb +145 -0
  23. data/lib/dsl_compose/interpreter/execution/method_calls/method_call.rb +53 -0
  24. data/lib/dsl_compose/interpreter/execution/method_calls.rb +25 -0
  25. data/lib/dsl_compose/interpreter/execution.rb +64 -0
  26. data/lib/dsl_compose/interpreter.rb +50 -0
  27. data/lib/dsl_compose/version.rb +2 -2
  28. data/lib/dsl_compose.rb +35 -4
  29. metadata +26 -3
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class DSL
5
+ class Arguments
6
+ class Argument
7
+ class LengthValidation
8
+ class ValidationFailedError < StandardError
9
+ def message
10
+ "The argument is invalid"
11
+ end
12
+ end
13
+
14
+ def initialize maximum: nil, minimum: nil, is: nil
15
+ @maximum = maximum
16
+ @minimum = minimum
17
+ @is = is
18
+ end
19
+
20
+ def validate! value
21
+ maximum = @maximum
22
+ unless maximum.nil?
23
+ raise ValidationFailedError if value.length > maximum
24
+ end
25
+
26
+ minimum = @minimum
27
+ unless minimum.nil?
28
+ raise ValidationFailedError if value.length < minimum
29
+ end
30
+
31
+ is = @is
32
+ unless is.nil?
33
+ raise ValidationFailedError if value.length != is
34
+ end
35
+
36
+ true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class DSL
5
+ class Arguments
6
+ class Argument
7
+ class LessThanOrEqualToValidation
8
+ class InvalidValueError < StandardError
9
+ def message
10
+ "The value provided to validate_greater_than must be a number"
11
+ end
12
+ end
13
+
14
+ class ValidationFailedError < StandardError
15
+ def message
16
+ "The argument is invalid"
17
+ end
18
+ end
19
+
20
+ def initialize value
21
+ unless value.is_a?(Numeric)
22
+ raise InvalidValueError
23
+ end
24
+
25
+ @value = value
26
+ end
27
+
28
+ def validate! value
29
+ raise ValidationFailedError unless value <= @value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class DSL
5
+ class Arguments
6
+ class Argument
7
+ class LessThanValidation
8
+ class InvalidValueError < StandardError
9
+ def message
10
+ "The value provided to validate_greater_than must be a number"
11
+ end
12
+ end
13
+
14
+ class ValidationFailedError < StandardError
15
+ def message
16
+ "The argument is invalid"
17
+ end
18
+ end
19
+
20
+ def initialize value
21
+ unless value.is_a?(Numeric)
22
+ raise InvalidValueError
23
+ end
24
+
25
+ @value = value
26
+ end
27
+
28
+ def validate! value
29
+ raise ValidationFailedError unless value < @value
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class DSL
5
+ class Arguments
6
+ class Argument
7
+ class NotInValidation
8
+ class InvalidValueError < StandardError
9
+ def message
10
+ "The value provided to validate_greater_than must be a number"
11
+ end
12
+ end
13
+
14
+ class ValidationFailedError < StandardError
15
+ def message
16
+ "The argument is invalid"
17
+ end
18
+ end
19
+
20
+ def initialize values
21
+ unless values.is_a?(Array)
22
+ raise InvalidValueError
23
+ end
24
+
25
+ @values = values
26
+ end
27
+
28
+ def validate! value
29
+ raise ValidationFailedError if @values.include?(value)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class DSL
5
+ class Arguments
6
+ class Argument
7
+ class ValidationIncompatibleError < StandardError
8
+ def message
9
+ "The validation is not compatible with this argument type"
10
+ end
11
+ end
12
+
13
+ class ValidationAlreadyExistsError < StandardError
14
+ def message
15
+ "This validation has already been applied to this method option."
16
+ end
17
+ end
18
+
19
+ class InvalidTypeError < StandardError
20
+ def message
21
+ "Argument type must be one of :integer, :boolean, :float, :string or :symbol"
22
+ end
23
+ end
24
+
25
+ class InvalidNameError < StandardError
26
+ def message
27
+ "The option name is invalid, it must be of type symbol."
28
+ end
29
+ end
30
+
31
+ class InvalidDescriptionError < StandardError
32
+ def message
33
+ "The option description is invalid, it must be of type string and have length greater than 0."
34
+ end
35
+ end
36
+
37
+ class DescriptionAlreadyExistsError < StandardError
38
+ def message
39
+ "The description has already been set"
40
+ end
41
+ end
42
+
43
+ # The name of this Argument.
44
+ attr_reader :name
45
+ # An arguments type. This determines what kind of value can be passed when calling the
46
+ # associated DSLMethod.
47
+ # `type` should be set to either :integer, :boolean, :float, :string or :symbol
48
+ attr_reader :type
49
+ # if required, then this Argument must be provided when calling its associated DSLMethod.
50
+ attr_reader :required
51
+ # An otional description of this Attribute, if provided then it must be a string.
52
+ # The description accepts markdown and is used when generating documentation.
53
+ attr_reader :description
54
+ # Optional validations that have been applied to this Argument. When the DSL is used
55
+ # each of these validations will be checked against the value provided to
56
+ # this Argument.
57
+ attr_reader :greater_than_validation
58
+ attr_reader :greater_than_or_equal_to_validation
59
+ attr_reader :less_than_validation
60
+ attr_reader :less_than_or_equal_to_validation
61
+ attr_reader :format_validation
62
+ attr_reader :equal_to_validation
63
+ attr_reader :in_validation
64
+ attr_reader :not_in_validation
65
+ attr_reader :length_validation
66
+
67
+ # Create a new Attribute object.
68
+ #
69
+ # `name` must be a symbol.
70
+ # `required` is a boolean which determines if this Attribute must be provided when
71
+ # calling its associated DSLMethod.
72
+ # `type` can be either :integer, :boolean, :float, :string or :symbol
73
+ # `block` contains the instructions to further configure this Attribute
74
+ def initialize name, required, type, &block
75
+ if name.is_a? Symbol
76
+ @name = name
77
+ else
78
+ raise InvalidNameError
79
+ end
80
+
81
+ if type == :integer || type == :boolean || type == :float || type == :string || type == :symbol
82
+ @type = type
83
+ else
84
+ raise InvalidTypeError
85
+ end
86
+
87
+ @required = required ? true : false
88
+
89
+ # If a block was provided, then we evaluate it using a seperate
90
+ # interpreter class. We do this because the interpreter class contains
91
+ # no other methods or variables, if it was evaluated in the context of
92
+ # this class then the block would have access to all of the methods defined
93
+ # in here.
94
+ if block
95
+ Interpreter.new(self).instance_eval(&block)
96
+ end
97
+ end
98
+
99
+ # Set the description for this Argument to the provided value.
100
+ #
101
+ # `description` must be a string with a length greater than 0.
102
+ # The `description` can only be set once per Argument
103
+ def set_description description
104
+ unless description.is_a?(String) && description.length > 0
105
+ raise InvalidDescriptionError
106
+ end
107
+
108
+ if has_description?
109
+ raise DescriptionAlreadyExistsError
110
+ end
111
+
112
+ @description = description
113
+ end
114
+
115
+ # Returns `true` if this DSL has a description, else false.
116
+ def has_description?
117
+ @description.nil? == false
118
+ end
119
+
120
+ # returns true if this DSLMethod is flagged as required, otherwise returns false.
121
+ def required?
122
+ @required == true
123
+ end
124
+
125
+ # returns true if this DSLMethod is flagged as optional, otherwise returns false.
126
+ def optional?
127
+ @required == false
128
+ end
129
+
130
+ def validate_greater_than value
131
+ if @greater_than_validation
132
+ raise ValidationAlreadyExistsError
133
+ end
134
+
135
+ unless @type == :integer || @type == :float
136
+ raise ValidationIncompatibleError
137
+ end
138
+
139
+ @greater_than_validation = GreaterThanValidation.new value
140
+ end
141
+
142
+ def validate_greater_than_or_equal_to value
143
+ if @greater_than_or_equal_to_validation
144
+ raise ValidationAlreadyExistsError
145
+ end
146
+
147
+ unless value.is_a?(Numeric)
148
+ raise ValidationInvalidArgumentError
149
+ end
150
+
151
+ unless @type == :integer || @type == :float
152
+ raise ValidationIncompatibleError
153
+ end
154
+
155
+ @greater_than_or_equal_to_validation = GreaterThanOrEqualToValidation.new value
156
+ end
157
+
158
+ def validate_less_than value
159
+ if @less_than_validation
160
+ raise ValidationAlreadyExistsError
161
+ end
162
+
163
+ unless value.is_a?(Numeric)
164
+ raise ValidationInvalidArgumentError
165
+ end
166
+
167
+ unless @type == :integer || @type == :float
168
+ raise ValidationIncompatibleError
169
+ end
170
+
171
+ @less_than_validation = LessThanValidation.new value
172
+ end
173
+
174
+ def validate_less_than_or_equal_to value
175
+ if @less_than_or_equal_to_validation
176
+ raise ValidationAlreadyExistsError
177
+ end
178
+
179
+ unless value.is_a?(Numeric)
180
+ raise ValidationInvalidArgumentError
181
+ end
182
+
183
+ unless @type == :integer || @type == :float
184
+ raise ValidationIncompatibleError
185
+ end
186
+
187
+ @less_than_or_equal_to_validation = LessThanOrEqualToValidation.new value
188
+ end
189
+
190
+ def validate_format regexp
191
+ if @format_validation
192
+ raise ValidationAlreadyExistsError
193
+ end
194
+
195
+ unless regexp.is_a? Regexp
196
+ raise ValidationInvalidArgumentError
197
+ end
198
+
199
+ unless @type == :string || @type == :symbol
200
+ raise ValidationIncompatibleError
201
+ end
202
+ @format_validation = FormatValidation.new regexp
203
+ end
204
+
205
+ def validate_equal_to value
206
+ if @equal_to_validation
207
+ raise ValidationAlreadyExistsError
208
+ end
209
+
210
+ unless @type == :integer || @type == :float || @type == :string || @type == :symbol || @type == :boolean
211
+ raise ValidationIncompatibleError
212
+ end
213
+
214
+ @equal_to_validation = EqualToValidation.new value
215
+ end
216
+
217
+ def validate_in values
218
+ if @in_validation
219
+ raise ValidationAlreadyExistsError
220
+ end
221
+
222
+ unless values.is_a? Array
223
+ raise ValidationInvalidArgumentError
224
+ end
225
+
226
+ unless @type == :integer || @type == :float || @type == :string || @type == :symbol
227
+ raise ValidationIncompatibleError
228
+ end
229
+
230
+ @in_validation = InValidation.new values
231
+ end
232
+
233
+ def validate_not_in values
234
+ if @not_in_validation
235
+ raise ValidationAlreadyExistsError
236
+ end
237
+
238
+ unless values.is_a? Array
239
+ raise ValidationInvalidArgumentError
240
+ end
241
+
242
+ unless @type == :integer || @type == :float || @type == :string || @type == :symbol
243
+ raise ValidationIncompatibleError
244
+ end
245
+
246
+ @not_in_validation = NotInValidation.new values
247
+ end
248
+
249
+ def validate_length maximum: nil, minimum: nil, is: nil
250
+ if @length_validation
251
+ raise ValidationAlreadyExistsError
252
+ end
253
+
254
+ unless @type == :string || @type == :symbol
255
+ raise ValidationIncompatibleError
256
+ end
257
+
258
+ @length_validation = LengthValidation.new(maximum: maximum, minimum: minimum, is: is)
259
+ end
260
+
261
+ # returns true if every provided integer validation also returns true
262
+ def validate_integer! value
263
+ (greater_than_validation.nil? || greater_than_validation.validate!(value)) &&
264
+ (greater_than_or_equal_to_validation.nil? || greater_than_or_equal_to_validation.validate!(value)) &&
265
+ (less_than_validation.nil? || less_than_validation.validate!(value)) &&
266
+ (less_than_or_equal_to_validation.nil? || less_than_or_equal_to_validation.validate!(value)) &&
267
+ (format_validation.nil? || format_validation.validate!(value)) &&
268
+ (equal_to_validation.nil? || equal_to_validation.validate!(value)) &&
269
+ (in_validation.nil? || in_validation.validate!(value)) &&
270
+ (not_in_validation.nil? || not_in_validation.validate!(value)) &&
271
+ (length_validation.nil? || length_validation.validate!(value))
272
+ end
273
+
274
+ # returns true if every provided symbol validation also returns true
275
+ def validate_symbol! value
276
+ (format_validation.nil? || format_validation.validate!(value)) &&
277
+ (equal_to_validation.nil? || equal_to_validation.validate!(value)) &&
278
+ (in_validation.nil? || in_validation.validate!(value)) &&
279
+ (not_in_validation.nil? || not_in_validation.validate!(value)) &&
280
+ (length_validation.nil? || length_validation.validate!(value))
281
+ end
282
+
283
+ # returns true if every provided string validation also returns true
284
+ def validate_string! value
285
+ (format_validation.nil? || format_validation.validate!(value)) &&
286
+ (equal_to_validation.nil? || equal_to_validation.validate!(value)) &&
287
+ (in_validation.nil? || in_validation.validate!(value)) &&
288
+ (not_in_validation.nil? || not_in_validation.validate!(value)) &&
289
+ (length_validation.nil? || length_validation.validate!(value))
290
+ end
291
+
292
+ # returns true if every provided boolean validation also returns true
293
+ def validate_boolean! value
294
+ (equal_to_validation.nil? || equal_to_validation.validate!(value))
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class DSL
5
+ class Arguments
6
+ class ArgumentDoesNotExistError < StandardError
7
+ def message
8
+ "This argument does not exist for this DSLMethod"
9
+ end
10
+ end
11
+
12
+ class ArgumentOrderingError < StandardError
13
+ def message
14
+ "Required arguments can not be added after optional ones"
15
+ end
16
+ end
17
+
18
+ class ArgumentAlreadyExistsError < StandardError
19
+ def message
20
+ "An argument with this name already exists for this DSL method"
21
+ end
22
+ end
23
+
24
+ class RequestedOptionalArgumentIsRequiredError < StandardError
25
+ def message
26
+ "A specific argument which was expected to be optional was requested, but the argument found was flagged as required"
27
+ end
28
+ end
29
+
30
+ class RequestedRequiredArgumentIsOptionalError < StandardError
31
+ def message
32
+ "A specific argument which was expected to be required was requested, but the argument found was flagged as optional"
33
+ end
34
+ end
35
+
36
+ def initialize
37
+ @arguments = {}
38
+ end
39
+
40
+ # Returns an array of all this DSLMethods Argument objects.
41
+ def arguments
42
+ @arguments.values
43
+ end
44
+
45
+ # Returns an array of only the optional Argument objects on this DSLMethod.
46
+ def optional_arguments
47
+ arguments.filter(&:optional?)
48
+ end
49
+
50
+ # Returns an array of only the required Argument objects on this DSLMethod.
51
+ def required_arguments
52
+ arguments.filter(&:required?)
53
+ end
54
+
55
+ # returns a specific Argument by it's name, if the Argument does not
56
+ # exist, then an error is raised
57
+ def argument name
58
+ if has_argument? name
59
+ @arguments[name]
60
+ else
61
+ raise ArgumentDoesNotExistError
62
+ end
63
+ end
64
+
65
+ # returns a specific optional Argument by it's name, if the Argument does not
66
+ # exist, or if it is required, then an error is raised
67
+ def optional_argument name
68
+ arg = argument name
69
+ if arg.optional?
70
+ @arguments[name]
71
+ else
72
+ raise RequestedOptionalArgumentIsRequiredError
73
+ end
74
+ end
75
+
76
+ # returns a specific required Argument by it's name, if the Argument does not
77
+ # exist, or if it is optional, then an error is raised
78
+ def required_argument name
79
+ arg = argument name
80
+ if arg.required?
81
+ @arguments[name]
82
+ else
83
+ raise RequestedRequiredArgumentIsOptionalError
84
+ end
85
+ end
86
+
87
+ # Returns `true` if an Argument with the provided name exists in this
88
+ # DSLMethod, otherwise it returns `false`.
89
+ def has_argument? name
90
+ @arguments.key? name
91
+ end
92
+
93
+ # Takes a method name, unique flag, required flag, and a block and creates
94
+ # a new Argument object.
95
+ #
96
+ # Argument `name` must be unique within the DSLMethod.
97
+ # `required` must be a boolean, and determines if this argument will be required
98
+ # or optional on the method which is exposed in our DSL.
99
+ def add_argument name, required, type, &block
100
+ if @arguments.key? name
101
+ raise ArgumentAlreadyExistsError
102
+ end
103
+
104
+ # required arguments may not come after optional ones
105
+ if required && optional_arguments.any?
106
+ raise ArgumentOrderingError
107
+ end
108
+
109
+ @arguments[name] = Argument.new(name, required, type, &block)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class DSL
5
+ class DSLMethod
6
+ # This class is reponsible for parsing and executing method definitions
7
+ # within our internal DSL. These method definitions determine what methods
8
+ # will be available within our new dynamic DSL.
9
+ #
10
+ # This class is instantaited by the DSLCompose::DSL::DSLMethod class and the method definition
11
+ # part of our internal DSL is evaluated by passing a block to `instance_eval` on this class.
12
+ #
13
+ # An example of our internal DSL which includes a complex method definition:
14
+ # define_dsl :my_dsl do
15
+ # add_method :my_method, required: true do
16
+ # description "Description of my method"
17
+ # optional :my_optional_argument, :string do
18
+ # # ...
19
+ # end
20
+ # optional :my_optional_arg, String
21
+ # end
22
+ # end
23
+ class Interpreter
24
+ def initialize dsl_method
25
+ @dsl_method = dsl_method
26
+ end
27
+
28
+ private
29
+
30
+ # sets the description of the DSLMethod
31
+ def description description
32
+ @dsl_method.set_description description
33
+ end
34
+
35
+ # adds a new optional argument to the DSLMethod
36
+ #
37
+ # name must be a symbol
38
+ # `type` can be either :integer, :boolean, :float, :string or :symbol
39
+ # `block` contains the argument definition and will be evaluated seperately
40
+ # by the Argument::Interpreter
41
+ def optional name, type, &block
42
+ @dsl_method.arguments.add_argument name, false, type, &block
43
+ end
44
+
45
+ # adds a new required argument to the DSLMethod
46
+ #
47
+ # name must be a symbol
48
+ # `type` can be either :integer, :boolean, :float, :string or :symbol
49
+ # `block` contains the argument definition and will be evaluated seperately
50
+ # by the Argument::Interpreter
51
+ def requires name, type, &block
52
+ @dsl_method.arguments.add_argument name, true, type, &block
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end