eco-helpers 3.2.5 → 3.2.7

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: 2878273b593e2556be3d41de808563fdb2bf4995c488a61274552f828d0c5b59
4
- data.tar.gz: 1cb620f1532563deee7671b28d8999c9a0dd877c572c542b840febc81646fde8
3
+ metadata.gz: bfe97cd5583e8650f73c0f7cb2557fde896d3b59b7e729703fdb01f9323d6fbb
4
+ data.tar.gz: e40c8fdeae21780ca957c043aa7a637d06d9e85088fdbca6eb7c488448c74ab5
5
5
  SHA512:
6
- metadata.gz: 715d86772a51b17cfc928ea241e818ead64efca009f34f5e9bdbcb56fde97a4e44157ce08a5836b29011c9bd6911b138ed374a0fd53f8316a48669cc48b56631
7
- data.tar.gz: ca74f83124a7ea16cd883ea1e34f4350f5a0505e3439ef6cc4eb729e7329dc6cd9eabbe1311e8992eeb7dd571f485404c748ea751678759572ded8d0bb35ad68
6
+ metadata.gz: c1f28e77164e7f8c439b09dba5d952594c34721457842963cfb9e037c90658de8049545c1029e25e6aa2aadda2a729a4fc103fb04335adcfb6359b1fe946ad60
7
+ data.tar.gz: '090c4c12745401c0191aec263bb3970bd92e5244c6b57f4d5ef6dd157acab37ab868ed239972d133bd52a4eb11ddcac0e4857a8e760e10321b7236f50a7d3c76'
data/CHANGELOG.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
- ## [3.2.6] - 2025-09-xx
5
+ ## [3.2.8] - 2025-09-xx
6
6
 
7
7
  ### Added
8
8
 
@@ -10,6 +10,30 @@ All notable changes to this project will be documented in this file.
10
10
 
11
11
  ### Fixed
12
12
 
13
+ ## [3.2.7] - 2025-09-09
14
+
15
+ ### Added
16
+
17
+ - `NodeBase` (and subclasses): **added** missed case `#self_parented?`
18
+
19
+ ### Fixed
20
+
21
+ - **Added** early detection of `self-parented` nodes (from the input file).
22
+
23
+ ## [3.2.6] - 2025-09-05
24
+
25
+ ### Added
26
+
27
+ - **Improvement**: classifications conversion to ignore ending `s` (plurals).
28
+
29
+ ### Fixed
30
+
31
+ - Node `id` and `parent_id` **upcase** in `NodePlain` (**case insensitive**)
32
+ - It was failing to pair children with their parent (on input files).
33
+ - **remove** _classifications treatment_ on `Organization::TagTree` and `NodePlain`
34
+ - This was a working around due to not using `as_nodes_json` as a **common** converter between `live_tree` and `file_tree` (which was fixed in a previous release).
35
+ - `as_nodes_json` already does the call to `node_parser_block`, which does the conversion of the **classifications**
36
+
13
37
  ## [3.2.5] - 2025-09-04
14
38
 
15
39
  ### Changed
@@ -66,10 +66,11 @@ module Eco
66
66
  treat_classication(val)
67
67
  end
68
68
 
69
+ # Enhances the lookup
69
70
  def treat_classication(value)
70
71
  return value unless value.is_a?(String)
71
72
 
72
- value.strip.gsub(/\W+/, '').downcase
73
+ value.strip.downcase.gsub(/s(?:\W|$)/, '').gsub(/\W+/, '-')
73
74
  end
74
75
 
75
76
  def init_caches
@@ -211,9 +211,9 @@ module Eco
211
211
  &block
212
212
  )
213
213
  max_depth ||= total_depth
214
- return nil if max_depth < depth
215
- return [] if top? && !include_children
216
- return nil if archived? && !include_archived
214
+ return if max_depth < depth
215
+ return [] if top? && !include_children
216
+ return if archived? && !include_archived
217
217
 
218
218
  if include_children
219
219
  child_nodes = nodes
@@ -441,14 +441,13 @@ module Eco
441
441
 
442
442
  def init_node
443
443
  return if source.is_a?(Array)
444
+
444
445
  @id = source.values_at('tag', 'id').compact.first&.upcase
445
446
  @name = source['name']
446
447
  @weight = source['weight']
447
448
  @archived = as_boolean(source['archived'])
448
449
  @archived_token = source['archived_token']
449
- @classifications = into_a(source['classifications']).map do |value|
450
- treat_classication(value)
451
- end
450
+ @classifications = into_a(source['classifications'])
452
451
  @classification_names = into_a(source['classification_names'])
