eco-helpers 3.0.27 → 3.0.29

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +52 -1
  3. data/eco-helpers.gemspec +1 -0
  4. data/lib/eco/api/common/class_helpers.rb +1 -136
  5. data/lib/eco/api/common/loaders/config/cli.rb +1 -1
  6. data/lib/eco/api/common/loaders/config/session.rb +1 -1
  7. data/lib/eco/api/common/loaders/config/workflow.rb +1 -1
  8. data/lib/eco/api/common/loaders/config.rb +2 -5
  9. data/lib/eco/api/common/loaders/use_case/target_model.rb +1 -1
  10. data/lib/eco/api/common/loaders/use_case/type.rb +1 -1
  11. data/lib/eco/api/common/people/supervisor_helpers.rb +3 -1
  12. data/lib/eco/api/common/session/helpers/prompt_user.rb +3 -1
  13. data/lib/eco/api/common/session/logger/channels.rb +1 -0
  14. data/lib/eco/api/common/session/sftp.rb +9 -2
  15. data/lib/eco/api/session/batch/job/sets.rb +1 -0
  16. data/lib/eco/api/session/batch/job/type.rb +1 -0
  17. data/lib/eco/api/session/batch/launcher/valid_methods.rb +3 -2
  18. data/lib/eco/api/usecases/base_case/model.rb +2 -1
  19. data/lib/eco/api/usecases/base_case/type.rb +2 -1
  20. data/lib/eco/api/usecases/base_io/validations.rb +2 -1
  21. data/lib/eco/api/usecases/graphql/helpers/location/command/diffs/stages/commandable.rb +3 -2
  22. data/lib/eco/api/usecases/lib/files/file_pattern.rb +0 -2
  23. data/lib/eco/api/usecases/lib/files/sftp.rb +9 -6
  24. data/lib/eco/api/usecases/samples/drivers/sftp_sample.rb +3 -1
  25. data/lib/eco/api/usecases/service/sftp.rb +36 -0
  26. data/lib/eco/api/usecases/service.rb +15 -0
  27. data/lib/eco/api/usecases.rb +1 -0
  28. data/lib/eco/data/files/helpers.rb +4 -2
  29. data/lib/eco/data/fuzzy_match.rb +4 -2
  30. data/lib/eco/data/hashes/diff_result/meta.rb +2 -1
  31. data/lib/eco/data/locations/node_diff/accessors.rb +2 -1
  32. data/lib/eco/language/delegation/chainable_delegator.rb +18 -0
  33. data/lib/eco/language/delegation/delegating_blank.rb +162 -0
  34. data/lib/eco/language/delegation/delegating_missing.rb +104 -0
  35. data/lib/eco/language/delegation/delegating_missing_const.rb +53 -0
  36. data/lib/eco/language/delegation/delegating_missing_on_class.rb +53 -0
  37. data/lib/eco/language/delegation/{const_delegator.rb → for_delegator/const_delegator.rb} +8 -6
  38. data/lib/eco/language/delegation/{const_lookup_hooks.rb → for_delegator/const_lookup_hooks.rb} +37 -19
  39. data/lib/eco/language/delegation/{delegated_class.rb → for_delegator/delegated_class.rb} +6 -19
  40. data/lib/eco/language/delegation/for_delegator.rb +11 -0
  41. data/lib/eco/language/delegation.rb +6 -3
  42. data/lib/eco/language/klass/builder.rb +29 -0
  43. data/lib/eco/language/klass/helpers_built.rb +9 -0
  44. data/lib/eco/language/klass/hierarchy.rb +34 -0
  45. data/lib/eco/language/klass/inheritable_class_vars.rb +45 -0
  46. data/lib/eco/language/klass/naming.rb +21 -0
  47. data/lib/eco/language/klass/resolver.rb +30 -0
  48. data/lib/eco/language/klass/when_inherited.rb +10 -13
  49. data/lib/eco/language/klass.rb +6 -0
  50. data/lib/eco/language/methods.rb +0 -1
  51. data/lib/eco/language/strings/underscore.rb +17 -0
  52. data/lib/eco/language/strings.rb +8 -0
  53. data/lib/eco/language.rb +1 -0
  54. data/lib/eco/version.rb +1 -1
  55. metadata +35 -6
  56. data/lib/eco/language/methods/delegate_missing.rb +0 -30
