eco-helpers 2.5.10 → 2.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/CHANGELOG.md +132 -4
  4. data/README.md +5 -0
  5. data/eco-helpers.gemspec +20 -16
  6. data/lib/eco/api/common/class_helpers.rb +1 -1
  7. data/lib/eco/api/common/loaders/base.rb +2 -9
  8. data/lib/eco/api/common/loaders/case_base.rb +0 -2
  9. data/lib/eco/api/common/loaders/config/workflow/mailer.rb +78 -0
  10. data/lib/eco/api/common/loaders/config/workflow.rb +11 -0
  11. data/lib/eco/api/common/loaders/config.rb +29 -0
  12. data/lib/eco/api/common/loaders/error_handler.rb +0 -2
  13. data/lib/eco/api/common/loaders/parser.rb +0 -1
  14. data/lib/eco/api/common/loaders/policy.rb +0 -2
  15. data/lib/eco/api/common/loaders/use_case.rb +27 -1
  16. data/lib/eco/api/common/loaders.rb +1 -0
  17. data/lib/eco/api/common/people/default_parsers.rb +2 -2
  18. data/lib/eco/api/common/people/person_entry.rb +3 -0
  19. data/lib/eco/api/common/people/person_entry_attribute_mapper.rb +111 -16
  20. data/lib/eco/api/common/session/base_session.rb +4 -0
  21. data/lib/eco/api/common/session/environment.rb +4 -0
  22. data/lib/eco/api/common/session/mailer.rb +3 -1
  23. data/lib/eco/api/common/session/sftp.rb +1 -1
  24. data/lib/eco/api/common/version_patches/exception.rb +2 -2
  25. data/lib/eco/api/common/version_patches/ruby3/object.rb +18 -0
  26. data/lib/eco/api/common/version_patches/ruby3.rb +1 -0
  27. data/lib/eco/api/common/version_patches.rb +3 -0
  28. data/lib/eco/api/custom/config.rb +10 -0
  29. data/lib/eco/api/custom/mailer.rb +9 -0
  30. data/lib/eco/api/custom/namespace.rb +2 -0
  31. data/lib/eco/api/custom/workflow.rb +9 -0
  32. data/lib/eco/api/custom.rb +3 -0
  33. data/lib/eco/api/organization/tag_tree.rb +20 -23
  34. data/lib/eco/api/session/batch/base_policy.rb +13 -5
  35. data/lib/eco/api/session/batch/job.rb +14 -11
  36. data/lib/eco/api/session/batch/jobs.rb +2 -2
  37. data/lib/eco/api/session/batch/jobs_groups.rb +2 -2
  38. data/lib/eco/api/session/config/files.rb +2 -2
  39. data/lib/eco/api/session/config/people.rb +2 -2
  40. data/lib/eco/api/session/config/sftp.rb +4 -0
  41. data/lib/eco/api/session/config/tagtree.rb +9 -8
  42. data/lib/eco/api/session/config/workflow.rb +95 -58
  43. data/lib/eco/api/session/config.rb +9 -2
  44. data/lib/eco/api/session.rb +17 -2
  45. data/lib/eco/api/usecases/base_io.rb +50 -4
  46. data/lib/eco/api/usecases/cli/dsl.rb +94 -0
  47. data/lib/eco/api/usecases/cli/option.rb +19 -0
  48. data/lib/eco/api/usecases/cli.rb +13 -0
  49. data/lib/eco/api/usecases/default/locations/cli/tagtree_extract_cli.rb +29 -0
  50. data/lib/eco/api/usecases/{default_cases → default/locations}/codes_to_tags_case.rb +1 -1
  51. data/lib/eco/api/usecases/{default_cases → default/locations}/create_tag_paths_case.rb +1 -1
  52. data/lib/eco/api/usecases/{default_cases → default/locations}/csv_to_tree_case.rb +1 -1
  53. data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +181 -0
  54. data/lib/eco/api/usecases/default/locations.rb +15 -0
  55. data/lib/eco/api/usecases/{default_cases → default/people}/analyse_people_case.rb +1 -1
  56. data/lib/eco/api/usecases/{default_cases → default/people}/change_email_case.rb +1 -1
  57. data/lib/eco/api/usecases/default/people/clean_unknown_tags_case.rb +66 -0
  58. data/lib/eco/api/usecases/{default_cases → default/people}/clear_abilities_case.rb +1 -1
  59. data/lib/eco/api/usecases/{default_cases → default/people}/org_data_convert_case.rb +1 -1
  60. data/lib/eco/api/usecases/{default_cases → default/people}/refresh_case.rb +1 -1
  61. data/lib/eco/api/usecases/{default_cases → default/people}/reinvite_sync_case.rb +1 -1
  62. data/lib/eco/api/usecases/{default_cases → default/people}/reinvite_trans_case.rb +1 -1
  63. data/lib/eco/api/usecases/default/people/reinvite_trans_cli.rb +5 -0
  64. data/lib/eco/api/usecases/{default_cases → default/people}/restore_db_case.rb +1 -1
  65. data/lib/eco/api/usecases/{default_cases → default/people}/set_default_tag_case.rb +1 -1
  66. data/lib/eco/api/usecases/{default_cases → default/people}/supers_cyclic_identify_case.rb +1 -1
  67. data/lib/eco/api/usecases/{default_cases → default/people}/supers_hierarchy_case.rb +1 -1
  68. data/lib/eco/api/usecases/{default_cases → default/people}/switch_supervisor_case.rb +1 -1
  69. data/lib/eco/api/usecases/{default_cases → default/people}/transfer_account_case.rb +1 -1
  70. data/lib/eco/api/usecases/default/people.rb +25 -0
  71. data/lib/eco/api/usecases/default.rb +16 -0
  72. data/lib/eco/api/usecases/default_cases/samples/cli/sftp_cli.rb +46 -0
  73. data/lib/eco/api/usecases/default_cases/samples/sftp_case.rb +21 -9
  74. data/lib/eco/api/usecases/default_cases.rb +2 -30
  75. data/lib/eco/api/usecases/graphql/helpers/location/base.rb +1 -2
  76. data/lib/eco/api/usecases/graphql/utils/sftp.rb +1 -1
  77. data/lib/eco/api/usecases/ooze_samples/register_update_case.rb +3 -3
  78. data/lib/eco/api/usecases/use_case.rb +31 -7
  79. data/lib/eco/api/usecases/use_case_chain.rb +2 -2
  80. data/lib/eco/api/usecases.rb +4 -1
  81. data/lib/eco/assets.rb +3 -5
  82. data/lib/eco/cli/config/filters/people_filters.rb +0 -1
  83. data/lib/eco/cli/config/filters.rb +2 -6
  84. data/lib/eco/cli/config/help.rb +0 -1
  85. data/lib/eco/cli/config/input.rb +0 -1
  86. data/lib/eco/cli/config/options_set.rb +3 -4
  87. data/lib/eco/cli/config/use_cases.rb +13 -6
  88. data/lib/eco/cli/config.rb +4 -5
  89. data/lib/eco/cli/scripting/args_helpers.rb +1 -1
  90. data/lib/eco/cli/scripting/argument.rb +0 -1
  91. data/lib/eco/cli/scripting/arguments.rb +0 -2
  92. data/lib/eco/cli.rb +0 -1
  93. data/lib/eco/{cli/config/default → cli_default}/input_filters.rb +0 -1
  94. data/lib/eco/{cli/config/default → cli_default}/people_filters.rb +0 -1
  95. data/lib/eco/{cli/config/default → cli_default}/usecases.rb +2 -52
  96. data/lib/eco/cli_default/workflow.rb +171 -0
  97. data/lib/eco/cli_default.rb +13 -0
  98. data/lib/eco/csv/table.rb +0 -1
  99. data/lib/eco/data/files/encoding.rb +1 -1
  100. data/lib/eco/data/files/helpers.rb +1 -1
  101. data/lib/eco/data/locations/convert.rb +8 -4
  102. data/lib/eco/data/locations/node_base/csv_convert.rb +4 -4
  103. data/lib/eco/data/locations/node_base/tag_validations.rb +19 -9
  104. data/lib/eco/data/locations/node_base/treeify.rb +193 -18
  105. data/lib/eco/data/locations/node_level.rb +1 -1
  106. data/lib/eco/data/locations/node_plain/parsing.rb +1 -1
  107. data/lib/eco/data/locations/node_plain/serial.rb +1 -1
  108. data/lib/eco/data/locations/node_plain.rb +4 -3
  109. data/lib/eco/data/mapper.rb +6 -1
  110. data/lib/eco/language/klass/when_inherited.rb +17 -0
  111. data/lib/eco/language/klass.rb +8 -0
  112. data/lib/eco/language/methods/delegate_missing.rb +28 -0
  113. data/lib/eco/language/methods/dsl_able.rb +25 -0
  114. data/lib/eco/language/methods.rb +9 -0
  115. data/lib/eco/language.rb +2 -0
  116. data/lib/eco/version.rb +1 -1
  117. metadata +169 -79
  118. data/lib/eco/api/usecases/default_cases/abstract_policygroup_abilities_case.rb +0 -160
  119. data/lib/eco/api/usecases/default_cases/append_usergroups_case.rb +0 -14
  120. data/lib/eco/api/usecases/default_cases/clean_unknown_tags_case.rb +0 -74
  121. data/lib/eco/api/usecases/default_cases/create_details_case.rb +0 -20
  122. data/lib/eco/api/usecases/default_cases/create_details_with_supervisor_case.rb +0 -21
  123. data/lib/eco/api/usecases/default_cases/email_as_id_case.rb +0 -12
  124. data/lib/eco/api/usecases/default_cases/new_email_case.rb +0 -13
  125. data/lib/eco/api/usecases/default_cases/new_id_case.rb +0 -12
  126. data/lib/eco/api/usecases/default_cases/remove_account_sync_case.rb +0 -10
  127. data/lib/eco/api/usecases/default_cases/remove_account_trans_case.rb +0 -16
  128. data/lib/eco/api/usecases/default_cases/reset_landing_page_case.rb +0 -18
  129. data/lib/eco/api/usecases/default_cases/set_supervisor_case.rb +0 -16
  130. data/lib/eco/api/usecases/default_cases/tagtree_case.rb +0 -42
  131. data/lib/eco/api/usecases/default_cases/update_details_case.rb +0 -15
  132. data/lib/eco/cli/config/default/workflow.rb +0 -188
  133. data/lib/eco/cli/config/default.rb +0 -16
  134. /data/lib/eco/{cli/config/default → cli_default}/input.rb +0 -0
  135. /data/lib/eco/{cli/config/default → cli_default}/options.rb +0 -0
  136. /data/lib/eco/{cli/config/default → cli_default}/people.rb +0 -0