453
452
  @raw_nodes = source['nodes'] || []
454
453
  end
@@ -491,11 +490,6 @@ module Eco
491
490
  [tnodes, ddepth]
492
491
  end
493
492
 
494
- def treat_classication(value)
495
- return value unless value.is_a?(String)
496
- value.strip.gsub(/\W+/, '').downcase
497
- end
498
-
499
493
  # Helper to convert to array
500
494
  def into_a(value)
501
495
  if value.is_a?(String)
@@ -510,6 +504,7 @@ module Eco
510
504
  return true if value == true
511
505
  return false if value.to_s.strip.empty?
512
506
  return true if %w[yes x true].include?(value.downcase)
507
+
513
508
  false
514
509
  end
515
510
  end
@@ -5,39 +5,46 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
5
5
 
6
6
  private
7
7
 
8
+ def to_classification_ids(values)
9
+ values = [values].flatten unless values.is_a?(Array)
10
+
11
+ values = values.compact.map {|val| val.split('|')}.flatten
12
+ values.compact.map {|val| to_classification(val)}.compact.uniq
13
+ end
14
+
8
15
  # @note
9
16
  # 1. It returns `nil` unless there are classifications defined
10
17
  # 2. If the value is wrong, it warns and returns `nil`
11
18
  def to_classification(value)
12
- return nil unless node_classifications?
19
+ return unless node_classifications?
13
20
 
14
- raise ArgumentError, "Expecting a single element. Given: #{value.class}" if value.is_a?(Enumerator)
21
+ msg = "Expecting a single element. Given: #{value.class}"
22
+ raise ArgumentError, msg if value.is_a?(Enumerator)
15
23
 
16
- return nil unless value.is_a?(String)
24
+ return unless value.is_a?(String)
17
25
 
18
26
  node_classifications.to_id(value).tap do |type|
19
- unknown_clasification!(value) unless type
20
- end
21
- end
27
+ next if type
22
28
 
23
- def to_classification_ids(values)
24
- values = [values].flatten unless values.is_a?(Array)
25
- values = values.compact.map {|val| val.split('|')}.flatten
26
- values.compact.map {|val| to_classification(val)}.compact.uniq
29
+ unknown_classification!(value)
30
+ end
27
31
  end
28
32
 
29
33
  # @note
30
34
  # 1. It returns `nil` unless there are classifications defined
31
35
  # 2. If the value is wrong, it warns and returns `nil`
32
36
  def to_classification_name(value)
33
- return nil unless node_classifications?
37
+ return unless node_classifications?
34
38
 
35
- raise ArgumentError, "Expecting a single element. Given: #{value.class}" if value.is_a?(Enumerator)
39
+ msg = "Expecting a single element. Given: #{value.class}"
40
+ raise ArgumentError, msg if value.is_a?(Enumerator)
36
41
 
37
- return nil unless value.is_a?(String)
42
+ return unless value.is_a?(String)
38
43
 
39
44
  node_classifications.to_name(value) do |name|
40
- unknown_clasification!(value) unless name
45
+ next unless name
46
+
47
+ unknown_classification!(value)
41
48
  end
42
49
  end
43
50
 
@@ -49,10 +56,11 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
49
56
  @node_classifications ||= session.node_classifications
50
57
  end
51
58
 
52
- def unknown_clasification!(value)
59
+ def unknown_classification!(value)
53
60
  return if unknown_classifications.include?(value)
54
61
 
55
62
  unknown_classifications << value
63
+
56
64
  log(:warn) {
57
65
  "Unknown location node classification '#{value}'"
58
66
  }
@@ -11,15 +11,19 @@ module Eco::Data::Locations::NodeBase
11
11
  case data
12
12
  when ::CSV::Table
13
13
  return Eco::Data::Locations::NodePlain if Eco::Data::Locations::NodePlain.csv_matches_format?(csv)
14
- return Eco::Data::Locations::NodeLevel if Eco::Data::Locations::NodeLevel.csv_matches_format?(csv)
14
+
15
+ Eco::Data::Locations::NodeLevel if Eco::Data::Locations::NodeLevel.csv_matches_format?(csv)
15
16
  when Array
16
- return nil unless sample = data.first
17
+ return unless (sample = data.first)
18
+
17
19
  node_class(sample)
18
20
  when Eco::Data::Locations::NodeBase
19
- return nil unless data.class < Eco::Data::Locations::NodeBase
21
+ return unless data.class < Eco::Data::Locations::NodeBase
22
+
20
23
  data.class
