eco-helpers 2.4.4 → 2.4.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c2d5e728987bee135ac2297b0a84858dfb14eab79b3decebc59ced1a74ccc4a
4
- data.tar.gz: b906c449d80dbc1440c1b7e7d91565bad53409167cae3c07b0788870f842cf5e
3
+ metadata.gz: 4c4f7620deee8791ef8b6bfa5552e6040657575fa2ee071efdf7ed76e588a998
4
+ data.tar.gz: a870243619d2b8842ef97525676097330b76a0686f4793f7c6720e4c658173aa
5
5
  SHA512:
6
- metadata.gz: e7a9781490f01e1d9ffafdec19179d492d760c9dca676e2e6f2771b61a8f422d5c9f481bb03c71563c91df97b72f89cee0e8aa664be42dcf1dd5d99576177add
7
- data.tar.gz: 44eed6e779b8ffdfdc1bea5913090713cc15dba649b7c2eddc9f333a8be67f92afa1ec6e1d06b0c31bcf2b29d062b92cac26c7c53b07404e50d089ae56698202
6
+ metadata.gz: 5eef75b2a425b14b6bd17db7d58c153752ad121a708665f27736f9e4457c2eca5f06ec5af1344fe941478419c3271d0debdb3b09fda826eecd06414bf9a06405
7
+ data.tar.gz: 50c9b2e722457a10eae9da4723816f906efb816b82a485562779251ad80dea4c737cc0bd572c5e80eff3b9e2f6b3b909245bf65b766781f7cbdb63a8d5d68daf
data/CHANGELOG.md CHANGED
@@ -1,12 +1,39 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## [2.4.5] - 2023-03-xx
4
+ ## [2.4.7] - 2023-04-xx
5
5
 
6
6
  ### Added
7
7
  ### Changed
8
8
  ### Fixed
9
9
 
10
+ ## [2.4.6] - 2023-04-02
11
+
12
+ ### Added
13
+ - `Eco::API::Organization::TagTree` added support for `archived_token`
14
+ - Added support for `block` in
15
+ - `Eco::API::Session#live_tree`
16
+ - `Eco::API::Session::Config::Tagtree` in `#live_trees` and `live_tree`
17
+
18
+ ### Changed
19
+ - Upgraded `ecoportal-api-graphql` **gem**
20
+
21
+
22
+ ## [2.4.5] - 2023-03-31
23
+
24
+ ### Added
25
+ - `Eco::API::Organization::TagTree` support for `archived` and `weight` properties
26
+ - `Eco::Data::Hashes::ArrayDiff` and `Eco::Data::Hashes::DiffResult`
27
+ - Enable easy comparison of array of hashes
28
+ - Input data can be diverse
29
+
30
+ ### Fixed
31
+ - `Eco::API::UseCases::DefaultCases::CsvToTree`
32
+ - Fixed the missed alignment children that jump levels
33
+ - `Eco::API::UseCases::DefaultCases::CsvToTree::Node`
34
+ - `to_h`: attrs param was being ingnored.
35
+
36
+
10
37
  ## [2.4.4] - 2023-03-29
11
38
 
12
39
  ### Added
data/eco-helpers.gemspec CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |spec|
32
32
 
33
33
  spec.add_dependency 'ecoportal-api', '>= 0.9.3', '< 0.10'
34
34
  spec.add_dependency 'ecoportal-api-v2', '>= 1.1.1', '< 1.2'
35
- spec.add_dependency 'ecoportal-api-graphql', '>= 0.3.1', '< 0.4'
35
+ spec.add_dependency 'ecoportal-api-graphql', '>= 0.3.3', '< 0.4'
36
36
  spec.add_dependency 'aws-sdk-s3', '>= 1.83.0', '< 2'
37
37
  spec.add_dependency 'aws-sdk-ses', '>= 1.36.0', '< 2'
38
38
  spec.add_dependency 'dotenv', '>= 2.7.6', '< 2.8'
@@ -9,6 +9,7 @@ module Eco
9
9
  # Helper factory class to generate entries (input entries).
10
10
  # @attr_reader schema [Ecoportal::API::V1::PersonSchema] person schema to be used in this entry factory
11
11
  class EntryFactory < Eco::API::Common::Session::BaseSession
12
+ include Eco::Data::Files
12
13
 
13
14
  attr_reader :schema, :person_parser
14
15
 
@@ -111,7 +112,10 @@ module Eco
111
112
  end
112
113
  # Get content only when it's not :xls
113
114
  # note: even if content was provided, file takes precedence
114
- content = get_file_content(file, format, encoding) if (format != :xls) && file
115
+ if (format != :xls) && file
116
+ content = get_file_content(file, encoding)
117
+ end
118
+ #content = get_file_content(file, format, encoding) if (format != :xls) && file
115
119
 
