dsl_compose 1.11.0 → 1.13.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: 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