21
24
  else
22
- raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
25
+ msg ="Expecting CSV::Table. Given: #{csv.class}"
26
+ raise ArgumentError, msg unless csv.is_a?(::CSV::Table)
23
27
  end
24
28
  end
25
29
  end
@@ -6,10 +6,19 @@ module Eco::Data::Locations::NodeBase
6
6
  # @param csv [CSV::Table]
7
7
  # @return [Array<NodePlain>, Array<NodeLevel>] with integrity issues resolved.
8
8
  def nodes_from_csv(csv)
9
- raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
10
- return Eco::Data::Locations::NodePlain.nodes_from_csv(csv) if Eco::Data::Locations::NodePlain.csv_matches_format?(csv)
11
- return Eco::Data::Locations::NodeLevel.nodes_from_csv(csv) if Eco::Data::Locations::NodeLevel.csv_matches_format?(csv)
12
- raise ArgumentError, "The input csv does not have the required format to read a locations structure."
9
+ msg = "Expecting CSV::Table. Given: #{csv.class}"
10
+ raise ArgumentError, msg unless csv.is_a?(::CSV::Table)
11
+
12
+ if Eco::Data::Locations::NodePlain.csv_matches_format?(csv)
13
+ return Eco::Data::Locations::NodePlain.nodes_from_csv(csv)
14
+ end
15
+
16
+ if Eco::Data::Locations::NodeLevel.csv_matches_format?(csv)
17
+ return Eco::Data::Locations::NodeLevel.nodes_from_csv(csv)
18
+ end
19
+
20
+ msg = "The input csv does not have the required format to read a locations structure."
21
+ raise ArgumentError, msg
13
22
  end
14
23
 
15
24
  # @yield [node, json] optional custom serializer
@@ -18,7 +27,9 @@ module Eco::Data::Locations::NodeBase
18
27
  # @yieldreturn [Hash] the serialized Node
19
28
  # @return [Array<Hash>] a hierarchical tree of nested Hashes via `nodes` key.
20
29
  def hash_tree_from_csv(csv, &block)
21
- raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
30
+ msg = "Expecting CSV::Table. Given: #{csv.class}"
31
+ raise ArgumentError, msg unless csv.is_a?(::CSV::Table)
32
+
22
33
  treeify(nodes_from_csv(csv), &block)
23
34
  end
24
35
 
@@ -2,7 +2,7 @@ module Eco::Data::Locations::NodeBase
2
2
  module TagValidations
3
3
  include Eco::Language::AuxiliarLogger
4
4
 