116
120
  case content
117
121
  when Hash
@@ -142,10 +146,8 @@ module Eco
142
146
  entry_hash["source_file"] = file
143
147
  end
144
148
  end
145
-
146
149
  end
147
150
 
148
-
149
151
  # Helper that generates a file out of `data:`.
150
152
  # @raise Exception
151
153
  # - if you try to provide `data:` in the wrong format.
@@ -164,7 +166,7 @@ module Eco
164
166
  fatal("There is no parser/serializer for format ':#{format.to_s}'") unless @person_parser.defined?(format)
165
167
 
166
168
  run = true
167
- if Eco::API::Common::Session::FileManager.file_exists?(file)
169
+ if self.class.file_exists?(file)
168
170
  prompt_user("Do you want to overwrite it? (Y/n):", explanation: "The file '#{file}' already exists.", default: "Y") do |response|
169
171
  run = (response == "") || response.upcase.start_with?("Y")
170
172
  end
@@ -183,41 +185,10 @@ module Eco
183
185
  fd.write(person_parser.serialize(format, data_entries))
184
186
  end
185
187
  end
186
-
187
188
  end
188
189
 
189
190
  private
190
191
 
191
- def get_file_content(file, format, encoding)
192
- unless Eco::API::Common::Session::FileManager.file_exists?(file)
193
- logger.error("File does not exist: #{file}")
194
- exit(1)
195
- end
196
- #ext = File.extname(file)
197
- encoding ||= Eco::API::Common::Session::FileManager.encoding(file)
198
- encoding = (encoding == "bom") ? "#{encoding}|utf-8": encoding
199
- puts "File encoding: '#{encoding}'" unless !encoding || encoding == 'utf-8'
200
- read_with_tolerance(file, encoding: encoding)
201
- end
202
-
203
- def read_with_tolerance(file, encoding:)
204
- if content = File.read(file, encoding: encoding)
205
- content = content.encode("utf-8") unless encoding.include?('utf-8')
206
- tolerance = 5
207
- content.scrub do |bytes|
208
- replacement = '<' + bytes.unpack('H*')[0] + '>'
209
- if tolerance <= 0
210
- logger.error("There were more than 5 encoding errors in the file '#{file}'.")
211
- return content
212
- else
213
- tolerance -= 1
214
- logger.error("Encoding problem in file '#{file}': '#{replacement}'.")
215
- replacement
216
- end
217
- end
218
- end
219
- end
220
-
221
192
  def fatal(msg)
222
193
  logger.fatal(msg)
223
194
  raise msg
@@ -88,7 +88,6 @@ module Eco
88
88
  File.open(file, mode) { |fd| fd << content + "\n" } # '\n' won't add line
89
89
  return file
90
90
  end
91
-
92
91
  end
93
92
  end
94
93
  end
@@ -5,7 +5,8 @@ module Eco
5
5
  class TagTree
6
6
  attr_accessor :id
7
7
  alias_method :tag, :id
8
- attr_accessor :name
8
+ attr_accessor :name, :weight
9
+ attr_accessor :archived, :archived_token
9
10
 
10
11
  attr_reader :parent
11
12
  attr_reader :nodes, :children_count
@@ -24,7 +25,7 @@ module Eco
24
25
  # ]}]
25
26
  # tree = TagTree.new(tree.to_json)
26
27
  # @param tagtree [String] representation of the tagtree in json.
27
- def initialize(tagtree = [], name: nil, id: nil, depth: -1, path: [], parent: nil, enviro: nil)
28
+ def initialize(tagtree = [], name: nil, id: nil, depth: -1, path: [], parent: nil, _weight: nil, enviro: nil)
28
29
  @depth = depth
29
30
  @parent = parent
30
31
 
@@ -39,25 +40,32 @@ module Eco
39
40
  @enviro = enviro
40
41
 
41
42
  if @source.is_a?(Array)
42
- @id = id
43
- @name = name
43
+ @id = id
44
+ @name = name
44
45
  @row_nodes = @source
45
46
  else
46
- @id = @source.values_at('tag', 'id').compact.first&.upcase
47
- @name = @source['name']
47
+ @id = @source.values_at('tag', 'id').compact.first&.upcase
48
+ @name = @source['name']
49
+ @archived = @source.fetch('archived', false)
50
+ @archived_token = @source['archived_token']
51
+ @weight = @source.fetch('weight', _weight)
48
52
  @row_nodes = @source['nodes'] || []
49
53
  end
50
54
 
51
55
  @path = path || []
52
56
  @path.push(@id) unless top?
53
57
 