@@ -0,0 +1,162 @@
1
+ # rubocop:disable Naming/MethodParameterName
2
+
3
+ module Eco::Language::Delegation
4
+ # Installs a middleware for each delegated entity
5
+ # @note It adds an implicit delegate missing to the target,
6
+ # unless it has already been defined.
7
+ module DelegatingBlank
8
+ class << self
9
+ def included(base)
10
+ super
11
+
12
+ base.include DelegatingMissing
13
+ base.extend ClassMethods
14
+ base.inheritable_class_vars :_delegating_blank
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ include Eco::Language::Strings::Underscore
20
+
21
+ def delegating_blank(*these, to:, safe: false)
22
+ msg = "Delegating to: should be String or Symbol. Given: #{to.class}"
23
+ raise ArgumentError, msg unless sym_or_string?(to)
24
+
25
+ these.map do |this|
26
+ msg = "Expecting String or Symbol. Given: #{this}."
27
+ raise ArgumentError, msg unless sym_or_string?(this)
28
+
29
+ this.to_sym
30
+ end.each do |this|
31
+ delegating_missing(this, to: to) unless delegating_missing?(this)
32
+ install_blank_delegator(at: this, safe: safe)
33
+
34
+ _delegating_blank[this] = to
35
+ _safe_blank_delegation[this] = safe
36
+ end
37
+
38
+ self
39
+ end
40
+
41
+ def delegating_blank?(sym)
42
+ return false unless sym_or_string?(sym)
43
+ return true if _delegating_blank.key?(sym.to_sym)
44
+
45
+ false
46
+ end
47
+
48
+ def safe_blank_delegation?(sym)
49
+ return false unless sym_or_string?(sym)
50
+ return false unless _safe_blank_delegation.key?(sym.to_sym)
51
+
52
+ _safe_blank_delegation[sym.to_sym]
53
+ end
54
+
55
+ def delegating_blank_target(instance, at: nil)
56
+ return unless delegating_blank?(at)
57
+ return unless (subject_ref = _delegating_blank[at])
58
+
59
+ instance_has_method =
60
+ instance.methods.include?(subject_ref) ||
61
+ instance.private_methods.include?(subject_ref)
62
+
63
+ return instance.method(subject_ref).call if instance_has_method
64
+ return unless at.to_s.start_with?('@')
65
+ return unless instance.instance_variable_defined?(at)
66
+
67
+ instance.instance_variable_get(at)
68
+ end
69
+
70
+ private
71
+
72
+ def method_added(method_name)
73
+ return super if (@installing ||= false)
74
+
75
+ method_name = method_name.to_sym
76
+ return super unless delegating_blank?(method_name)
77
+
78
+ @installing = true
79
+ install_blank_delegator(at: method_name)
80
+ @installing = false
81
+ super
82
+ end
83
+
84
+ def _delegating_blank
85
+ @_delegating_blank ||= {}
86
+ end
87
+
88
+ def _safe_blank_delegation
89
+ @_safe_blank_delegation ||= {}
90
+ end
91
+
92
+ # If `base` does NOT have the method, don't install the middleware.
93
+ def install_blank_delegator(base = self, at:, safe: safe_blank_delegation?(at))
94
+ method_name = underscore(at).gsub('@', '').to_sym
95
+ install_safe = safe
96
+
97
+ return unless instance_method?(base, name: method_name, include_private: true)
98
+
99
+ method_was_inherited = inherited_method?(base, name: method_name)
100
+ original_method = :"delegated_blank_original_#{method_name}"
101
+
102
+ return if instance_method?(base, name: original_method, include_private: true, inherited: false)
103
+
104
+ base.alias_method(original_method, method_name) unless method_was_inherited
105
+
106
+ method_def = proc do |*args, **kargs, &block|
107
+ if method_was_inherited
108
+ super(*args, **kargs, &block)
109
+ else
110
+ send(original_method, *args, **kargs, &block)
111
+ end.then do |value|
112
+ next value unless blank?(value)
113
+
114
+ target = self.class.delegating_blank_target(self, at: at)
115
+
116
+ if at.to_s.start_with?('@')
117
+ target.instance_variable_get(at)
118
+ elsif !install_safe || target.respond_to?(method_name, true)
119
+ target.send(method_name, *args, **kargs, &block)
120
+ end
121
+ end
122
+ end
123
+
124
+ base.define_method(method_name, &method_def)
125
+ end
126
+
127
+ def instance_method?(base = self, name:, include_private: false, inherited: true)
128
+ return true if base.method_defined?(name, inherited)
129
+ return false unless include_private
130
+
131
+ base.private_instance_methods(inherited).include?(name.to_sym)
132
+ end
133
+
134
+ def inherited_method?(base = self, name:)
135
+ return false if base.public_instance_methods(false).include?(name.to_sym)
136
+ return false if base.private_instance_methods(false).include?(name.to_sym)
137
+
138
+ true
139
+ end
140
+
141
+ def sym_or_string?(value)
142
+ return false unless value.is_a?(Symbol) || value.is_a?(String)
143
+ return false if value.to_s.strip.empty?
144
+
145
+ true
146
+ end
147
+ end
148
+
149
+ # INSTANCE Methods
150
+
151
+ def blank?(value)
152
+ return super if defined?(super)
153
+ return true if value.nil?
154
+ return true if value.to_s.strip.empty?
155
+ return true if value.respond_to?(:empty?) && value.empty?
156
+
157
+ false
158
+ end
159
+ end
160
+ end
161
+
162
+ # rubocop:enable Naming/MethodParameterName
@@ -0,0 +1,104 @@
1
+ # rubocop:disable Naming/MethodParameterName
2
+
3
+ module Eco::Language::Delegation
4
+ module DelegatingMissing
5
+ class << self
6
+ def included(base)
7
+ super
8
+
9
+ base.extend Eco::Language::Klass::HelpersBuilt
10
+ base.extend ClassMethods
11
+ base.inheritable_class_vars :_delegating_missing, :delegating_missing_to
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def delegating_missing(*these, to:)
17
+ msg = "Delegating to: should be String or Symbol. Given: #{to.class}"
18
+ raise ArgumentError, msg unless sym_or_string?(to)
19
+
20
+ these.map do |this|
21
+ msg = "Expecting String or Symbol. Given: #{this}."
22
+ raise ArgumentError, msg unless sym_or_string?(this)
23
+
24
+ this.to_sym
25
+ end.each do |this|
26
+ _delegating_missing[this] = to
27
+ end
28
+
29
+ self
30
+ end
31
+
32
+ def delegating_missing_to(to = nil)
33
+ return @delegating_missing_to if to.nil?
34
+
35
+ msg = "Expecting String or Symbol. Given: #{to}."
36
+ raise ArgumentError, msg unless sym_or_string?(to)
37
+
38
+
39
+ @delegating_missing_to = to.to_sym
40
+ end
41
+
42
+ def delegating_missing?(sym)
43
+ return true if sym.nil? && delegating_missing_to
44
+ return false unless sym_or_string?(sym)
45
+ return true if _delegating_missing.key?(sym.to_sym)
46
+ return true if delegating_missing_to
47
+
48
+ false
49
+ end
50
+
51
+ def delegating_missing_target(instance, at: nil)
52
+ return unless delegating_missing?(at)
53
+ return unless (subject_ref = _delegating_missing[at] || delegating_missing_to)
54
+
55
+ instance_has_method =
56
+ instance.methods.include?(subject_ref) ||
57
+ instance.private_methods.include?(subject_ref)
58
+
59
+ return instance.method(subject_ref).call if instance_has_method
60
+ return unless at.to_s.start_with?('@')
61
+ return unless instance.instance_variable_defined?(at)
62
+
63
+ instance.instance_variable_get(at)
64
+ end
65
+
66
+ private
67
+
68
+ def _delegating_missing
69
+ @_delegating_missing ||= {}
70
+ end
71
+
72
+ def sym_or_string?(value)
73
+ return false unless value.is_a?(Symbol) || value.is_a?(String)
74
+ return false if value.to_s.strip.empty?
75
+
76
+ true
77
+ end
78
+ end
79
+
80
+ # INSTANCE Methods
81
+
82
+ def respond_to_missing?(method_name, include_private = false)
83
+ super
84
+ end
85
+
86
+ private
87
+
88
+ # @note if missing, redirect all parameters to `receiver`,
89
+ # as long as responds_to `method_name`
90
+ def method_missing(method_name, *args, **kargs, &block)
91
+ return super unless self.class.delegating_missing?(method_name)
92
+
93
+ target = self.class.delegating_missing_target(self, at: method_name)
94
+
95
+ if method_name.to_s.start_with?('@')
96
+ target.instance_variable_get(method_name)
97
+ else
98
+ target.send(method_name, *args, **kargs, &block)
99
+ end
100
+ end
101
+ end
102
+ end
103
+
104
+ # rubocop:enable Naming/MethodParameterName
@@ -0,0 +1,53 @@
1
+ module Eco::Language::Delegation
2
+ # Class to lookup constants through `delegated_class`.
3
+ # @note it uses `DelegatingMissing`.
4
+ # @note it tries to lookup on the `class` of the `delegating_missing_to`
5
+ # object. Which requires an instance of the delegator to exist, with
6
+ # the aim of scoping its class (so it can chain constant lookup), unless
7
+ # `delegated_class` is explicitly stated.
8
+ # @note that not setting `delegated_class` explicitly could fail
9
+ # to chain the looup.
10
+ module DelegatingMissingConst
11
+ class << self
12
+ def included(base)
13
+ super
14
+
15
+ base.send :include, DelegatingMissing
16
+ base.send :include, ForDelegator::ConstDelegator
17
+ base.extend ClassMethods
18
+
19
+ # because we inject `#initialize`
20
+ base.send :prepend, InstanceMethods
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+ # Set always `delegated_class` when fetching the
26
+ # `delegating_missing_to` object.
27
+ def delegating_missing_target(instance, at: nil)
28
+ super.tap do |target|
29
+ next unless target
30
+ next unless at.nil?
31
+
32
+ delegated_class target.class
33
+ end
34
+ end
35
+ end
36
+
37
+ module InstanceMethods
38
+ # Set the `delegated_class` to chain **constant lookup**
39
+ # via `delegating_missing_to.class`.
40
+ # @note this requires that the target can be fetched without passing
41
+ # parameters (as it is typically the case with `delegating_missing_to`).
42
+ def initialize(...)
43
+ super if defined?(super)
44
+
45
+ delegated_class.tap do |klass|
46
+ next unless (target = self.class.delegating_missing_target(self))
47
+
48
+ self.class.delegated_class target.class
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,53 @@
1
+ module Eco::Language::Delegation
2
+ module DelegatingMissingOnClass
3
+ class << self
4
+ def included(base)
5
+ super
6
+
7
+ base.extend Eco::Language::Klass::HelpersBuilt
8
+ base.extend ClassMethods
9
+ base.inheritable_class_vars :delegating_missing_on_class_to
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ def delegating_missing_on_class_to(to = nil)
15
+ return @delegating_missing_on_class_to if to.nil?
16
+
17
+ msg = "Expecting String or Symbol. Given: #{to}."
18
+ raise ArgumentError, msg unless sym_or_string?(to)
19
+
20
+ @delegating_missing_on_class_to = to
21
+ end
22
+
23
+ def respond_to_missing?(method_name, include_private = false)
24
+ super
25
+ end
26
+
27
+ private
28
+
29
+ # @note if missing, redirect all parameters to `target`,
30
+ # as long as responds_to `method_name`
31
+ def method_missing(method_name, *args, **kargs, &block)
32
+ return super unless (target = delegating_missing_on_class_target)
33
+
34
+ target.send(method_name, *args, **kargs, &block)
35
+ end
36
+
37
+ # retrieves the delegating_missing_on_class_to object
38
+ def delegating_missing_on_class_target
39
+ return unless @delegating_missing_on_class_to
40
+
41
+ # fetch the object behind the method
42
+ method(@delegating_missing_on_class_to).call
43
+ end
44
+
45
+ def sym_or_string?(value)
46
+ return false unless value.is_a?(Symbol) || value.is_a?(String)
47
+ return false if value.to_s.strip.empty?
48
+
49
+ true
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,7 +1,9 @@
1
- module Eco::Language::Delegation
1
+ module Eco::Language::Delegation::ForDelegator
2
2
  # Module that is to be used with ruby `Delegator` or related helpers,
