dsl_compose 1.11.0 → 1.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6a23cf9faddf37ebe1fdec13fe554e8e7601e01f5c53495d8d3d37f28a63faf
4
- data.tar.gz: 7254f3f5c46716e462189c72d2bd02b0131cdca1fb39387a8b86fd79cb2e9c6c
3
+ metadata.gz: 45e24e8af1b43cc20cdfe1b9089c54812b774f9d377f4d74842b202d4b6bd1c2
4
+ data.tar.gz: 55ee3564fadfa1ff647a340a612ef1b840e616054008616565db52838ba2b2eb
5
5
  SHA512:
6
- metadata.gz: c71462286d85bdc0b4f2d3879b08bf04a142b4f10bb9554c230fa263d80e69d08fe73bd3cc198d516d8950df1a8b39863823cf0993b3a80af2110b80de69f2a6
7
- data.tar.gz: 5b03471c8e676610297c16312b517bd62692fa5d85e5a610aed709684058032764668550622c84089d4cc6b7240d76c2d6a9d21487a4abf074eecaf93b6c19bd
6
+ metadata.gz: 307a73bcdd19bda7531e329710d1c559f31f6635d2a7cd495e9cf9644e02db140e53e286ecb9aded78a64b2d11ae617f1178093cb857226346b55bce50fa5ae7
7
+ data.tar.gz: 837abd419951f900d30a234d0665ff12d2e6cb9ad43750fc2dc58b2d07a5b08a3c9de2faf9c4a0149da65ff4f3aa6381af5b5a67bb1d7c969c6d64429d5cbac8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.13.0](https://github.com/craigulliott/dsl_compose/compare/v1.12.0...v1.13.0) (2023-07-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * added for_final_children_of, for_inherited_dsl and for_dsl_or_inherited_dsl methods to the parser ([#35](https://github.com/craigulliott/dsl_compose/issues/35)) ([b01a629](https://github.com/craigulliott/dsl_compose/commit/b01a629e1b18540cbbd90ba53090b41b8fb41dfd))
9
+
10
+ ## [1.12.0](https://github.com/craigulliott/dsl_compose/compare/v1.11.0...v1.12.0) (2023-07-13)
11
+
12
+
13
+ ### Features
14
+
15
+ * parsers are now aware of class inheritance, and all classes now assume the DSL configurations of all their ancestors ([#33](https://github.com/craigulliott/dsl_compose/issues/33)) ([6a6cfb1](https://github.com/craigulliott/dsl_compose/commit/6a6cfb1fd9bb44c2ea949888c7b7be14ddc0cef8))
16
+
3
17
  ## [1.11.0](https://github.com/craigulliott/dsl_compose/compare/v1.10.0...v1.11.0) (2023-07-12)
4
18
 
5
19
 
data/README.md CHANGED
@@ -241,19 +241,30 @@ A parser class can be used to process complicated DSLs. In the example below, a
241
241
  # create your own parser by creating a new class which extends DSLCompose::Parser
242
242
  MyParser < DSLCompose::Parser
243
243
  # `for_children_of` will process SomeBaseClass and yield the provided
244
- # block once for every class which extends SomeBaseClass and uses at
245
- # least one of the DSLs that have been defined on it.
244
+ # block once for every class which extends SomeBaseClass.
245
+ #
246
+ # If you only want to process classes at the end of the class hierarchy (classes
247
+ # which extend the provided base class, but do not have their own children) then
248
+ # use `for_final_children_of` instead of `for_children_of`
246
249
  for_children_of SomeBaseClass do |child_class:|
247
- # `for_dsl` accepts a DSL name or an array of DSL names and will yield
248
- # it's provided block once for each time a DSL of that name has been
249
- # used on the child_class.
250
+ # `for_dsl` accepts a DSL name or an array of DSL names and will yield it's
251
+ # provided block once for each time a DSL of that name has been used
252
+ # directly on the child_class.
250
253
  #
251
- # An error will be raised if any of the provided DSL names does not exist
254
+ # If you want to yield the provided block for classes which didn't directly use
255
+ # one of the provided DSLs, but the DSL was used on one of their ancestors, then
256
+ # use `for_inherited_dsl :dsl_name` instead of `for_dsl :dsl_name`. If you want
257
+ # the block to yield whether the DSL was used directly on the provided class or
258
+ # anywhere in it's ancestor chain, then use `for_dsl_or_inherited_dsl :dsl_name`.
259
+ #
260
+ # An error will be raised if any of the provided DSL names does not exist.
252
261
  #
253
262
  # You can optionally provide keyword arguments which correspond to any
254
263
  # arguments that were defined for the DSL, if multiple dsl names are provided
255
264
  # then the requested dsl argument must be present on all DSLs otherwise an
256
- # error will be raised.
265
+ # error will be raised. The parser is aware of class inheritance, and will
266
+ # consider a DSL to have been executed on the actual class it was used, and
267
+ # any classes which are descendants of that class
257
268
  for_dsl [:dsl1, :dsl2] do |dsl_name:, a_dsl_argument:|
258
269
  # `for_method` accepts a method name or an array of method names and will
259
270
  # yield it's provided block once for each time a method with this name is
@@ -40,9 +40,10 @@ module DSLCompose
40
40
  @executions.filter { |e| e.dsl.name == dsl_name }
41
41
  end
42
42
 
43
- # Returns an array of all executions for a given name and class.
44
- def class_dsl_executions klass, dsl_name
45
- @executions.filter { |e| e.klass == klass && e.dsl.name == dsl_name }
43
+ # Returns an array of all executions for a given name and class. This includes
44
+ # any ancestors of the provided class
45
+ def class_dsl_executions klass, dsl_name, on_current_class, on_ancestor_class
46
+ @executions.filter { |e| e.dsl.name == dsl_name && ((on_current_class && e.klass == klass) || (on_ancestor_class && klass < e.klass)) }
46
47
  end
47
48
 
48
49
  # removes all executions from the interpreter, this is primarily used from
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSLCompose
4
+ class Parser
5
+ class ForChildrenOfParser
6
+ class Descendents
7
+ def initialize base_class, final_children_only
8
+ @base_class = base_class
9
+ @final_children_only = final_children_only
10
+ end
11
+
12
+ def classes
13
+ # all objects which extend the provided base class
14
+ extending_classes = ObjectSpace.each_object(Class).select { |klass| klass < @base_class }
15
+
16
+ # sort the results, classes are ordered first by the depth of their namespace, and second
17
+ # by their name
18
+ extending_classes.sort_by! do |child_class|
19
+ "#{child_class.name.split("::").count}_#{child_class.name}"
20
+ end
21
+
22
+ # if this is not a final child, but we are processing final children only, then skip it
23
+ if @final_children_only
24
+ # reject any classes which have descendents
25
+ extending_classes.reject! do |child_class|
26
+ has_descendents child_class
27
+ end
28
+ end
29
+
30
+ extending_classes
31
+ end
32
+
33
+ private
34
+
35
+ def has_descendents base_class
36
+ ObjectSpace.each_object(Class).count { |klass| klass < base_class } > 0
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -20,7 +20,7 @@ module DSLCompose
20
20
  # of the provided name is used by the child class.
21
21
  #
22
22
  # base_class and child_class are set from the ForChildrenOfParser
23
- def initialize base_class, child_class, dsl_names, &block
23
+ def initialize base_class, child_class, dsl_names, on_current_class, on_ancestor_class, &block
24
24
  @base_class = base_class
25
25
  @child_class = child_class
26
26
  @dsl_names = dsl_names
@@ -31,7 +31,7 @@ module DSLCompose
31
31
  end
32
32
 
33
33
  # if any arguments were provided, then assert that they are valid
34
- if block.parameters.any?
34
+ if block&.parameters&.any?
35
35
  # all parameters must be keyword arguments
36
36
  if block.parameters.filter { |p| p.first != :keyreq }.any?
37
37
  raise AllBlockParametersMustBeKeywordParametersError, "All block parameters must be keyword parameters, i.e. `for_dsl :dsl_name do |dsl_name:|`"
@@ -62,7 +62,7 @@ module DSLCompose
62
62
  dsl_names.each do |dsl_name|
63
63
  # a dsl can be execued multiple times on a class, so we find all of the executions
64
64
  # here and then yield the block once for each execution
65
- base_class.dsls.class_dsl_executions(child_class, dsl_name).each do |dsl_execution|
65
+ base_class.dsls.class_dsl_executions(child_class, dsl_name, on_current_class, on_ancestor_class).each do |dsl_execution|
66
66
  # we only provide the requested arguments to the block, this allows
67
67
  # us to use keyword arguments to force a naming convention on these arguments
68
68
  # and to validate their use
@@ -15,9 +15,27 @@ module DSLCompose
15
15
  class NoChildClassError < StandardError
16
16
  end
17
17
 
18
- # This class will yield to the provided block for each class which extends the base_class, provided
19
- # that the child also uses at least one of the DSLs which have been defined on the base_class
20
- def initialize base_class, &block
18
+ # This class will yield to the provided block for each descendant
19
+ # class of the provided base_class
20
+ #
21
+ # For example:
22
+ #
23
+ # class BaseClass
24
+ # include DSLCompose::Composer
25
+ # define_dsl :my_foo_dsl
26
+ # end
27
+ #
28
+ # class ChildClass < BaseClass
29
+ # my_foo_dsl
30
+ # end
31
+ #
32
+ # class GrandchildClass < ChildClass
33
+ # end
34
+ #
35
+ # parser.for_children_of BaseClass do |child_class:|
36
+ # # this will yield for ChildClass and GrandchildClass
37
+ # and
38
+ def initialize base_class, final_children_only, &block
21
39
  # assert the provided class has the DSLCompose::Composer module installed
22
40
  unless base_class.respond_to? :dsls
23
41
  raise ClassDoesNotUseDSLComposeError, base_class
@@ -31,23 +49,25 @@ module DSLCompose
31
49
  end
32
50
 
33
51
  # if any arguments were provided, then assert that they are valid
34
- if block.parameters.any?
52
+ if block&.parameters&.any?
35
53
  # all parameters must be keyword arguments
36
54
  if block.parameters.filter { |p| p.first != :keyreq }.any?
37
55
  raise AllBlockParametersMustBeKeywordParametersError, "All block parameters must be keyword parameters, i.e. `for_children_of FooClass do |base_class:|`"
38
56
  end
39
57
  end
40
58
 
41
- # yield the provided block for each child class of the provided base_class
42
- # which uses a defined DSL
43
- base_class.dsls.executions_by_class.each do |child_class, dsl|
59
+ # yeild the block for all descendents of the provided base_class
60
+ Descendents.new(base_class, final_children_only).classes.each do |child_class|
61
+ # determine which arguments to send to the block
44
62
  args = {}
45
63
  if BlockArguments.accepts_argument?(:child_class, &block)
46
64
  args[:child_class] = child_class
47
65
  end
66
+
48
67
  # set the child_class in an instance variable so that method calls to
49
68
  # `for_dsl` from within the block will have access to it
50
69
  @child_class = child_class
70
+
51
71
  # yield the block in the context of this class
52
72
  instance_exec(**args, &block)
53
73
  end
@@ -57,7 +77,7 @@ module DSLCompose
57
77
 
58
78
  # Given a dsl name, or array of dsl names, this method will yield to the
59
79
  # provided block once for each time a dsl with one of the provided names
60
- # was used on the correspondng child_class
80
+ # was used on the correspondng child_class or any of its ancestors.
61
81
  #
62
82
  # The value of child_class and base_class are set from the yield of this
63
83
  # classes initializer, meaning that the use of this method should look
@@ -68,14 +88,38 @@ module DSLCompose
68
88
  # ...
69
89
  # end
70
90
  # end
71
- def for_dsl dsl_names, &block
91
+ #
92
+ # If `on_current_class` is true, then the block will be yielded to for each DSL
93
+ # which was used directly on the current class. If `oncurrent_class` is false,
94
+ # then the block will not be yielded to for any DSL which was used directly on.
95
+ # If `on_ancestor_class` is true, then the block will be yielded to for each DSL
96
+ # which was used on any class in the current classes ancestry. If `on_ancestor_class`
97
+ # is false, then the block will not be yielded to for any DSL which was used on
98
+ # any class in the current classes ancestry.
99
+ def for_dsl dsl_names, on_current_class: true, on_ancestor_class: false, &block
72
100
  child_class = @child_class
73
101
 
74
102
  unless child_class
75
103
  raise NoChildClassError, "No child_class was found, please call this method from within a `for_children_of` block"
76
104
  end
77
105
 
78
- ForDSLParser.new(@base_class, child_class, dsl_names, &block)
106
+ ForDSLParser.new(@base_class, child_class, dsl_names, on_current_class, on_ancestor_class, &block)
107
+ end
108
+
109
+ # this is a wrapper for the `for_dsl` method, but it provides a value of true
110
+ # for the `on_ancestor_class` argument and a value of false for the `on_current_class`
111
+ # argument. This will cause the parser to only yeild for dsls which were used on
112
+ # a class which is in the current classes ancestry, but not on the current class
113
+ def for_inherited_dsl dsl_names, &block
114
+ for_dsl dsl_names, on_current_class: false, on_ancestor_class: true, &block
115
+ end
116
+
117
+ # this is a wrapper for the `for_dsl` method, but it provides a value of true
118
+ # for the `on_ancestor_class` argument and a value of true for the `on_current_class`
119
+ # argument. This will cause the parser to yeild for dsls which were used on either
120
+ # the current class or any class in its ancestry
121
+ def for_dsl_or_inherited_dsl dsl_names, &block
122
+ for_dsl dsl_names, on_current_class: true, on_ancestor_class: true, &block
79
123
  end
80
124
  end
81
125
  end
@@ -24,30 +24,44 @@ module DSLCompose
24
24
  raise NotInitializable
25
25
  end
26
26
 
27
- # the first step in defining a parser is to set the base_class, this method
27
+ # The first step in defining a parser is to set the base_class, this method
28
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
29
+ # provided that the child_class uses at least one of the base_classes defined DSLs.
30
+ # If `final_children_only` is true, then this will cause the parser to only return
31
+ # classes which are at the end of their class hierachy (meaning they dont have any
32
+ # children of their own)
33
+ def self.for_children_of base_class, final_children_only: false, rerun: false, &block
31
34
  unless rerun
32
35
  @runs ||= []
33
36
  @runs << {
34
37
  base_class: base_class,
38
+ final_children_only: final_children_only,
35
39
  block: block
36
40
  }
37
41
  end
38
42
 
39
43
  # we parse the provided block in the context of the ForChildrenOfParser class
40
44
  # to help make this code more readable, and to limit polluting the current namespace
41
- ForChildrenOfParser.new(base_class, &block)
45
+ ForChildrenOfParser.new(base_class, final_children_only, &block)
46
+ end
47
+
48
+ # this is a wrapper for the `for_children_of` method, but it provides a value
49
+ # of true for the `final_children_only` argument. This will cause the parser to only
50
+ # return classes which are at the end of the class hierachy (meaning they dont have
51
+ # any children of their own)
52
+ def self.for_final_children_of base_class, &block
53
+ for_children_of base_class, final_children_only: true, &block
42
54
  end
43
55
 
44
56
  # this method is used to rerun the parser, this is most useful from within a test suite
45
57
  # when you are testing the parser itself
46
58
  def self.rerun
59
+ # rerun each parset tests
47
60
  @runs&.each do |run|
48
61
  base_class = run[:base_class]
49
62
  block = run[:block]
50
- for_children_of base_class, true, &block
63
+ final_children_only = run[:final_children_only]
64
+ for_children_of base_class, rerun: true, final_children_only: final_children_only, &block
51
65
  end
52
66
  end
53
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSLCompose
4
- VERSION = "1.11.0"
4
+ VERSION = "1.13.0"
5
5
  end
data/lib/dsl_compose.rb CHANGED
@@ -36,6 +36,7 @@ require "dsl_compose/interpreter"
36
36
 
37
37
  require "dsl_compose/parser/for_children_of_parser/for_dsl_parser/for_method_parser"
38
38
  require "dsl_compose/parser/for_children_of_parser/for_dsl_parser"
39
+ require "dsl_compose/parser/for_children_of_parser/descendents"
39
40
  require "dsl_compose/parser/for_children_of_parser"
40
41
  require "dsl_compose/parser/block_arguments"
41
42
  require "dsl_compose/parser"
@@ -49,6 +50,4 @@ require "dsl_compose/shared_configuration"
49
50
  require "dsl_compose/dsls"
50
51
 
51
52
  module DSLCompose
52
- class Error < StandardError
53
- end
54
53
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dsl_compose
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.11.0
4
+ version: 1.13.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-07-12 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-07-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: class_spec_helper
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Ruby gem to add dynamic DSLs to classes. DSLs are added to classes by
14
28
  including the DSLCompose module, and then calling the add_dsl singleton method within
15
29
  the class or a child class.
@@ -56,6 +70,7 @@ files:
56
70
  - lib/dsl_compose/parser.rb
57
71
  - lib/dsl_compose/parser/block_arguments.rb
58
72
  - lib/dsl_compose/parser/for_children_of_parser.rb
73
+ - lib/dsl_compose/parser/for_children_of_parser/descendents.rb
59
74
  - lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser.rb
60
75
  - lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser/for_method_parser.rb
61
76
  - lib/dsl_compose/shared_configuration.rb