dsl_compose 1.2.0 → 1.4.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 +15 -0
- data/README.md +60 -6
- data/lib/dsl_compose/composer.rb +4 -4
- data/lib/dsl_compose/dsl/arguments/argument.rb +17 -0
- data/lib/dsl_compose/interpreter/execution/arguments.rb +2 -2
- data/lib/dsl_compose/interpreter/execution/method_calls.rb +4 -0
- data/lib/dsl_compose/interpreter.rb +24 -1
- data/lib/dsl_compose/parser/block_arguments.rb +9 -0
- data/lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser/for_method_parser.rb +99 -0
- data/lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser.rb +118 -0
- data/lib/dsl_compose/parser/for_children_of_parser.rb +88 -0
- data/lib/dsl_compose/parser.rb +36 -0
- data/lib/dsl_compose/version.rb +1 -1
- data/lib/dsl_compose.rb +6 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1a173b5c152d3e0e4ac3867b9f55989593a82f724ad27c26aa0445500c81295
|
4
|
+
data.tar.gz: 391d451dcef58cf46915b0b54249b527097210d916aa041e1d49de4c576845d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d049bc5ed8c830468376cefcb198fc2671c7eff26b9a0a3fff33377d0ac2c27c03f2b2c2fa1527ef648a847667d5fe58b814c355f058e5cb3bceb80439e0ac7
|
7
|
+
data.tar.gz: 7af648a16098f0ce9b6a0c20936b0b87195cf0e2c8e7869769b5ff293d059b6ca39a0da30b15fb1605d558faeaf5965e616c3cb253bc34daec6e683879ee4da9
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [1.4.0](https://github.com/craigulliott/dsl_compose/compare/v1.3.0...v1.4.0) (2023-06-26)
|
4
|
+
|
5
|
+
|
6
|
+
### Features
|
7
|
+
|
8
|
+
* added a Parser class which can be used to react to any DSLs which have been defined and used ([8d2f16b](https://github.com/craigulliott/dsl_compose/commit/8d2f16b67ae98486a47e6e8d6d6af81be508cb06))
|
9
|
+
|
10
|
+
## [1.3.0](https://github.com/craigulliott/dsl_compose/compare/v1.2.0...v1.3.0) (2023-06-22)
|
11
|
+
|
12
|
+
|
13
|
+
### Features
|
14
|
+
|
15
|
+
* adding an executions_by_class method to make processing the configuration more convenient ([#8](https://github.com/craigulliott/dsl_compose/issues/8)) ([313507c](https://github.com/craigulliott/dsl_compose/commit/313507ca572f59c03162a91d58e62500a9464f25))
|
16
|
+
* fixing release please job ([#10](https://github.com/craigulliott/dsl_compose/issues/10)) ([880fa5b](https://github.com/craigulliott/dsl_compose/commit/880fa5b2aceed67aa9ff1e7cf53629af23e31b10))
|
17
|
+
|
3
18
|
## [1.2.0](https://github.com/craigulliott/dsl_compose/compare/v1.1.0...v1.2.0) (2023-06-22)
|
4
19
|
|
5
20
|
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@ Ruby gem to add dynamic DSLs to classes
|
|
13
13
|
* Takes special care not to pollute the namespace of classes where it is used
|
14
14
|
* Use of your declared DSLs is validated at run time
|
15
15
|
* Automatically generate documentation and instructions for your DSLs
|
16
|
-
* Complete test
|
16
|
+
* Complete test coverage
|
17
17
|
* Very lightweight and no external dependencies
|
18
18
|
|
19
19
|
## Installation
|
@@ -28,7 +28,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
28
28
|
|
29
29
|
## Usage
|
30
30
|
|
31
|
-
DSLs are added to classes by including the DSLCompose module, and then calling the
|
31
|
+
DSLs are added to classes by including the DSLCompose::Composer module, and then calling the define_dsl singleton method within the class or a child class.
|
32
32
|
|
33
33
|
### Defining your DSL
|
34
34
|
|
@@ -48,6 +48,18 @@ class Foo
|
|
48
48
|
You can use **Markdown** in this description.
|
49
49
|
DESCRIPTION
|
50
50
|
|
51
|
+
# You can add required or optional arguments to the initial method which is used
|
52
|
+
# to enter your dynamic DSL (for optional arguments, use `optional` instead of
|
53
|
+
# `required`).
|
54
|
+
#
|
55
|
+
# Arguments are validated, and their expected type must be defined. Supported
|
56
|
+
# argument types are :integer, :boolean, :float, :string or :symbol
|
57
|
+
requires :first_dsl_argument, :symbol do
|
58
|
+
# You should provide descriptions for your arguments. These descriptions will
|
59
|
+
# be used when generating your documentation. This description supports markdown
|
60
|
+
description "A description of the first argument for this method"
|
61
|
+
end
|
62
|
+
|
51
63
|
# Define a method which will be available within your DSL. These
|
52
64
|
# methods will be exposed inside your DSL and can be called multiple times.
|
53
65
|
add_method :an_optional_method do
|
@@ -83,7 +95,7 @@ class Foo
|
|
83
95
|
#
|
84
96
|
# Arguments are validated, and their expected type must be defined. Supported
|
85
97
|
# argument types are :integer, :boolean, :float, :string or :symbol
|
86
|
-
requires :
|
98
|
+
requires :first_method_argument, :string do
|
87
99
|
# You should provide descriptions for your arguments. These descriptions will
|
88
100
|
# be used when generating your documentation. This description supports markdown
|
89
101
|
description "A description of the first argument for this method"
|
@@ -92,7 +104,7 @@ class Foo
|
|
92
104
|
# You can also add optional arguments to your DSL methods. All optional
|
93
105
|
# arguments must be added after required ones. An error will be raised if
|
94
106
|
# you define a required argument after an optional one.
|
95
|
-
optional :
|
107
|
+
optional :optional_argument, :integer do
|
96
108
|
description "A description of an optional argument"
|
97
109
|
|
98
110
|
# You can add validation to your arguments. A full list is provided later in this document
|
@@ -112,8 +124,8 @@ Child classes can then use your new DSL
|
|
112
124
|
```ruby
|
113
125
|
class Bar << Foo
|
114
126
|
|
115
|
-
my_dsl do
|
116
|
-
my_method
|
127
|
+
my_dsl :first_dsl_argument, do
|
128
|
+
my_method "first_method_argument", optional_argument: 123
|
117
129
|
end
|
118
130
|
|
119
131
|
end
|
@@ -175,6 +187,48 @@ MyClientLibrary.configure do
|
|
175
187
|
end
|
176
188
|
```
|
177
189
|
|
190
|
+
## Parsing complicated DSLs
|
191
|
+
|
192
|
+
A parser class can be used to process complicated DSLs. In the example below, a base class named SomeBaseClass has DSLs named :dsl1, and :dsl2.
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
# create your own parser by creating a new class which extends DSLCompose::Parser
|
196
|
+
MyParser < DSLCompose::Parser
|
197
|
+
# `for_children_of` will process SomeBaseClass and yield the provided
|
198
|
+
# block once for every class which extends SomeBaseClass and uses at
|
199
|
+
# least one of the DSLs that have been defined on it.
|
200
|
+
for_children_of SomeBaseClass do |child_class:|
|
201
|
+
# `for_dsl` accepts a DSL name or an array of DSL names and will yield
|
202
|
+
# it's provided block once for each time a DSL of that name has been
|
203
|
+
# used on the child_class.
|
204
|
+
#
|
205
|
+
# An error will be raised if any of the provided DSL names does not exist
|
206
|
+
#
|
207
|
+
# You can optionally provide keyword arguments which correspond to any
|
208
|
+
# arguments that were defined for the DSL, if multiple dsl names are provided
|
209
|
+
# then the requested dsl argument must be present on all DSLs otherwise an
|
210
|
+
# error will be raised.
|
211
|
+
for_dsl [:dsl1, :dsl2] do |dsl_name:, a_dsl_argument:|
|
212
|
+
# `for_method` accepts a method name or an array of method names and will
|
213
|
+
# yield it's provided block once for each time a method with this name is
|
214
|
+
# executed within the DSL.
|
215
|
+
#
|
216
|
+
# An error will be raised if any of the provided method names does not exist
|
217
|
+
#
|
218
|
+
# You can optionally provide keyword arguments which correspond to any
|
219
|
+
# arguments that were defined for the DSL method, if multiple method names
|
220
|
+
# are provided then the requested dsl argument must be present on all DSLs
|
221
|
+
# otherwise an error will be raised.
|
222
|
+
for_method :some_method_name do |method_name:, a_method_argument:|
|
223
|
+
# your business logic goes here
|
224
|
+
...
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
|
178
232
|
## Argument validations
|
179
233
|
|
180
234
|
The following validations can be added to the arguments of your DSL methods. Validations can be added to both required and optional arguments, and you can add multiple validations to each argument.
|
data/lib/dsl_compose/composer.rb
CHANGED
@@ -10,13 +10,13 @@ module DSLCompose
|
|
10
10
|
|
11
11
|
class MethodAlreadyExistsWithThisDSLNameError < StandardError
|
12
12
|
def message
|
13
|
-
"The define_dsl singleton method already exists for this class."
|
13
|
+
"The `define_dsl` singleton method already exists for this class."
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
class GetDSLExecutionResultsMethodAlreadyExistsError < StandardError
|
18
18
|
def message
|
19
|
-
"The
|
19
|
+
"The `dsls` singleton method already exists for this class."
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -30,11 +30,11 @@ module DSLCompose
|
|
30
30
|
interpreter = DSLCompose::Interpreter.new
|
31
31
|
|
32
32
|
# return a specific DSL which is defined for this class
|
33
|
-
if klass.respond_to? :
|
33
|
+
if klass.respond_to? :dsls
|
34
34
|
raise GetDSLExecutionResultsMethodAlreadyExistsError
|
35
35
|
end
|
36
36
|
|
37
|
-
klass.define_singleton_method :
|
37
|
+
klass.define_singleton_method :dsls do
|
38
38
|
interpreter
|
39
39
|
end
|
40
40
|
|
@@ -40,6 +40,18 @@ module DSLCompose
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
+
class ArgumentNameReservedError < StandardError
|
44
|
+
def message
|
45
|
+
"This argument name is a reserved word. The names #{RESERVED_ARGUMENT_NAMES.join ", "} can not be used here because the Parser uses it to express a structural part of the DSL"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
RESERVED_ARGUMENT_NAMES = [
|
50
|
+
:child_class,
|
51
|
+
:dsl_name,
|
52
|
+
:method_name
|
53
|
+
].freeze
|
54
|
+
|
43
55
|
# The name of this Argument.
|
44
56
|
attr_reader :name
|
45
57
|
# An arguments type. This determines what kind of value can be passed when calling the
|
@@ -73,6 +85,11 @@ module DSLCompose
|
|
73
85
|
# `block` contains the instructions to further configure this Attribute
|
74
86
|
def initialize name, required, type, &block
|
75
87
|
if name.is_a? Symbol
|
88
|
+
|
89
|
+
if RESERVED_ARGUMENT_NAMES.include? name
|
90
|
+
raise ArgumentNameReservedError
|
91
|
+
end
|
92
|
+
|
76
93
|
@name = name
|
77
94
|
else
|
78
95
|
raise InvalidNameError
|
@@ -6,13 +6,13 @@ module DSLCompose
|
|
6
6
|
class Arguments
|
7
7
|
class MissingRequiredArgumentsError < StandardError
|
8
8
|
def initialize required_count, provided_count
|
9
|
-
super "This
|
9
|
+
super "This requires #{required_count} arguments, but only #{provided_count} were provided"
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
13
|
class TooManyArgumentsError < StandardError
|
14
14
|
def message
|
15
|
-
"Too many arguments provided
|
15
|
+
"Too many arguments provided"
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -27,11 +27,16 @@ module DSLCompose
|
|
27
27
|
@executions.filter { |e| e.klass == klass }
|
28
28
|
end
|
29
29
|
|
30
|
-
# Returns an array of all executions for a given
|
30
|
+
# Returns an array of all executions for a given name.
|
31
31
|
def dsl_executions dsl_name
|
32
32
|
@executions.filter { |e| e.dsl.name == dsl_name }
|
33
33
|
end
|
34
34
|
|
35
|
+
# Returns an array of all executions for a given name and class.
|
36
|
+
def class_dsl_executions klass, dsl_name
|
37
|
+
@executions.filter { |e| e.klass == klass && e.dsl.name == dsl_name }
|
38
|
+
end
|
39
|
+
|
35
40
|
def to_h dsl_name
|
36
41
|
h = {}
|
37
42
|
dsl_executions(dsl_name).each do |execution|
|
@@ -46,5 +51,23 @@ module DSLCompose
|
|
46
51
|
end
|
47
52
|
h
|
48
53
|
end
|
54
|
+
|
55
|
+
def executions_by_class
|
56
|
+
h = {}
|
57
|
+
executions.each do |execution|
|
58
|
+
h[execution.klass] ||= {}
|
59
|
+
h[execution.klass][execution.dsl.name] ||= []
|
60
|
+
execution_h = {
|
61
|
+
arguments: execution.arguments.to_h,
|
62
|
+
method_calls: {}
|
63
|
+
}
|
64
|
+
execution.method_calls.method_calls.each do |method_call|
|
65
|
+
execution_h[:method_calls][method_call.method_name] ||= []
|
66
|
+
execution_h[:method_calls][method_call.method_name] << method_call.to_h
|
67
|
+
end
|
68
|
+
h[execution.klass][execution.dsl.name] << execution_h
|
69
|
+
end
|
70
|
+
h
|
71
|
+
end
|
49
72
|
end
|
50
73
|
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class Parser
|
5
|
+
class ForChildrenOfParser
|
6
|
+
class ForDSLParser
|
7
|
+
class ForMethodParser
|
8
|
+
class AllBlockParametersMustBeKeywordParametersError < StandardError
|
9
|
+
def message
|
10
|
+
"All block parameters must be keyword parameters, i.e. `for_children_of FooClass do |base_class:|`"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NoBlockProvided < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class MethodDoesNotExistError < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
class MethodNamesShouldBeSymbolsError < StandardError
|
21
|
+
def message
|
22
|
+
"Method names must be provided with a symbol or array of symbols"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# This class will yield to the provided block once for each time a method
|
27
|
+
# with the provided name is called within a DSL called on the child class.
|
28
|
+
#
|
29
|
+
# base_class and child_class are set from the ForChildrenOfParser
|
30
|
+
# dsl_execution is set from the ForDSLParser
|
31
|
+
def initialize base_class, child_class, dsl_execution, method_names, &block
|
32
|
+
@base_class = base_class
|
33
|
+
@child_class = child_class
|
34
|
+
@dsl_execution = dsl_execution
|
35
|
+
@method_names = method_names
|
36
|
+
|
37
|
+
# assert that a block was provided
|
38
|
+
unless block
|
39
|
+
raise NoBlockProvided
|
40
|
+
end
|
41
|
+
|
42
|
+
# if any arguments were provided, then assert that they are valid
|
43
|
+
if block.parameters.any?
|
44
|
+
# all parameters must be keyword arguments
|
45
|
+
if block.parameters.filter { |p| p.first != :keyreq }.any?
|
46
|
+
raise AllBlockParametersMustBeKeywordParametersError
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# if the provided dsl name is a symbol, then convert it to an array
|
51
|
+
if method_names.is_a? Symbol
|
52
|
+
method_names = [method_names]
|
53
|
+
end
|
54
|
+
|
55
|
+
# assert that the provided dsl name is an array
|
56
|
+
unless method_names.is_a? Array
|
57
|
+
raise MethodNamesShouldBeSymbolsError
|
58
|
+
end
|
59
|
+
|
60
|
+
# assert that the provided dsl name is an array of symbols
|
61
|
+
unless method_names.all? { |method_name| method_name.is_a? Symbol }
|
62
|
+
raise MethodNamesShouldBeSymbolsError
|
63
|
+
end
|
64
|
+
|
65
|
+
# assert that the provided method names all exist for the scoped DSL
|
66
|
+
unless method_names.all? { |method_name| dsl_execution.dsl.has_dsl_method?(method_name) }
|
67
|
+
raise MethodDoesNotExistError
|
68
|
+
end
|
69
|
+
|
70
|
+
# for each provided dsl name, yield to the provided block
|
71
|
+
method_names.each do |method_name|
|
72
|
+
# we only provide the requested arguments to the block, this allows
|
73
|
+
# us to use keyword arguments to force a naming convention on these arguments
|
74
|
+
# and to validate their use
|
75
|
+
args = {}
|
76
|
+
if BlockArguments.accepts_argument?(:method_name, &block)
|
77
|
+
args[:method_name] = method_name
|
78
|
+
end
|
79
|
+
|
80
|
+
# methods can be executed multiple times, so yield once for each method call
|
81
|
+
# add any arguments that were provided to the DSL method
|
82
|
+
dsl_execution.method_calls.method_calls_by_name(method_name).each do |method_call|
|
83
|
+
# add any arguments that were provided to the method call
|
84
|
+
method_call.arguments.arguments.each do |name, value|
|
85
|
+
if BlockArguments.accepts_argument?(name, &block)
|
86
|
+
args[name] = value
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# yeild the block in the context of this class
|
91
|
+
instance_exec(**args, &block)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class Parser
|
5
|
+
class ForChildrenOfParser
|
6
|
+
class ForDSLParser
|
7
|
+
class AllBlockParametersMustBeKeywordParametersError < StandardError
|
8
|
+
def message
|
9
|
+
"All block parameters must be keyword parameters, i.e. `for_dsl :dsl_name do |dsl_name:|`"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class NoBlockProvided < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
class DSLDoesNotExistError < StandardError
|
17
|
+
end
|
18
|
+
|
19
|
+
class DSLNamesShouldBeSymbolsError < StandardError
|
20
|
+
def message
|
21
|
+
"DSL names must be provided with a symbol or array of symbols"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This class will yield to the provided block once for each time a DSL
|
26
|
+
# of the provided name is used by the child class.
|
27
|
+
#
|
28
|
+
# base_class and child_class are set from the ForChildrenOfParser
|
29
|
+
def initialize base_class, child_class, dsl_names, &block
|
30
|
+
@base_class = base_class
|
31
|
+
@child_class = child_class
|
32
|
+
@dsl_names = dsl_names
|
33
|
+
|
34
|
+
# assert that a block was provided
|
35
|
+
unless block
|
36
|
+
raise NoBlockProvided
|
37
|
+
end
|
38
|
+
|
39
|
+
# if any arguments were provided, then assert that they are valid
|
40
|
+
if block.parameters.any?
|
41
|
+
# all parameters must be keyword arguments
|
42
|
+
if block.parameters.filter { |p| p.first != :keyreq }.any?
|
43
|
+
raise AllBlockParametersMustBeKeywordParametersError
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# if the provided dsl name is a symbol, then convert it to an array
|
48
|
+
if dsl_names.is_a? Symbol
|
49
|
+
dsl_names = [dsl_names]
|
50
|
+
end
|
51
|
+
|
52
|
+
# assert that the provided dsl name is an array
|
53
|
+
unless dsl_names.is_a? Array
|
54
|
+
raise DSLNamesShouldBeSymbolsError
|
55
|
+
end
|
56
|
+
|
57
|
+
# assert that the provided dsl name is an array of symbols
|
58
|
+
unless dsl_names.all? { |dsl_name| dsl_name.is_a? Symbol }
|
59
|
+
raise DSLNamesShouldBeSymbolsError
|
60
|
+
end
|
61
|
+
|
62
|
+
# assert that the provided dsl names all exist
|
63
|
+
unless dsl_names.all? { |dsl_name| DSLs.class_dsl_exists?(base_class, dsl_name) }
|
64
|
+
raise DSLDoesNotExistError
|
65
|
+
end
|
66
|
+
|
67
|
+
# for each provided dsl name, yield to the provided block
|
68
|
+
dsl_names.each do |dsl_name|
|
69
|
+
# a dsl can be execued multiple times on a class, so we find all of the executions
|
70
|
+
# here and then yield the block once for each execution
|
71
|
+
base_class.dsls.class_dsl_executions(child_class, dsl_name).each do |dsl_execution|
|
72
|
+
# we only provide the requested arguments to the block, this allows
|
73
|
+
# us to use keyword arguments to force a naming convention on these arguments
|
74
|
+
# and to validate their use
|
75
|
+
args = {}
|
76
|
+
if BlockArguments.accepts_argument?(:dsl_name, &block)
|
77
|
+
args[:dsl_name] = dsl_execution.dsl.name
|
78
|
+
end
|
79
|
+
# add any arguments that were provided to the DSL
|
80
|
+
dsl_execution.arguments.arguments.each do |name, value|
|
81
|
+
if BlockArguments.accepts_argument?(name, &block)
|
82
|
+
args[name] = value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
# set the dsl_execution in an instance variable so that method calls to `for_method`
|
86
|
+
# from within the block will have access to it
|
87
|
+
@dsl_execution = dsl_execution
|
88
|
+
# yield the block in the context of this class
|
89
|
+
instance_exec(**args, &block)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
# Given a method name, or array of method names, this method will yield to the
|
97
|
+
# provided block once for each time a dsl method with one of the provided names
|
98
|
+
# was used within one of the correspondng dsls, on each child_class
|
99
|
+
#
|
100
|
+
# The values of base_class and child_class are set from the yield of the parent
|
101
|
+
# class (ForChildrenOfParser) initializer, the value of dsl_execution is set from
|
102
|
+
# this classes initializer, meaning that the use of this method should look
|
103
|
+
# something like this:
|
104
|
+
#
|
105
|
+
# for_children_of PlatformRecord do |base_class:|
|
106
|
+
# for_dsl [:number_field, :float_field] do |name:|
|
107
|
+
# for_method :some_method_name do |method_name:, a_method_argument:|
|
108
|
+
# ...
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
def for_method method_names, &block
|
113
|
+
ForMethodParser.new(@base_class, @child_class, @dsl_execution, method_names, &block)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
class Parser
|
5
|
+
class ForChildrenOfParser
|
6
|
+
class AllBlockParametersMustBeKeywordParametersError < StandardError
|
7
|
+
def message
|
8
|
+
"All block parameters must be keyword parameters, i.e. `for_children_of FooClass do |base_class:|`"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ClassDoesNotUseDSLComposeError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class NoBlockProvided < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
class NoChildClassError < StandardError
|
19
|
+
def message
|
20
|
+
"No child_class was found, please call this method from within a `for_children_of` block"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# This class will yield to the provided block for each class which extends the base_class, provided
|
25
|
+
# that the child also uses at least one of the DSLs which have been defined on the base_class
|
26
|
+
def initialize base_class, &block
|
27
|
+
# assert the provided class has the DSLCompose::Composer module installed
|
28
|
+
unless base_class.respond_to? :dsls
|
29
|
+
raise ClassDoesNotUseDSLComposeError
|
30
|
+
end
|
31
|
+
|
32
|
+
@base_class = base_class
|
33
|
+
|
34
|
+
# assert that a block was provided
|
35
|
+
unless block
|
36
|
+
raise NoBlockProvided
|
37
|
+
end
|
38
|
+
|
39
|
+
# if any arguments were provided, then assert that they are valid
|
40
|
+
if block.parameters.any?
|
41
|
+
# all parameters must be keyword arguments
|
42
|
+
if block.parameters.filter { |p| p.first != :keyreq }.any?
|
43
|
+
raise AllBlockParametersMustBeKeywordParametersError
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# yield the provided block for each child class of the provided base_class
|
48
|
+
# which uses a defined DSL
|
49
|
+
base_class.dsls.executions_by_class.each do |child_class, dsl|
|
50
|
+
args = {}
|
51
|
+
if BlockArguments.accepts_argument?(:child_class, &block)
|
52
|
+
args[:child_class] = child_class
|
53
|
+
end
|
54
|
+
# set the child_class in an instance variable so that method calls to
|
55
|
+
# `for_dsl` from within the block will have access to it
|
56
|
+
@child_class = child_class
|
57
|
+
# yield the block in the context of this class
|
58
|
+
instance_exec(**args, &block)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Given a dsl name, or array of dsl names, this method will yield to the
|
65
|
+
# provided block once for each time a dsl with one of the provided names
|
66
|
+
# was used on the correspondng child_class
|
67
|
+
#
|
68
|
+
# The value of child_class and base_class are set from the yield of this
|
69
|
+
# classes initializer, meaning that the use of this method should look
|
70
|
+
# something like this:
|
71
|
+
#
|
72
|
+
# for_children_of PlatformRecord do |base_class:|
|
73
|
+
# for_dsl [:number_field, :float_field] do |name:|
|
74
|
+
# ...
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
def for_dsl dsl_names, &block
|
78
|
+
child_class = @child_class
|
79
|
+
|
80
|
+
unless child_class
|
81
|
+
raise NoChildClassError
|
82
|
+
end
|
83
|
+
|
84
|
+
ForDSLParser.new(@base_class, child_class, dsl_names, &block)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DSLCompose
|
4
|
+
# A base class for building parsers which can be used to act upon
|
5
|
+
# our dynamically defined DSLs
|
6
|
+
#
|
7
|
+
# Example syntax...
|
8
|
+
#
|
9
|
+
# for_children_of SomeBaseClass do |child_class:|
|
10
|
+
# for_dsl [:dsl_name1, :dsl_name2] do |dsl_name:, a_dsl_argument:|
|
11
|
+
# for_method :some_method_name do |method_name:, a_method_argument:|
|
12
|
+
# ...
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
class Parser
|
17
|
+
class NotInitializable < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
# this class is not designed to be initialized, the parser is made up of
|
21
|
+
# singlerton methods and is designed to be executed as soon as it is required
|
22
|
+
# into an application
|
23
|
+
def initialize
|
24
|
+
raise NotInitializable
|
25
|
+
end
|
26
|
+
|
27
|
+
# the first step in defining a parser is to set the base_class, this method
|
28
|
+
# will yield to the provided block for each child class of the provided base_class
|
29
|
+
# provided that the child_class uses at least one of the base_classes defined DSLs
|
30
|
+
def self.for_children_of base_class, &block
|
31
|
+
# we parse the provided block in the context of the ForChildrenOfParser class
|
32
|
+
# to help make this code more readable, and to limit polluting the current namespace
|
33
|
+
ForChildrenOfParser.new(base_class, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/dsl_compose/version.rb
CHANGED
data/lib/dsl_compose.rb
CHANGED
@@ -29,6 +29,12 @@ require "dsl_compose/interpreter/execution/arguments"
|
|
29
29
|
require "dsl_compose/interpreter/execution"
|
30
30
|
require "dsl_compose/interpreter"
|
31
31
|
|
32
|
+
require "dsl_compose/parser/for_children_of_parser/for_dsl_parser/for_method_parser"
|
33
|
+
require "dsl_compose/parser/for_children_of_parser/for_dsl_parser"
|
34
|
+
require "dsl_compose/parser/for_children_of_parser"
|
35
|
+
require "dsl_compose/parser/block_arguments"
|
36
|
+
require "dsl_compose/parser"
|
37
|
+
|
32
38
|
require "dsl_compose/composer"
|
33
39
|
|
34
40
|
require "dsl_compose/dsls"
|
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: 1.
|
4
|
+
version: 1.4.0
|
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-06-
|
11
|
+
date: 2023-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Ruby gem to add dynamic DSLs to classes. DSLs are added to classes by
|
14
14
|
including the DSLCompose module, and then calling the add_dsl singleton method within
|
@@ -47,6 +47,11 @@ files:
|
|
47
47
|
- lib/dsl_compose/interpreter/execution/arguments.rb
|
48
48
|
- lib/dsl_compose/interpreter/execution/method_calls.rb
|
49
49
|
- lib/dsl_compose/interpreter/execution/method_calls/method_call.rb
|
50
|
+
- lib/dsl_compose/parser.rb
|
51
|
+
- lib/dsl_compose/parser/block_arguments.rb
|
52
|
+
- lib/dsl_compose/parser/for_children_of_parser.rb
|
53
|
+
- lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser.rb
|
54
|
+
- lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser/for_method_parser.rb
|
50
55
|
- lib/dsl_compose/version.rb
|
51
56
|
homepage:
|
52
57
|
licenses:
|