3
3
  # such as `SimpleDelegator` or `Forwardable`.
4
- # @note it includes `DelegatedClass`
4
+ # @note
5
+ # 1. It includes `DelegatedClass`
6
+ # 2. This class only provides lookups integration.
5
7
  # @note it looks up the constant via `delegated_class`. This is because
6
8
  # on `Delegator` approaches, the intance object (`self`) points to the
7
9
  # delegated object, which doesn't allow to refer to `self.class` to
@@ -15,13 +17,13 @@ module Eco::Language::Delegation
15
17
  def included(base)
16
18
  super
17
19
 
18
- base.send :include, Eco::Language::DelegatedClass
19
- base.extend(ClassMethods)
20
+ base.send :include, DelegatedClass
21
+ base.extend ClassMethods
20
22
  end
21
23
  end
22
24
 
23
25
  module ClassMethods
24
- def const_defined?(str, inherit = true, delegated: true)
26
+ def const_defined?(str, inherit = true, delegated: true) # rubocop:disable Style/OptionalBooleanParameter
25
27
  return super(str, inherit) if super(str, inherit) || !delegated
26
28
  return true if delegated_class&.const_defined?(str, inherit)
27
29
 
@@ -41,7 +43,7 @@ module Eco::Language::Delegation
41
43
  const_get(str)