5
- ALLOWED_CHARACTERS = "A-Za-z0-9 &_'\/.-".freeze
5
+ ALLOWED_CHARACTERS = "A-Za-z0-9 &_'/.-".freeze
6
6
  VALID_TAG_REGEX = /^[#{ALLOWED_CHARACTERS}]+$/
7
7
  INVALID_TAG_REGEX = /[^#{ALLOWED_CHARACTERS}]+/
8
8
  VALID_TAG_CHARS = /[#{ALLOWED_CHARACTERS}]+/
@@ -11,9 +11,11 @@ module Eco::Data::Locations::NodeBase
11
11
  def clean_id(str, notify: true, ref: '')
12
12
  blanks_x2 = has_double_blanks?(str) # dubocop:disable Naming/VariableNumber
13
13
  partial = replace_not_allowed(str)
14
+
14
15
  remove_double_blanks(partial).tap do |result|
15
16
  next unless notify
16
17
  next if invalid_warned?(str)
18
+
17
19
  if partial != str
18
20
  invalid_chars = identify_invalid_characters(str)
19
21
  log(:warn) {
@@ -24,6 +26,7 @@ module Eco::Data::Locations::NodeBase
24
26
  "* #{ref}Double blanks removed: '#{str}' :_converted_>> '#{result}'"
25
27
  }
26
28
  end
29
+
27
30
  invalid_warned!(str)
28
31
  end
29
32
  end
@@ -46,13 +49,15 @@ module Eco::Data::Locations::NodeBase
46
49
  end
47
50
 
48
51
  def remove_double_blanks(str)
49
- return nil if str.nil?
52
+ return if str.nil?
53
+
50
54
  str.gsub(DOUBLE_BLANKS, ' ').strip
51
55
  end
52
56
 
53
57
  def replace_not_allowed(str)
54
- return nil if str.nil?
58
+ return if str.nil?
55
59
  return str if str.match(VALID_TAG_REGEX)
60
+
56
61
  str.gsub(INVALID_TAG_REGEX, ' ')
57
62
  end
58
63
 
@@ -28,10 +28,12 @@ module Eco::Data::Locations::NodeBase
28
28
  # @return [Array<Hash>] a hierarchical tree of nested Hashes via `nodes` key.
29
29
  def treeify(nodes, skipped: [], unlinked_trees: [], &block)
30
30
  return [] if nodes.empty?
31
+
31
32
  block ||= nodes.first.class.serializer
32
33
  done_ids = {}
33
34
  warns = []
34
35
  parents = parents_hash(nodes)
36
+
35
37
  get_children(
36
38
  nil, parents,
37
39
  done_ids: done_ids, skipped: skipped,
@@ -43,6 +45,7 @@ module Eco::Data::Locations::NodeBase
43
45
  skipped: skipped, unlinked_trees: unlinked_trees,
44
46
  warns: warns, &block
45
47
  )
48
+
46
49
  log(:warn) { warns.join("\n") } unless warns.empty?
47
50
  end
48
51
  end
@@ -52,8 +55,25 @@ module Eco::Data::Locations::NodeBase
52
55
  # @return [Hash] where `key`s are all the `parentId` of the nodes
53
56
  # and `value` and `Array` of those nodes that have that `parentId`
54
57
  def parents_hash(nodes)
58
+ self_parented = []
59
+
55
60
  nodes.each_with_object({}) do |node, parents|
61
+ next self_parented.push(node) if node.self_parented?
62
+
56
63
  (parents[node.parentId] ||= []).push(node)
64
+ end.tap do
65
+ next if self_parented.empty?
66
+
67
+ log(:error) {
68
+ msg = "#{self_parented.count} nodes are self-parented: "
69
+ msg << self_parented.map(&:id).map do |node_id|
70
+ "'#{node_id}'"
71
+ end.join(', ')
72
+ msg << "\nPlease amend the data."
73
+ msg
74
+ }
75
+
76
+ exit 1
57
77
  end
58
78
  end
59
79
 
@@ -135,7 +155,6 @@ module Eco::Data::Locations::NodeBase
135
155
  # skipped keys is inherent, as they were excluded because of id clash with done_ids
136
156
  unlinked_parent_ids = (parents.keys - done_ids.keys).compact
137
157
 
138
-
139
158
  # The reason of missing nodes in the output tree is unknown!
140
159
  if skipped.empty? && unlinked_parent_ids.empty?
141
160
  msg = []
@@ -14,6 +14,10 @@ module Eco::Data::Locations
14
14
 
15
15
  attr_accessor :tracked_level, :parent
16
16
 
17
+ def self_parented?
18
+ raise 'You should implement this method in child classes'
19
+ end
20
+
17
21
  def copy
18
22
  self.class.new.set_attrs(**to_h)
19
23
  end
@@ -23,7 +27,7 @@ module Eco::Data::Locations
23
27
  end
24
28
 
25
29
  def attr?(sym)
26
- !attr(sym).to_s.strip.empty?
30
+ !attr(sym).to_s.strip.empty? # rubocop:disable Style/Attr
27
31
  end
28
32
 
29
33
  def set_attrs(**kargs)
@@ -57,6 +61,7 @@ module Eco::Data::Locations
57
61
 
58
62
  def slice(*attrs)
59
63
  return {} if attrs.empty?
64
+
60
65
  to_h(*attrs)
61
66
  end
62
67
  end
@@ -17,6 +17,7 @@ class Eco::Data::Locations::NodeLevel
17
17
  # @return [Boolean] whether or not it's worthy trying to parse with `NodeLevel`.
18
18
  def csv_matches_format?(csv)
19
19
  return false unless csv.is_a?(::CSV::Table)
20
+
20
21
  !Eco::Data::Locations::NodePlain.csv_matches_format?(csv)
21
22
  end
22
23
 
@@ -26,7 +27,8 @@ class Eco::Data::Locations::NodeLevel
26
27
  # @param `csv` [CSV::Table]
27
28
  # @return [Array<NodeLevel>]
28
29
  def nodes_from_csv(csv)
29
- raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
30
+ msg = "Expecting CSV::Table. Given: #{csv.class}"
31
+ raise ArgumentError, msg unless csv.is_a?(::CSV::Table)
30
32
 
31
33
  possible_classifications = csv.headers
32
34
 
@@ -52,6 +54,7 @@ class Eco::Data::Locations::NodeLevel
52
54
 
53
55
  prev_node = node
54
56
  end
57
+
55
58
  tidy_nodes(nodes)
56
59
  end
57
60
  end
@@ -36,15 +36,23 @@ module Eco::Data::Locations
36
36
  end
37
37
 
38
38
  # In node level the parent id is set via parsing
39
- attr_accessor :parentId # rubocop:disable Naming/MethodName
40
- alias_method :parent_id=, :parentId=
41
- alias_method :parent_id, :parentId
39
+ attr_writer :parent_id
40
+ alias_method :parentId=, :parent_id= # rubocop:disable Naming/MethodName
41
+
42
+ def parent_id
43
+ @parent_id&.upcase
44
+ end
45
+ alias_method :parentId, :parent_id # rubocop:disable Naming/MethodName
42
46
 
43
47
  def id
44
48
  tag.upcase
45
49
  end
46
- alias_method :nodeId, :id
47
- alias_method :node_id, :nodeId
50
+ alias_method :node_id, :id
51
+ alias_method :nodeId, :id # rubocop:disable Naming/MethodName
52
+
53
+ def self_parented?
54
+ id == parent_id
55
+ end
48
56
 
49
57
  def name
50
58
  tag
@@ -16,6 +16,7 @@ class Eco::Data::Locations::NodePlain
16
16
  # @return [Boolean] whether or not it's worthy trying to parse with `NodePlain`.
17
17
  def csv_matches_format?(csv)
18
18
  return false unless csv.is_a?(::CSV::Table)
19
+
19
20
  (basic_headers & csv.headers) == basic_headers
20
21
  end
21
22
 
@@ -23,7 +24,9 @@ class Eco::Data::Locations::NodePlain
23
24
  # @param `csv` [CSV::Table] with specific headers
24
25
  # @return [Array<NodePlain>]
25
26
  def nodes_from_csv(csv)
26
- raise ArgumentError, "Expecting CSV::Table. Given: #{csv.class}" unless csv.is_a?(::CSV::Table)
27
+ msg = "Expecting CSV::Table. Given: #{csv.class}"
28
+ raise ArgumentError, msg unless csv.is_a?(::CSV::Table)
29
+
27
30
  # Convert to Eco::CSV::Table for a fresh start
28
31
  csv = Eco::CSV.parse(csv.to_csv).nil_blank_cells.add_index_column(:row_num)
29
32
  headers = node_class::ALL_ATTRS.map(&:to_s)
@@ -22,19 +22,30 @@ module Eco::Data::Locations
22
22
  PROP_ATTRS = (ALL_ATTRS - ADDITIONAL_ATTRS).freeze
23
23
 
24
24
  def id
25
- clean_id(super, ref: "(Row: #{row_num}) ")
25
+ clean_id(
26
+ super,
27
+ ref: "(Row: #{row_num}) "
28
+ )&.upcase
26
29
  end
27
30
  # backwards compatibility
28
31
  alias_method :tag, :id
29
32
 
30
- def name
31
- super || id
33
+ def parent_id
34
+ clean_id(
35
+ super,
36
+ notify: false,
37
+ ref: "(Row: #{row_num} - parent_id) "
38
+ )&.upcase
32
39
  end
40
+ alias_method :parentId, :parent_id # rubocop:disable Naming/MethodName
33
41
 
34
- def parent_id
35
- clean_id(super, notify: false, ref: "(Row: #{row_num} - parent_id) ")
42
+ def self_parented?
43
+ id == parent_id
44
+ end
45
+
46
+ def name
47
+ super || id
36
48
  end
37
- alias_method :parentId, :parent_id
38
49
 
39
50
  def archived
40
51
  value = super
@@ -42,13 +53,12 @@ module Eco::Data::Locations
42
53
  return true if value == true
43
54
  return false if value.to_s.strip.empty?
44
55
  return true if %w[yes x true].include?(value.downcase)
56
+
45
57
  false
46
58
  end
47
59
 
48
60
  def classifications
49
- into_a(super).map do |value|
50
- treat_classication(value)
51
- end
61
+ into_a(super)
52
62
  end
53
63
 
54
64
  def classification_names
@@ -68,11 +78,6 @@ module Eco::Data::Locations
68
78
 
69
79
  private
70
80
 
71
- def treat_classication(value)
72
- return value unless value.is_a?(String)
73
- value.strip.gsub(/\W+/, '').downcase
74
- end
75
-
76
81
  # Helper to convert to array
77
82
  def into_a(value)
78
83
  if value.is_a?(String)
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = '3.2.5'.freeze
2
+ VERSION = '3.2.7'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eco-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.5
4
+ version: 3.2.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-04 00:00:00.000000000 Z
11
+ date: 2025-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug