dsl_compose 1.3.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8b82032e4bea7187d0dd4a00528472ce1a5b29d4aea64b94d6090b8d013b47b
4
- data.tar.gz: 628dda5efddcfdc2b9de8df09245c5af67c233de24dee7654d7266b193b6ef95
3
+ metadata.gz: 5f215d3a5226d56e6f0f5e62b340e0212607a52c3097228f7fc24ef1f5028a20
4
+ data.tar.gz: afcf1aa8a22966ca83475f7e0d41780c5e0d44e50b13dc91b33a71011b6d1fc4
5
5
  SHA512:
6
- metadata.gz: 670c5b83e8d8e3dd986933499d96d92604da968088b31dba613421712beb5ded7c7defec91714a1c23df1a4ba82d55a15a815864376d2ad3970a426232bb42cc
7
- data.tar.gz: 48f76cf798a28b60efe4cf1c69d2f21c3594e86de5bbcf674d4a16f68114e76cde998a68c8dfbfbe8860362165bd9af7c37f6be3842e6efbdf9db8568ce72246
6
+ metadata.gz: fff6880b4032551cabd769fa1c2c6eda13179f4b1e50cb652bdef9a6ecd0055e45994235d0ebb78ee06445086368c304279fa04feddcde41b78e046677aea86c
7
+ data.tar.gz: 409bae7843d156e2c1e6854ebd87381c2eafcccc8adaf3cd2adfbcc076282efde0d0d12c69fb661f95ac0404c7bfd1b28328bdc6e54f211790fb62571b1dd7a0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.0](https://github.com/craigulliott/dsl_compose/compare/v1.4.0...v1.5.0) (2023-07-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * adding a parser method to rerun the parser, this is most useful from within a test suite when you are testing the parser itself ([#15](https://github.com/craigulliott/dsl_compose/issues/15)) ([fe4de0e](https://github.com/craigulliott/dsl_compose/commit/fe4de0e53f78aff34de4ba1c432b10022d2b6ab4))
9
+
10
+ ## [1.4.0](https://github.com/craigulliott/dsl_compose/compare/v1.3.0...v1.4.0) (2023-06-26)
11
+
12
+
13
+ ### Features
14
+
15
+ * 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))
16
+
3
17
  ## [1.3.0](https://github.com/craigulliott/dsl_compose/compare/v1.2.0...v1.3.0) (2023-06-22)
4
18
 
5
19
 
data/README.md CHANGED
@@ -11,9 +11,9 @@ Ruby gem to add dynamic DSLs to classes
11
11
 
12
12
  * Contains a simple internal DSL which is used to declare dynamic DSLs on your classes
13
13
  * Takes special care not to pollute the namespace of classes where it is used
14
- * Use of your declared DSLs is validated at run time
15
- * Automatically generate documentation and instructions for your DSLs
16
- * Complete test covereage
14
+ * Use of your declared DSLs is strictly validated at run time
15
+ * Automatically generate documentation for your DSLs
16
+ * Extensive 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 add_dsl singleton method within the class or a child class.
31
+ DSLs are defined and added to classes by including the `DSLCompose::Composer` module, and then calling the `define_dsl` singleton method on that class. Those DSLs can then be used on child classes (classes which extend the original class).
32
32
 
33
33
  ### Defining your DSL
34
34
 
@@ -36,11 +36,11 @@ DSLs are added to classes by including the DSLCompose module, and then calling t
36
36
  class Foo
37
37
  include DSLCompose::Composer
38
38
 
39
- # Define and name the DSL. Your DSL will available on this
39
+ # Define and name your DSL. Your DSL will be available on this
40
40
  # class and any children of this class.
41
- define_dsl :my_dsl do
41
+ define_dsl :your_dsl do
42
42
 
43
- # A description of your DSL.
43
+ # A description of your DSL
44
44
  description <<-DESCRIPTION
45
45
  Add a description of your DSL here, this description will be
46
46
  used when generating the documentation for your DSL.