42
44
  end
43
45
 
44
- def constants(inherited = true, delegated: true)
46
+ def constants(inherited = true, delegated: true) # rubocop:disable Style/OptionalBooleanParameter
45
47
  super(inherited).dup.tap do |consts|
46
48
  next unless delegated && delegated_class
47
49
 
@@ -1,6 +1,8 @@
1
- module Eco::Language::Delegation
1
+ module Eco::Language::Delegation::ForDelegator
2
2
  # This approach only makes sense to be used in `Delegator` and related helpers.
3
- # @note it includes `ConstDelegator` (which includes `DelegatedClass`)
3
+ # @note
4
+ # 1. It includes `ConstDelegator` (which includes `DelegatedClass`).
5
+ # 2. This class installs new instance methods.
4
6
  # @note for each not inherited constant that isn't part of the delegated object's
5
7
  # class, it will create a undercore/snake named instance method, to ease
6
8
  # constant lookup througout chained/wrapped delegators.
@@ -11,22 +13,49 @@ module Eco::Language::Delegation
11
13
  def included(base)
12
14
  super
13
15
 
14
- base.send :include, Eco::Language::Delegation::ConstDelegator
16
+ msg = 'Only to be included in Delegator subclasses (i.e. SimpleDelegator). '
17
+ msg << 'As this concern is just a work around to not being possible to fetch '
18
+ msg << 'the Decorator class, because self.class refers to the class of the '
19
+ msg << 'decorated object. Meaning that it is not necessary on other patterns.'
20
+ raise ArgumentError, msg unless base <= Delegator
15
21
 
