eco-helpers 3.0.37 → 3.1.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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -0
  3. data/CHANGELOG.md +116 -1
  4. data/lib/eco/api/common/loaders/base.rb +2 -2
  5. data/lib/eco/api/common/loaders/case_base.rb +2 -0
  6. data/lib/eco/api/common/loaders/config/block.rb +78 -0
  7. data/lib/eco/api/common/loaders/config/workflow/mailer.rb +39 -7
  8. data/lib/eco/api/common/loaders/config.rb +3 -26
  9. data/lib/eco/api/common/loaders/error_handler.rb +2 -0
  10. data/lib/eco/api/common/loaders/parser.rb +1 -4
  11. data/lib/eco/api/common/people/entries.rb +23 -6
  12. data/lib/eco/api/common/people/entry_factory.rb +1 -1
  13. data/lib/eco/api/common/people/person_entry.rb +104 -27
  14. data/lib/eco/api/common/people/person_parser.rb +50 -16
  15. data/lib/eco/api/common/people/supervisor_helpers.rb +12 -6
  16. data/lib/eco/api/common/session/base_session.rb +75 -81
  17. data/lib/eco/api/common/session/environment.rb +49 -55
  18. data/lib/eco/api/common/session/file_manager.rb +132 -135
  19. data/lib/eco/api/common/session/helpers/prompt_user.rb +23 -30
  20. data/lib/eco/api/common/session/helpers.rb +10 -15
  21. data/lib/eco/api/common/session/logger/cache.rb +89 -96
  22. data/lib/eco/api/common/session/logger/channels.rb +24 -32
  23. data/lib/eco/api/common/session/logger/log.rb +38 -46
  24. data/lib/eco/api/common/session/logger.rb +50 -54
  25. data/lib/eco/api/common/session/mailer/aws_provider.rb +63 -71
  26. data/lib/eco/api/common/session/mailer/provider_base.rb +43 -48
  27. data/lib/eco/api/common/session/mailer/sendgrid_provider.rb +101 -109
  28. data/lib/eco/api/common/session/mailer.rb +78 -83
  29. data/lib/eco/api/common/session/s3_uploader.rb +132 -138
  30. data/lib/eco/api/common/session/sftp.rb +202 -208
  31. data/lib/eco/api/common.rb +0 -3
  32. data/lib/eco/api/custom/mailer.rb +4 -2
  33. data/lib/eco/api/error/handlers.rb +1 -1
  34. data/lib/eco/api/microcases/people/apply_changes/set_core/core_excluded.rb +8 -2
  35. data/lib/eco/api/microcases/people/manage/search.rb +1 -1
  36. data/lib/eco/api/organization/people/similarity.rb +3 -3
  37. data/lib/eco/api/session/batch/base_policy.rb +42 -27
  38. data/lib/eco/api/session/batch/launcher/mode_size.rb +6 -1
  39. data/lib/eco/api/session/batch/launcher.rb +16 -3
  40. data/lib/eco/api/session/config/api.rb +4 -3
  41. data/lib/eco/api/session/config/apis/one_off.rb +1 -1
  42. data/lib/eco/api/session/config/files.rb +13 -12
  43. data/lib/eco/api/session/config/workflow.rb +1 -373
  44. data/lib/eco/api/session/config.rb +30 -9
  45. data/lib/eco/api/usecases/base_case/model.rb +6 -6
  46. data/lib/eco/api/usecases/base_case.rb +1 -1
  47. data/lib/eco/api/usecases/cli.rb +1 -1
  48. data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +8 -9
  49. data/lib/eco/api/usecases/default_cases/to_csv_case.rb +4 -1
  50. data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +2 -2
  51. data/lib/eco/api/usecases/lib/base/env.rb +21 -23
  52. data/lib/eco/api/usecases/lib/files/file_pattern.rb +41 -14
  53. data/lib/eco/api/usecases/lib/files/input_file.rb +110 -0
  54. data/lib/eco/api/usecases/lib/files/sftp.rb +5 -2
  55. data/lib/eco/api/usecases/lib/files.rb +1 -0
  56. data/lib/eco/api/usecases/lib/locations/base.rb +23 -0
  57. data/lib/eco/api/usecases/lib/locations/mapping.rb +94 -0
  58. data/lib/eco/api/usecases/lib/locations.rb +7 -0
  59. data/lib/eco/api/usecases/lib/people/base.rb +20 -0
  60. data/lib/eco/api/usecases/lib/people.rb +6 -0
  61. data/lib/eco/api/usecases/lib.rb +2 -0
  62. data/lib/eco/api/usecases/ooze_cases.rb +1 -1
  63. data/lib/eco/api/usecases/ooze_samples/register_export_case.rb +1 -0
  64. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +1 -0
  65. data/lib/eco/api/usecases/samples/drivers/sftp_sample.rb +2 -0
  66. data/lib/eco/api/usecases/samples/drivers/url_pull_sample.rb +8 -2
  67. data/lib/eco/api/usecases/service/sftp/with_target_config.rb +0 -33
  68. data/lib/eco/api/usecases/service/sftp.rb +7 -1
  69. data/lib/eco/api/usecases/use_case.rb +3 -2
  70. data/lib/eco/api/usecases/workflow.rb +5 -0
  71. data/lib/eco/api/usecases.rb +12 -5
  72. data/lib/eco/cli/scripting/args_helpers.rb +1 -9
  73. data/lib/eco/cli_default/options.rb +98 -68
  74. data/lib/eco/cli_default/workflow/end.rb +22 -0
  75. data/lib/eco/cli_default/workflow/launch_jobs.rb +14 -0
  76. data/lib/eco/cli_default/workflow/load/data.rb +27 -0
  77. data/lib/eco/cli_default/workflow/load/input.rb +28 -0
  78. data/lib/eco/cli_default/workflow/load.rb +13 -0
  79. data/lib/eco/cli_default/workflow/options.rb +10 -0
  80. data/lib/eco/cli_default/workflow/post_launch.rb +65 -0
  81. data/lib/eco/cli_default/workflow/report.rb +17 -0
  82. data/lib/eco/cli_default/workflow/rescued_exception.rb +21 -0
  83. data/lib/eco/cli_default/workflow/usecases.rb +23 -0
  84. data/lib/eco/cli_default/workflow.rb +24 -180
  85. data/lib/eco/data/count_trace.rb +51 -0
  86. data/lib/eco/data/files/content.rb +39 -0
  87. data/lib/eco/data/files/directory.rb +78 -45
  88. data/lib/eco/data/files/encoding.rb +12 -21
  89. data/lib/eco/data/files/file_pattern.rb +15 -8
  90. data/lib/eco/data/files/folder.rb +196 -0
  91. data/lib/eco/data/files/relative_path.rb +54 -0
  92. data/lib/eco/data/files/timestamp.rb +18 -0
  93. data/lib/eco/data/files.rb +46 -5
  94. data/lib/eco/data/fuzzy_match.rb +1 -1
  95. data/lib/eco/data/hashes/array_diff.rb +11 -5
  96. data/lib/eco/data/hashes/diff_result/meta.rb +12 -4
  97. data/lib/eco/data/locations/node_diff/accessors.rb +1 -1
  98. data/lib/eco/data/mapper.rb +5 -1
  99. data/lib/eco/data.rb +1 -0
  100. data/lib/eco/language/delegation/delegating_missing.rb +1 -1
  101. data/lib/eco/language/delegation/delegating_missing_const.rb +1 -1
  102. data/lib/eco/language/delegation/delegating_missing_on_class.rb +1 -1
  103. data/lib/eco/language/delegation/for_delegator/delegated_class.rb +1 -1
  104. data/lib/eco/language/klass/auto_loader.rb +129 -0
  105. data/lib/eco/language/klass/builder.rb +6 -6
  106. data/lib/eco/language/klass/const.rb +19 -0
  107. data/lib/eco/language/klass/helpers_built.rb +3 -1
  108. data/lib/eco/language/klass/hierarchy.rb +5 -1
  109. data/lib/eco/language/klass/naming.rb +5 -2
  110. data/lib/eco/language/klass/resolver.rb +21 -6
  111. data/lib/eco/language/klass/uid.rb +12 -0
  112. data/lib/eco/language/klass/when_inherited.rb +30 -6
  113. data/lib/eco/language/klass.rb +5 -2
  114. data/lib/eco/language/methods/access_modifier.rb +23 -0
  115. data/lib/eco/language/methods/instance_method_helpers.rb +6 -1
  116. data/lib/eco/language/methods.rb +1 -0
  117. data/lib/eco/language/models/hierarchy.rb +41 -0
  118. data/lib/eco/language/models/workflow.rb +385 -0
  119. data/lib/eco/language/models.rb +2 -1
  120. data/lib/eco/version.rb +1 -1
  121. metadata +31 -7
  122. data/lib/eco/api/common/class_auto_loader.rb +0 -114
  123. data/lib/eco/api/common/class_helpers.rb +0 -9
  124. data/lib/eco/api/common/class_hierarchy.rb +0 -45
  125. data/lib/eco/data/files/helpers.rb +0 -152
  126. data/lib/eco/language/models/class_helpers.rb +0 -136
