eco-helpers 2.4.4 → 2.4.5
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 +4 -4
- data/CHANGELOG.md +16 -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 +14 -8
- data/lib/eco/api/session/config/tagtree.rb +4 -4
- data/lib/eco/api/session.rb +1 -1
- 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 +9 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 333d58d63c6ffad6b06865c89dce3e48a50347ba998361f34eb9a2e230ea83f2
|
4
|
+
data.tar.gz: 7be152e20c00765217b6650790dfd4aeba729bb614e6d0928a7e7ea82d94de33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cb79f7d5429bf4c3cd8b8b37cbbdb2b8208da2161559fb6a09b08a67cea1ded2d08355928af4ee136b39bc10fda3f82ece275f5991c65bfcf41c4290d7d7269
|
7
|
+
data.tar.gz: 78723001238f02cac999e2af5200e1e13f444572366291357a70ec951732ded65bd708be9b6c0259a9e6bf797fc5c803df17a28167d01b1bf1223900f8226cc7
|
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,27 @@
|
|
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.6] - 2023-03-xx
|
5
5
|
|
6
6
|
### Added
|
7
7
|
### Changed
|
8
8
|
### Fixed
|
9
9
|
|
10
|
+
## [2.4.5] - 2023-03-31
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- `Eco::API:Organization::TagTree` support for `archived` and `weight` properties
|
14
|
+
- `Eco::Data::Hashes::ArrayDiff` and `Eco::Data::Hashes::DiffResult`
|
15
|
+
- Enable easy comparison of array of hashes
|
16
|
+
- Input data can be diverse
|
17
|
+
|
18
|
+
### Fixed
|
19
|
+
- `Eco::API::UseCases::DefaultCases::CsvToTree`
|
20
|
+
- Fixed the missed alignment children that jump levels
|
21
|
+
- `Eco::API::UseCases::DefaultCases::CsvToTree::Node`
|
22
|
+
- `to_h`: attrs param was being ingnored.
|
23
|
+
|
24
|
+
|
10
25
|
## [2.4.4] - 2023-03-29
|
11
26
|
|
12
27
|
### Added
|
@@ -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,7 @@ 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, :archived, :weight
|
9
9
|
|
10
10
|
attr_reader :parent
|
11
11
|
attr_reader :nodes, :children_count
|
@@ -24,7 +24,7 @@ module Eco
|
|
24
24
|
# ]}]
|
25
25
|
# tree = TagTree.new(tree.to_json)
|
26
26
|
# @param tagtree [String] representation of the tagtree in json.
|
27
|
-
def initialize(tagtree = [], name: nil, id: nil, depth: -1, path: [], parent: nil, enviro: nil)
|
27
|
+
def initialize(tagtree = [], name: nil, id: nil, depth: -1, path: [], parent: nil, _weight: nil, enviro: nil)
|
28
28
|
@depth = depth
|
29
29
|
@parent = parent
|
30
30
|
|
@@ -39,25 +39,31 @@ module Eco
|
|
39
39
|
@enviro = enviro
|
40
40
|
|
41
41
|
if @source.is_a?(Array)
|
42
|
-
@id
|
43
|
-
@name
|
42
|
+
@id = id
|
43
|
+
@name = name
|
44
44
|
@row_nodes = @source
|
45
45
|
else
|
46
|
-
@id
|
47
|
-
@name
|
46
|
+
@id = @source.values_at('tag', 'id').compact.first&.upcase
|
47
|
+
@name = @source['name']
|
48
|
+
@archived = @source.fetch('archived', false)
|
49
|
+
@weight = @source.fetch('weight', _weight)
|
48
50
|
@row_nodes = @source['nodes'] || []
|
49
51
|
end
|
50
52
|
|
51
53
|
@path = path || []
|
52
54
|
@path.push(@id) unless top?
|
53
55
|
|
54
|
-
@nodes = @row_nodes.map do |cnode|
|
55
|
-
TagTree.new(cnode, depth: depth + 1, path: @path.dup, parent: self, enviro: @enviro)
|
56
|
+
@nodes = @row_nodes.map.with_index do |cnode, idx|
|
57
|
+
TagTree.new(cnode, depth: depth + 1, path: @path.dup, parent: self, _weight: idx, enviro: @enviro)
|
56
58
|
end
|
57
59
|
|
58
60
|
init_hashes
|
59
61
|
end
|
60
62
|
|
63
|
+
def archived?
|
64
|
+
@archived
|
65
|
+
end
|
66
|
+
|
61
67
|
# @return [Eco::API::Organization::TagTree]
|
62
68
|
def dup
|
63
69
|
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)
|
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)
|
26
26
|
@live_tree = trees.reject do |tree|
|
27
27
|
tree.empty?
|
28
28
|
end.max do |a,b|
|
@@ -36,12 +36,12 @@ 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)
|
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
47
|
next unless trees = graphql.currentOrganization.locationStructures(**kargs)
|
data/lib/eco/api/session.rb
CHANGED
@@ -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.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oscar Segura
|
@@ -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
|