16
- base.extend(ClassMethods)
22
+ base.send :include, ConstDelegator
23
+
24
+ base.extend ClassMethods
17
25
  base.send :install_constant_lookup_hooks
18
26
  end
19
27
  end
20
28
 
21
29
  module ClassMethods
30
+ include Eco::Language::Strings::Underscore
31
+
32
+ # @note
33
+ # - We don't install const hook methods when `delegated_class` is (re)defined,
34
+ # because these hooks aim only the delegator (not the delegated).
35
+ # @todo **However**It is still to be seen if we should include this concern
36
+ # to the `delegated_class`, provided that const lookup methods are aligned
37
+ # (as it is a requirement).
38
+ # When `delegated_class` is **defined**, install const hook methods too it.
39
+ # def delegated_class(klass = nil)
40
+ # super.tap do
41
+ # next if klass.nil?
42
+ # next if klass < ConstLookupHooks
43
+
44
+ # klass.send :include, ConstLookupHooks
45
+ # end
46
+ # end
47
+
22
48
  private
23
49
 
50
+ # When **inherited**, install const hook methods to the `subclass`.
24
51
  def inherited(subclass)
25
52
  super
26
53
 
27
54
  subclass.send :install_constant_lookup_hooks
28
55
  end
29
56
 
57
+ # When a constant is **added** install const hook methods to it.
58
+ # to the `subclass`.
30
59
  def const_added(sym)