@@ -8,7 +8,7 @@ module Eco::Data::Locations
8
8
  # @return [Eco::CSV::Table]
9
9
  def csv_from(filename, encoding: 'utf-8')
10
10
  raise ArgumentError, "Expecting String filename. Given: #{filename.class}" unless filename.is_a?(String)
11
- raise "Missing #{filename}" unless File.exists?(filename)
11
+ raise "Missing #{filename}" unless File.exist?(filename)
12
12
  Eco::CSV.read(filename, encoding: encoding)
13
13
  rescue CSV::MalformedCSVError => e
14
14
  if match = e.message.match(/line (?<line>\d+)/i)
@@ -21,11 +21,14 @@ module Eco::Data::Locations
21
21
  # @note The steps of usage would be:
22
22
  # 1. First **treeify** your input (i.e. `Eco::API::Organization::TagTree#as_json`,
23
23
  # or `treeify(nodes)`
24
+ # @yield [str_node, node] block for custom output node name
25
+ # @yiledreturn [String] the node name
24
26
  # @param hash_nodes [Array<Hash>] a hierarchical tree of Hash nodes, nested via `nodes`
25
27
  # @return [CSV::Table] ready to be made a hierarchical csv tree (i.e. out.to_csv)
26
- def hash_tree_to_tree_csv(hash_nodes, out: [], done_ids: [], repeated_ids: [], level: 0)
28
+ def hash_tree_to_tree_csv(hash_nodes, out: [], done_ids: [], repeated_ids: [], level: 0, attrs: [:id])
27
29
  lev = level + 1