54
- @nodes = @row_nodes.map do |cnode|
55
- TagTree.new(cnode, depth: depth + 1, path: @path.dup, parent: self, enviro: @enviro)
58
+ @nodes = @row_nodes.map.with_index do |cnode, idx|
59
+ TagTree.new(cnode, depth: depth + 1, path: @path.dup, parent: self, _weight: idx, enviro: @enviro)
56
60
  end
57
61
 
58
62
  init_hashes
59
63
  end
60
64
 
65
+ def archived?
66
+ @archived
67
+ end
68
+
61
69
  # @return [Eco::API::Organization::TagTree]
62
70
  def dup
63
71
  self.class.new(as_json)
@@ -20,9 +20,9 @@ module Eco
20
20
  end
21
21
 
22
22
  # Among all the locations structures it selects the one with more location nodes
23
- def live_tree(enviro: nil)
23
+ def live_tree(enviro: nil, include_archived: false, &block)
24
24
  return @live_tree if instance_variable_defined?(:@live_tree) && @live_tree.enviro == enviro
25
- trees = live_trees(enviro: enviro)
25
+ trees = live_trees(enviro: enviro, include_archived: include_archived, &block)
26
26
  @live_tree = trees.reject do |tree|
27
27
  tree.empty?
28
28
  end.max do |a,b|
@@ -36,15 +36,15 @@ module Eco
36
36
  end
37
37
 
38
38
  # Retrieves all the location structures of the organisation
39
- def live_trees(enviro: nil)
39
+ def live_trees(enviro: nil, include_archived: false, &block)
40
40
  [].tap do |eco_trees|
41
41
  next unless apis.active_api.version_available?(:graphql)
42
42
  next unless graphql = apis.api(version: :graphql)
43
43
  kargs = {
44
- includeArchived: false,
44
+ includeArchived: include_archived,
45
45
  includeUnpublished: false
46
46
  }
47
- next unless trees = graphql.currentOrganization.locationStructures(**kargs)
47
+ next unless trees = graphql.currentOrganization.locationStructures(**kargs, &block)
48
48
  trees.each do |tree|
49
49
  args = { enviro: enviro, id: tree.id, name: tree.name}
50
50
  eco_tree = Eco::API::Organization::TagTree.new(tree.treeify, **args)
@@ -45,8 +45,8 @@ module Eco
45
45
  end
46
46
 
47
47
  # @see Eco::API::Session::Config#live_tree
48
- def live_tree
49
- config.live_tree(enviro: enviro)
48
+ def live_tree(include_archived: false, &block)
49
+ config.live_tree(enviro: enviro, &block)
50
50
  end
51
51
 
52
52
  # @see Eco::API::Session::Config#schemas
@@ -14,9 +14,9 @@ class Eco::API::UseCases::DefaultCases::CsvToTree
14
14
  result
15
15
  end
16
16
 
17
- def csv_nodes(filename)
17
+ def nodes_from_csv(csv)
18
18
  i = 1; prev_level = nil; prev_node = nil; prev_nodes = Array(1..11).zip(Array.new(11, nil)).to_h
19
- nodes = csv_from(filename).each_with_object([]) do |row, out|
19
+ nodes = csv.each_with_object([]) do |row, out|
20
20
  values = row.fields.map do |value|
21
21
  value = value.to_s.strip
22
22
  value.empty?? nil : value
@@ -41,6 +41,10 @@ class Eco::API::UseCases::DefaultCases::CsvToTree
41
41
  tidy_nodes(nodes)
42
42
  end
43
43
 
44
+ def csv_nodes(filename)
45
+ nodes_from_csv(csv_from(filename))
46
+ end
47
+
44
48
  private
45
49
 
46
50
  def csv_from_content(filename)
@@ -25,19 +25,29 @@ class Eco::API::UseCases::DefaultCases::CsvToTree
25
25
 
26
26
  def tag
27
27
  raw_tag.yield_self do |str|
28
- partial = replace_not_allowed(str)
28
+ blanks_x2 = has_double_blanks?(str)
29
+ partial = replace_not_allowed(str)
29
30
  remove_double_blanks(partial).tap do |result|
31
+ next if invalid_warned?
30
32
  if partial != str
31
33
  invalid_chars = identify_invalid_characters(str)
32
34
  puts "• (Row: #{self.row_num}) Invalid characters _#{invalid_chars}_ (removed): '#{str}' (converted to '#{result}')"
35
+ elsif blanks_x2
36
+ puts "• (Row: #{self.row_num}) Double blanks (removed): '#{str}' (converted to '#{result}')"
33
37
  end
34
- if result != partial
35
- #puts "• There were DOUBLE BLANKS: tag '#{str}' (converted to '#{result}')"
36
- end
38
+ invalid_warned!
37
39
  end
38
40
  end
39
41
  end
40
42
 
43
+ def invalid_warned?
44
+ @invalid_warned ||= false
45
+ end
46
+
47
+ def invalid_warned!
48
+ @invalid_warned = true
49
+ end
50
+
41
51
  def raw_tag
