dsl_compose 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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