31
60
  install_constant_lookup_hook(const: sym)
32
61
  end
@@ -45,6 +74,7 @@ module Eco::Language::Delegation
45
74
  end
46
75
  end
47
76
 
77
+ # If `base` does NOT have the constant hook method, it defines it.
48
78
  def install_constant_lookup_hook(base = self, const:)
49
79
  meth_name = underscore(const).to_sym
50
80
  return if instance_method?(base, name: meth_name, include_private: true)
@@ -58,23 +88,11 @@ module Eco::Language::Delegation
58
88
  base.define_method(meth_name, &method_def)
59
89
  end
60
90
 
61
- # @see https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-underscore
62
- def underscore(sym)
63
- return super if defined?(super)
64
- return sym.to_s.dup unless /[A-Z-]|::/.match?(sym)
65
-
66
- word = sym.to_s.gsub('::', '/')
67
- word.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, '_')
68
- word.tr!('-', '_')
69
- word.downcase!
70
- word
71
- end
72
-
73
- def instance_method?(base = self, name:, include_private: false)
74
- return true if base.method_defined?(name)
91
+ def instance_method?(base = self, name:, include_private: false, inherited: true)
92
+ return true if base.method_defined?(name, inherited)
75
93
  return false unless include_private
76
94
 
77
- base.private_instance_methods.include?(name.to_sym)
95
+ base.private_instance_methods(inherited).include?(name.to_sym)
78
96
  end
79
97
  end
80
98
  end
@@ -1,4 +1,4 @@
1
- module Eco::Language::Delegation
1
+ module Eco::Language::Delegation::ForDelegator
2
2
  module DelegatedClass
3
3
  class << self
4
4
  private
@@ -6,7 +6,11 @@ module Eco::Language::Delegation
6
6
  def included(base)
7
7
  super
8
8
 
9
- base.extend(ClassMethods)
9
+ base.extend Eco::Language::Klass::HelpersBuilt
10
+ base.extend ClassMethods
11
+
12
+ base.inheritable_class_vars :delegated_class
13
+
10
14
  base.send :patch_class_compare
11
15
  end
12
16
  end
@@ -20,12 +24,6 @@ module Eco::Language::Delegation
20
24
 
21
25
  private
22
26
 
23
- def inherited(subclass)
24
- super
25
-
26
- subclass.instance_variable_set(:@delegated_class, @delegated_class)
27
- end
28
-
29
27
  def patch_class_compare(base = self)
30
28
  %i[< <= ==].each do |op|
31
29
  base.singleton_class.define_method(op) do |other|
@@ -40,17 +38,6 @@ module Eco::Language::Delegation
40
38
 
41
39
  # Instance methods
42
40
 
43
- # It allows to chain delegators, when the first parameter is an
44
- # instance of `delegated_class`.
45
- # @note it also allows to create an instance of `delegated_class`
46
- # on initialization.
47
- def initialize(*args, **kargs, &block)
48
- obj = args.first
49
- obj = new(*args, **kargs, &block) unless of_kind?(obj)
50
-
51
- super(obj)
52
- end
53
-
54
41
  def delegated_class