42
52
  values_at(*TAGS_ATTRS.reverse).compact.first
43
53
  end
@@ -163,7 +173,7 @@ class Eco::API::UseCases::DefaultCases::CsvToTree
163
173
 
164
174
  def to_h(*attrs)
165
175
  attrs = ALL_ATTRS if attrs.empty?
166
- ALL_ATTRS.zip(values_at(*ALL_ATTRS)).to_h
176
+ attrs.zip(values_at(*attrs)).to_h
167
177
  end
168
178
 
169
179
  def slice(*attrs)
@@ -188,6 +198,11 @@ class Eco::API::UseCases::DefaultCases::CsvToTree
188
198
  TAGS_ATTRS.length
189
199
  end
190
200
 
201
+ def has_double_blanks?(str)
202
+ return false if str.nil?
203
+ str.match(DOUBLE_BLANKS)
204
+ end
205
+
191
206
  def remove_double_blanks(str)
192
207
  return nil if str.nil?
193
208
  str.gsub(DOUBLE_BLANKS, ' ').strip
@@ -12,7 +12,7 @@ class Eco::API::UseCases::DefaultCases::CsvToTree
12
12
  nodes.tap do |nodes|
13
13
  prev_nodes = Array(1..11).zip(Array.new(11, nil)).to_h
14
14
  nodes.each do |node|
15
- if parent_node = prev_nodes[node.raw_level - 1]
15
+ if parent_node = prev_nodes[node.actual_level - 1]
16
16
  node.parentId = parent_node.id
17
17
  end
18
18
  prev_nodes[node.raw_level] = node
@@ -15,10 +15,16 @@ class Eco::API::UseCases::GraphQL::Base < Eco::API::Common::Loaders::UseCase
15
15
  raise "You need to inherit from this class ('#{self.class}') and call super with a block"
16
16
  end
17
17
 
18
+ private
19
+
18
20
  def graphql
19
21
  @graphql ||= session.api(version: :graphql)
20
22
  end
21
23
 
24
+ def simulate?
25
+ options.dig(:simulate)
26
+ end
27
+
22
28
  def exit_error(msg)
23
29
  logger.error(msg)
24
30
  exit(1)
@@ -0,0 +1,4 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers::Locations
2
+ class Commands
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Eco::API::UseCases::GraphQL::Helpers
2
+ module Locations
3
+ end
4
+ end
5
+
6
+ require_relative 'locations/commands'
@@ -0,0 +1,6 @@
1
+ module Eco::API::UseCases::GraphQL
2
+ module Helpers
3
+ end
4
+ end
5
+
6
+ require_relative 'helpers/locations'
@@ -8,3 +8,4 @@ module Eco
8
8
  end
9
9
 
10
10
  require_relative 'graphql/base'
11
+ require_relative 'graphql/helpers'
@@ -9,9 +9,44 @@ module Eco
9
9
  base.extend(ClassMethods)
10
10
  end
11
11
  end
12
-
13
- module InstanceMethods
14
12
 
13
+ module InstanceMethods
14
+ include Eco::Language::AuxiliarLogger
15
+
16
+ # It offers a resilient way to read content from a file
17
+ # @tolerance [Integer] the number of allowed encoding errors.
18
+ # @return [String] the content of the file
19
+ def get_file_content(file, encoding, tolerance: 5)
20
+ unless self.class.file_exists?(file)
21
+ logger.error("File does not exist: #{file}")
22
+ exit(1)
23
+ end
24
+ encoding ||= self.class.encoding(file)
25
+ encoding = (encoding == "bom") ? "#{encoding}|utf-8": encoding
26
+ unless !encoding || encoding == 'utf-8'
27
+ msg = "File encoding: '#{encoding}'"
28
+ logger.debug(msg)
29
+ puts msg
30
+ end
31
+ read_with_tolerance(file, encoding: encoding, tolerance: tolerance)
32
+ end
33
+
34
+ def read_with_tolerance(file, encoding:, tolerance: 5)
35
+ if content = File.read(file, encoding: encoding)
36
+ content = content.encode("utf-8") unless encoding.include?('utf-8')
37
+ content.scrub do |bytes|
38
+ replacement = '<' + bytes.unpack('H*')[0] + '>'
39
+ if tolerance <= 0
40
+ logger.error("There were more than 5 encoding errors in the file '#{file}'.")
41
+ return content
42
+ else
43
+ tolerance -= 1
44
+ logger.error("Encoding problem in file '#{file}': '#{replacement}'.")
45
+ replacement
46
+ end
47
+ end
48
+ end
49
+ end
15
50
  end
16
51
 
17
52
  module ClassMethods