@@ -0,0 +1,196 @@
1
+ module Eco
2
+ module Data
3
+ module Files
4
+ module Folder
5
+ PRESERVED_FILES = [
6
+ /.*\.rb$/,
7
+ /.*\.sh$/,
8
+ /.*\.ps1$/,
9
+ /.*\.ya?ml$/,
10
+ /.*\.md$/,
11
+ /.*\.gemspec$/,
12
+ /Gem/,
13
+ /Rake/,
14
+ /LICENSE/,
15
+ /(?:^|[\\\/])\.[^\\\/]+$/ # i.e. `.env`, `.gitignore`
16
+ ].freeze
17
+
18
+ include Eco::Language::AuxiliarLogger
19
+ include RelativePath
20
+
21
+ # It ensure that the path to the file exists
22
+ def ensure_file_path!(filename)
23
+ return if File.exist?(filename)
24
+
25
+ FileUtils.mkdir_p(File.dirname(filename))
26
+ end
27
+
28
+ # @param pattern [String, Symbol] the `Dir` expression to match
29
+ # the target files. When a Symbol is provided, it is used as
30
+ # the expected extension of the files in the target folder `folder`.
31
+ # @param regexp [Regexp] a regular expression that will be matched
32
+ # against the **basename** of the files in the `folder` that
33
+ # matched the `pattern`.
34
+ # @param older_than [Integer] days
35
+ # @param newer_than [Integer] days
36
+ def folder_files( # rubocop:disable Metrics/AbcSize
37
+ folder = '.',
38
+ pattern = '*',
39
+ regexp: nil,
40
+ older_than: nil,
41
+ newer_than: nil,
42
+ &block
43
+ )
44
+ return [] unless folder.is_a?(String)
45
+
46
+ unless Dir.exist?(folder)
47
+ log(:error) {
48
+ "Folder '#{folder}' does not exist"
49
+ }
50
+ return []
51
+ end
52
+
53
+ folder = to_relative_path(folder)
54
+ pattern ||= '.'
55
+ pattern = "*.#{pattern}" if pattern.is_a?(Symbol)
56
+ target = File.join(File.expand_path(folder), pattern)
57
+
58
+ to_relative_path(
59
+ Dir[target]
60
+ ).tap do |dir_files|
61
+ dir_files.select! {|f| File.file?(f)}
62
+
63
+ if older_than
64
+ dir_files.select! do |f|
65
+ File.mtime(f) < (Time.now - (60*60*24*older_than))
66
+ end
67
+ end
68
+
69
+ if newer_than
70
+ dir_files.select! do |f|
71
+ File.mtime(f) > (Time.now - (60*60*24*newer_than))
72
+ end
73
+ end
74
+
75
+ if regexp.is_a?(Regexp)
76
+ dir_files.select! do |f|
77
+ File.basename(f).match(regexp)
78
+ end
79
+ end
80
+ end.sort.tap do |files|
81
+ if files.empty?
82
+ matchers = [pattern, regexp].compact
83
+ log(:info) {
84
+ "Couldn't find local files (#{matchers}): in folder '#{folder}'"
85
+ }
86
+ else
87
+ log(:info) {
88
+ msg = 'Found local files: '
89
+ msg << "\n • "
90
+ msg << files.join("\n • ")
91
+ msg << "\n"
92
+ msg
93
+ }
94
+ end
95
+
96
+ next unless block_given?
97
+
98
+ files.each(&block)
99
+ end
100
+ end
101
+
102
+ def csv_files(
103
+ folder = '.',
104
+ regexp: nil,
105
+ older_than: nil,
106
+ newer_than: nil,
107
+ &block
108
+ )
109
+ folder_files(
110
+ folder,
111
+ '*.csv',
112
+ regexp: regexp,
113
+ older_than: older_than,
114
+ newer_than: newer_than,
115
+ &block
116
+ )
117
+ end
118
+
119
+ # @note it will exclude files matching any of the patterns defined
120
+ # in `PRESERVED_FILES`
121
+ # @note never make `folder` an argument with default
122
+ # (force it to be explicitly declared)
123
+ def clear_folder(
124
+ folder,
125
+ pattern = '*',
126
+ regexp: nil,
127
+ older_than: nil,
128
+ newer_than: nil,
129
+ timeout: 3
130
+ )
131
+ target_files = folder_files(
132
+ folder,
133
+ pattern,
134
+ regexp: regexp,
135
+ older_than: older_than,
136
+ newer_than: newer_than
137
+ ).select do |file|
138
+ File.file?(file)
139
+ end
140
+
141
+ # safety check (exclude certain files)
142
+ PRESERVED_FILES.reduce(target_files) do |mem, rex|
143
+ mem.grep_v(rex) # excluse those matching pattern
144
+ end.tap do |files|
145
+ next if files.empty?
146
+
147
+ msg = ' • '
148
+ msg << files.join("\n • ")
149
+ msg << "\n"
150
+
151
+ if respond_to?(:session, true)
152
+ msg = "The following files will be removed:\n#{msg}"
153
+
154
+ send(:session).prompt_user(
155
+ 'Do you want to proceed? (Y/n):',
156
+ explanation: msg,
157
+ default: 'Y',
158
+ timeout: timeout
159
+ ) do |response|
160
+ next unless response.upcase.start_with?('Y')
161
+
162
+ File.delete(*files)
163
+ end
164
+ else
165
+ msg = "The following files have been removed:\n#{msg}"
166
+
167
+ File.delete(*files)
168
+
169
+ log(:info) { msg }
170
+ end
171
+ end
172
+ end
173
+
174
+ def archive_file(filename, subfolder: 'Archive')
175
+ src_folder = File.dirname(filename)
176
+ subpath = File.join(src_folder, subfolder)
177
+ FileUtils.mkdir_p(subpath)
178
+
179
+ basename = File.basename(filename)
180
+ archived_file = to_relative_path(File.join(subpath, basename))
181
+
182
+ log(:info) {
183
+ msg = []
184
+ msg << "Moving file:"
185
+ msg << " • from: '#{to_relative_path(filename)}'"
186
+ msg << " • to: '#{archived_file}'"
187
+ msg.join("\n")
188
+ }
189
+
190
+ File.rename(filename, archived_file)
191
+ archived_file
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,54 @@
1
+ # rubocop: disable Naming/MethodParameterName
2
+
3
+ module Eco
4
+ module Data
5
+ module Files
6
+ module RelativePath
7
+ def to_relative_path(of, from: '.')
8
+ require 'pathname'
9
+
10
+ pn_from = to_pathname(from)
11
+
12
+ case of
13
+ when String, Pathname
14
+ pn_of = to_pathname(of)
15
+
16
+ begin
17
+ pn_of.relative_path_from(pn_from).to_s
18
+ rescue ArgumentError => err
19
+ puts err
20
+ of
21
+ end
22
+ when Array
23
+ of.map do |path|
24
+ to_relative_path(path, from: pn_from)
25
+ end
26
+ else
27
+ msg = 'Expected `of` argument to be '
28
+ msg << 'String, Pathname, Array<String>, or Array<Pathname>. '
29
+ msg << "Given: #{of.class}"
30
+ raise ArgumentError, msg
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def to_pathname(value)
37
+ abs_path =
38
+ case value
39
+ when String, Pathname
40
+ File.expand_path(value.to_s)
41
+ else
42
+ msg << 'String, or Pathname. '
43
+ msg << "Given: #{value.class}"
44
+ raise ArgumentError, msg
45
+ end
46
+
47
+ Pathname.new(abs_path)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ # rubocop:enable Naming/MethodParameterName
@@ -0,0 +1,18 @@
1
+ module Eco
2
+ module Data
3
+ module Files
4
+ module Timestamp
5
+ DEFAULT_TIMESTAMP = '%Y-%m-%dT%H%M%S'.freeze
6
+
7
+ def timestamp(timestamp_pattern = DEFAULT_TIMESTAMP, date = Time.now)
8
+ date.strftime(timestamp_pattern)
9
+ end
10
+
11
+ def timestamp_file(filename, timestamp_pattern = DEFAULT_TIMESTAMP)
12
+ file_pattern = Eco::Data::Files::FilePattern.new(filename)
13
+ file_pattern.resolve(start: "#{timestamp(timestamp_pattern)}_")
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,11 +1,52 @@
1
+ require 'fileutils'
2
+
3
+ require_relative 'files/encoding'
4
+ require_relative 'files/content'
5
+ require_relative 'files/relative_path'
6
+ require_relative 'files/folder'
7
+ require_relative 'files/file_pattern'
8
+ require_relative 'files/timestamp'
9
+ require_relative 'files/directory'
10
+
1
11
  module Eco
