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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +300 -3
- data/lib/dsl_compose/composer.rb +74 -0
- data/lib/dsl_compose/dsl/arguments/argument/equal_to_validation.rb +25 -0
- data/lib/dsl_compose/dsl/arguments/argument/format_validation.rb +25 -0
- data/lib/dsl_compose/dsl/arguments/argument/greater_than_or_equal_to_validation.rb +35 -0
- data/lib/dsl_compose/dsl/arguments/argument/greater_than_validation.rb +35 -0
- data/lib/dsl_compose/dsl/arguments/argument/in_validation.rb +35 -0
- data/lib/dsl_compose/dsl/arguments/argument/interpreter.rb +86 -0
- data/lib/dsl_compose/dsl/arguments/argument/length_validation.rb +42 -0
- data/lib/dsl_compose/dsl/arguments/argument/less_than_or_equal_to_validation.rb +35 -0
- data/lib/dsl_compose/dsl/arguments/argument/less_than_validation.rb +35 -0
- data/lib/dsl_compose/dsl/arguments/argument/not_in_validation.rb +35 -0
- data/lib/dsl_compose/dsl/arguments/argument.rb +299 -0
- data/lib/dsl_compose/dsl/arguments.rb +113 -0
- data/lib/dsl_compose/dsl/dsl_method/interpreter.rb +57 -0
- data/lib/dsl_compose/dsl/dsl_method.rb +143 -0
- data/lib/dsl_compose/dsl/interpreter.rb +72 -0
- data/lib/dsl_compose/dsl.rb +152 -0
- data/lib/dsl_compose/dsls.rb +80 -0
- data/lib/dsl_compose/interpreter/execution/arguments.rb +145 -0
- data/lib/dsl_compose/interpreter/execution/method_calls/method_call.rb +53 -0
- data/lib/dsl_compose/interpreter/execution/method_calls.rb +25 -0
- data/lib/dsl_compose/interpreter/execution.rb +64 -0
- data/lib/dsl_compose/interpreter.rb +50 -0
- data/lib/dsl_compose/version.rb +2 -2
- data/lib/dsl_compose.rb +35 -4
- metadata +26 -3
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class DSL
|
5
|
+
class DSLMethod
|
6
|
+
class ArgumentDoesNotExistError < StandardError
|
7
|
+
def message
|
8
|
+
"This argument does not exist for this DSLMethod"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidNameError < StandardError
|
13
|
+
def message
|
14
|
+
"The method #{method_name} is invalid, it must be of type symbol"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MethodNameIsReservedError < StandardError
|
19
|
+
def message
|
20
|
+
"This method already would override an existing internal method"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvalidDescriptionError < StandardError
|
25
|
+
def message
|
26
|
+
"The DSL method description is invalid, it must be of type string and have length greater than 0"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class DescriptionAlreadyExistsError < StandardError
|
31
|
+
def message
|
32
|
+
"The description has already been set"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class ArgumentOrderingError < StandardError
|
37
|
+
def message
|
38
|
+
"Required arguments can not be added after optional ones"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ArgumentAlreadyExistsError < StandardError
|
43
|
+
def message
|
44
|
+
"An argument with this name already exists for this DSL method"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class RequestedOptionalArgumentIsRequiredError < StandardError
|
49
|
+
def message
|
50
|
+
"A specific argument which was expected to be optional was requested, but the argument found was flagged as required"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class RequestedRequiredArgumentIsOptionalError < StandardError
|
55
|
+
def message
|
56
|
+
"A specific argument which was expected to be required was requested, but the argument found was flagged as optional"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# The name of this DSLMethod.
|
61
|
+
attr_reader :name
|
62
|
+
# if unique, then this DSLMethod can only be called once within the DSL.
|
63
|
+
attr_reader :unique
|
64
|
+
# if required, then this DSLMethod must be called at least once within the DSL.
|
65
|
+
attr_reader :required
|
66
|
+
# An otional description of this DSLMethod, if provided then it must be a string.
|
67
|
+
# The description accepts markdown and is used when generating documentation.
|
68
|
+
attr_reader :description
|
69
|
+
# an object which represents the argument configuration
|
70
|
+
attr_reader :arguments
|
71
|
+
|
72
|
+
# Create a new DSLMethod object with the provided name and class.
|
73
|
+
#
|
74
|
+
# `name` must be a symbol.
|
75
|
+
# `unique` is a boolean which determines if this DSLMethod can only be called once witihn the DSL.
|
76
|
+
# `required` is a boolean which determines if this DSLMethod must be called at least once within the DSL.
|
77
|
+
# `block` contains the instructions to further configure this DSLMethod
|
78
|
+
def initialize name, unique, required, &block
|
79
|
+
@arguments = Arguments.new
|
80
|
+
|
81
|
+
if name.is_a? Symbol
|
82
|
+
|
83
|
+
# don't allow methods to override existing internal methods
|
84
|
+
if Class.respond_to? name
|
85
|
+
raise MethodNameIsReservedError
|
86
|
+
end
|
87
|
+
|
88
|
+
@name = name
|
89
|
+
else
|
90
|
+
raise InvalidNameError
|
91
|
+
end
|
92
|
+
|
93
|
+
@unique = unique ? true : false
|
94
|
+
@required = required ? true : false
|
95
|
+
|
96
|
+
# If a block was provided, then we evaluate it using a seperate
|
97
|
+
# interpreter class. We do this because the interpreter class contains
|
98
|
+
# no other methods or variables, if it was evaluated in the context of
|
99
|
+
# this class then the block would have access to all of the methods defined
|
100
|
+
# in here.
|
101
|
+
if block
|
102
|
+
Interpreter.new(self).instance_eval(&block)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Set the description for this DSLMethod to the provided value.
|
107
|
+
#
|
108
|
+
# `description` must be a string with a length greater than 0.
|
109
|
+
# The `description` can only be set once per DSLMethod
|
110
|
+
def set_description description
|
111
|
+
unless description.is_a?(String) && description.length > 0
|
112
|
+
raise InvalidDescriptionError
|
113
|
+
end
|
114
|
+
|
115
|
+
if has_description?
|
116
|
+
raise DescriptionAlreadyExistsError
|
117
|
+
end
|
118
|
+
|
119
|
+
@description = description
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns `true` if this DSL has a description, else false.
|
123
|
+
def has_description?
|
124
|
+
@description.nil? == false
|
125
|
+
end
|
126
|
+
|
127
|
+
# returns true if this DSLMethod is flagged as unique, otherwise returns false.
|
128
|
+
def unique?
|
129
|
+
@unique == true
|
130
|
+
end
|
131
|
+
|
132
|
+
# returns true if this DSLMethod is flagged as required, otherwise returns false.
|
133
|
+
def required?
|
134
|
+
@required == true
|
135
|
+
end
|
136
|
+
|
137
|
+
# returns true if this DSLMethod is flagged as optional, otherwise returns false.
|
138
|
+
def optional?
|
139
|
+
@required == false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class DSL
|
5
|
+
# The class is reponsible for parsing and executing our internal DSL which is used to define
|
6
|
+
# a new dynamic DSL. This class is instantaited by the DSLCompose::DSL class and our internal
|
7
|
+
# DSL is evaluated by passing a block to `instance_eval` on this class.
|
8
|
+
#
|
9
|
+
# An example of our internal DSL:
|
10
|
+
# define_dsl :my_dsl do
|
11
|
+
# description "This is my DSL"
|
12
|
+
# add_method :my_method do
|
13
|
+
# # ...
|
14
|
+
# end
|
15
|
+
# add_unique_method :my_uniq_method do
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
class Interpreter
|
20
|
+
def initialize dsl
|
21
|
+
@dsl = dsl
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# sets the description of the DSL
|
27
|
+
def description description
|
28
|
+
@dsl.set_description description
|
29
|
+
end
|
30
|
+
|
31
|
+
# adds a new method to the DSL
|
32
|
+
#
|
33
|
+
# methods flagged as `required` will cause your DSLs to raise an error
|
34
|
+
# if they are not used at least once within your new DSLs
|
35
|
+
# `block` contains the method definition and will be evaluated seperately
|
36
|
+
# by the DSLMethod::Interpreter
|
37
|
+
def add_method name, required: nil, &block
|
38
|
+
@dsl.add_method name, false, required ? true : false, &block
|
39
|
+
end
|
40
|
+
|
41
|
+
# adds a new unique method to the DSL
|
42
|
+
#
|
43
|
+
# methods flagged as `required` will cause your DSLs to raise an error
|
44
|
+
# if they are not used at least once within your new DSLs
|
45
|
+
# `block` contains the method definition and will be evaluated seperately
|
46
|
+
# by the DSLMethod::Interpreter
|
47
|
+
def add_unique_method name, required: nil, &block
|
48
|
+
@dsl.add_method name, true, required ? true : false, &block
|
49
|
+
end
|
50
|
+
|
51
|
+
# adds a new optional argument to the DSLMethod
|
52
|
+
#
|
53
|
+
# name must be a symbol
|
54
|
+
# `type` can be either :integer, :boolean, :float, :string or :symbol
|
55
|
+
# `block` contains the argument definition and will be evaluated seperately
|
56
|
+
# by the Argument::Interpreter
|
57
|
+
def optional name, type, &block
|
58
|
+
@dsl.arguments.add_argument name, false, type, &block
|
59
|
+
end
|
60
|
+
|
61
|
+
# adds a new required argument to the DSLMethod
|
62
|
+
#
|
63
|
+
# name must be a symbol
|
64
|
+
# `type` can be either :integer, :boolean, :float, :string or :symbol
|
65
|
+
# `block` contains the argument definition and will be evaluated seperately
|
66
|
+
# by the Argument::Interpreter
|
67
|
+
def requires name, type, &block
|
68
|
+
@dsl.arguments.add_argument name, true, type, &block
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
# The class is reponsible for creating and representing a dynamic DSL
|
5
|
+
#
|
6
|
+
# These new dynamic DSL's are created using our own internal DSL, which is accessed
|
7
|
+
# by calling `define_dsl` in a class and passing it a block which contains the DSL definition
|
8
|
+
class DSL
|
9
|
+
class MethodDoesNotExistError < StandardError
|
10
|
+
def message
|
11
|
+
"This method does not exist for this DSL"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MethodAlreadyExistsError < StandardError
|
16
|
+
def message
|
17
|
+
"This method already exists for this DSL"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class InvalidNameError < StandardError
|
22
|
+
def message
|
23
|
+
"This DSL name is invalid, it must be of type symbol"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class InvalidDescriptionError < StandardError
|
28
|
+
def message
|
29
|
+
"This DSL description is invalid, it must be of type string and have length greater than 0"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class DescriptionAlreadyExistsError < StandardError
|
34
|
+
def message
|
35
|
+
"The DSL description has already been set"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class NoBlockProvidedError < StandardError
|
40
|
+
def message
|
41
|
+
"No block was provided for this DSL"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# The name of this DSL.
|
46
|
+
attr_reader :name
|
47
|
+
# klass will be the class where `define_dsl` was called.
|
48
|
+
attr_reader :klass
|
49
|
+
# An otional description of this DSL, if provided then it must be a string.
|
50
|
+
# The description accepts markdown and is used when generating documentation.
|
51
|
+
attr_reader :description
|
52
|
+
# an object which represents the argument configuration
|
53
|
+
attr_reader :arguments
|
54
|
+
|
55
|
+
# Create a new DSL object with the provided name and class.
|
56
|
+
#
|
57
|
+
# `name` must be a symbol.
|
58
|
+
# `klass` should be the class in which `define_dsl` is being called.
|
59
|
+
def initialize name, klass
|
60
|
+
@dsl_methods = {}
|
61
|
+
|
62
|
+
@arguments = Arguments.new
|
63
|
+
|
64
|
+
if name.is_a? Symbol
|
65
|
+
@name = name
|
66
|
+
else
|
67
|
+
raise InvalidNameError
|
68
|
+
end
|
69
|
+
|
70
|
+
@klass = klass
|
71
|
+
end
|
72
|
+
|
73
|
+
# Evaluate the configuration block which defines our new DSL
|
74
|
+
# `block` contains the DSL definition and will be evaluated to create
|
75
|
+
# the rest of the DSL.
|
76
|
+
def evaluate_configuration_block &block
|
77
|
+
if block
|
78
|
+
# We evaluate the internal DSL configuration blocks using a seperate interpreter
|
79
|
+
# class. We do this because the interpreter class contains no other methods or
|
80
|
+
# variables, if it was evaluated in the context of this class then the block
|
81
|
+
# would have access to all of the methods defined in here.
|
82
|
+
Interpreter.new(self).instance_eval(&block)
|
83
|
+
else
|
84
|
+
raise NoBlockProvidedError
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Set the description for this DSL to the provided value.
|
89
|
+
#
|
90
|
+
# `description` must be a string with a length greater than 0.
|
91
|
+
# The `description` can only be set once per DSL
|
92
|
+
def set_description description
|
93
|
+
unless description.is_a?(String) && description.length > 0
|
94
|
+
raise InvalidDescriptionError
|
95
|
+
end
|
96
|
+
|
97
|
+
if has_description?
|
98
|
+
raise DescriptionAlreadyExistsError
|
99
|
+
end
|
100
|
+
|
101
|
+
@description = description
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns `true` if this DSL has a description, else false.
|
105
|
+
def has_description?
|
106
|
+
@description.nil? == false
|
107
|
+
end
|
108
|
+
|
109
|
+
# Takes a method name, unique flag, required flag, and a block and creates
|
110
|
+
# a new DSLMethod object.
|
111
|
+
#
|
112
|
+
# Method `name` must be unique within the DSL.
|
113
|
+
def add_method name, unique, required, &block
|
114
|
+
if has_dsl_method? name
|
115
|
+
raise MethodAlreadyExistsError
|
116
|
+
end
|
117
|
+
|
118
|
+
@dsl_methods[name] = DSLMethod.new(name, unique, required, &block)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Returns an array of all this DSLs DSLMethods.
|
122
|
+
def dsl_methods
|
123
|
+
@dsl_methods.values
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns an array of only the required DSLMethods in this DSL.
|
127
|
+
def required_dsl_methods
|
128
|
+
dsl_methods.filter(&:required?)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Returns an array of only the optional DSLMethods in this DSL.
|
132
|
+
def optional_dsl_methods
|
133
|
+
dsl_methods.filter(&:optional?)
|
134
|
+
end
|
135
|
+
|
136
|
+
# returns a specific DSLMethod by it's name, if the DSLMethod does not
|
137
|
+
# exist, then an error is raised
|
138
|
+
def dsl_method name
|
139
|
+
if has_dsl_method? name
|
140
|
+
@dsl_methods[name]
|
141
|
+
else
|
142
|
+
raise MethodDoesNotExistError
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns `true` if a DSLMethod with the provided name exists in this
|
147
|
+
# DSL, otherwise it returns `false`.
|
148
|
+
def has_dsl_method? name
|
149
|
+
@dsl_methods.key? name
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
module DSLs
|
5
|
+
class ClassDSLDefinitionDoesNotExistError < StandardError
|
6
|
+
def message
|
7
|
+
"The requested DSL does not exist on this class"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class DSLAlreadyExistsError < StandardError
|
12
|
+
def message
|
13
|
+
"A DSL with this name already exists"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class NoDSLDefinitionsForClassError < StandardError
|
18
|
+
def message
|
19
|
+
"No DSLs have been defined for this class"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# an object to hold all of the defined DSLs in our application, the DSLs are
|
24
|
+
# organized by the class in which they were defined
|
25
|
+
@dsls = {}
|
26
|
+
|
27
|
+
# create a new DSL definition for a class
|
28
|
+
# `klass` here is the class in which `define_dsl` was called
|
29
|
+
def self.create_dsl klass, name
|
30
|
+
@dsls[klass] ||= {}
|
31
|
+
|
32
|
+
if @dsls[klass].key? name
|
33
|
+
raise DSLAlreadyExistsError
|
34
|
+
end
|
35
|
+
|
36
|
+
@dsls[klass][name] = DSLCompose::DSL.new(name, klass)
|
37
|
+
end
|
38
|
+
|
39
|
+
# return all of the DSL definitions
|
40
|
+
def self.dsls
|
41
|
+
@dsls
|
42
|
+
end
|
43
|
+
|
44
|
+
# return a DSL with a provided name for the provided class, if the DSL doesn't
|
45
|
+
# exist then it will be automatically created
|
46
|
+
def self.class_dsl_exists? klass, name
|
47
|
+
@dsls.key?(klass) && @dsls[klass].key?(name)
|
48
|
+
end
|
49
|
+
|
50
|
+
# return an array of DSL definitions for a provided class, if no DSLs
|
51
|
+
# exist for the provided class, then an error is raised
|
52
|
+
def self.class_dsls klass
|
53
|
+
if @dsls.key? klass
|
54
|
+
@dsls[klass].values
|
55
|
+
else
|
56
|
+
raise NoDSLDefinitionsForClassError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# return a specific DSL definition for a provided class
|
61
|
+
# if it does not exist, then an error is raised
|
62
|
+
def self.class_dsl klass, name
|
63
|
+
if @dsls.key? klass
|
64
|
+
if @dsls[klass].key? name
|
65
|
+
@dsls[klass][name]
|
66
|
+
else
|
67
|
+
raise ClassDSLDefinitionDoesNotExistError
|
68
|
+
end
|
69
|
+
else
|
70
|
+
raise NoDSLDefinitionsForClassError
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# removes all DSL deinitions, this method is typically used by the test
|
75
|
+
# suite for resetting state inbetween each test
|
76
|
+
def self.reset
|
77
|
+
@dsls = {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class Interpreter
|
5
|
+
class Execution
|
6
|
+
class Arguments
|
7
|
+
class MissingRequiredArgumentsError < StandardError
|
8
|
+
def initialize required_count, provided_count
|
9
|
+
super "This method requires #{required_count} arguments, but only #{required_count} were provided"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class TooManyArgumentsError < StandardError
|
14
|
+
def message
|
15
|
+
"Too many arguments provided to this method"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class OptionalArgsShouldBeHashError < StandardError
|
20
|
+
def message
|
21
|
+
"If provided, then the optional arguments must be last, and be represented as a Hash"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class InvalidArgumentTypeError < StandardError
|
26
|
+
def message
|
27
|
+
"The provided argument is the wrong type"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :arguments
|
32
|
+
|
33
|
+
def initialize arguments, *args
|
34
|
+
@arguments = {}
|
35
|
+
|
36
|
+
required_argument_count = arguments.required_arguments.count
|
37
|
+
has_optional_arguments = arguments.optional_arguments.any?
|
38
|
+
|
39
|
+
# the first N args, where N = required_argument_count, are the
|
40
|
+
# provided required arguments
|
41
|
+
required_args = args.slice(0, required_argument_count)
|
42
|
+
# the optional arg which comes next is the hash which represents
|
43
|
+
# all the optional arguments
|
44
|
+
optional_arg = args[required_argument_count]
|
45
|
+
|
46
|
+
# assert that a value is provided for every required argument
|
47
|
+
unless required_argument_count == required_args.count
|
48
|
+
raise MissingRequiredArgumentsError.new required_argument_count, required_args.count
|
49
|
+
end
|
50
|
+
|
51
|
+
# assert that too many arguments have not been provided
|
52
|
+
if args.count > required_argument_count + (has_optional_arguments ? 1 : 0)
|
53
|
+
raise TooManyArgumentsError
|
54
|
+
end
|
55
|
+
|
56
|
+
# asset that, if provided, then the optional argument (always the last one) is a Hash
|
57
|
+
if has_optional_arguments && optional_arg.nil? === false
|
58
|
+
unless optional_arg.is_a? Hash
|
59
|
+
raise OptionalArgsShouldBeHashError
|
60
|
+
end
|
61
|
+
|
62
|
+
# assert the each provided optional argument is valid
|
63
|
+
optional_arg.keys.each do |optional_argument_name|
|
64
|
+
optional_arg_value = optional_arg[optional_argument_name]
|
65
|
+
optional_argument = arguments.optional_argument optional_argument_name
|
66
|
+
|
67
|
+
case optional_argument.type
|
68
|
+
when :integer
|
69
|
+
unless optional_arg_value.is_a? Integer
|
70
|
+
raise InvalidArgumentTypeError
|
71
|
+
end
|
72
|
+
optional_argument.validate_integer! optional_arg_value
|
73
|
+
|
74
|
+
when :symbol
|
75
|
+
unless optional_arg_value.is_a? Symbol
|
76
|
+
raise InvalidArgumentTypeError
|
77
|
+
end
|
78
|
+
optional_argument.validate_symbol! optional_arg_value
|
79
|
+
|
80
|
+
when :string
|
81
|
+
unless optional_arg_value.is_a? String
|
82
|
+
raise InvalidArgumentTypeError
|
83
|
+
end
|
84
|
+
optional_argument.validate_string! optional_arg_value
|
85
|
+
|
86
|
+
when :boolean
|
87
|
+
unless optional_arg_value.is_a?(TrueClass) || optional_arg_value.is_a?(FalseClass)
|
88
|
+
raise InvalidArgumentTypeError
|
89
|
+
end
|
90
|
+
optional_argument.validate_boolean! optional_arg_value
|
91
|
+
|
92
|
+
else
|
93
|
+
raise InvalidArgumentTypeError
|
94
|
+
end
|
95
|
+
|
96
|
+
# the provided value appears valid for this argument, save the value
|
97
|
+
@arguments[optional_argument_name] = optional_arg_value
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
# validate the value provided to each required argument
|
103
|
+
arguments.required_arguments.each_with_index do |required_argument, i|
|
104
|
+
arg = args[i]
|
105
|
+
case required_argument.type
|
106
|
+
when :integer
|
107
|
+
unless arg.is_a? Integer
|
108
|
+
raise InvalidArgumentTypeError
|
109
|
+
end
|
110
|
+
required_argument.validate_integer! arg
|
111
|
+
|
112
|
+
when :symbol
|
113
|
+
unless arg.is_a? Symbol
|
114
|
+
raise InvalidArgumentTypeError
|
115
|
+
end
|
116
|
+
required_argument.validate_symbol! arg
|
117
|
+
|
118
|
+
when :string
|
119
|
+
unless arg.is_a? String
|
120
|
+
raise InvalidArgumentTypeError
|
121
|
+
end
|
122
|
+
required_argument.validate_string! arg
|
123
|
+
|
124
|
+
when :boolean
|
125
|
+
unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
|
126
|
+
raise InvalidArgumentTypeError
|
127
|
+
end
|
128
|
+
required_argument.validate_boolean! arg
|
129
|
+
|
130
|
+
else
|
131
|
+
raise InvalidArgumentTypeError
|
132
|
+
end
|
133
|
+
|
134
|
+
# the provided value appears valid for this argument, save the value
|
135
|
+
@arguments[required_argument.name] = arg
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_h
|
140
|
+
@arguments
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class Interpreter
|
5
|
+
class Execution
|
6
|
+
class MethodCalls
|
7
|
+
class MethodCall
|
8
|
+
attr_reader :dsl_method
|
9
|
+
attr_reader :arguments
|
10
|
+
|
11
|
+
class MissingRequiredArgumentsError < StandardError
|
12
|
+
def initialize required_count, provided_count
|
13
|
+
super "This method requires #{required_count} arguments, but only #{required_count} were provided"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class TooManyArgumentsError < StandardError
|
18
|
+
def message
|
19
|
+
"Too many arguments provided to this method"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class OptionalArgsShouldBeHashError < StandardError
|
24
|
+
def message
|
25
|
+
"If provided, then the optional arguments must be last, and be represented as a Hash"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class InvalidArgumentTypeError < StandardError
|
30
|
+
def message
|
31
|
+
"The provided argument is the wrong type"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize dsl_method, *args, &block
|
36
|
+
@dsl_method = dsl_method
|
37
|
+
@arguments = Arguments.new(dsl_method.arguments, *args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_name
|
41
|
+
@dsl_method.name
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_h
|
45
|
+
{
|
46
|
+
arguments: @arguments.to_h
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class Interpreter
|
5
|
+
class Execution
|
6
|
+
class MethodCalls
|
7
|
+
attr_reader :method_calls
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@method_calls = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_called? method_name
|
14
|
+
@method_calls.filter { |mc| mc.method_name == method_name }.any?
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_method_call dsl_method, *args, &block
|
18
|
+
method_call = MethodCall.new(dsl_method, *args, &block)
|
19
|
+
@method_calls << method_call
|
20
|
+
method_call
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|