@@ -86,12 +121,28 @@ module Eco
86
121
  path = File.dirname($0)
87
122
  File.join(path, basename)
88
123
  end
124
+
125
+ def folder_files(folder = ".", pattern = "*", regexp: nil, older_than: nil)
126
+ target = File.join(File.expand_path(folder), pattern)
127
+ Dir[target].tap do |dir_files|
128
+ dir_files.select! {|f| File.file?(f)}
129
+ if older_than
130
+ dir_files.select! {|f| File.mtime(f) < (Time.now - (60*60*24*older_than))}
131
+ end
132
+ if regexp && regexp.is_a?(Regexp)
133
+ dir_files.select! {|f| File.basename(f).match(regexp)}
134
+ end
135
+ end.sort
136
+ end
137
+
138
+ def csv_files(folder = ".", regexp: nil, older_than: nil)
139
+ folder_files(folder, "*.csv", regexp: regexp, older_than: older_than).sort
140
+ end
89
141
  end
90
142
 
91
143
  class << self
92
144
  include Files::ClassMethods
93
145
  end
94
-
95
146
  end
96
147
  end
97
148
  end
@@ -0,0 +1,154 @@
1
+ module Eco
2
+ module Data
3
+ module Hashes
4
+ class ArrayDiff
5
+ extend Eco::Language::Models::ClassHelpers
6
+
7
+ class << self
8
+ def key(value = nil)
9
+ return @key unless value
10
+ @key = value.to_s
11
+ end
12
+
13
+ def key?
14
+ !!@key
15
+ end
16
+
17
+ def compare(*attrs)
18
+ compared_attrs.push(*attrs.map(&:to_s)).uniq!
19
+ end
20
+
21
+ def case_sensitive(value = nil)
22
+ @case_sensitive = false unless instance_variable_defined?(:@case_sensitive)
23
+ return @case_sensitive unless value
24
+ @case_sensitive = !!value
25
+ end
26
+
27
+ def case_sensitive?
28
+ !!@case_sensitive
29
+ end
30
+
31
+ def compared_attrs
32
+ @compared_attrs ||= []
33
+ @compared_attrs
34
+ end
35
+ end
36
+
37
+ attr_reader :source1, :source2
38
+ attr_reader :src_h1, :src_h2
39
+ attr_reader :logger
40
+
41
+ class_resolver :diff_result_class, "Eco::Data::Hash::DiffResult"
42
+
43
+ def initialize(source1, source2, logger: ::Logger.new(IO::NULL), **kargs)
44
+ @logger = logger
45
+ @options = kargs
46
+ @source1 = source1
47
+ @source2 = source2
48
+ @src_h1 = by_key(source1)
49
+ @src_h2 = by_key(source2)
50
+ raise "Missing source1" unless !!self.src_h1
51
+ raise "Missing source2" unless !!self.src_h2
52
+ end
53
+
54
+ # @note
55
+ # - A `Hash::DiffResult` object, offers `hash_diff` with the attrs that have changed value
56
+ # - It also allows to know the original value
57
+ # @return [Hash] where `key` is the key of the record, and `value` a `DiffResult` object
58
+ def diffs
59
+ @diffs ||= paired_sources.each_with_object([]) do |(src1, src2), diffs|
60
+ args = {
61
+ key: key,
62
+ compare: compared_attrs,
63
+ case_sensitive: case_sensitive?
64
+ }
65
+ diff_result_class.new(src1, src2, **args).tap do |res|
66
+ diffs << res if res.diff?
67
+ end
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ # It pairs the hashes of `source1` and `source2`
74
+ # @note
75
+ # - It also ensures they are in their Hash form (with string keys)
76
+ # - This will merge entries of the same source that hold the same `key` attr value (latest wins)
77
+ def paired_sources
78
+ keys1 = src_h1.keys; keys2 = src_h2.keys
79
+ all_keys = keys1 | keys2
80
+ all_keys.map {|key| [src_h1[key], src_h2[key]]}
81
+ end
82
+
83
+ def key
84
+ @key ||= options_or(:key) do
85
+ self.class.key
86
+ end.tap do |k|
87
+ raise "missing main key attr to pair records. Given: #{k}" unless k.is_a?(String)
88
+ end
89
+ end
90
+
91
+ def case_sensitive?
92
+ @case_sensitive ||= options_or(:case_sensitive) { self.class.case_sensitive? }
93
+ end
94
+
95
+ def compared_attrs
96
+ @compared_attrs ||= options_or(:compared_attrs) do
97
+ self.class.compared_attrs
98
+ end.yield_self do |attrs|
99
+ raise "compared_attrs should be an array" unless attrs.is_a?(Array)
100
+ attrs.map(&:to_s)
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def options_or(opt)
107
+ opt = opt.to_sym
108
+ return @options[opt] if @options.key?(opt)
109
+ yield
110
+ end
111
+
112
+ def symbolize_keys(hash)
113
+ hash.each_with_object({}) do |(k, v), h|
114
+ h[k.to_sym] = v
115
+ end
116
+ end
117
+
118
+ def stringify_keys(hash)
119
+ hash.each_with_object({}) do |(k, v), h|
120
+ h[k.to_s] = v
121
+ end
122
+ end
123
+
124
+ def by_key(content)
125
+ to_array_of_hashes(content).each_with_object({}) do |item, out|
126
+ out[item[key]] = item
127
+ end
128
+ end
129
+
130
+ def to_array_of_hashes(content)
131
+ case content
132
+ when Hash
133
+ logger.error("Input data as 'Hash' not supported. Expecting 'Enumerable' or 'String'")
134
+ exit(1)
135
+ when String
136
+ to_array_of_hashes(Eco::CSV.parse(content))
137
+ when Enumerable
138
+ sample = content.to_a.first
139
+ case sample
140
+ when Hash, Array, ::CSV::Row
141
+ Eco::CSV::Table.new(content).to_array_of_hashes
142
+ else
143
+ logger.error("Input content 'Array' of '#{sample.class}' is not supported.")
144
+ exit(1)
145
+ end
146
+ else
147
+ logger.error("Could not obtain any data out content: '#{content.class}'")
148
+ exit(1)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,103 @@
1
+ module Eco
2
+ module Data
3
+ module Hashes
4
+ class DiffResult
5
+
6
+ attr_reader :key
7
+ attr_reader :src1, :src2
8
+
9
+ # @param [Array<String>, sym]
10
+ # - `:all` compares the matching attrs between both hashes only
11
+ def initialize(src1, src2, key:, compare: :all, case_sensitive: false)
12
+ @key = key
13
+ @compare = compare
14
+ @case_sensitive = case_sensitive
15
+ @src1 = src1
16
+ @src2 = src2
17
+ end
18
+
19
+ def new?
20
+ !src1 && !!src2
21
+ end
22
+
23
+ def del?
24
+ !!src1 && !src2
25
+ end
26
+
27
+ def update?
28
+ !new? && !del? && diff?
29
+ end
30
+
31
+ def diff?
32
+ new? || del? || !diff_attrs.empty?
33
+ end
34
+
35
+ # Is the key attr value changing?
36
+ def key?
37
+ !(new? || del?) && diff_attr?(key)
38
+ end
39
+
40
+ def diff_attr?(attr)
41
+ return true if new?
42
+ return true if del?
43
+ diff_attrs.include?(attr.to_s)
44
+ end
45
+
46
+ def attr(attr)
47
+ return nil unless src2
48
+ src2[attr.to_s]
49
+ end
50
+
51
+ def attr_prev(attr)
52
+ return nil unless src1
53
+ src1[attr.to_s]
54
+ end
55
+
56
+ def previous(attr)
57
+ src1 && src1[attr.to_s]
58
+ end
59
+
60
+ def diff_hash
61
+ target_attrs = [key] | compared_attrs
62
+ return src2.slice(*target_attrs) if new?
63
+ return src1.slice(key) if del?
64
+ src2.slice(key, *diff_attrs)
65
+ end
66
+
67
+ def diff_attrs
68
+ @diff_attrs ||= comparable_attrs.each_with_object([]) do |attr, out|
69
+ out << attr unless eq?(src1[attr], src2[attr])
70
+ end
71
+ end
72
+
73
+ def eq?(val1, val2)
74
+ return true if val1 == val2
75
+ return false if case_sensitive?
76
+ return false if !val2 || !val1
77
+ val1.upcase == val2.upcase
78
+ end
79
+
80
+ def case_sensitive?
81
+ !!@case_sensitive
82
+ end
83
+
84
+ def comparable_attrs
85
+ return [] if new? || del?
86
+ compared_attrs
87
+ end
88
+
89
+ def compared_attrs
90
+ return @compared_attrs if instance_variable_defined?(:@compared_attrs)
91
+ @compared_attrs = \
92
+ if @compare == :all
93
+ src1.keys & src2.keys
94
+ elsif @compare.is_a?(Array)
95
+ @compare.map(&:to_s)
96
+ else
97
+ raise "Expecting 'compare' to be sym (:all) or Array<String>. Given: #{@compare.class}"
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,9 @@
1
+ module Eco
2
+ module Data
3
+ module Hashes
4
+ end
5
+ end
6
+ end
7
+
8
+ require_relative 'hashes/diff_result'
9
+ require_relative 'hashes/array_diff'
data/lib/eco/data.rb CHANGED
@@ -7,3 +7,4 @@ require_relative 'data/crypto'
7
7
  require_relative 'data/files'