2
12
  module Data
3
13
  module Files
14
+ class << self
15
+ def included(base)
16
+ super
17
+
18
+ base.send :include, InstanceMethods
19
+ base.extend ClassMethods
20
+ end
21
+ end
22
+
23
+ module InstanceMethods
24
+ include Eco::Data::Files::Content
25
+ end
26
+
27
+ module ClassMethods
28
+ include Eco::Data::Files::Encoding
29
+ include Eco::Data::Files::Folder
30
+ include Eco::Data::Files::Timestamp
31
+ include Eco::Data::Files::RelativePath
32
+
33
+ # @todo: change method name to `split_path`
34
+ def split(path)
35
+ dir_path, file = File.split(path)
36
+ dir_path = dir_path.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
37
+ dir_path.split(File::ALT_SEPARATOR || File::SEPARATOR).push(file)
38
+ end
39
+
40
+ def copy_file(source_file, dest_file, time_stamp: false)
41
+ dest_file = timestamp_file(dest_file) if time_stamp
42
+ File.write(dest_file, File.read(source_file))
43
+ end
44
+ end
45
+
46
+ class << self
47
+ include Files::InstanceMethods
48
+ include Files::ClassMethods
49
+ end
4
50
  end
5
51
  end
6
52
  end
7
-
8
- require_relative 'files/encoding'
9
- require_relative 'files/helpers'
10
- require_relative 'files/file_pattern'
11
- require_relative 'files/directory'
@@ -8,7 +8,6 @@ require_relative 'fuzzy_match/ngrams_score'
8
8
  module Eco
