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 +4 -4
- data/CHANGELOG.md +28 -1
- data/eco-helpers.gemspec +1 -1
- data/lib/eco/api/common/people/entry_factory.rb +6 -35
- data/lib/eco/api/common/session/file_manager.rb +0 -1
- data/lib/eco/api/organization/tag_tree.rb +16 -8
- data/lib/eco/api/session/config/tagtree.rb +5 -5
- data/lib/eco/api/session.rb +2 -2
- data/lib/eco/api/usecases/default_cases/csv_to_tree_case/helper.rb +6 -2
- data/lib/eco/api/usecases/default_cases/csv_to_tree_case/node.rb +20 -5
- data/lib/eco/api/usecases/default_cases/csv_to_tree_case/nodes_cleaner.rb +1 -1
- data/lib/eco/api/usecases/graphql/base.rb +6 -0
- data/lib/eco/api/usecases/graphql/helpers/locations/commands.rb +4 -0
- data/lib/eco/api/usecases/graphql/helpers/locations.rb +6 -0
- data/lib/eco/api/usecases/graphql/helpers.rb +6 -0
- data/lib/eco/api/usecases/graphql.rb +1 -0
- data/lib/eco/data/files/helpers.rb +54 -3
- data/lib/eco/data/hashes/array_diff.rb +154 -0
- data/lib/eco/data/hashes/diff_result.rb +103 -0
- data/lib/eco/data/hashes.rb +9 -0
- data/lib/eco/data.rb +1 -0
- data/lib/eco/language/auxiliar_logger.rb +19 -0
- data/lib/eco/language/models/class_helpers.rb +134 -0
- data/lib/eco/language/models.rb +1 -0
- data/lib/eco/language.rb +1 -0
- data/lib/eco/version.rb +1 -1
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c4f7620deee8791ef8b6bfa5552e6040657575fa2ee071efdf7ed76e588a998
|
4
|
+
data.tar.gz: a870243619d2b8842ef97525676097330b76a0686f4793f7c6720e4c658173aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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
|
-
|
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
|
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
|
@@ -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
|
43
|
-
@name
|
43
|
+
@id = id
|
44
|
+
@name = name
|
44
45
|
@row_nodes = @source
|
45
46
|
else
|
46
|
-
@id
|
47
|
-
@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:
|
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)
|
data/lib/eco/api/session.rb
CHANGED
@@ -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
|
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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
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)
|
@@ -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
|
data/lib/eco/data.rb
CHANGED
@@ -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
|
data/lib/eco/language/models.rb
CHANGED
data/lib/eco/language.rb
CHANGED
data/lib/eco/version.rb
CHANGED
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
|
+
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.
|
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.
|
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
|