dsl_compose 1.12.0 → 1.13.1

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: a22a3595bbf6990c27628cf872f751939156cc9cda865d6ae05ca9cd6e6ba544
4
- data.tar.gz: 79fcd4ddb46ecc10cc1dd84f50515c4bb85067a0b26482d7a6250112b4d44fc8
3
+ metadata.gz: 9ca102e9e28dc1732f0a6c14282bdfc6b0cbc3edddc8b76864921c8d5a295ec0
4
+ data.tar.gz: 349941451a9954f29938fb91b0324662b306d215ce725e68faa00d25937b883d
5
5
  SHA512:
6
- metadata.gz: 4cc5bf277a33d63d0dba6aa3053bfec1b5154d7f337c7e0e6fd0db2e5f429718e12097b640069904e44d95d02d2d576604d571cc45c96687ab8316064b4de3a7
7
- data.tar.gz: 52a29affc55dd62fe4abbc965dc22973c1a01ba7acc170e33e8f7191ddf1ab19e4456f288cf69864282bea49afa8ac445ce1c1316053c07a6b4f2ef0ef1ba10a
6
+ metadata.gz: '002348ac24e6f8bf513478702c6c08368fb1ee8c788ad8a893d7a848e41103ee6208e5e24f260e0be79eb069700047d408b0c51e51ea3b861bccf55fe2312882'
7
+ data.tar.gz: 14431a1691709376eb73196c5c7280a7d0ec451d4eb58a3e8ffa3bc43bb6cf26f4433b8f4d6fb488db57d9be06bbcf4f974e5175719d44766a1247271ea76e34
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.13.1](https://github.com/craigulliott/dsl_compose/compare/v1.13.0...v1.13.1) (2023-07-19)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * unused optional arguments no longer raise an error within the parser ([#37](https://github.com/craigulliott/dsl_compose/issues/37)) ([99cd04b](https://github.com/craigulliott/dsl_compose/commit/99cd04b83387c7940e364dc86d527fdb51152405))
9
+
10
+ ## [1.13.0](https://github.com/craigulliott/dsl_compose/compare/v1.12.0...v1.13.0) (2023-07-17)
11
+
12
+
13
+ ### Features
14
+
15
+ * 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))
16
+
3
17
  ## [1.12.0](https://github.com/craigulliott/dsl_compose/compare/v1.11.0...v1.12.0) (2023-07-13)
4
18
 
5
19
 
data/README.md CHANGED
@@ -241,13 +241,23 @@ 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
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`
245
249
  for_children_of SomeBaseClass do |child_class:|
246
- # `for_dsl` accepts a DSL name or an array of DSL names and will yield
247
- # it's provided block once for each time a DSL of that name has been
248
- # 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.
249
253
  #
250
- # 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.
251
261
  #
252
262
  # You can optionally provide keyword arguments which correspond to any
253
263
  # arguments that were defined for the DSL, if multiple dsl names are provided
@@ -44,6 +44,13 @@ module DSLCompose
44
44
  raise TooManyArgumentsError, "Too many arguments provided"
45
45
  end
46
46
 
47
+ # assume all optonal arguments are nil. If actual values were provided, then they will be set below
48
+ if arguments.optional_arguments.any?
49
+ arguments.optional_arguments.each do |optional_argument|
50
+ @arguments[optional_argument.name] = nil
51
+ end
52
+ end
53
+
47
54
  # asset that, if provided, then the optional argument (always the last one) is a Hash
48
55
  if has_optional_arguments && optional_arg.nil? === false
49
56
  unless optional_arg.is_a? Hash
@@ -42,8 +42,8 @@ module DSLCompose
42
42
 
43
43
  # Returns an array of all executions for a given name and class. This includes
44
44
  # any ancestors of the provided class
45
- def class_dsl_executions klass, dsl_name
46
- @executions.filter { |e| e.dsl.name == dsl_name && (e.klass == klass || klass < e.klass) }
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)) }
47
47
  end
48
48
 
49
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
@@ -35,7 +35,7 @@ module DSLCompose
35
35
  # parser.for_children_of BaseClass do |child_class:|
36
36
  # # this will yield for ChildClass and GrandchildClass
37
37
  # and
38
- def initialize base_class, &block
38
+ def initialize base_class, final_children_only, &block
39
39
  # assert the provided class has the DSLCompose::Composer module installed
40
40
  unless base_class.respond_to? :dsls
41
41
  raise ClassDoesNotUseDSLComposeError, base_class
@@ -49,7 +49,7 @@ module DSLCompose
49
49
  end
50
50
 
51
51
  # if any arguments were provided, then assert that they are valid
52
- if block.parameters.any?
52
+ if block&.parameters&.any?
53
53
  # all parameters must be keyword arguments
54
54
  if block.parameters.filter { |p| p.first != :keyreq }.any?
55
55
  raise AllBlockParametersMustBeKeywordParametersError, "All block parameters must be keyword parameters, i.e. `for_children_of FooClass do |base_class:|`"
@@ -57,14 +57,17 @@ module DSLCompose
57
57
  end
58
58
 
59
59
  # yeild the block for all descendents of the provided base_class
60
- ObjectSpace.each_object(Class).select { |klass| klass < base_class }.each do |child_class|
60
+ Descendents.new(base_class, final_children_only).classes.each do |child_class|
61
+ # determine which arguments to send to the block
61
62
  args = {}
62
63
  if BlockArguments.accepts_argument?(:child_class, &block)
63
64
  args[:child_class] = child_class
64
65
  end
66
+
65
67
  # set the child_class in an instance variable so that method calls to
66
68
  # `for_dsl` from within the block will have access to it
67
69
  @child_class = child_class
70
+
68
71
  # yield the block in the context of this class
69
72
  instance_exec(**args, &block)
70
73
  end
@@ -85,14 +88,38 @@ module DSLCompose
85
88
  # ...
86
89
  # end
87
90
  # end
88
- 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
89
100
  child_class = @child_class
90
101
 
91
102
  unless child_class
92
103
  raise NoChildClassError, "No child_class was found, please call this method from within a `for_children_of` block"
93
104
  end
94
105
 
95
- 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
96
123
  end
97
124
  end
98
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.12.0"
4
+ VERSION = "1.13.1"
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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dsl_compose
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.0
4
+ version: 1.13.1
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-13 00:00:00.000000000 Z
11
+ date: 2023-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: class_spec_helper
@@ -70,6 +70,7 @@ files:
70
70
  - lib/dsl_compose/parser.rb
71
71
  - lib/dsl_compose/parser/block_arguments.rb
72
72
  - lib/dsl_compose/parser/for_children_of_parser.rb
73
+ - lib/dsl_compose/parser/for_children_of_parser/descendents.rb
73
74
  - lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser.rb
74
75
  - lib/dsl_compose/parser/for_children_of_parser/for_dsl_parser/for_method_parser.rb
75
76
  - lib/dsl_compose/shared_configuration.rb