9
9
  module Data
10
10
  module FuzzyMatch
11
-
12
11
  class << self
13
12
  def included(base)
14
13
  super
@@ -27,6 +26,7 @@ module Eco
27
26
 
28
27
  def jaro_winkler(str_1, str_2, **options)
29
28
  return 0 if !str1 || !str2
29
+
30
30
  options = {
31
31
  ignore_case: true,
32
32
  weight: 0.25
@@ -2,7 +2,7 @@ module Eco
2
2
  module Data
3
3
  module Hashes
4
4
  class ArrayDiff
5
- extend Eco::Language::Models::ClassHelpers
5
+ extend Eco::Language::Klass::HelpersBuilt
6
6
  # We can change the `DiffResult` class (items)
7
7
  class_resolver :diff_result_class, "Eco::Data::Hash::DiffResult"
8
8
 
@@ -36,9 +36,10 @@ module Eco
36
36
  # All the items that contain the diff of a node.
37
37
  # @return [Array<Eco::Data::Hash::DiffResult>]
38
38
  def source_results
39
- @source_results ||= paired_sources.each_with_object([]) do |(src_1, src_2), res|
40
- res << diff_result_class.new(src_1, src_2)
41
- end
39
+ @source_results ||=
40
+ paired_sources.each_with_object([]) do |(src_1, src_2), res|
41
+ res << diff_result_class.new(src_1, src_2)
42
+ end
42
43
  end
43
44
 
44
45
  protected
@@ -55,7 +56,11 @@ module Eco
55
56
  # @return [String] the `key` attribute of `diff_result_class`
56
57
  def key
57
58
  diff_result_class.key.tap do |k|
58
- raise "#{diff_result_class}: missing main key attr to pair records. Given: #{k}" unless k.is_a?(String)
59
+ next if k.is_a?(String)
60
+
61
+ msg = "#{diff_result_class}: missing main key attr to pair records. "
62
+ msg << "Given: #{k}"
63
+ raise msg
59
64
  end
60
65
  end
61
66
 
@@ -85,6 +90,7 @@ module Eco
85
90
  log(:error) {
86
91
  "(ArrayDiff) Input data as 'Hash' not supported. Expecting 'Enumerable' or 'String'"
87
92
  }
93
+
88
94
  exit(1)
89
95
  when String
90
96
  to_array_of_hashes(Eco::CSV.parse(content))
@@ -7,7 +7,7 @@ module Eco
7
7
  def included(base)
8
8
  super
9
9
 
10
- base.extend Eco::Language::Models::ClassHelpers
10
+ base.extend Eco::Language::Klass::HelpersBuilt
11
11
  base.extend ClassMethods
12
12
  base.inheritable_class_vars :key, :compared_attrs, :case_sensitive
13
13
  end
@@ -18,6 +18,7 @@ module Eco
18
18
  # @return [String] the attribute that is key of the node diff elements.
19
19
  def key(value = nil)
20
20
  return @key unless value
21
+
21
22
  @key = value.to_s
22
23
  end
23
24
 
@@ -41,7 +42,8 @@ module Eco
41
42
  # Whether or not the diff calc of a node should be done case sensitive or insensitive.
42
43
  def case_sensitive(value = nil)
43
44
  @case_sensitive = false unless instance_variable_defined?(:@case_sensitive)
44
- return @case_sensitive unless value
45
+ return @case_sensitive unless value
46
+
45
47
  @case_sensitive = !!value
46
48
  end
47
49
 
@@ -92,6 +94,7 @@ module Eco
92
94
  return true if val_1 == val_2
93
95
  return false if case_sensitive?
94
96
  return false unless val_2 && val_1
97
+
95
98
  val_1.upcase == val_2.upcase
96
99
  end
97
100
 
@@ -102,8 +105,10 @@ module Eco
102
105
  def eq_ary?(val_1, val_2)
103
106
  return false unless [val_1, val_2].all? {|v| v.is_a?(Array)}
104
107
  return true if val_1 == val_2
108
+
105
109
  v_1 = case_sensitive?? val_1 : val_1.map {|v| v&.upcase}
106
110
  v_2 = case_sensitive?? val_2 : val_2.map {|v| v&.upcase}
111
+
107
112
  joined = (v_1 | v_2)
108
113
  (joined & v_1) == (joined & v_2)
109
114
  end
@@ -112,17 +117,20 @@ module Eco
112
117
  # @note DSL to be able to modify behaviour
113
118
  # (i.e. indifferent access for camel and snake case keys)
114
119
  def get_attr(src, key)
115
- return nil unless src
120
+ return unless src
121
+
116
122
  src[key.to_s]
117
123
  end
118
124
 
119
125
  def slice_attrs(src, *keys)
120
- return nil unless src
126
+ return unless src
127
+
121
128
  src.slice(*keys)
122
129
  end
123
130
 
124
131
  def key_present?(src, key)
125
132
  return false unless src
133
+
126
134
  src.key?(key)
127
135
  end
128
136
  end
@@ -4,7 +4,7 @@ class Eco::Data::Locations::NodeDiff
4
4
  def included(base)
5
5
  super
6
6
 
7
- base.extend Eco::Language::Models::ClassHelpers
7
+ base.extend Eco::Language::Klass::HelpersBuilt
8
8
  base.extend ClassMethods
9
9
  base.inheritable_class_vars :exposed_attrs
10
10
  end
@@ -34,6 +34,10 @@ module Eco
34
34
  end
35
35
  end
36
36
 
37
+ def empty?
38
+ @by_external.empty? && @by_internal.empty?
39
+ end
40
+
37
41
  def insensitive?
38
42
  @insensitive
39
43
  end
@@ -44,7 +48,7 @@ module Eco
44
48
 
45
49
  def to_json(internal: @internal_order)
46
50
  content = as_json(internal: internal).map do |pair|
47
- " " + pair.to_json
51
+ " #{pair.to_json}"
48
52
  end.join(",\n")
49
53
 
50
54
  "[\n#{content}\n]"
data/lib/eco/data.rb CHANGED
@@ -6,6 +6,7 @@ end
6
6
  require_relative 'data/strings'
7
7
  require_relative 'data/files'
8
8
  require_relative 'data/mapper'
9
+ require_relative 'data/count_trace'
9
10
  require_relative 'data/fuzzy_match'
10
11
  require_relative 'data/hashes'
11
12
  require_relative 'data/locations'
@@ -6,7 +6,7 @@ module Eco::Language::Delegation
6
6
  def included(base)
7
7
  super
8
8
 
9
- base.extend Eco::Language::Klass::HelpersBuilt
9
+ base.extend Eco::Language::Klass::InheritableClassVars
10
10
  base.extend ClassMethods
11
11
  base.inheritable_class_vars :_delegating_missing, :delegating_missing_to
12
12
  end
@@ -6,7 +6,7 @@ module Eco::Language::Delegation
6
6
  # the aim of scoping its class (so it can chain constant lookup), unless
7
7
  # `delegated_class` is explicitly stated.
8
8
  # @note that not setting `delegated_class` explicitly could fail
9
- # to chain the looup.
9
+ # to chain the lookup.
10
10
  module DelegatingMissingConst
11
11
  class << self
12
12
  def included(base)
@@ -4,7 +4,7 @@ module Eco::Language::Delegation
4
4
  def included(base)
5
5
  super
6
6
 
7
- base.extend Eco::Language::Klass::HelpersBuilt
7
+ base.extend Eco::Language::Klass::InheritableClassVars
8
8
  base.extend ClassMethods
9
9
  base.inheritable_class_vars :delegating_missing_on_class_to
10
10
  end
@@ -6,7 +6,7 @@ module Eco::Language::Delegation::ForDelegator
6
6
  def included(base)
7
7
  super
8
8
 
9
- base.extend Eco::Language::Klass::HelpersBuilt
9
+ base.extend Eco::Language::Klass::InheritableClassVars
10
10
  base.extend ClassMethods
11
11
 
12
12
  base.inheritable_class_vars :delegated_class
@@ -0,0 +1,129 @@
1
+ module Eco::Language::Klass
2
+ # Helpers for dynamic object loading based on class declaration
3
+ # @note
4
+ # - this helpers aim to boost the usage of the ruby language
5
+ # in complex integration configurations.
6
+ module AutoLoader
7
+ include Resolver
8
+ include Hierarchy
9
+
10
+ # It loads/creates a new instance of children classes pending to be loaded.
11
+ # @note This method **should be explicitly called**.
12
+ # @return [Boolean] `true` if there were children loaded, `false` otherwise.
13
+ def autoload_children!(object)
14
+ return false if @loading_children
15
+ return false unless autoloaded_class
16
+
17
+ pending_children = unloaded_children
18
+ return false if pending_children.empty?
19
+
20
+ @loading_children = true
21
+
22
+ pending_children.each do |klass|
23
+ @child = klass.new(object)
24
+ rescue TypeError => _e
25
+ # Can't create from this class (must be the singleton class)
26
+ # Just ignore
27
+ ensure
28
+ autoloaded_children.push(klass)
29
+ end
30
+
31
+ true
32
+ ensure
33
+ @loading_children = false
34
+ end
35
+
36
+ # To enable the class autoloader, you should use this method
37
+ def autoloads_children_of(klass)
38
+ class_resolver :autoloader_class, klass
39
+ @autoloaded_class = klass
40
+ end
41
+
42
+ # Resolves the class `autoloader_class` if it has been defined via `autoloads_children_of`
43
+ def autoloaded_class
44
+ return unless @autoloaded_class
45
+
46
+ autoloader_class
47
+ end
48
+
49
+ # To which restricted namespaces this class autoloads from
50
+ def autoloaded_namespaces(type = :include)
51
+ @autoloaded_namespaces ||= {}
52
+ @autoloaded_namespaces[type] ||= []
53
+ end
54
+
55
+ # To restrict which namespaces it is allowed to load from
56
+ def autoload_namespace(*namespaces)
57
+ _autoload_namespace(:include, *namespaces)
58
+ end
59
+
60
+ # To ignore certain namespaces this class should not autoload from
61
+ def autoload_namespace_ignore(*namespaces)
62
+ _autoload_namespace(:ignore, *namespaces)
63
+ end
64
+
65
+ def _autoload_namespace(type, *namespaces)
66
+ autoloaded_namespaces(type).concat(namespaces) unless namespaces.empty?
67
+ end
68
+
69
+ # @param constant [Class, String] a class or namespace we want to check auto-load entitlement thereof.
70
+ # @return [Boolean] determines if a given namespace is entitled for autoloading
71
+ def autoload_class?(constant)
72
+ constants = constant.to_s.split("::").compact
73
+ autoload = true
74
+
75
+ unless autoloaded_namespaces(:include).empty?
76
+ autoload = autoloaded_namespaces(:include).any? do |ns|
77
+ ns.to_s.split("::").compact.zip(constants).all? {|(r, c)| r == c}
78
+ end
79
+ end
80
+
81
+ unless autoloaded_namespaces(:ignore).empty?
82
+ autoload &&= autoloaded_namespaces(:ignore).none? do |ns|
83
+ ns.to_s.split("::").compact.zip(constants).all? {|(r, c)| r == c}
84
+ end
85
+ end
86
+
87
+ autoload
88
+ end
89
+
90
+ # As children are loaded as they are declared, we should not load twice same children.
91
+ def autoloaded_children
92
+ @auto_loaded_children ||= [] # rubocop:disable Naming/MemoizedInstanceVariableName
93
+ end
94
+
95
+ # Children classes of `autoloader_class` that have not been created an instance of.
96
+ def unloaded_children
97
+ return [] unless autoloaded_class
98
+
99
+ new_detected = new_classes
100
+ known_class!(*new_detected)
101
+
102
+ descendants(
103
+ parent_class: autoloaded_class,
104
+ scope: new_detected
105
+ ).select do |child_class|
106
+ next false if autoloaded_children.include?(child_class)
107
+ next false unless autoload_class?(child_class)
108
+
109
+ true
110
+ end.sort
111
+ end
112
+
113
+ # Known namespaces serves the purpose to discover recently added namespaces
114
+ # provided that the namespace discovery is optimized
115
+ def known_classes
116
+ @known_classes ||= []
117
+ end
118
+
119
+ # Add to known namespaces
120
+ def known_class!(*classes)
121
+ known_classes.concat(classes)
122
+ end
123
+
124
+ # List all new namespaces
125
+ def new_classes
126
+ ObjectSpace.each_object(::Class).to_a - known_classes
127
+ end
128
+ end
129
+ end