8
8
  require_relative 'data/mapper'
9
9
  require_relative 'data/fuzzy_match'
10
+ require_relative 'data/hashes'
@@ -0,0 +1,19 @@
1
+ module Eco
2
+ module Language
3
+ # Some modules/classes use logger, but they may not be connected to session.
4
+ # This prevents errors with this.
5
+ module AuxiliarLogger
6
+ def logger
7
+ if defined?(super)
8
+ super
9
+ elsif respond_to?(:session)
10
+ session.logger
11
+ elsif instance_variable_defined?(:@session)
12
+ @session.logger
13
+ else
14
+ @logger ||= ::Logger.new(IO::NULL)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,134 @@
1
+ module Eco
2
+ module Language
3
+ module Models
4
+ module ClassHelpers
5
+ NOT_USED = "no_used!"
6
+
7
+ def redef_without_warning(const, value)
8
+ self.class.send(:remove_const, const) if self.class.const_defined?(const)
9
+ self.class.const_set(const, value)
10
+ end
11
+
12
+ def class_resolver(name, klass)
13
+ define_singleton_method(name) { resolve_class(klass) }
14
+ define_method(name) { self.class.resolve_class(klass) }
15
+ end
16
+
17
+ # Class resolver
18
+ # @note it caches the resolved `klass`es
19
+ # @raise [Exception] when could not resolve if `exception` is `true`
20
+ # @param klass [Class, String, Symbol] the class to resolve
21
+ # @param source_class [Class] when the reference to `klass` belongs to a different inheritance chain.
22
+ # @param exception [Boolean] if it should raise exception when could not resolve
23
+ # @return [Class] the `Class` constant
24
+ def resolve_class(klass, source_class: self, exception: true)
25
+ @resolved ||= {}
26
+ @resolved[klass] ||=
27
+ case klass
28
+ when Class
29
+ klass
30
+ when String
31
+ begin
32
+ Kernel.const_get(klass)
33
+ rescue NameError => e
34
+ raise if exception
35
+ end
36
+ when Symbol
37
+ source_class.resolve_class(source_class.send(klass))
38
+ when Hash
39
+ referrer, referred = klass.first
40
+ resolve_class(referred, source_class: referrer, exception: exception)
41
+ else
42
+ raise "Unknown class: #{klass}" if exception
43
+ end
44
+ end
45
+
46
+ # Helper to normalize `key` into a correct `ruby` **constant name**
47
+ # @note it removes namespace syntax `::`
48
+ # @param key [String, Symbol] to be normalized
49
+ # @return [String] a correct constant name
50
+ def to_constant(key)
51
+ str_name = key.to_s.strip.split(/::/).compact.map do |str|
52
+ str.slice(0).upcase + str.slice(1..-1)
53
+ end.join("").split(/[\-\_ :]+/i).compact.map do |str|
54
+ str.slice(0).upcase + str.slice(1..-1)
55
+ end.join("")
56
+ end
57
+
58
+ # Helper to create an instance variable `name`
59
+ # @param [String, Symbol] the name of the variable
60
+ # @reutrn [String] the name of the created instance variable
61
+ def instance_variable_name(name)
62
+ str = name.to_s
63
+ str = "@#{str}" unless str.start_with?("@")
64
+ str
65
+ end
66
+
67
+ # If the class for `name` exists, it returns it. Otherwise it generates it.
68
+ # @param name [String, Symbol] the name of the new class
69
+ # @param inherits [Class] the parent class to _inherit_ from
70
+ # @param namespace [Class, String] an existing `constant` (class or module) the new class will be namespaced on
71
+ # @yield [child_class] configure the new class
72
+ # @yieldparam child_class [Class] the new class
73
+ # @return [Class] the new generated class
74
+ def new_class(name = "Child#{uid}", inherits: self, namespace: inherits)
75
+ name = name.to_s.to_sym.freeze
76
+ class_name = to_constant(name)
77
+
78
+ unless target_class = resolve_class("#{namespace}::#{class_name}", exception: false)
79
+ target_class = Class.new(inherits)
80
+ Kernel.const_get(namespace.to_s).const_set class_name, target_class
81
+ end
82
+
83
+ target_class.tap do |klass|
84
+ yield(klass) if block_given?
85
+ end
86
+ end
87
+
88
+ # Helper to determine if a paramter has been used
89
+ # @note to effectivelly use this helper, you should initialize your target
90
+ # paramters with the constant `NOT_USED`
91
+ # @param val [] the value of the paramter
92
+ # @return [Boolean] `true` if value other than `NOT_USED`, `false` otherwise
93
+ def used_param?(val)
94
+ val != NOT_USED
95
+ end
96
+
97
+ # Keeps track on class instance variables that should be inherited by child classes.
98
+ # @note
99
+ # - subclasses will inherit the value as is at that moment
100
+ # - any change afterwards will be only on the specific class (in line with class instance variables)
101
+ # - adapted from https://stackoverflow.com/a/10729812/4352306
102
+ # TODO: this separates the logic of the method to the instance var. Think if would be possible to join them somehow.
103
+ def inheritable_class_vars(*vars)
104
+ @inheritable_class_vars ||= [:inheritable_class_vars]
105
+ @inheritable_class_vars += vars
106
+ end
107
+
108
+ # Builds the attr_reader and attr_writer of `attrs` and registers the associated instance variable as inheritable.
109
+ def inheritable_attrs(*attrs)
110
+ attrs.each do |attr|
111
+ class_eval %(
112
+ class << self; attr_accessor :#{attr} end
113
+ )
114
+ end
115
+ inheritable_class_vars(*attrs)
116
+ end
117
+
118
+ # This callback method is called whenever a subclass of the current class is created.
119
+ # @note
120
+ # - values of the instance variables are copied as they are (no dups or clones)
121
+ # - the above means: avoid methods that change the state of the mutable object on it
122
+ # - mutating methods would reflect the changes on other classes as well
123
+ # - therefore, `freeze` will be called on the values that are inherited.
124
+ def inherited(subclass)
125
+ inheritable_class_vars.each do |var|
126
+ instance_var = instance_variable_name(var)
127
+ value = instance_variable_get(instance_var)
128
+ subclass.instance_variable_set(instance_var, value.freeze)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -5,6 +5,7 @@ module Eco
5
5
  end
