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