55
42
  self.class.delegated_class
56
43
  end
@@ -0,0 +1,11 @@
1
+ require 'delegate'
2
+
3
+ module Eco::Language::Delegation
4
+ # Helpers to be included on native `Delegator` subclasses.
5
+ module ForDelegator
6
+ end
7
+ end
8
+
9
+ require_relative 'for_delegator/delegated_class'
10
+ require_relative 'for_delegator/const_delegator'
11
+ require_relative 'for_delegator/const_lookup_hooks'
@@ -3,6 +3,9 @@ module Eco::Language
3
3
  end
4
4
  end
5
5
 
6
- require_relative 'delegation/delegated_class'
7
- require_relative 'delegation/const_delegator'
8
- require_relative 'delegation/const_lookup_hooks'
6
+ require_relative 'delegation/for_delegator'
7
+ require_relative 'delegation/chainable_delegator'
8
+ require_relative 'delegation/delegating_missing_on_class'
9
+ require_relative 'delegation/delegating_missing'
10
+ require_relative 'delegation/delegating_missing_const'
11
+ require_relative 'delegation/delegating_blank'
@@ -0,0 +1,29 @@
1
+ module Eco::Language::Klass
2
+ module Builder
3
+ include Resolver
4
+ include Naming
5
+
6
+ # If the class for `name` exists, it returns it. Otherwise it generates it.
7
+ # @param name [String, Symbol] the name of the new class
8
+ # @param inherits [Class] the parent class to _inherit_ from
9
+ # @param parent_space [String] parent namespace of the generated class, if not given: `self`
10
+ # @yield [child_class] configure the new class
11
+ # @yieldparam child_class [Class] the new class
12
+ # @return [Class] the new generated class
13
+ def new_class(name, inherits:, parent_space: nil)
14
+ name = name.to_sym.freeze
15
+ class_name = to_constant(name)
16
+ parent_space = parent_space ? resolve_class(parent_space) : self
17
+ full_class_name = "#{parent_space}::#{class_name}"
18
+
19
+ unless (target_class = resolve_class(full_class_name, exception: false))
20
+ target_class = Class.new(inherits)
21
+ parent_space.const_set class_name, target_class
22
+ end
23
+
24
+ target_class.tap do |klass|
25
+ yield(klass) if block_given?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ module Eco::Language::Klass
2
+ module HelpersBuilt
3
+ include Resolver
4
+ include Naming
5
+ include Builder
6
+ include Hierarchy
7
+ include InheritableClassVars
8
+ end
9
+ end
@@ -0,0 +1,34 @@
1
+ module Eco::Language::Klass
2
+ module Hierarchy
3
+ # Finds all child classes of the current class.
4
+ # @param parent_class [Class] the parent class we want to find children of.
5
+ # @param direct [Boolean] it will only include direct child classes.
6
+ # @param scope [nil, Array] to only look for descendants among the ones in `scope`.
7
+ # @return [Arrary<Class>] the child classes in hierarchy order.
8
+ def descendants(parent_class: self, direct: false, scope: nil)
9
+ scope ||= ObjectSpace.each_object(::Class)
10
+ return [] if scope.empty?
11
+
12
+ scope.select do |klass|
13
+ klass < parent_class
14
+ end.sort do |k_1, k_2|
15
+ next -1 if k_2 < k_1
16
+ next 1 if k_1 < k_2
17
+ 0
18
+ end.tap do |siblings|
19
+ next unless direct
20
+
21
+ siblings.reject! do |si|
22
+ siblings.any? {|s| si < s}
23
+ end
24
+ end
25
+ end
26
+
27
+ # @param parent_class [Class] the parent class we want to find children of.
28
+ # @param direct [Boolean] it will only include direct child classes.
29
+ # @return [Boolean] `true` if the current class has child classes, and `false` otherwise.
30
+ def descendants?(parent_class: self, direct: false)
31
+ descendants(parent_class: parent_class, direct: direct).length.positive?
32
+ end
33
+ end
34
+ end