6
6
  end
7
7
 
8
+ require_relative 'models/class_helpers'
8
9
  require_relative 'models/modifier'
9
10
  require_relative 'models/collection'
10
11
  require_relative 'models/parser_serializer'
data/lib/eco/language.rb CHANGED
@@ -10,3 +10,4 @@ require_relative 'language/hash_transform'
10
10
  require_relative 'language/values_at'
11
11
  require_relative 'language/match_modifier'
12
12
  require_relative 'language/match'
13
+ require_relative 'language/auxiliar_logger'
data/lib/eco/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Eco
2
- VERSION = "2.4.4"
2
+ VERSION = "2.4.6"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eco-helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.4
4
+ version: 2.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oscar Segura
@@ -156,7 +156,7 @@ dependencies:
156
156
  requirements:
157
157
  - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: 0.3.1
159
+ version: 0.3.3
160
160
  - - "<"
161
161
  - !ruby/object:Gem::Version
162
162
  version: '0.4'
@@ -166,7 +166,7 @@ dependencies:
166
166
  requirements:
167
167
  - - ">="
168
168
  - !ruby/object:Gem::Version
169
- version: 0.3.1
169
+ version: 0.3.3
170
170
  - - "<"
171
171
  - !ruby/object:Gem::Version
172
172
  version: '0.4'