@@ -48,43 +48,55 @@ 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 call 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
54
66
  # You should provide descriptions for your methods. These descriptions will
55
67
  # be used when generating your documentation. Both of these descriptions
56
68
  # accept markdown
57
- description "A description of my awesome method"
69
+ description "A description of your method"
58
70
 
59
- # add your method argument definition here
71
+ # Add your method argument definition here
60
72
  end
61
73
 
62
- # Define a required within your DSL. If a class uses your DSL but
63
- # does not execute this method then an error will be raised.
74
+ # Define a required method within your DSL. An error will be raised if a class
75
+ # uses your DSL but does not execute this method
64
76
  add_method :a_required_method, required: true do
65
- # add your description and method argument definition here (see below)
77
+ # Add your description here
78
+ # Add any method arguments here (more info below about method arguments)
66
79
  end
67
80
 
68
81
  # Define a method which can only be called once within your DSL. These
69
- # methods will raise an error of they are called multiple times.
82
+ # "unique" methods will raise an error of they are called multiple times.
70
83
  #
71
- # There "unique" methods can be optionally marked as required.
84
+ # unique methods can be optionally marked as required.
72
85
  add_unique_method :an_optional_method do
73
- # add your description and method argument definition here (see below)
86
+ # Add your description and any method arguments here (more info below about method arguments)
74
87
  end
75
88
 
76
89
  # Define a method in your DSL which takes arguments
77
- add_method :my_method do
78
- # A description of my DSL method
79
- description "A description of my DSL method"
90
+ add_method :your_method do
91
+ description "A description of your DSL method"
80
92
 
81
93
  # You can add required arguments to your methods. The order in which you
82
94
  # define these arguments determines the order of the arguments in your final DSL.
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 :my_first_argument, :symbol do
87
- # You should provide descriptions for your arguments. These descriptions will
98
+ requires :first_method_argument, :string do
99
+ # You should provide descriptions for your arguments too. 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"
90
102
  end
@@ -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 :an_optional_argument, :integer do
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 my_first_argument, my_second_argument, optional_arg: optional_arg_value
127
+ your_dsl :first_dsl_argument, do
128
+ your_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.
@@ -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 method requires #{required_count} arguments, but only #{required_count} were provided"
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 to this method"
15
+ "Too many arguments provided"
16
16
  end
17
17
  end
18
18
 
@@ -19,6 +19,10 @@ module DSLCompose
19
19
  @method_calls << method_call
20
20
  method_call
21
21
  end
22
+
23
+ def method_calls_by_name method_name
24
+ @method_calls.filter { |mc| mc.method_name == method_name }
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -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 class.
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|
@@ -0,0 +1,9 @@
1
+ module DSLCompose
2
+ class Parser
3
+ class BlockArguments
4
+ def self.accepts_argument? arg_name, &block
5
+ block.parameters.any? { |type, name| name == arg_name }
6
+ end
7
+ end
8
+ end
9
+ 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,54 @@
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, rerun = false, &block
31
+ unless rerun
32
+ @runs ||= []
33
+ @runs << {
34
+ base_class: base_class,
35
+ block: block
36
+ }
37
+ end
38
+
39
+ # we parse the provided block in the context of the ForChildrenOfParser class
40
+ # to help make this code more readable, and to limit polluting the current namespace
41
+ ForChildrenOfParser.new(base_class, &block)
42
+ end
43
+
44
+ # this method is used to rerun the parser, this is most useful from within a test suite
45
+ # when you are testing the parser itself
46
+ def self.rerun
47
+ @runs&.each do |run|
48
+ base_class = run[:base_class]
49
+ block = run[:block]
50
+ for_children_of base_class, true, &block
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSLCompose
4
- VERSION = "1.3.0"
4
+ VERSION = "1.5.0"
5
5
  end
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.3.0
4
+ version: 1.5.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-22 00:00:00.000000000 Z
11
+ date: 2023-07-07 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: