eco-helpers 3.2.11 → 3.2.13
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 +33 -1
- data/lib/eco/api/usecases/default/locations/cli/tagtree_extract_cli.rb +5 -0
- data/lib/eco/api/usecases/default/locations/tagtree_extract_case.rb +28 -5
- data/lib/eco/api/usecases/default/utils/cli/merge_csv_cli.rb +27 -0
- data/lib/eco/api/usecases/default/utils/group_csv_case.rb +19 -15
- data/lib/eco/api/usecases/default/utils/merge_csv_case.rb +313 -0
- data/lib/eco/api/usecases/default/utils/split_csv_case.rb +6 -1
- data/lib/eco/api/usecases/default/utils.rb +1 -0
- data/lib/eco/api/usecases/graphql/helpers/location/command/result.rb +2 -2
- data/lib/eco/api/usecases/graphql/helpers/location/command/results.rb +2 -1
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_map.rb +5 -1
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap/tags_set.rb +6 -3
- data/lib/eco/api/usecases/graphql/helpers/location/tags_remap.rb +3 -2
- data/lib/eco/api/usecases/graphql/samples/location/command/dsl.rb +16 -6
- data/lib/eco/api/usecases/graphql/samples/location/command/service/tree_update.rb +2 -1
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/heading.rb +10 -2
- data/lib/eco/api/usecases/graphql/samples/location/service/tree_diff/convertible/parsing.rb +0 -6
- data/lib/eco/cli_default/options.rb +1 -1
- data/lib/eco/csv/split.rb +47 -19
- data/lib/eco/csv/stream.rb +51 -1
- data/lib/eco/csv.rb +6 -3
- data/lib/eco/version.rb +1 -1
- metadata +5 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 164a0d7e0bea8396208dae904e45bac439f288773a138e01869fe19ec9eafe21
|
|
4
|
+
data.tar.gz: b56add1010ab7feee79f58c8bac0835bc39f9bfceb8a56fcf1cb6aff176bdd1d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a584c9593325bbd6fa7ec08794de3d839bd675c719613899c25e7d3d53bbfb58b03bc33f703b0b38a906a0ce08c30ec31c11d180653e9181f25b510db3bc3dd
|
|
7
|
+
data.tar.gz: ddfd53f1a2e72cbbf4136ee4ebb25481e098512ac258ff21bfea188417a6824e22aab407f16cb3a3d966b21d320b6aa56659e71f0782accda71f921d5db24601
|
data/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [3.2.
|
|
5
|
+
## [3.2.14] - 2026-04-xx
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
|
|
@@ -10,6 +10,38 @@ All notable changes to this project will be documented in this file.
|
|
|
10
10
|
|
|
11
11
|
### Fixed
|
|
12
12
|
|
|
13
|
+
## [3.2.13] - 2026-04-15
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `-split-csv` case
|
|
18
|
+
- Allow custom split criteria via `splitter` named argument.
|
|
19
|
+
- `-merge-csv` case
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- improved `Stream` with methods `eof?` and `shift`
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Locations remap on RS update
|
|
28
|
+
- `-group-csv`: correct rows count
|
|
29
|
+
|
|
30
|
+
## [3.2.12] - 2026-01-19
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **support** for native CSV RS-update headers
|
|
35
|
+
- **removed** case insensitiveness on header names
|
|
36
|
+
- `-tagtree-extract` cli option `-structure-id`
|
|
37
|
+
- To be able to target only one structure
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Wrong option name `-default-tag` renamed to `-exclude-default-tag`
|
|
44
|
+
|
|
13
45
|
## [3.2.11] - 2025-12-04
|
|
14
46
|
|
|
15
47
|
### Fixed
|
|
@@ -26,6 +26,11 @@ class Eco::API::UseCases::Default::Locations::TagtreeExtract
|
|
|
26
26
|
options.deep_merge!(output: {indent: indent})
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
add_option('-structure-id', 'Target structure id') do |options|
|
|
30
|
+
id = SCR.get_arg('-structure-id', with_param: true)
|
|
31
|
+
options.deep_merge!(source: {structure_id: id})
|
|
32
|
+
end
|
|
33
|
+
|
|
29
34
|
add_option('-file-as-source', 'Use a file as locations input (rather than the live locations)') do |options|
|
|
30
35
|
file = SCR.get_file('-file-as-source', required: true, should_exist: true)
|
|
31
36
|
options.deep_merge!(source: {file: file})
|
|
@@ -21,7 +21,11 @@ class Eco::API::UseCases::Default::Locations::TagtreeExtract < Eco::API::UseCase
|
|
|
21
21
|
when :excel_tree, :excel_nodes
|
|
22
22
|
excel(output_filename) do |workbook|
|
|
23
23
|
tag_trees.each do |tree|
|
|
24
|
-
excel_sheet(
|
|
24
|
+
excel_sheet(
|
|
25
|
+
workbook,
|
|
26
|
+
compact_name(tree.name),
|
|
27
|
+
header: format == :excel_nodes
|
|
28
|
+
) do |sheet|
|
|
25
29
|
tree_extract(tree).each_with_index do |row, _idx|
|
|
26
30
|
sheet.append_row(row)
|
|
27
31
|
end
|
|
@@ -121,9 +125,18 @@ class Eco::API::UseCases::Default::Locations::TagtreeExtract < Eco::API::UseCase
|
|
|
121
125
|
end
|
|
122
126
|
|
|
123
127
|
def retrieve_live_trees
|
|
124
|
-
session.live_trees(
|
|
128
|
+
session.live_trees(
|
|
129
|
+
include_archived: include_archived?
|
|
130
|
+
).reject do |tree|
|
|
131
|
+
next false if tree.id == tagtree_id
|
|
132
|
+
next true if tagtree_id
|
|
133
|
+
|
|
125
134
|
tree.empty?.tap do |rejected|
|
|
126
|
-
|
|
135
|
+
next unless rejected
|
|
136
|
+
|
|
137
|
+
log(:warn) {
|
|
138
|
+
"Tree '#{tree.name}' does NOT have location nodes. Skipping..."
|
|
139
|
+
}
|
|
127
140
|
end
|
|
128
141
|
end
|
|
129
142
|
end
|
|
@@ -138,6 +151,10 @@ class Eco::API::UseCases::Default::Locations::TagtreeExtract < Eco::API::UseCase
|
|
|
138
151
|
options.dig(:input, :file, :encoding) || 'utf-8'
|
|
139
152
|
end
|
|
140
153
|
|
|
154
|
+
def tagtree_id
|
|
155
|
+
options.dig(:source, :structure_id)
|
|
156
|
+
end
|
|
157
|
+
|
|
141
158
|
def file_as_source?
|
|
142
159
|
return true unless file_as_source.nil?
|
|
143
160
|
|
|
@@ -163,11 +180,17 @@ class Eco::API::UseCases::Default::Locations::TagtreeExtract < Eco::API::UseCase
|
|
|
163
180
|
end
|
|
164
181
|
|
|
165
182
|
def output_filename(name = self.class::OUT_FILENAME)
|
|
166
|
-
File.join(
|
|
183
|
+
File.join(
|
|
184
|
+
output_folder,
|
|
185
|
+
"#{timestamp}_#{config.active_enviro}_#{name}.#{output_file_format}"
|
|
186
|
+
)
|
|
167
187
|
end
|
|
168
188
|
|
|
169
189
|
def output_folder
|
|
170
|
-
|
|
190
|
+
File.join(
|
|
191
|
+
config.active_enviro,
|
|
192
|
+
self.class::OUT_FOLDER
|
|
193
|
+
)
|
|
171
194
|
end
|
|
172
195
|
|
|
173
196
|
def timestamp(date = Time.now)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Eco::API::UseCases::Default::Utils::MergeCsv
|
|
2
|
+
class Cli < Eco::API::UseCases::Cli
|
|
3
|
+
str_desc = 'Merges the csv rows by a pivot field. '
|
|
4
|
+
str_desc << 'It assumes the pivot field is sorted '
|
|
5
|
+
str_desc << '(same values should be consecutive)'
|
|
6
|
+
|
|
7
|
+
desc str_desc
|
|
8
|
+
|
|
9
|
+
callback do |_session, options, _usecase|
|
|
10
|
+
if (file = SCR.get_file(cli_name, required: true, should_exist: true))
|
|
11
|
+
options.deep_merge!(input: {file: {name: file}})
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
add_option('-merge', 'The CSV file that should be merged onto the original') do |options|
|
|
16
|
+
if (file = SCR.get_file('-merge', required: true, should_exist: true))
|
|
17
|
+
options.deep_merge!(input: {merge_file: {name: file}})
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
add_option('-by', 'The column that should be used to merge') do |options|
|
|
22
|
+
if (file = SCR.get_arg('-by', with_param: true))
|
|
23
|
+
options.deep_merge!(input: {merge_by_field: file})
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -27,7 +27,6 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def generate_file # rubocop:disable Metrics/AbcSize
|
|
30
|
-
row_count = 0
|
|
31
30
|
in_index = nil
|
|
32
31
|
|
|
33
32
|
CSV.open(output_filename, 'wb') do |out_csv|
|
|
@@ -49,24 +48,19 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
49
48
|
next unless pivotable?(row, idx)
|
|
50
49
|
next unless (last_group = pivot_row(row))
|
|
51
50
|
|
|
52
|
-
row_count
|
|
53
|
-
|
|
54
|
-
if (row_count % 500).zero?
|
|
55
|
-
print "... Done #{row_count} rows \r"
|
|
56
|
-
$stdout.flush
|
|
57
|
-
end
|
|
51
|
+
row_count!
|
|
58
52
|
|
|
59
53
|
out_csv << last_group.values_at(*headers)
|
|
60
54
|
end
|
|
61
55
|
|
|
62
56
|
# finalize
|
|
63
|
-
if (
|
|
64
|
-
row_count
|
|
65
|
-
out_csv <<
|
|
57
|
+
if (l_row = pivot_row)
|
|
58
|
+
row_count!
|
|
59
|
+
out_csv << l_row.values_at(*headers)
|
|
66
60
|
end
|
|
67
61
|
ensure
|
|
68
62
|
msg = "Generated file '#{output_filename}' "
|
|
69
|
-
msg << "with #{row_count} rows (out of #{in_index})."
|
|
63
|
+
msg << "with #{row_count} rows (out of #{in_index + 1})."
|
|
70
64
|
|
|
71
65
|
log(:info) { msg } unless simulate?
|
|
72
66
|
end
|
|
@@ -97,7 +91,7 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
97
91
|
last unless last == @group
|
|
98
92
|
end
|
|
99
93
|
|
|
100
|
-
attr_reader :group
|
|
94
|
+
attr_reader :group, :row_count
|
|
101
95
|
attr_reader :headers, :headers_rest
|
|
102
96
|
|
|
103
97
|
def headers!(row)
|
|
@@ -112,11 +106,21 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
112
106
|
instance_variable_defined?(:@headers)
|
|
113
107
|
end
|
|
114
108
|
|
|
109
|
+
def row_count!
|
|
110
|
+
@row_count ||= 0
|
|
111
|
+
(@row_count += 1).tap do |cnt|
|
|
112
|
+
if (cnt % 500).zero?
|
|
113
|
+
print "... Done #{cnt} rows \r"
|
|
114
|
+
$stdout.flush
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
115
119
|
def pivotable?(row, idx)
|
|
116
120
|
return true unless row[group_by_field].to_s.strip.empty?
|
|
117
121
|
|
|
118
122
|
msg = "Row #{idx} doesn't have value for pivot field '#{group_by_field}'"
|
|
119
|
-
msg << '. Skipping (
|
|
123
|
+
msg << '. Skipping (discarded) ...'
|
|
120
124
|
log(:warn) { msg }
|
|
121
125
|
false
|
|
122
126
|
end
|
|
@@ -130,7 +134,7 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
130
134
|
end
|
|
131
135
|
|
|
132
136
|
def start_at
|
|
133
|
-
return
|
|
137
|
+
return unless (num = options.dig(:input, :file, :start_at))
|
|
134
138
|
|
|
135
139
|
num = num.to_i
|
|
136
140
|
num = nil if num.zero?
|
|
@@ -138,7 +142,7 @@ class Eco::API::UseCases::Default::Utils::GroupCsv < Eco::API::Custom::UseCase
|
|
|
138
142
|
end
|
|
139
143
|
|
|
140
144
|
def output_filename
|
|
141
|
-
return
|
|
145
|
+
return unless input_name
|
|
142
146
|
|
|
143
147
|
File.join(input_dir, "#{input_name}_grouped#{input_ext}")
|
|
144
148
|
end
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# This script assumes that for the `MERGE_BY_FIELD` rows are consecutive.
|
|
2
|
+
# @note you might run first the `sort-csv` case.
|
|
3
|
+
# @note at the moment, it does NOT add new fields from the merge file.
|
|
4
|
+
# It only uses the headers of the original file.
|
|
5
|
+
# @note you must inherit from this case and define the constants.
|
|
6
|
+
#
|
|
7
|
+
# MERGE_BY_FIELD = 'target_csv_field'.freeze
|
|
8
|
+
# # those not merged are overridden
|
|
9
|
+
# JOINED_FIELDS = [
|
|
10
|
+
# 'joined_field_1',
|
|
11
|
+
# 'joined_field_2',
|
|
12
|
+
# 'joined_field_3',
|
|
13
|
+
# ].freeze
|
|
14
|
+
#
|
|
15
|
+
class Eco::API::UseCases::Default::Utils::MergeCsv < Eco::API::Custom::UseCase
|
|
16
|
+
name 'merge-csv'
|
|
17
|
+
type :other
|
|
18
|
+
|
|
19
|
+
require_relative 'cli/merge_csv_cli'
|
|
20
|
+
|
|
21
|
+
def main(*_args)
|
|
22
|
+
if simulate?
|
|
23
|
+
count = Eco::CSV.count(input_file)
|
|
24
|
+
log(:info) { "CSV '#{input_file}' has #{count} rows." }
|
|
25
|
+
else
|
|
26
|
+
generate_file
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def generate_file # rubocop:disable Metrics/AbcSize
|
|
33
|
+
in_index = nil
|
|
34
|
+
|
|
35
|
+
CSV.open(output_filename, 'wb') do |out_csv|
|
|
36
|
+
pending = false
|
|
37
|
+
first = true
|
|
38
|
+
m_first = true
|
|
39
|
+
row = nil
|
|
40
|
+
idx = nil
|
|
41
|
+
|
|
42
|
+
puts "\n"
|
|
43
|
+
|
|
44
|
+
streamed_merging.for_each do |m_row, m_idx|
|
|
45
|
+
if m_first
|
|
46
|
+
m_first = false
|
|
47
|
+
require_merge_by_field!(m_row, file: merge_file)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
next unless pivotable?(m_row, m_idx, file: merge_file)
|
|
51
|
+
|
|
52
|
+
merging_row(m_row)
|
|
53
|
+
merge_done = false
|
|
54
|
+
|
|
55
|
+
loop do
|
|
56
|
+
unless pending
|
|
57
|
+
row = nil
|
|
58
|
+
streamed_input.shift do |o_row, i|
|
|
59
|
+
idx = i
|
|
60
|
+
row = o_row
|
|
61
|
+
|
|
62
|
+
if first
|
|
63
|
+
first = false
|
|
64
|
+
headers!(row)
|
|
65
|
+
out_csv << headers
|
|
66
|
+
require_merge_by_field!(row, file: input_file)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
break unless row
|
|
72
|
+
|
|
73
|
+
in_index = idx
|
|
74
|
+
next unless pivotable?(row, idx, file: input_file)
|
|
75
|
+
|
|
76
|
+
row_count!
|
|
77
|
+
added = original_row(row) do |merged_row, merged:|
|
|
78
|
+
out_csv << merged_row.values_at(*headers)
|
|
79
|
+
merge_done = true if merged
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
pending = !added
|
|
83
|
+
|
|
84
|
+
break if merge_done
|
|
85
|
+
break unless added
|
|
86
|
+
break if streamed_input.eof?
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
row = nil unless pending
|
|
90
|
+
|
|
91
|
+
if pending || streamed_input.eof?
|
|
92
|
+
msg = "Could not merge row #{m_idx} (#{merging_row[merge_by_field]}) "
|
|
93
|
+
msg << "because the pivot value does not exist in the original file"
|
|
94
|
+
msg << ". Skipping (discarded) ..."
|
|
95
|
+
log(:warn) { msg }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# finalize
|
|
100
|
+
loop do
|
|
101
|
+
row = nil
|
|
102
|
+
streamed_input.shift do |o_row, i|
|
|
103
|
+
idx = i
|
|
104
|
+
row = o_row
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
break unless row
|
|
108
|
+
|
|
109
|
+
in_index = idx
|
|
110
|
+
next unless pivotable?(row, idx, file: input_file)
|
|
111
|
+
|
|
112
|
+
row_count!
|
|
113
|
+
out_csv << row.values_at(*headers)
|
|
114
|
+
|
|
115
|
+
break if streamed_input.eof?
|
|
116
|
+
end
|
|
117
|
+
ensure
|
|
118
|
+
msg = "Generated file '#{output_filename}' "
|
|
119
|
+
msg << "with #{row_count} rows (out of #{in_index + 1})."
|
|
120
|
+
|
|
121
|
+
log(:info) { msg } unless simulate?
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# It tracks the current merging row
|
|
126
|
+
# @return [Nil, Hash] the last merge row when `row` doesn't belong
|
|
127
|
+
# or `nil` otherwise
|
|
128
|
+
def merging_row(row = nil)
|
|
129
|
+
return @merging_row unless row
|
|
130
|
+
|
|
131
|
+
@merging_row = row.to_h
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# It tracks the current grouped row
|
|
135
|
+
# @return [Nil, Hash] the last grouped row when `row` doesn't belong
|
|
136
|
+
# or `nil` otherwise
|
|
137
|
+
def original_row(row)
|
|
138
|
+
pivot_value = row[merge_by_field]
|
|
139
|
+
merge_pivot = merging_row[merge_by_field]
|
|
140
|
+
|
|
141
|
+
if pivot_value > merge_pivot
|
|
142
|
+
# as both files are sorted, we can't add the original row now
|
|
143
|
+
# and we need to just return false
|
|
144
|
+
return false
|
|
145
|
+
elsif pivot_value < merge_pivot
|
|
146
|
+
yield(row.to_h, merged: false) if block_given?
|
|
147
|
+
return true
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
merged_row = {}
|
|
151
|
+
merged_row = {merge_by_field => pivot_value}
|
|
152
|
+
|
|
153
|
+
joined_fields.each do |field|
|
|
154
|
+
original_values = row[field].to_s.split('|').compact.uniq
|
|
155
|
+
merge_values = merging_row[field].to_s.split('|').compact.uniq
|
|
156
|
+
|
|
157
|
+
merged_row[field] = (original_values | merge_values).join('|')
|
|
158
|
+
merged_row[field] = nil if merged_row[field].to_s.strip.empty?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
headers_rest.each do |field|
|
|
162
|
+
merged_row[field] = row[field]
|
|
163
|
+
merged_row[field] = merging_row[field] if merging_row.key?(field)
|
|
164
|
+
merged_row[field] = nil if merged_row[field].to_s.strip.empty?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
missed_headers = (merging_row.keys - headers)
|
|
168
|
+
if missed_headers.any? && !warned_missed_headers?
|
|
169
|
+
msg = "Missing headers in merged file: #{missed_headers.join(', ')}"
|
|
170
|
+
log(:warn) { msg }
|
|
171
|
+
@warned_missed_headers = true
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
merged_row = merged_row.slice(*headers)
|
|
175
|
+
yield(merged_row, merged: true) if block_given?
|
|
176
|
+
|
|
177
|
+
true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
attr_reader :merge, :row_count
|
|
181
|
+
attr_reader :headers, :headers_rest
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Whether if we already warned about merging headers that
|
|
185
|
+
# are not in the original
|
|
186
|
+
def warned_missed_headers?
|
|
187
|
+
@warned_missed_headers ||= false
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def headers!(row)
|
|
191
|
+
return if headers?
|
|
192
|
+
|
|
193
|
+
@headers = row.to_h.keys
|
|
194
|
+
@joined_fields = @headers & joined_fields
|
|
195
|
+
@headers_rest = @headers - @joined_fields - [merge_by_field]
|
|
196
|
+
@headers = [merge_by_field, *@joined_fields, *@headers_rest]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def headers?
|
|
200
|
+
instance_variable_defined?(:@headers)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def row_count!
|
|
204
|
+
@row_count ||= 0
|
|
205
|
+
(@row_count += 1).tap do |cnt|
|
|
206
|
+
if (cnt % 500).zero?
|
|
207
|
+
print "... Done #{cnt} rows \r"
|
|
208
|
+
$stdout.flush
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def pivotable?(row, idx, file:)
|
|
214
|
+
return false if row.nil?
|
|
215
|
+
return true unless row[merge_by_field].to_s.strip.empty?
|
|
216
|
+
|
|
217
|
+
msg = "Row #{idx} doesn't have value for pivot field '#{merge_by_field}'"
|
|
218
|
+
msg << " (file: '#{file}'). Skipping (discarded) ..."
|
|
219
|
+
log(:warn) { msg }
|
|
220
|
+
false
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def streamed_input
|
|
224
|
+
@streamed_input ||= Eco::CSV::Stream.new(input_file)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def streamed_merging
|
|
228
|
+
@streamed_merging ||= Eco::CSV::Stream.new(merge_file)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def input_file
|
|
232
|
+
options.dig(:input, :file, :name)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def merge_file
|
|
236
|
+
options.dig(:input, :merge_file, :name)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def output_filename
|
|
240
|
+
return unless input_name
|
|
241
|
+
|
|
242
|
+
File.join(input_dir, "#{input_name}_merged#{input_ext}")
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def input_name
|
|
246
|
+
@input_name ||= File.basename(input_basename, input_ext)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def input_ext
|
|
250
|
+
@input_ext ||= input_basename.split('.')[1..].join('.').then do |name|
|
|
251
|
+
".#{name}"
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def input_basename
|
|
256
|
+
@input_basename ||= File.basename(input_full_filename)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def input_dir
|
|
260
|
+
@input_dir = File.dirname(input_full_filename)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def input_full_filename
|
|
264
|
+
@input_full_filename ||= File.expand_path(input_file)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def require_merge_by_field!(row, file:)
|
|
268
|
+
return true if row.key?(merge_by_field)
|
|
269
|
+
|
|
270
|
+
msg = "Pivot field '#{merge_by_field}' missing in header of file '#{file}'"
|
|
271
|
+
log(:error) { msg }
|
|
272
|
+
raise msg
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def merge_by_field
|
|
276
|
+
return @merge_by_field if instance_variable_defined?(:@merge_by_field)
|
|
277
|
+
|
|
278
|
+
return (@merge_by_field = opts_merge_by) if opts_merge_by
|
|
279
|
+
|
|
280
|
+
unless self.class.const_defined?(:MERGE_BY_FIELD)
|
|
281
|
+
msg = "(#{self.class}) You must define MERGE_BY_FIELD constant"
|
|
282
|
+
log(:error) { msg }
|
|
283
|
+
raise msg
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
@merge_by_field = self.class::MERGE_BY_FIELD
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def joined_fields
|
|
290
|
+
return @joined_fields if instance_variable_defined?(:@joined_fields)
|
|
291
|
+
|
|
292
|
+
unless self.class.const_defined?(:JOINED_FIELDS)
|
|
293
|
+
msg = "(#{self.class}) You must define JOINED_FIELDS constant"
|
|
294
|
+
log(:error) { msg }
|
|
295
|
+
raise msg
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
@joined_fields ||= [self.class::JOINED_FIELDS].flatten.compact.tap do |flds|
|
|
299
|
+
next unless flds.empty?
|
|
300
|
+
|
|
301
|
+
log(:warn) {
|
|
302
|
+
msg = 'There were no fields to be joined (JOINED_FIELDS). '
|
|
303
|
+
msg << 'This means all fields present in the merging file '
|
|
304
|
+
msg << ' will be overridden in the original file.'
|
|
305
|
+
msg
|
|
306
|
+
}
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def opts_merge_by
|
|
311
|
+
options.dig(:input, :merge_by_field)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class Eco::API::UseCases::Default::Utils::SplitCsv < Eco::API::Common::Loaders::UseCase
|
|
2
2
|
require_relative 'cli/split_csv_cli'
|
|
3
3
|
|
|
4
|
-
MAX_ROWS =
|
|
4
|
+
MAX_ROWS = :unused
|
|
5
5
|
|
|
6
6
|
name 'split-csv'
|
|
7
7
|
type :other
|
|
@@ -15,6 +15,7 @@ class Eco::API::UseCases::Default::Utils::SplitCsv < Eco::API::Common::Loaders::
|
|
|
15
15
|
input_file,
|
|
16
16
|
max_rows: max_rows,
|
|
17
17
|
start_at: start_at,
|
|
18
|
+
**params,
|
|
18
19
|
&filter
|
|
19
20
|
).tap do |split|
|
|
20
21
|
msg = []
|
|
@@ -31,6 +32,10 @@ class Eco::API::UseCases::Default::Utils::SplitCsv < Eco::API::Common::Loaders::
|
|
|
31
32
|
|
|
32
33
|
private
|
|
33
34
|
|
|
35
|
+
def params
|
|
36
|
+
{}
|
|
37
|
+
end
|
|
38
|
+
|
|
34
39
|
def filter
|
|
35
40
|
nil
|
|
36
41
|
end
|
|
@@ -40,7 +40,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location::Command
|
|
|
40
40
|
return nil unless error?
|
|
41
41
|
|
|
42
42
|
msg = []
|
|
43
|
-
msg << "(#{
|
|
43
|
+
msg << "(#{command_type} '#{node_id}') #{error.message}"
|
|
44
44
|
|
|
45
45
|
feed = []
|
|
46
46
|
feed.concat(error.validationErrors.map(&:message)) unless error.validationErrors.empty?
|
|
@@ -55,7 +55,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location::Command
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
def command_input_data
|
|
58
|
-
input[
|
|
58
|
+
input[command_type]
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def command_id
|
|
@@ -53,7 +53,8 @@ module Eco::API::UseCases::GraphQL::Helpers::Location::Command
|
|
|
53
53
|
next applied unless with_id_change
|
|
54
54
|
|
|
55
55
|
applied.select do |result|
|
|
56
|
-
next false unless (command = result.command_result_data)
|
|
56
|
+
# next false unless (command = result.command_result_data)
|
|
57
|
+
next false unless (command = result.command_input_data)
|
|
57
58
|
|
|
58
59
|
command.keys.include?(:newId)
|
|
59
60
|
end
|
|
@@ -22,9 +22,10 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
22
22
|
# both are being moved (specific/long mappings first)
|
|
23
23
|
return 1 if from.subset_of?(other.from)
|
|
24
24
|
return -1 if from.superset_of?(other.from)
|
|
25
|
-
return -1
|
|
25
|
+
return -1 unless from.intersect?(other.from)
|
|
26
26
|
return -1 if from.length >= other.from.length
|
|
27
27
|
return 1 if from.length < other.from.length
|
|
28
|
+
|
|
28
29
|
-1
|
|
29
30
|
end
|
|
30
31
|
|
|
@@ -49,16 +50,19 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
49
50
|
def maps?
|
|
50
51
|
return false if any?(&:empty?)
|
|
51
52
|
return false if from == to
|
|
53
|
+
|
|
52
54
|
true
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
def rename?
|
|
56
58
|
return false unless maps?
|
|
59
|
+
|
|
57
60
|
both? {|set| set.length == 1}
|
|
58
61
|
end
|
|
59
62
|
|
|
60
63
|
def move?
|
|
61
64
|
return false unless maps?
|
|
65
|
+
|
|
62
66
|
!rename?
|
|
63
67
|
end
|
|
64
68
|
end
|
|
@@ -4,7 +4,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
4
4
|
class << self
|
|
5
5
|
def attr_compare(*attrs)
|
|
6
6
|
attrs.each do |attr|
|
|
7
|
-
meth = "#{attr}"
|
|
7
|
+
meth = :"#{attr}"
|
|
8
8
|
define_method meth do |value|
|
|
9
9
|
set.send(meth, to_set(value))
|
|
10
10
|
end
|
|
@@ -13,7 +13,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
13
13
|
|
|
14
14
|
def attr_operate(*attrs)
|
|
15
15
|
attrs.each do |attr|
|
|
16
|
-
meth = "#{attr}"
|
|
16
|
+
meth = :"#{attr}"
|
|
17
17
|
define_method meth do |value|
|
|
18
18
|
self.class.new(set.send(meth, to_set(value)))
|
|
19
19
|
end
|
|
@@ -57,6 +57,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
57
57
|
def include?(value)
|
|
58
58
|
value = value.to_s.strip
|
|
59
59
|
return false if value.empty?
|
|
60
|
+
|
|
60
61
|
set.include?(value)
|
|
61
62
|
end
|
|
62
63
|
|
|
@@ -82,7 +83,9 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
82
83
|
return value.ini_tags.dup if value.is_a?(self.class)
|
|
83
84
|
return value.dup if value.is_a?(Array)
|
|
84
85
|
return value.to_a if value.is_a?(Set)
|
|
85
|
-
|
|
86
|
+
|
|
87
|
+
msg = "Expecting #{self.class}, Set or Array. Given: #{value.class}"
|
|
88
|
+
raise ArgumentError, msg
|
|
86
89
|
end
|
|
87
90
|
|
|
88
91
|
def to_set(value)
|
|
@@ -22,7 +22,7 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def to_csv(filename)
|
|
25
|
-
CSV.open(filename,
|
|
25
|
+
CSV.open(filename, 'w') do |fd|
|
|
26
26
|
fd << %w[src_tags dst_tags]
|
|
27
27
|
|
|
28
28
|
each do |tags_map|
|
|
@@ -67,7 +67,8 @@ module Eco::API::UseCases::GraphQL::Helpers::Location
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def <<(pair)
|
|
70
|
-
|
|
70
|
+
msg = "Expecting pair of Array in Array. Given: #{pair}"
|
|
71
|
+
raise ArgumentError, msg unless self.class.correct_pair?(pair)
|
|
71
72
|
|
|
72
73
|
add(*pair)
|
|
73
74
|
end
|
|
@@ -76,6 +76,8 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
|
76
76
|
) do |input, stage|
|
|
77
77
|
next unless input
|
|
78
78
|
|
|
79
|
+
self.id_name_input = input if simulate? && stage == :id_name
|
|
80
|
+
|
|
79
81
|
some_update = true
|
|
80
82
|
|
|
81
83
|
sliced_batches(
|
|
@@ -98,8 +100,8 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
|
98
100
|
rearchive
|
|
99
101
|
end
|
|
100
102
|
|
|
101
|
-
rescued { delete_or_publish_draft
|
|
102
|
-
rescued { manage_remaps_table
|
|
103
|
+
rescued { delete_or_publish_draft }
|
|
104
|
+
rescued { manage_remaps_table if some_update }
|
|
103
105
|
end
|
|
104
106
|
end
|
|
105
107
|
|
|
@@ -131,6 +133,8 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
|
131
133
|
|
|
132
134
|
private
|
|
133
135
|
|
|
136
|
+
attr_accessor :id_name_input
|
|
137
|
+
|
|
134
138
|
# Work with adapted diff builders.
|
|
135
139
|
def nodes_diff_class
|
|
136
140
|
Eco::API::UseCases::GraphQL::Helpers::Location::Command::Diffs
|
|
@@ -231,11 +235,17 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
|
231
235
|
end
|
|
232
236
|
|
|
233
237
|
def manage_remaps_table
|
|
234
|
-
return unless results.final_response?
|
|
235
|
-
|
|
236
238
|
rescued do
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
if simulate? && id_name_input
|
|
240
|
+
id_name_input[:commands].each do |command|
|
|
241
|
+
update_tags_remap_table(command[:update])
|
|
242
|
+
end
|
|
243
|
+
elsif results.final_response?
|
|
244
|
+
results.applied_commands(with_id_change: true).each do |result|
|
|
245
|
+
update_tags_remap_table(result.command_input_data)
|
|
246
|
+
end
|
|
247
|
+
else
|
|
248
|
+
return
|
|
239
249
|
end
|
|
240
250
|
end
|
|
241
251
|
|
|
@@ -36,8 +36,9 @@ class Eco::API::UseCases::GraphQL::Samples::Location
|
|
|
36
36
|
# @note the SFTP push only happens if `remote_subfolder` is defined, via:
|
|
37
37
|
# 1. `options.dig(:sftp, :remote_subfolder)`
|
|
38
38
|
# 2. `REMOTE_FOLDER` const
|
|
39
|
-
def close_handling_tags_remap_csv
|
|
39
|
+
def close_handling_tags_remap_csv # rubocop:disable Naming/PredicateMethod
|
|
40
40
|
return false unless super
|
|
41
|
+
return true if simulate?
|
|
41
42
|
|
|
42
43
|
upload(tags_remap_csv_file) unless remote_subfolder.nil?
|
|
43
44
|
true
|
|
@@ -5,12 +5,20 @@ module Eco::API::UseCases::GraphQL::Samples::Location::Service
|
|
|
5
5
|
module Heading
|
|
6
6
|
# Define the maps src -> dst heading name
|
|
7
7
|
# @example {'parent_id' => 'parentId' }
|
|
8
|
-
HEADER_MAPS = {
|
|
8
|
+
HEADER_MAPS = {
|
|
9
|
+
'Node Name' => 'name',
|
|
10
|
+
'Node ID' => 'id',
|
|
11
|
+
'Parent ID' => 'parent_id',
|
|
12
|
+
'Classification IDs' => 'classifications',
|
|
13
|
+
'classification_ids' => 'classifications'
|
|
14
|
+
}.freeze
|
|
9
15
|
|
|
10
16
|
private
|
|
11
17
|
|
|
12
18
|
def header_maps
|
|
13
|
-
|
|
19
|
+
HEADER_MAPS.merge(
|
|
20
|
+
self.class::HEADER_MAPS
|
|
21
|
+
)
|
|
14
22
|
end
|
|
15
23
|
end
|
|
16
24
|
end
|
|
@@ -22,12 +22,6 @@ module Eco::API::UseCases::GraphQL::Samples::Location::Service
|
|
|
22
22
|
msg << "Given: #{csv.class}"
|
|
23
23
|
raise ArgumentError, msg unless csv.is_a?(Eco::CSV::Table)
|
|
24
24
|
|
|
25
|
-
csv = csv.transform_headers do |name|
|
|
26
|
-
name = name.to_s.strip.downcase
|
|
27
|
-
name = 'classifications' if name == 'classification_ids'
|
|
28
|
-
name
|
|
29
|
-
end
|
|
30
|
-
|
|
31
25
|
csv.each do |row|
|
|
32
26
|
row_transform&.call(row)
|
|
33
27
|
transform_classifications(row)
|
|
@@ -338,7 +338,7 @@ ASSETS.cli.config do |cnf| # rubocop:disable Metrics/BlockLength
|
|
|
338
338
|
options.deep_merge!(exclude: {policy_groups: true})
|
|
339
339
|
end
|
|
340
340
|
|
|
341
|
-
options_set.add('-default-tag', 'default_tag is not set with the input data') do |options|
|
|
341
|
+
options_set.add('-exclude-default-tag', 'default_tag is not set with the input data') do |options|
|
|
342
342
|
options.deep_merge!(exclude: {default_tag: true})
|
|
343
343
|
end
|
|
344
344
|
options_set.add('-exclude-abilities', 'permissions_custom is not set with the input data') do |options|
|
data/lib/eco/csv/split.rb
CHANGED
|
@@ -3,14 +3,17 @@ module Eco
|
|
|
3
3
|
class Split
|
|
4
4
|
include Eco::Language::AuxiliarLogger
|
|
5
5
|
|
|
6
|
+
MAX_ROWS_DEFAULT = 1_000_000
|
|
7
|
+
|
|
6
8
|
attr_reader :filename
|
|
7
9
|
|
|
8
|
-
def initialize(filename, max_rows
|
|
10
|
+
def initialize(filename, max_rows: :unused, start_at: nil, **kargs)
|
|
9
11
|
msg = "File '#{filename}' does not exist"
|
|
10
12
|
raise ArgumentError, msg unless ::File.exist?(filename)
|
|
11
13
|
|
|
12
14
|
@filename = filename
|
|
13
15
|
@max_rows = max_rows
|
|
16
|
+
@max_rows = MAX_ROWS_DEFAULT if max_rows == :unused
|
|
14
17
|
@start_at = start_at
|
|
15
18
|
@params = kargs
|
|
16
19
|
|
|
@@ -34,16 +37,17 @@ module Eco
|
|
|
34
37
|
@out_files ||= []
|
|
35
38
|
end
|
|
36
39
|
|
|
37
|
-
# @yield [
|
|
40
|
+
# @yield [row, ridx, fidx, file] block to spot if the row should be included
|
|
38
41
|
# @yieldparam idx [Integer] the number of the file
|
|
39
42
|
# @yieldparam file [String] the default name of the file
|
|
40
|
-
# @
|
|
41
|
-
#
|
|
43
|
+
# @yieldparam fidx [Integer] the number of the file
|
|
44
|
+
# @yieldparam file [String] the default name of the file
|
|
45
|
+
# @yieldreturn [Bollean] whether the row should be included
|
|
42
46
|
# @return [Array<String>] names of the generated files
|
|
43
|
-
def call(&
|
|
47
|
+
def call(&filter)
|
|
44
48
|
stream.for_each(start_at_idx: start_at) do |row, ridx|
|
|
45
49
|
self.total_count += 1
|
|
46
|
-
copy_row(row, ridx, &
|
|
50
|
+
copy_row(row, ridx, &filter)
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
out_files
|
|
@@ -56,33 +60,42 @@ module Eco
|
|
|
56
60
|
|
|
57
61
|
attr_reader :params
|
|
58
62
|
attr_reader :idx, :max_rows, :start_at
|
|
59
|
-
attr_reader :headers, :row_idx
|
|
63
|
+
attr_reader :headers, :row_idx, :out_row_idx
|
|
64
|
+
attr_reader :last_cut_desc
|
|
60
65
|
|
|
61
66
|
attr_accessor :exception
|
|
62
67
|
|
|
63
|
-
def copy_row(row, ridx
|
|
68
|
+
def copy_row(row, ridx)
|
|
64
69
|
@headers ||= row.headers
|
|
65
70
|
@row_idx = ridx
|
|
66
71
|
|
|
67
|
-
current_csv(
|
|
72
|
+
current_csv(row) do |csv, fidx, file_out|
|
|
68
73
|
included = true
|
|
69
|
-
included &&=
|
|
74
|
+
included &&= yield(row, ridx, fidx, file_out) if block_given?
|
|
70
75
|
next unless included
|
|
71
76
|
|
|
77
|
+
@out_row_idx += 1
|
|
72
78
|
self.copy_count += 1
|
|
73
79
|
csv << row.fields
|
|
74
80
|
end
|
|
75
81
|
end
|
|
76
82
|
|
|
77
|
-
def current_csv(
|
|
78
|
-
if split?(
|
|
79
|
-
|
|
83
|
+
def current_csv(row)
|
|
84
|
+
if (cut = split?(row, &splitter)) || @csv.nil?
|
|
85
|
+
cut = nil if cut.is_a?(TrueClass) || cut.to_s.empty? || !cut
|
|
86
|
+
msg = "Split at row #{row_idx}"
|
|
87
|
+
msg << " (cut: #{cut})" unless cut.nil?
|
|
88
|
+
puts msg
|
|
89
|
+
|
|
90
|
+
@last_cut_desc = cut unless cut.nil?
|
|
91
|
+
|
|
80
92
|
@csv&.close
|
|
81
93
|
|
|
82
|
-
out_filename = generate_name(next_idx)
|
|
94
|
+
out_filename = generate_name(next_idx, desc: last_cut_desc)
|
|
83
95
|
@csv = ::CSV.open(out_filename, "w")
|
|
84
96
|
@csv << headers
|
|
85
97
|
out_files << out_filename
|
|
98
|
+
@out_row_idx = 0
|
|
86
99
|
end
|
|
87
100
|
|
|
88
101
|
yield(@csv, idx, out_files.last) if block_given?
|
|
@@ -90,8 +103,19 @@ module Eco
|
|
|
90
103
|
@csv
|
|
91
104
|
end
|
|
92
105
|
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
# @note client scripts can tweak this method.
|
|
107
|
+
def split?(row)
|
|
108
|
+
return yield(row, row_idx) if block_given?
|
|
109
|
+
|
|
110
|
+
((row_idx + 1) % max_rows).zero?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def splitter
|
|
114
|
+
@splitter ||= params[:splitter]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def splitter?
|
|
118
|
+
splitter.is_a?(Proc)
|
|
95
119
|
end
|
|
96
120
|
|
|
97
121
|
def next_idx
|
|
@@ -103,11 +127,15 @@ module Eco
|
|
|
103
127
|
end
|
|
104
128
|
|
|
105
129
|
def stream
|
|
106
|
-
@stream ||= Eco::CSV::Stream.new(
|
|
130
|
+
@stream ||= Eco::CSV::Stream.new(
|
|
131
|
+
filename,
|
|
132
|
+
**params
|
|
133
|
+
)
|
|
107
134
|
end
|
|
108
135
|
|
|
109
|
-
def generate_name(fidx)
|
|
110
|
-
|
|
136
|
+
def generate_name(fidx, desc: nil)
|
|
137
|
+
desc = "_#{desc}" unless desc.nil?
|
|
138
|
+
File.join(input_dir, "#{input_name}_#{file_number(fidx)}#{desc}#{input_ext}")
|
|
111
139
|
end
|
|
112
140
|
|
|
113
141
|
def file_number(num)
|
data/lib/eco/csv/stream.rb
CHANGED
|
@@ -3,6 +3,16 @@ module Eco
|
|
|
3
3
|
class Stream
|
|
4
4
|
include Eco::Language::AuxiliarLogger
|
|
5
5
|
|
|
6
|
+
CSV_PARAMS = %i[
|
|
7
|
+
col_sep row_sep quote_char
|
|
8
|
+
headers skip_blanks skip_lines
|
|
9
|
+
nil_value empty_value
|
|
10
|
+
converters unconverted_fields
|
|
11
|
+
return_headers header_converters
|
|
12
|
+
liberal_parsing
|
|
13
|
+
field_size_limit
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
6
16
|
attr_reader :filename
|
|
7
17
|
|
|
8
18
|
def initialize(filename, **kargs)
|
|
@@ -16,9 +26,42 @@ module Eco
|
|
|
16
26
|
init
|
|
17
27
|
end
|
|
18
28
|
|
|
29
|
+
def eof?
|
|
30
|
+
started? && !row
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def started?
|
|
34
|
+
@started ||= false
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def shift
|
|
38
|
+
raise ArgumentError, 'Expecting block, but not given.' unless block_given?
|
|
39
|
+
|
|
40
|
+
@started = true
|
|
41
|
+
yield(row, next_idx) if (self.row = csv.shift)
|
|
42
|
+
rescue StandardError => err
|
|
43
|
+
self.exception = err
|
|
44
|
+
raise
|
|
45
|
+
ensure
|
|
46
|
+
unless row || !fd.is_a?(::File)
|
|
47
|
+
fd.close
|
|
48
|
+
@fd = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if exception
|
|
52
|
+
# Give some feedback if it crashes
|
|
53
|
+
msg = []
|
|
54
|
+
msg << "Last row IDX: #{idx}"
|
|
55
|
+
msg << "Last row content: #{row.to_h.pretty_inspect}"
|
|
56
|
+
puts msg
|
|
57
|
+
log(:debug) { msg.join("\n") }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
19
61
|
def for_each(start_at_idx: 0)
|
|
20
62
|
raise ArgumentError, 'Expecting block, but not given.' unless block_given?
|
|
21
63
|
|
|
64
|
+
@started = true
|
|
22
65
|
move_to_idx(start_at_idx)
|
|
23
66
|
|
|
24
67
|
yield(row, next_idx) while (self.row = csv.shift)
|
|
@@ -38,6 +81,7 @@ module Eco
|
|
|
38
81
|
end
|
|
39
82
|
|
|
40
83
|
def move_to_idx(start_at_idx)
|
|
84
|
+
@started = true
|
|
41
85
|
start_at_idx ||= 0
|
|
42
86
|
next_idx while (idx < start_at_idx) && (self.row = csv.shift)
|
|
43
87
|
end
|
|
@@ -58,12 +102,18 @@ module Eco
|
|
|
58
102
|
return @csv if instance_variable_defined?(:@csv)
|
|
59
103
|
|
|
60
104
|
@fd = ::File.open(filename, 'r')
|
|
61
|
-
@csv = Eco::CSV.new(fd, **params)
|
|
105
|
+
@csv = Eco::CSV.new(fd, **params.slice(*csv_params))
|
|
62
106
|
end
|
|
63
107
|
|
|
64
108
|
def init
|
|
65
109
|
@idx ||= 0 # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
66
110
|
end
|
|
111
|
+
|
|
112
|
+
def csv_params
|
|
113
|
+
return self.class::CSV_PARAMS if self.class.const_defined?(:CSV_PARAMS)
|
|
114
|
+
|
|
115
|
+
CSV_PARAMS
|
|
116
|
+
end
|
|
67
117
|
end
|
|
68
118
|
end
|
|
69
119
|
end
|
data/lib/eco/csv.rb
CHANGED
|
@@ -19,7 +19,7 @@ module Eco
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
# Splits the csv `filename` into `max_rows`
|
|
22
|
-
# @yield [row, ridx, fidx, file]
|
|
22
|
+
# @yield [row, ridx, fidx, file] block to spot if the row should be included
|
|
23
23
|
# @yieldparam row [Integer] the row
|
|
24
24
|
# @yieldparam ridx [Integer] the index of the row in the source file
|
|
25
25
|
# @yieldparam fidx [Integer] the number of the file
|
|
@@ -29,15 +29,18 @@ module Eco
|
|
|
29
29
|
# @param max_rows [Integer] number of rows per file
|
|
30
30
|
# @param start_at [Integer] row that sets the starting point.
|
|
31
31
|
# Leave empty for the full set of rows.
|
|
32
|
+
# @param kargs [Hash] additional parameters
|
|
33
|
+
# - `:splitter` [Proc] custom splitter (criteria)
|
|
34
|
+
# - Receives the row idx and the row itself
|
|
32
35
|
# @return [Eco::CSV::Split]
|
|
33
|
-
def split(filename, max_rows
|
|
36
|
+
def split(filename, max_rows: :unused, start_at: nil, **kargs, &filter)
|
|
34
37
|
Eco::CSV::Split.new(
|
|
35
38
|
filename,
|
|
36
39
|
max_rows: max_rows,
|
|
37
40
|
start_at: start_at,
|
|
38
41
|
**kargs
|
|
39
42
|
).tap do |splitter|
|
|
40
|
-
splitter.call(&
|
|
43
|
+
splitter.call(&filter)
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
|
data/lib/eco/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eco-helpers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.2.
|
|
4
|
+
version: 3.2.13
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Oscar Segura
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: byebug
|
|
@@ -536,7 +535,6 @@ dependencies:
|
|
|
536
535
|
- - "~>"
|
|
537
536
|
- !ruby/object:Gem::Version
|
|
538
537
|
version: 6.7.0
|
|
539
|
-
description:
|
|
540
538
|
email:
|
|
541
539
|
- oscar@ecoportal.co.nz
|
|
542
540
|
executables: []
|
|
@@ -799,12 +797,14 @@ files:
|
|
|
799
797
|
- lib/eco/api/usecases/default/utils/cli/entries_to_csv_cli.rb
|
|
800
798
|
- lib/eco/api/usecases/default/utils/cli/group_csv_cli.rb
|
|
801
799
|
- lib/eco/api/usecases/default/utils/cli/json_to_csv_cli.rb
|
|
800
|
+
- lib/eco/api/usecases/default/utils/cli/merge_csv_cli.rb
|
|
802
801
|
- lib/eco/api/usecases/default/utils/cli/sort_csv_cli.rb
|
|
803
802
|
- lib/eco/api/usecases/default/utils/cli/split_csv_cli.rb
|
|
804
803
|
- lib/eco/api/usecases/default/utils/cli/split_json_cli.rb
|
|
805
804
|
- lib/eco/api/usecases/default/utils/entries_to_csv_case.rb
|
|
806
805
|
- lib/eco/api/usecases/default/utils/group_csv_case.rb
|
|
807
806
|
- lib/eco/api/usecases/default/utils/json_to_csv_case.rb
|
|
807
|
+
- lib/eco/api/usecases/default/utils/merge_csv_case.rb
|
|
808
808
|
- lib/eco/api/usecases/default/utils/sort_csv_case.rb
|
|
809
809
|
- lib/eco/api/usecases/default/utils/split_csv_case.rb
|
|
810
810
|
- lib/eco/api/usecases/default/utils/split_json_case.rb
|
|
@@ -1083,7 +1083,6 @@ licenses:
|
|
|
1083
1083
|
- MIT
|
|
1084
1084
|
metadata:
|
|
1085
1085
|
rubygems_mfa_required: 'true'
|
|
1086
|
-
post_install_message:
|
|
1087
1086
|
rdoc_options: []
|
|
1088
1087
|
require_paths:
|
|
1089
1088
|
- lib
|
|
@@ -1098,8 +1097,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
1098
1097
|
- !ruby/object:Gem::Version
|
|
1099
1098
|
version: '0'
|
|
1100
1099
|
requirements: []
|
|
1101
|
-
rubygems_version:
|
|
1102
|
-
signing_key:
|
|
1100
|
+
rubygems_version: 4.0.8
|
|
1103
1101
|
specification_version: 4
|
|
1104
1102
|
summary: eco-helpers to manage people api cases
|
|
1105
1103
|
test_files: []
|