28
30
  base = empty_array(level)
31
+ sattrs = attrs.map(&:to_s)
29
32
 
30
33
  hash_nodes.each_with_object(out) do |node, out|
31
34
  if done_ids.include?(id = node["id"])
@@ -33,8 +36,9 @@ module Eco::Data::Locations
33
36
  else
34
37
  has_offspring = (children = node["nodes"]) && !children.empty?
35
38
  done_ids << id
36
- out << (base.dup << node["id"])
37
- hash_tree_to_tree_csv(node["nodes"], out: out, done_ids: done_ids, repeated_ids: repeated_ids, level: lev)
39
+ str_node = node.values_at(*sattrs).join(" ## ")
40
+ out << (base.dup << str_node)
41
+ hash_tree_to_tree_csv(node["nodes"], out: out, done_ids: done_ids, repeated_ids: repeated_ids, level: lev, attrs: attrs)
38
42
  end
39
43
  end.tap do |out|
40
44
  if level == 0
@@ -23,7 +23,7 @@ module Eco::Data::Locations::NodeBase
23
23
  # ready to be parsed as an organization tagtree
24
24
  def hash_tree(value, &block)
25
25
  return hash_tree_from_csv(value, &block) if value.is_a?(::CSV::Table)
26
- return value.as_json if value.is_a?(tree_class)
26
+ return value.as_json(&block) if value.is_a?(tree_class)
27
27
  raise ArgumentError, "Expecting Eco::API::Organization::TagTree or CSV::Table. Given: #{value.class}"
