csvops 0.6.0.alpha → 0.7.0.alpha
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/README.md +51 -12
- data/docs/architecture.md +61 -4
- data/docs/release-v0.7.0-alpha.md +87 -0
- data/lib/csvtool/application/use_cases/run_csv_split.rb +97 -0
- data/lib/csvtool/cli.rb +5 -1
- data/lib/csvtool/domain/csv_split_session/split_options.rb +27 -0
- data/lib/csvtool/domain/csv_split_session/split_session.rb +20 -0
- data/lib/csvtool/domain/csv_split_session/split_source.rb +17 -0
- data/lib/csvtool/infrastructure/csv/csv_splitter.rb +64 -0
- data/lib/csvtool/infrastructure/output/csv_split_manifest_writer.rb +20 -0
- data/lib/csvtool/interface/cli/errors/presenter.rb +8 -0
- data/lib/csvtool/interface/cli/menu_loop.rb +5 -2
- data/lib/csvtool/interface/cli/prompts/chunk_size_prompt.rb +21 -0
- data/lib/csvtool/interface/cli/prompts/split_manifest_prompt.rb +30 -0
- data/lib/csvtool/interface/cli/prompts/split_output_prompt.rb +38 -0
- data/lib/csvtool/interface/cli/workflows/builders/csv_split_session_builder.rb +44 -0
- data/lib/csvtool/interface/cli/workflows/presenters/csv_split_presenter.rb +26 -0
- data/lib/csvtool/interface/cli/workflows/run_csv_split_workflow.rb +89 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/build_session_step.rb +30 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step.rb +43 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step.rb +30 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step.rb +31 -0
- data/lib/csvtool/interface/cli/workflows/steps/csv_split/execute_step.rb +36 -0
- data/lib/csvtool/version.rb +1 -1
- data/test/csvtool/application/use_cases/run_csv_split_test.rb +124 -0
- data/test/csvtool/cli_test.rb +76 -29
- data/test/csvtool/infrastructure/csv/csv_splitter_test.rb +68 -0
- data/test/csvtool/infrastructure/output/csv_split_manifest_writer_test.rb +25 -0
- data/test/csvtool/interface/cli/menu_loop_test.rb +81 -130
- data/test/csvtool/interface/cli/prompts/chunk_size_prompt_test.rb +17 -0
- data/test/csvtool/interface/cli/prompts/split_manifest_prompt_test.rb +42 -0
- data/test/csvtool/interface/cli/prompts/split_output_prompt_test.rb +22 -0
- data/test/csvtool/interface/cli/workflows/builders/csv_split_session_builder_test.rb +30 -0
- data/test/csvtool/interface/cli/workflows/presenters/csv_split_presenter_test.rb +26 -0
- data/test/csvtool/interface/cli/workflows/run_csv_split_workflow_test.rb +200 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/build_session_step_test.rb +40 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step_test.rb +64 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step_test.rb +30 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/collect_output_step_test.rb +32 -0
- data/test/csvtool/interface/cli/workflows/steps/csv_split/execute_step_test.rb +83 -0
- data/test/fixtures/split_people_25.csv +26 -0
- metadata +34 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class SplitManifestPrompt
|
|
8
|
+
def initialize(stdin:, stdout:, yes_no_prompt:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@yes_no_prompt = yes_no_prompt
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(default_path:)
|
|
15
|
+
write_manifest = @yes_no_prompt.call(
|
|
16
|
+
label: "Write manifest file? [y/N]: ",
|
|
17
|
+
default: false
|
|
18
|
+
)
|
|
19
|
+
return { write_manifest: false, manifest_path: nil } unless write_manifest
|
|
20
|
+
|
|
21
|
+
@stdout.print "Manifest file path [#{default_path}]: "
|
|
22
|
+
path = @stdin.gets&.strip.to_s
|
|
23
|
+
path = default_path if path.empty?
|
|
24
|
+
{ write_manifest: true, manifest_path: path }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class SplitOutputPrompt
|
|
8
|
+
def initialize(stdin:, stdout:, yes_no_prompt:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@yes_no_prompt = yes_no_prompt
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(default_directory:, default_prefix:)
|
|
15
|
+
@stdout.print "Output directory [#{default_directory}]: "
|
|
16
|
+
output_directory = @stdin.gets&.strip.to_s
|
|
17
|
+
output_directory = default_directory if output_directory.empty?
|
|
18
|
+
|
|
19
|
+
@stdout.print "Output file prefix [#{default_prefix}]: "
|
|
20
|
+
file_prefix = @stdin.gets&.strip.to_s
|
|
21
|
+
file_prefix = default_prefix if file_prefix.empty?
|
|
22
|
+
|
|
23
|
+
overwrite_existing = @yes_no_prompt.call(
|
|
24
|
+
label: "Overwrite existing chunk files? [y/N]: ",
|
|
25
|
+
default: false
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
output_directory: output_directory,
|
|
30
|
+
file_prefix: file_prefix,
|
|
31
|
+
overwrite_existing: overwrite_existing
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csvtool/domain/csv_split_session/split_source"
|
|
4
|
+
require "csvtool/domain/csv_split_session/split_options"
|
|
5
|
+
require "csvtool/domain/csv_split_session/split_session"
|
|
6
|
+
|
|
7
|
+
module Csvtool
|
|
8
|
+
module Interface
|
|
9
|
+
module CLI
|
|
10
|
+
module Workflows
|
|
11
|
+
module Builders
|
|
12
|
+
class CsvSplitSessionBuilder
|
|
13
|
+
def call(
|
|
14
|
+
file_path:,
|
|
15
|
+
col_sep:,
|
|
16
|
+
headers_present:,
|
|
17
|
+
chunk_size:,
|
|
18
|
+
output_directory: nil,
|
|
19
|
+
file_prefix: nil,
|
|
20
|
+
overwrite_existing: false,
|
|
21
|
+
write_manifest: false,
|
|
22
|
+
manifest_path: nil
|
|
23
|
+
)
|
|
24
|
+
source = Domain::CsvSplitSession::SplitSource.new(
|
|
25
|
+
path: file_path,
|
|
26
|
+
separator: col_sep,
|
|
27
|
+
headers_present: headers_present
|
|
28
|
+
)
|
|
29
|
+
options = Domain::CsvSplitSession::SplitOptions.new(
|
|
30
|
+
chunk_size: chunk_size,
|
|
31
|
+
output_directory: output_directory,
|
|
32
|
+
file_prefix: file_prefix,
|
|
33
|
+
overwrite_existing: overwrite_existing,
|
|
34
|
+
write_manifest: write_manifest,
|
|
35
|
+
manifest_path: manifest_path
|
|
36
|
+
)
|
|
37
|
+
Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Workflows
|
|
7
|
+
module Presenters
|
|
8
|
+
class CsvSplitPresenter
|
|
9
|
+
def initialize(stdout:)
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def print_summary(data)
|
|
14
|
+
@stdout.puts "Split complete."
|
|
15
|
+
@stdout.puts "Chunk size: #{data[:chunk_size]}"
|
|
16
|
+
@stdout.puts "Data rows: #{data[:data_rows]}"
|
|
17
|
+
@stdout.puts "Chunks written: #{data[:chunk_count]}"
|
|
18
|
+
@stdout.puts "Manifest: #{data[:manifest_path]}" if data[:manifest_path]
|
|
19
|
+
data[:chunk_paths].each { |path| @stdout.puts path }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csvtool/application/use_cases/run_csv_split"
|
|
4
|
+
require "csvtool/interface/cli/errors/presenter"
|
|
5
|
+
require "csvtool/interface/cli/prompts/file_path_prompt"
|
|
6
|
+
require "csvtool/interface/cli/prompts/separator_prompt"
|
|
7
|
+
require "csvtool/interface/cli/prompts/headers_present_prompt"
|
|
8
|
+
require "csvtool/interface/cli/prompts/chunk_size_prompt"
|
|
9
|
+
require "csvtool/interface/cli/prompts/yes_no_prompt"
|
|
10
|
+
require "csvtool/interface/cli/prompts/split_output_prompt"
|
|
11
|
+
require "csvtool/interface/cli/prompts/split_manifest_prompt"
|
|
12
|
+
require "csvtool/interface/cli/workflows/builders/csv_split_session_builder"
|
|
13
|
+
require "csvtool/interface/cli/workflows/presenters/csv_split_presenter"
|
|
14
|
+
require "csvtool/interface/cli/workflows/support/result_error_handler"
|
|
15
|
+
require "csvtool/interface/cli/workflows/steps/workflow_step_pipeline"
|
|
16
|
+
require "csvtool/interface/cli/workflows/steps/csv_split/collect_inputs_step"
|
|
17
|
+
require "csvtool/interface/cli/workflows/steps/csv_split/collect_output_step"
|
|
18
|
+
require "csvtool/interface/cli/workflows/steps/csv_split/collect_manifest_step"
|
|
19
|
+
require "csvtool/interface/cli/workflows/steps/csv_split/build_session_step"
|
|
20
|
+
require "csvtool/interface/cli/workflows/steps/csv_split/execute_step"
|
|
21
|
+
|
|
22
|
+
module Csvtool
|
|
23
|
+
module Interface
|
|
24
|
+
module CLI
|
|
25
|
+
module Workflows
|
|
26
|
+
class RunCsvSplitWorkflow
|
|
27
|
+
def initialize(stdin:, stdout:, use_case: Application::UseCases::RunCsvSplit.new)
|
|
28
|
+
@stdin = stdin
|
|
29
|
+
@stdout = stdout
|
|
30
|
+
@use_case = use_case
|
|
31
|
+
@errors = Interface::CLI::Errors::Presenter.new(stdout: stdout)
|
|
32
|
+
@session_builder = Builders::CsvSplitSessionBuilder.new
|
|
33
|
+
@presenter = Presenters::CsvSplitPresenter.new(stdout: stdout)
|
|
34
|
+
@result_error_handler = Support::ResultErrorHandler.new(errors: @errors)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def call
|
|
38
|
+
context = {
|
|
39
|
+
use_case: @use_case,
|
|
40
|
+
session_builder: @session_builder,
|
|
41
|
+
presenter: @presenter,
|
|
42
|
+
handle_error: method(:handle_error)
|
|
43
|
+
}
|
|
44
|
+
pipeline = Steps::WorkflowStepPipeline.new(steps: [
|
|
45
|
+
Steps::CsvSplit::CollectInputsStep.new(
|
|
46
|
+
file_path_prompt: Interface::CLI::Prompts::FilePathPrompt.new(stdin: @stdin, stdout: @stdout),
|
|
47
|
+
separator_prompt: Interface::CLI::Prompts::SeparatorPrompt.new(stdin: @stdin, stdout: @stdout, errors: @errors),
|
|
48
|
+
headers_present_prompt: Interface::CLI::Prompts::HeadersPresentPrompt.new(stdin: @stdin, stdout: @stdout),
|
|
49
|
+
chunk_size_prompt: Interface::CLI::Prompts::ChunkSizePrompt.new(stdin: @stdin, stdout: @stdout),
|
|
50
|
+
errors: @errors
|
|
51
|
+
),
|
|
52
|
+
Steps::CsvSplit::CollectOutputStep.new(
|
|
53
|
+
split_output_prompt: Interface::CLI::Prompts::SplitOutputPrompt.new(
|
|
54
|
+
stdin: @stdin,
|
|
55
|
+
stdout: @stdout,
|
|
56
|
+
yes_no_prompt: Interface::CLI::Prompts::YesNoPrompt.new(stdin: @stdin, stdout: @stdout)
|
|
57
|
+
)
|
|
58
|
+
),
|
|
59
|
+
Steps::CsvSplit::CollectManifestStep.new(
|
|
60
|
+
split_manifest_prompt: Interface::CLI::Prompts::SplitManifestPrompt.new(
|
|
61
|
+
stdin: @stdin,
|
|
62
|
+
stdout: @stdout,
|
|
63
|
+
yes_no_prompt: Interface::CLI::Prompts::YesNoPrompt.new(stdin: @stdin, stdout: @stdout)
|
|
64
|
+
)
|
|
65
|
+
),
|
|
66
|
+
Steps::CsvSplit::BuildSessionStep.new,
|
|
67
|
+
Steps::CsvSplit::ExecuteStep.new
|
|
68
|
+
])
|
|
69
|
+
pipeline.call(context)
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def handle_error(result)
|
|
76
|
+
@result_error_handler.call(result, {
|
|
77
|
+
file_not_found: ->(r, errors) { errors.file_not_found(r.data[:path]) },
|
|
78
|
+
no_headers: ->(_r, errors) { errors.no_headers },
|
|
79
|
+
could_not_parse_csv: ->(_r, errors) { errors.could_not_parse_csv },
|
|
80
|
+
cannot_read_file: ->(r, errors) { errors.cannot_read_file(r.data[:path]) },
|
|
81
|
+
cannot_write_output_file: ->(r, errors) { errors.cannot_write_output_file(r.data[:path], r.data[:error_class]) },
|
|
82
|
+
output_file_exists: ->(r, errors) { errors.output_file_exists(r.data[:path]) }
|
|
83
|
+
})
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Workflows
|
|
7
|
+
module Steps
|
|
8
|
+
module CsvSplit
|
|
9
|
+
class BuildSessionStep
|
|
10
|
+
def call(context)
|
|
11
|
+
context[:session] = context.fetch(:session_builder).call(
|
|
12
|
+
file_path: context.fetch(:file_path),
|
|
13
|
+
col_sep: context.fetch(:col_sep),
|
|
14
|
+
headers_present: context.fetch(:headers_present),
|
|
15
|
+
chunk_size: context.fetch(:chunk_size),
|
|
16
|
+
output_directory: context[:output_directory],
|
|
17
|
+
file_prefix: context[:file_prefix],
|
|
18
|
+
overwrite_existing: context.fetch(:overwrite_existing, false),
|
|
19
|
+
write_manifest: context.fetch(:write_manifest, false),
|
|
20
|
+
manifest_path: context[:manifest_path]
|
|
21
|
+
)
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Workflows
|
|
7
|
+
module Steps
|
|
8
|
+
module CsvSplit
|
|
9
|
+
class CollectInputsStep
|
|
10
|
+
def initialize(file_path_prompt:, separator_prompt:, headers_present_prompt:, chunk_size_prompt:, errors:)
|
|
11
|
+
@file_path_prompt = file_path_prompt
|
|
12
|
+
@separator_prompt = separator_prompt
|
|
13
|
+
@headers_present_prompt = headers_present_prompt
|
|
14
|
+
@chunk_size_prompt = chunk_size_prompt
|
|
15
|
+
@errors = errors
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call(context)
|
|
19
|
+
context[:file_path] = @file_path_prompt.call(label: "Source CSV file path: ")
|
|
20
|
+
col_sep = @separator_prompt.call
|
|
21
|
+
return :halt if col_sep.nil?
|
|
22
|
+
|
|
23
|
+
context[:col_sep] = col_sep
|
|
24
|
+
context[:headers_present] = @headers_present_prompt.call
|
|
25
|
+
chunk_size = Integer(@chunk_size_prompt.call)
|
|
26
|
+
if chunk_size <= 0
|
|
27
|
+
@errors.invalid_chunk_size
|
|
28
|
+
return :halt
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context[:chunk_size] = chunk_size
|
|
32
|
+
nil
|
|
33
|
+
rescue ArgumentError, TypeError
|
|
34
|
+
@errors.invalid_chunk_size
|
|
35
|
+
:halt
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Workflows
|
|
7
|
+
module Steps
|
|
8
|
+
module CsvSplit
|
|
9
|
+
class CollectManifestStep
|
|
10
|
+
def initialize(split_manifest_prompt:)
|
|
11
|
+
@split_manifest_prompt = split_manifest_prompt
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(context)
|
|
15
|
+
default_path = File.join(
|
|
16
|
+
context.fetch(:output_directory),
|
|
17
|
+
"#{context.fetch(:file_prefix)}_manifest.csv"
|
|
18
|
+
)
|
|
19
|
+
manifest = @split_manifest_prompt.call(default_path: default_path)
|
|
20
|
+
context[:write_manifest] = manifest[:write_manifest]
|
|
21
|
+
context[:manifest_path] = manifest[:manifest_path]
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Workflows
|
|
7
|
+
module Steps
|
|
8
|
+
module CsvSplit
|
|
9
|
+
class CollectOutputStep
|
|
10
|
+
def initialize(split_output_prompt:)
|
|
11
|
+
@split_output_prompt = split_output_prompt
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(context)
|
|
15
|
+
file_path = context.fetch(:file_path)
|
|
16
|
+
output = @split_output_prompt.call(
|
|
17
|
+
default_directory: File.dirname(file_path),
|
|
18
|
+
default_prefix: File.basename(file_path, ".*")
|
|
19
|
+
)
|
|
20
|
+
context[:output_directory] = output[:output_directory]
|
|
21
|
+
context[:file_prefix] = output[:file_prefix]
|
|
22
|
+
context[:overwrite_existing] = output[:overwrite_existing]
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Workflows
|
|
7
|
+
module Steps
|
|
8
|
+
module CsvSplit
|
|
9
|
+
class ExecuteStep
|
|
10
|
+
def call(context)
|
|
11
|
+
headers_result = context.fetch(:use_case).read_headers(
|
|
12
|
+
file_path: context.fetch(:file_path),
|
|
13
|
+
col_sep: context.fetch(:col_sep),
|
|
14
|
+
headers_present: context.fetch(:headers_present)
|
|
15
|
+
)
|
|
16
|
+
unless headers_result.ok?
|
|
17
|
+
context.fetch(:handle_error).call(headers_result)
|
|
18
|
+
return :halt
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
result = context.fetch(:use_case).call(session: context.fetch(:session))
|
|
22
|
+
unless result.ok?
|
|
23
|
+
context.fetch(:handle_error).call(result)
|
|
24
|
+
return :halt
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context.fetch(:presenter).print_summary(result.data.merge(chunk_size: context.fetch(:chunk_size)))
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/csvtool/version.rb
CHANGED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/application/use_cases/run_csv_split"
|
|
5
|
+
require "csvtool/domain/csv_split_session/split_source"
|
|
6
|
+
require "csvtool/domain/csv_split_session/split_options"
|
|
7
|
+
require "csvtool/domain/csv_split_session/split_session"
|
|
8
|
+
require "tmpdir"
|
|
9
|
+
require "fileutils"
|
|
10
|
+
|
|
11
|
+
class RunCsvSplitTest < Minitest::Test
|
|
12
|
+
def fixture_path(name)
|
|
13
|
+
File.expand_path("../../../fixtures/#{name}", __dir__)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def test_splits_25_rows_into_10_10_5_with_headers
|
|
17
|
+
use_case = Csvtool::Application::UseCases::RunCsvSplit.new
|
|
18
|
+
|
|
19
|
+
Dir.mktmpdir do |dir|
|
|
20
|
+
source_path = File.join(dir, "people.csv")
|
|
21
|
+
FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
|
|
22
|
+
|
|
23
|
+
source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
|
|
24
|
+
options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(chunk_size: 10)
|
|
25
|
+
session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
|
|
26
|
+
|
|
27
|
+
result = use_case.call(session: session)
|
|
28
|
+
|
|
29
|
+
assert result.ok?
|
|
30
|
+
assert_equal 3, result.data[:chunk_count]
|
|
31
|
+
assert_equal 25, result.data[:data_rows]
|
|
32
|
+
assert_equal 3, result.data[:chunk_paths].length
|
|
33
|
+
|
|
34
|
+
chunk_1 = File.read(result.data[:chunk_paths][0]).lines.map(&:strip)
|
|
35
|
+
chunk_2 = File.read(result.data[:chunk_paths][1]).lines.map(&:strip)
|
|
36
|
+
chunk_3 = File.read(result.data[:chunk_paths][2]).lines.map(&:strip)
|
|
37
|
+
|
|
38
|
+
assert_equal 11, chunk_1.length
|
|
39
|
+
assert_equal 11, chunk_2.length
|
|
40
|
+
assert_equal 6, chunk_3.length
|
|
41
|
+
assert_equal "name,city", chunk_1.first
|
|
42
|
+
assert_equal "name,city", chunk_2.first
|
|
43
|
+
assert_equal "name,city", chunk_3.first
|
|
44
|
+
assert_equal "Name01,City01", chunk_1[1]
|
|
45
|
+
assert_equal "Name10,City10", chunk_1[10]
|
|
46
|
+
assert_equal "Name11,City11", chunk_2[1]
|
|
47
|
+
assert_equal "Name20,City20", chunk_2[10]
|
|
48
|
+
assert_equal "Name21,City21", chunk_3[1]
|
|
49
|
+
assert_equal "Name25,City25", chunk_3[5]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_returns_output_file_exists_when_overwrite_is_disabled
|
|
54
|
+
use_case = Csvtool::Application::UseCases::RunCsvSplit.new
|
|
55
|
+
|
|
56
|
+
Dir.mktmpdir do |dir|
|
|
57
|
+
source_path = File.join(dir, "people.csv")
|
|
58
|
+
FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
|
|
59
|
+
File.write(File.join(dir, "people_part_001.csv"), "sentinel\n")
|
|
60
|
+
|
|
61
|
+
source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
|
|
62
|
+
options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(chunk_size: 10, overwrite_existing: false)
|
|
63
|
+
session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
|
|
64
|
+
|
|
65
|
+
result = use_case.call(session: session)
|
|
66
|
+
|
|
67
|
+
refute result.ok?
|
|
68
|
+
assert_equal :output_file_exists, result.error
|
|
69
|
+
assert_equal File.join(dir, "people_part_001.csv"), result.data[:path]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_creates_output_directory_when_it_does_not_exist
|
|
74
|
+
use_case = Csvtool::Application::UseCases::RunCsvSplit.new
|
|
75
|
+
|
|
76
|
+
Dir.mktmpdir do |dir|
|
|
77
|
+
source_path = File.join(dir, "people.csv")
|
|
78
|
+
output_dir = File.join(dir, "new_chunks")
|
|
79
|
+
FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
|
|
80
|
+
|
|
81
|
+
source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
|
|
82
|
+
options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(
|
|
83
|
+
chunk_size: 10,
|
|
84
|
+
output_directory: output_dir,
|
|
85
|
+
file_prefix: "batch"
|
|
86
|
+
)
|
|
87
|
+
session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
|
|
88
|
+
|
|
89
|
+
result = use_case.call(session: session)
|
|
90
|
+
|
|
91
|
+
assert result.ok?
|
|
92
|
+
assert Dir.exist?(output_dir)
|
|
93
|
+
assert File.file?(File.join(output_dir, "batch_part_001.csv"))
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_writes_manifest_when_enabled
|
|
98
|
+
use_case = Csvtool::Application::UseCases::RunCsvSplit.new
|
|
99
|
+
|
|
100
|
+
Dir.mktmpdir do |dir|
|
|
101
|
+
source_path = File.join(dir, "people.csv")
|
|
102
|
+
manifest_path = File.join(dir, "manifest.csv")
|
|
103
|
+
FileUtils.cp(fixture_path("split_people_25.csv"), source_path)
|
|
104
|
+
|
|
105
|
+
source = Csvtool::Domain::CsvSplitSession::SplitSource.new(path: source_path, separator: ",", headers_present: true)
|
|
106
|
+
options = Csvtool::Domain::CsvSplitSession::SplitOptions.new(
|
|
107
|
+
chunk_size: 10,
|
|
108
|
+
write_manifest: true,
|
|
109
|
+
manifest_path: manifest_path
|
|
110
|
+
)
|
|
111
|
+
session = Csvtool::Domain::CsvSplitSession::SplitSession.start(source: source, options: options)
|
|
112
|
+
|
|
113
|
+
result = use_case.call(session: session)
|
|
114
|
+
|
|
115
|
+
assert result.ok?
|
|
116
|
+
assert_equal manifest_path, result.data[:manifest_path]
|
|
117
|
+
lines = File.read(manifest_path).lines.map(&:strip)
|
|
118
|
+
assert_equal "chunk_index,chunk_path,row_count", lines.first
|
|
119
|
+
assert_includes lines[1], ",10"
|
|
120
|
+
assert_includes lines[2], ",10"
|
|
121
|
+
assert_includes lines[3], ",5"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|