@@ -591,6 +591,9 @@ files:
591
591
  - lib/eco/api/usecases/default_cases/upsert_case.rb
592
592
  - lib/eco/api/usecases/graphql.rb
593
593
  - lib/eco/api/usecases/graphql/base.rb
594
+ - lib/eco/api/usecases/graphql/helpers.rb
595
+ - lib/eco/api/usecases/graphql/helpers/locations.rb
596
+ - lib/eco/api/usecases/graphql/helpers/locations/commands.rb
594
597
  - lib/eco/api/usecases/ooze_cases.rb
595
598
  - lib/eco/api/usecases/ooze_cases/export_register_case.rb
596
599
  - lib/eco/api/usecases/ooze_samples.rb
@@ -657,14 +660,19 @@ files:
657
660
  - lib/eco/data/fuzzy_match/score.rb
658
661
  - lib/eco/data/fuzzy_match/stop_words.rb
659
662
  - lib/eco/data/fuzzy_match/string_helpers.rb
663
+ - lib/eco/data/hashes.rb
664
+ - lib/eco/data/hashes/array_diff.rb
665
+ - lib/eco/data/hashes/diff_result.rb
660
666
  - lib/eco/data/mapper.rb
661
667
  - lib/eco/language.rb
668
+ - lib/eco/language/auxiliar_logger.rb
662
669
  - lib/eco/language/curry.rb
663
670
  - lib/eco/language/hash_transform.rb
664
671
  - lib/eco/language/hash_transform_modifier.rb
665
672
  - lib/eco/language/match.rb
666
673
  - lib/eco/language/match_modifier.rb
667
674
  - lib/eco/language/models.rb
675
+ - lib/eco/language/models/class_helpers.rb
668
676
  - lib/eco/language/models/collection.rb
669
677
  - lib/eco/language/models/modifier.rb
670
678
  - lib/eco/language/models/parser_serializer.rb