28
28
  end
29
29
 
@@ -40,8 +40,8 @@ module Eco::Data::Locations::NodeBase
40
40
  # @yield [Node] optional custom serializer
41
41
  # @yieldreturn [Hash] the serialized Node
42
42
  # @return [CSV::Table] a table with L1 to Ln columns ready for dump to csv
43
- def csv_tree(value, encoding: 'utf-8', &block)
44
- Eco::CSV::Table.new(hash_tree_to_tree_csv(hash_tree(value, &block)))
43
+ def csv_tree(value, encoding: 'utf-8', attrs: [:id], &block)
44
+ Eco::CSV::Table.new(hash_tree_to_tree_csv(hash_tree(value, &block), attrs: attrs))
45
45
  end
46
46
 
47
47
  # @note it just converts to an organizational tagtree and uses a helper method.
@@ -51,7 +51,7 @@ module Eco::Data::Locations::NodeBase
51
51
  # @return [CSV::Table] a table with a list of nodes and their parents
52
52
  def csv_list(value, &block)
53
53
  value = org_tree(value, &block) unless value.is_a?(tree_class)
54
- Eco::CSV.Table.new(hash_list(value))
54
+ Eco::CSV::Table.new(hash_list(value))
55
55
  end
56
56
  end
57
57
  end
@@ -1,33 +1,43 @@
1
1
  module Eco::Data::Locations::NodeBase
2
2
  module TagValidations
3
+ include Eco::Language::AuxiliarLogger
4
+
3
5
  ALLOWED_CHARACTERS = "A-Za-z0-9 &_'\/.-"
4
6
  VALID_TAG_REGEX = /^[#{ALLOWED_CHARACTERS}]+$/
5
7
  INVALID_TAG_REGEX = /[^#{ALLOWED_CHARACTERS}]+/
6
8
  VALID_TAG_CHARS = /[#{ALLOWED_CHARACTERS}]+/
7
9
  DOUBLE_BLANKS = /\s\s+/
8
10
 
9
- def clean_id(str, notify: true)
11
+ def clean_id(str, notify: true, ref: '')
10
12
  blanks_x2 = has_double_blanks?(str)
11
13
  partial = replace_not_allowed(str)
12
14
  remove_double_blanks(partial).tap do |result|
13
15
  next unless notify
14
- next if invalid_warned?
16
+ next if invalid_warned?(str)
15
17
  if partial != str
16
18
  invalid_chars = identify_invalid_characters(str)
17
- puts "• (Row: #{self.row_num}) Invalid characters _#{invalid_chars}_ (removed): '#{str}' (converted to '#{result}')"
19
+ log(:warn) {
20
+ "• #{ref}Invalid characters _#{invalid_chars}_ <<_removed_: '#{str}' :_converted_>> '#{result}'"
21
+ }
18
22
  elsif blanks_x2
19
- puts "• (Row: #{self.row_num}) Double blanks (removed): '#{str}' (converted to '#{result}')"
23
+ log(:warn) {
24
+ "• #{ref}Double blanks removed: '#{str}' :_converted_>> '#{result}'"
25
+ }
20
26
  end
21
- invalid_warned!
27
+ invalid_warned!(str)
22
28
  end
23
29
  end
24
30
 
25
- def invalid_warned?
26
- @invalid_warned ||= false
31
+ def invalid_warned?(str)
32
+ invalid_warned[str] ||= false
33
+ end
34
+
35
+ def invalid_warned!(str)
36
+ invalid_warned[str] = true
27
37
  end
28
38
 
29
- def invalid_warned!
30
- @invalid_warned = true
39
+ def invalid_warned
40
+ @invalid_warned ||= {}
31
41
  end
32
42
 
33
43
  def has_double_blanks?(str)
@@ -12,14 +12,31 @@ module Eco::Data::Locations::NodeBase
12
12
  # @yieldreturn [Hash] custom hash model when treeifying (allows to set more keys/properties).
13
13
  # @nodes [Array<NodeBase>] list of nodes
14
14
  # @return [Array<Hash>] a hierarchical tree of nested Hashes via `nodes` key.
15
- def treeify(nodes, &block)
15
+ def treeify(nodes, skipped: [], unlinked_trees: [], &block)
16
16
  return [] if nodes.empty?
17
17
  block ||= nodes.first.class.serializer
18
- get_children(nil, parents_hash(nodes), &block)
18
+ done_ids = {}
19
+ warns = []
20
+ parents = parents_hash(nodes)
21
+ get_children(nil, parents, done_ids: done_ids, skipped: skipped, warns: warns, &block).tap do |tree|
22
+ check_results(
23
+ tree,
24
+ nodes,
25
+ parents,
26
+ done_ids: done_ids,
27
+ skipped: skipped,
28
+ unlinked_trees: unlinked_trees,
29
+ warns: warns,
30
+ &block
31
+ )
32
+ log(:warn) { warns.join("\n") } unless warns.empty?
33
+ end
19
34
  end
20
35
 
21
36
  private
22
37
 
38
+ # @return [Hash] where `key`s are all the `parentId` of the nodes
39
+ # and `value` and `Array` of those nodes that have that `parentId`
23
40
  def parents_hash(nodes)
24
41
  nodes.each_with_object({}) do |node, parents|
25
42
  (parents[node.parentId] ||= []).push(node)
@@ -32,12 +49,21 @@ module Eco::Data::Locations::NodeBase
32
49
  # 3. The above can translate into some
33
50
  # @yield [node]
34
51
  # @yieldreturn [Hash] custom hash model when treeifying
35
- def get_children(node_id, parents, parent: nil, done_ids: {}, level: 0, &block)
52
+ def get_children(node_id, parents, parent: nil, level: 0, done_ids: {}, skipped: [], warns: [], &block)
36
53
  level_ids = []
37
54
  (parents[node_id] ||= []).each_with_object([]) do |child, results|
38
55
  # Skipping done id. Add proper warnings...
39
56
  # => rely on `done_ids` to identify if an `id` has already been done
40
- next report_skipped_node(child, parent, done_ids, level, level_ids, parents) if done_ids[child.id]
57
+ next report_skipped_node(
58
+ child,
59
+ parent,
60
+ done_ids,
61
+ level,
62
+ level_ids,
63
+ parents,
64
+ skipped: skipped,
65
+ warns: warns
66
+ ) if done_ids[child.id]
41
67
 
42
68
  # Fill in tracking data
43
69
  child.parent = parent
@@ -52,8 +78,26 @@ module Eco::Data::Locations::NodeBase
52
78
  node_hash.merge(yield(child)) if block_given?
53
79
  # we must register the `id` before recursing down
54
80
  done_ids[child.id] = child
81
+
82
+ children = get_children(
83
+ child.id,
84
+ parents,
85
+ parent: child,
86
+ done_ids: done_ids,
87
+ level: level + 1,
88
+ skipped: skipped,
89
+ warns: warns,
90
+ &block
91
+ ).tap do |desc|
92
+ if (nil_count = desc.count(nil)) > 0
93
+ log(:debug) {
94
+ "get_children gave #{nil_count} nil values for nodes of #{child.id}"
95
+ }
96
+ end
97
+ end
98
+
55
99
  results << node_hash.merge({
56
- "nodes" => get_children(child.id, parents, parent: child, done_ids: done_ids, level: level + 1, &block).compact
100
+ "nodes" => children.compact
57
101
  })
58
102
  end
59
103
  end
@@ -70,8 +114,138 @@ module Eco::Data::Locations::NodeBase
70
114
  "#{" " * level}"
71
115
  end
72
116
 
73
- # Gives different warnings, depending the case
74
- def report_skipped_node(node, parent, done_ids, level, level_ids, parents)
117
+ # Method to ensure the results are consistent
118
+ # @param skipped [Array<NodePlain>] those skipped because repeated
119
+ # 1. It will add children of them that were skipped. This won't clash with unlinked nodes
120
+ # because otherwise would be part of `done_ids` anyway.
121
+ # @param unlinked_trees [Array<Hash>] by excluding those done and skipped,
122
+ # it will treeify the unlinked nodes (the exclusion applies to `parants_hash`)
123
+ def check_results(tree, nodes, parents, done_ids: {}, skipped: [], unlinked_trees: [], warns: [], &block)
124
+ update_skipped(skipped, parents, done_ids: done_ids) unless skipped.empty?
125
+
126
+ if done_ids.count != nodes.count
127
+ tracked_nodes = done_ids.values
128
+ untracked_nodes = nodes - tracked_nodes - skipped
129
+ # skipped keys is inherent, as they were excluded because of id clash with done_ids
130
+ unlinked_parent_ids = (parents.keys - done_ids.keys).compact
131
+
132
+ msg = []
133
+
134
+ # The reason of missing nodes in the output tree is unknown!
135
+ if skipped.empty? && unlinked_parent_ids.empty?
136
+ msg << "BUG in this library (open issue with maintainers)."
137
+ msg << "There were no skipped nodes nor missin referred parents, and yet:"
138
+ msg << " • the tree nodes count: #{done_ids.count} ..."
139
+ msg << " • doesn't match the original nodes count: #{nodes.count}"
140
+ raise msg.join("\n")
141
+ end
142
+
143
+ unless unlinked_parent_ids.empty?
144
+ msg << "There are #{unlinked_parent_ids.count} referred parent_id's NOT linked to the root:"
145
+ msg << " • total_nodes: #{nodes.count}"
146
+ msg << " • tracked_nodes: #{tracked_nodes.count}"
147
+ msg << " • untracked_nodes: #{untracked_nodes.count}"
148
+ msg << " • unlinked_parents: #{unlinked_parent_ids.count}"
149
+ msg << " • skipped (repeated) nodes: #{skipped.count}" unless skipped.empty?
150
+
151
+ unlinked_nodes = nodes - skipped
152
+ unlinked_parents = parents.slice(*unlinked_parent_ids) # doesn'thave skipped ones
153
+
154
+ residual_skipped = []
155
+ unlinked_trees.concat \
156
+ get_unlinked_trees(
157
+ unlinked_nodes,
158
+ unlinked_parents,
159
+ done_ids: done_ids,
160
+ skipped: residual_skipped,
161
+ warns: warns,
162
+ &block
163
+ )
164
+
165
+ update_skipped(skipped, parents, with: residual_skipped, done_ids: done_ids) unless residual_skipped.empty?
166
+
167
+ tracked_nodes = done_ids.values
168
+ untracked_nodes = nodes - tracked_nodes - skipped
169
+ unlinked_parent_ids = (parents.keys - done_ids.keys).compact
170
+
171
+ msg << "After treeifying via the unlinked_parents:"
172
+ msg << " • total_nodes: #{nodes.count}"
173
+ msg << " • tracked_nodes: #{tracked_nodes.count}"
174
+ msg << " • untracked_nodes: #{untracked_nodes.count}"
175
+ msg << " • unlinked_parents: #{unlinked_parent_ids.count}"
176
+ msg << " • skipped in this step: #{residual_skipped.count}"
177
+ end
178
+
179
+ msg << " • total skipped (repeated) nodes: #{skipped.count} !!" unless skipped.empty?
180
+ warns << msg.join("\n")
181
+ nil
182
+ end
183
+ end
184
+
185
+ # Treeifies the unlinked nodes by scoping existing parent ids.
186
+ def get_unlinked_trees(nodes, parents, done_ids: {}, skipped: [], warns: [], &block)
187
+ node_ids = nodes.map(&:id)
188
+ parent_ids = parents.keys & node_ids
189
+ missing_parent_ids = parents.keys - parent_ids
190
+ missing_parents = parents.slice(*missing_parent_ids)
191
+ warns << " • missing_parents: #{missing_parents.count}"
192
+ nil_parent_nodes = missing_parents.each_with_object([]) do |(id, nodes), mem|
193
+ nodes.each {|node| node.parent_id = nil}
194
+ mem.concat(nodes)
195
+ end
196
+ rest_parents = parents.slice(*parent_ids).merge({
197
+ nil => nil_parent_nodes
198
+ })
199
+ get_children(nil, rest_parents, done_ids: done_ids, skipped: skipped, warns: warns, &block)
200
+ end
201
+
202
+ # Same as `get_children` but not performing checks and with
203
+ # option to retrieve the source nodes (rather than parsing to `Hash`).
204
+ # @note serves the purpose to identify what linked children got inherently
205
+ # skipped, because their parent was skipped.
206
+ def get_tree_nodes_raw(node_id, parents, src_plain: true, &block)
207
+ (parents[node_id] ||= []).each_with_object([]) do |child, results|
208
+ unless src_plain
209
+ node_hash = {
210
+ "id" => child.id,
211
+ "name" => child.name,
212
+ "parent_id" => node_id
213
+ }
214
+ node_hash.merge(yield(child)) if block_given?
215
+ end
216
+
217
+ descendants = get_tree_nodes_raw(child.id, parents, src_plain: src_plain, &block).tap do |desc|
218
+ if (nil_count = desc.count(nil)) > 0
219
+ puts "get_tree_nodes_raw gave #{nil_count} nil values for nodes of #{child.id}"
220
+ end
221
+ end
222
+
223
+ if src_plain
224
+ results.concat(descendants)
225
+ else
226
+ results << node_hash.merge({
227
+ "nodes" => descendants.compact
228
+ })
229
+ end
230
+ end
231
+ end
232
+
233
+ # It goes through the `with` skipped nodes, and adds them to the `skipped` ones
234
+ # by including their not tracked/done/included children.
235
+ def update_skipped(skipped, parents, with: skipped, done_ids: {})
236
+ raw_skipped_children = with.each_with_object([]) do |node, mem|
237
+ mem << node
238
+ mem.concat get_tree_nodes_raw(node.id, parents)
239
+ end.uniq
240
+ skipped_children = raw_skipped_children - done_ids.values
241
+ skipped.concat(skipped_children).uniq!
242
+ skipped
243
+ end
244
+
245
+ # With given a skipped `node` (repeated `id`), it gives different warnings,
246
+ # provided that the context in which the double-up `id` happened is identified.
247
+ def report_skipped_node(node, parent, done_ids, level, level_ids, parents, skipped: [], warns: [])
248
+ skipped << node
75
249
  lev = level + 1
76
250
  done_node = done_ids[node.id]
77
251
  prev_parent = node.parent
@@ -84,6 +258,9 @@ module Eco::Data::Locations::NodeBase
84
258
  row_str = row_num ? "(Row: #{row_num}) " : ''
85
259
  node_str = "#{row_str}Node '#{node.id}' #{level_msg(lev)} (#{parent_msg(parent)})"
86
260
 
261
+ msg = []
262
+ msg << "#{indent(level)}Skipping #{node_str}."
263
+
87
264
  # Implementation integrity guard
88
265
  # => as we don't register in `done_ids` those that are skipped,
89
266
  # when a `node` has already a tracked `parent` or `level`,
@@ -114,18 +291,15 @@ module Eco::Data::Locations::NodeBase
114
291
  cyclic = multi_parent && done_node == node
115
292
  double_up = node_dup || lev_dup
116
293
 
117
- msg = []
118
- msg << "#{indent(level)}WARNING: Skipping #{node_str}."
119
-
120
294
  if cyclic
121
- str = "#{indent(level)+1}Cyclic definition. By skipping the node, "
295
+ str = "#{indent(level + 1)}Cyclic definition. By skipping the node, "
122
296
  str << "it will remain as #{parent_msg(done_node.parent)} (#{level_msg(prev_level)})."
123
297
  msg << str
124
298
  end
125
299
 
126
300
  if double_up
127
- str = "#{indent(level)+1}The node ID has been tracked as #{level_msg(done_node.tracked_level)}, "
128
- str << "as #{parent_msg(node_dup.parent)} "
301
+ str = "#{indent(level + 1)}Node ID was already tracked as #{level_msg(done_node.tracked_level)}, "
302
+ str << "as #{parent_msg(done_node.parent)} "
129
303
  str << "(same parent)." if lev_dup
130
304
  str << "(different parent)." if multi_parent
131
305
  msg << str
@@ -133,18 +307,19 @@ module Eco::Data::Locations::NodeBase
133
307
 
134
308
  unless cyclic || double_up
135
309
  str = "Integrity issue in Treeify. "
136
- str = "Skipping is only applicable to double_ups or cyclic nodes."
310
+ str << "Skipping is only applicable to double_ups or cyclic nodes."
137
311
  str << "\n • #{node_str}."
138
312
  raise str
139
313
  end
140
314
 
141
- if children = parents[node.id]
142
- str = "#{indent(level)+1}Immediate children of skipped node (will probably be missing): "
143
- str << children.map {|gc| "'#{gc.id}'"}.join(", ")
315
+ unless (children = parents[node.id] || []).empty?
316
+ str = "#{indent(level + 1)}Immediate children of skipped node (will probably be missing): "
317
+ str << children.map {|ch| "'#{ch.id}'"}.join(", ")
144
318
  msg << str
145
319
  end
146
320
 
147
- log(:warn) { msg.join('\n') }
321
+ warns << msg.join("\n")
322
+ nil
148
323
  end
149
324
  end
150
325
  end
@@ -28,7 +28,7 @@ module Eco::Data::Locations
28
28
  end
29
29
 
30
30
  def tag
31
- clean_id(raw_tag)
31
+ clean_id(raw_tag, ref: "(Row: #{self.row_num}) ")
32
32
  end
33
33
 
34
34
  def raw_tag
@@ -20,7 +20,7 @@ class Eco::Data::Locations::NodePlain
20
20
  end
21
21
 
22
22
  # It builds each NodePlain from the input csv.
23
- # @param `csv` [CSV::Table]
23
+ # @param `csv` [CSV::Table] with specific headers
24
24
  # @return [Array<NodePlain>]
25
25
  def nodes_from_csv(csv)
26
26
  raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
@@ -6,7 +6,7 @@ class Eco::Data::Locations::NodePlain
6
6
  def serializer
7
7
  @serializer ||= proc do |node|
8
8
  raise "Expecting NodePlain. Given: #{node.class}" unless node.is_a?(Eco::Data::Locations::NodePlain)
9
- keys = Eco::Data::Locations::NodePlain::NODE_PLAIN_ATTRS
9
+ keys = Eco::Data::Locations::NodePlain::ALL_ATTRS
10
10
  node.to_h(*keys)
11
11
  end
12
12
  end
@@ -15,7 +15,7 @@ module Eco::Data::Locations
15
15
  PROP_ATTRS = ALL_ATTRS - ADDITIONAL_ATTRS
16
16
 
17
17
  def id
18
- clean_id(super)
18
+ clean_id(super, ref: "(Row: #{self.row_num}) ")
19
19
  end
20
20
  # backwards compatibility
21
21
  alias_method :tag, :id
@@ -24,8 +24,9 @@ module Eco::Data::Locations
24
24
  super || self.id
25
25
  end
26
26
 
27
- def parentId
28
- self.parent_id
27
+ def parent_id
28
+ clean_id(super, notify: false, ref: "(Row: #{self.row_num} - parent_id) ")
29
29
  end
30
+ alias_method :parentId, :parent_id
30
31
  end
31
32
  end
@@ -62,6 +62,12 @@ module Eco
62
62
  internal?(value) || external?(value)
63
63
  end
64
64
 
65
+ # Whether `value` maps to itself
66
+ def self_mapped?(value)
67
+ return false unless include?(value)
68
+ value == to_internal(value)
69
+ end
70
+
65
71
  def to_internal(value)
66
72
  return value if !@source
67
73
  @by_external[value]
@@ -71,7 +77,6 @@ module Eco
71
77
  return value if !@source
72
78
  @by_internal[value]
73
79
  end
74
-
75
80
  end
76
81
  end
77
82
  end
@@ -0,0 +1,17 @@
1
+ module Eco
2
+ module Language
3
+ module Klass
4
+ module WhenInherited
5
+ def inherited(subclass)
6
+ super
7
+ subclass.instance_exec(&when_inherited)
8
+ end
9
+
10
+ def when_inherited(&block)
11
+ return @when_inherited unless block_given?
12
+ @when_inherited = block
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module Eco
2
+ module Language
3
+ module Klass
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative 'klass/when_inherited'
@@ -0,0 +1,28 @@
1
+ module Eco
2
+ module Language
3
+ module Methods
4
+ module DelegateMissing
5
+ def delegate_missing_to(meth)
6
+ @delegate_missing_to = meth
7
+ end
8
+
9
+ def method_missing(method_name, *args, **kargs, &block)
10
+ super unless receiver = object_missing_delegated_to
11
+ receiver.send(method_name, *args, **kargs, &block)
12
+ end
13
+
14
+ def respond_to_missing?(method_name, include_private = false)
15
+ super
16
+ end
17
+
18
+ private
19
+
20
+ # retrieve the delegate_missing_to object
21
+ def object_missing_delegated_to
22
+ return nil unless @delegate_missing_to
23
+ self.method(@delegate_missing_to).call
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,25 @@
1
+ module Eco
2
+ module Language
3
+ module Methods
4
+ module DslAble
5
+ # It runs the `block` within this object context
6
+ # @note if the object misses any method, redirects the method to the
7
+ # original evaluate caller.
8
+ def evaluate(*args, **kargs, &block)
9
+ return unless block_given?
10
+ @self_before_evaluate = eval "self", block.binding
11
+ instance_exec(*args, **kargs, &block).tap do
12
+ @self_before_evaluate = nil
13
+ end
14
+ end
15
+
16
+ # When it's the case, redirect to the original `evaluate` caller
17
+ # @see https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
18
+ def method_missing(method, *args, **kargs, &block)
19
+ super unless @self_before_evaluate
20
+ @self_before_evaluate.send(method, *args, **kargs, &block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module Eco
2
+ module Language
3
+ module Methods
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative 'methods/dsl_able'
9
+ require_relative 'methods/delegate_missing'
data/lib/eco/language.rb CHANGED
@@ -3,6 +3,8 @@ module Eco
3
3
  end
4
4
  end
5
5
 
6
+ require_relative 'language/klass'
7
+ require_relative 'language/methods'
6
8
  require_relative 'language/models'
7
9
  require_relative 'language/auxiliar_logger'
8
10
  require_relative 'language/basic_logger'
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = "2.5.10"
2
+ VERSION = "2.6.1"
3
3
  end