csvops 0.3.0.alpha → 0.5.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.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +69 -149
  3. data/docs/architecture.md +396 -0
  4. data/docs/release-v0.4.0-alpha.md +87 -0
  5. data/docs/release-v0.5.0-alpha.md +89 -0
  6. data/lib/csvtool/application/use_cases/run_cross_csv_dedupe.rb +96 -0
  7. data/lib/csvtool/application/use_cases/run_extraction.rb +63 -88
  8. data/lib/csvtool/application/use_cases/run_row_extraction.rb +45 -73
  9. data/lib/csvtool/application/use_cases/run_row_randomization.rb +56 -73
  10. data/lib/csvtool/cli.rb +11 -7
  11. data/lib/csvtool/domain/cross_csv_dedupe_session/column_selector.rb +44 -0
  12. data/lib/csvtool/domain/cross_csv_dedupe_session/cross_csv_dedupe_session.rb +46 -0
  13. data/lib/csvtool/domain/cross_csv_dedupe_session/csv_profile.rb +24 -0
  14. data/lib/csvtool/domain/cross_csv_dedupe_session/key_mapping.rb +22 -0
  15. data/lib/csvtool/domain/cross_csv_dedupe_session/match_options.rb +29 -0
  16. data/lib/csvtool/domain/row_randomization_session/randomization_source.rb +1 -0
  17. data/lib/csvtool/domain/row_session/row_source.rb +3 -0
  18. data/lib/csvtool/domain/{column_session → shared}/output_destination.rb +1 -1
  19. data/lib/csvtool/infrastructure/csv/cross_csv_deduper.rb +85 -0
  20. data/lib/csvtool/infrastructure/csv/selector_validator.rb +30 -0
  21. data/lib/csvtool/infrastructure/output/csv_cross_csv_dedupe_file_writer.rb +23 -0
  22. data/lib/csvtool/infrastructure/output/csv_file_writer.rb +1 -7
  23. data/lib/csvtool/infrastructure/output/csv_randomized_row_file_writer.rb +23 -0
  24. data/lib/csvtool/infrastructure/output/csv_row_file_writer.rb +2 -9
  25. data/lib/csvtool/interface/cli/menu_loop.rb +5 -2
  26. data/lib/csvtool/interface/cli/prompts/dedupe_key_selector_prompt.rb +30 -0
  27. data/lib/csvtool/interface/cli/prompts/file_path_prompt.rb +4 -2
  28. data/lib/csvtool/interface/cli/prompts/headers_present_prompt.rb +4 -2
  29. data/lib/csvtool/interface/cli/prompts/separator_prompt.rb +4 -2
  30. data/lib/csvtool/interface/cli/prompts/yes_no_prompt.rb +26 -0
  31. data/lib/csvtool/interface/cli/workflows/builders/column_session_builder.rb +32 -0
  32. data/lib/csvtool/interface/cli/workflows/builders/cross_csv_dedupe_session_builder.rb +35 -0
  33. data/lib/csvtool/interface/cli/workflows/builders/row_extraction_session_builder.rb +22 -0
  34. data/lib/csvtool/interface/cli/workflows/builders/row_randomization_session_builder.rb +28 -0
  35. data/lib/csvtool/interface/cli/workflows/presenters/column_extraction_presenter.rb +25 -0
  36. data/lib/csvtool/interface/cli/workflows/presenters/cross_csv_dedupe_presenter.rb +39 -0
  37. data/lib/csvtool/interface/cli/workflows/presenters/row_extraction_presenter.rb +34 -0
  38. data/lib/csvtool/interface/cli/workflows/presenters/row_randomization_presenter.rb +34 -0
  39. data/lib/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow.rb +86 -0
  40. data/lib/csvtool/interface/cli/workflows/run_extraction_workflow.rb +88 -0
  41. data/lib/csvtool/interface/cli/workflows/run_row_extraction_workflow.rb +86 -0
  42. data/lib/csvtool/interface/cli/workflows/run_row_randomization_workflow.rb +80 -0
  43. data/lib/csvtool/interface/cli/workflows/steps/cross_csv_dedupe/collect_options_step.rb +55 -0
  44. data/lib/csvtool/interface/cli/workflows/steps/cross_csv_dedupe/collect_profiles_step.rb +52 -0
  45. data/lib/csvtool/interface/cli/workflows/steps/cross_csv_dedupe/execute_step.rb +34 -0
  46. data/lib/csvtool/interface/cli/workflows/steps/extraction/build_preview_step.rb +40 -0
  47. data/lib/csvtool/interface/cli/workflows/steps/extraction/collect_destination_step.rb +28 -0
  48. data/lib/csvtool/interface/cli/workflows/steps/extraction/collect_inputs_step.rb +47 -0
  49. data/lib/csvtool/interface/cli/workflows/steps/extraction/execute_step.rb +32 -0
  50. data/lib/csvtool/interface/cli/workflows/steps/row_extraction/collect_destination_step.rb +33 -0
  51. data/lib/csvtool/interface/cli/workflows/steps/row_extraction/collect_range_step.rb +35 -0
  52. data/lib/csvtool/interface/cli/workflows/steps/row_extraction/collect_source_step.rb +32 -0
  53. data/lib/csvtool/interface/cli/workflows/steps/row_extraction/execute_step.rb +43 -0
  54. data/lib/csvtool/interface/cli/workflows/steps/row_extraction/read_headers_step.rb +29 -0
  55. data/lib/csvtool/interface/cli/workflows/steps/row_randomization/collect_destination_step.rb +34 -0
  56. data/lib/csvtool/interface/cli/workflows/steps/row_randomization/collect_inputs_step.rb +49 -0
  57. data/lib/csvtool/interface/cli/workflows/steps/row_randomization/execute_step.rb +37 -0
  58. data/lib/csvtool/interface/cli/workflows/steps/workflow_step_pipeline.rb +25 -0
  59. data/lib/csvtool/interface/cli/workflows/support/output_destination_mapper.rb +23 -0
  60. data/lib/csvtool/interface/cli/workflows/support/result_error_handler.rb +22 -0
  61. data/lib/csvtool/version.rb +1 -1
  62. data/test/csvtool/application/use_cases/io_boundary_test.rb +26 -0
  63. data/test/csvtool/application/use_cases/run_cross_csv_dedupe_test.rb +141 -0
  64. data/test/csvtool/application/use_cases/run_extraction_test.rb +72 -16
  65. data/test/csvtool/application/use_cases/run_row_extraction_test.rb +82 -102
  66. data/test/csvtool/application/use_cases/run_row_randomization_test.rb +96 -86
  67. data/test/csvtool/cli_test.rb +130 -16
  68. data/test/csvtool/cli_unit_test.rb +16 -3
  69. data/test/csvtool/domain/column_session/column_session_test.rb +2 -2
  70. data/test/csvtool/domain/column_session/csv_source_test.rb +10 -0
  71. data/test/csvtool/domain/cross_csv_dedupe_session/column_selector_test.rb +42 -0
  72. data/test/csvtool/domain/cross_csv_dedupe_session/cross_csv_dedupe_session_test.rb +75 -0
  73. data/test/csvtool/domain/cross_csv_dedupe_session/csv_profile_test.rb +26 -0
  74. data/test/csvtool/domain/cross_csv_dedupe_session/key_mapping_test.rb +31 -0
  75. data/test/csvtool/domain/cross_csv_dedupe_session/match_options_test.rb +52 -0
  76. data/test/csvtool/domain/row_randomization_session/randomization_session_test.rb +2 -2
  77. data/test/csvtool/domain/row_randomization_session/randomization_source_test.rb +15 -1
  78. data/test/csvtool/domain/row_session/row_session_test.rb +2 -2
  79. data/test/csvtool/domain/row_session/row_source_test.rb +16 -0
  80. data/test/csvtool/domain/shared/output_destination_test.rb +24 -0
  81. data/test/csvtool/infrastructure/csv/cross_csv_deduper_test.rb +155 -0
  82. data/test/csvtool/infrastructure/csv/selector_validator_test.rb +72 -0
  83. data/test/csvtool/infrastructure/output/csv_cross_csv_dedupe_file_writer_test.rb +32 -0
  84. data/test/csvtool/infrastructure/output/csv_file_writer_test.rb +0 -4
  85. data/test/csvtool/infrastructure/output/csv_randomized_row_file_writer_test.rb +32 -0
  86. data/test/csvtool/infrastructure/output/csv_row_file_writer_test.rb +1 -4
  87. data/test/csvtool/interface/cli/menu_loop_test.rb +50 -13
  88. data/test/csvtool/interface/cli/prompts/dedupe_key_selector_prompt_test.rb +30 -0
  89. data/test/csvtool/interface/cli/prompts/file_path_prompt_test.rb +9 -0
  90. data/test/csvtool/interface/cli/prompts/headers_present_prompt_test.rb +10 -0
  91. data/test/csvtool/interface/cli/prompts/separator_prompt_test.rb +10 -0
  92. data/test/csvtool/interface/cli/prompts/yes_no_prompt_test.rb +22 -0
  93. data/test/csvtool/interface/cli/workflows/builders/column_session_builder_test.rb +17 -0
  94. data/test/csvtool/interface/cli/workflows/builders/cross_csv_dedupe_session_builder_test.rb +36 -0
  95. data/test/csvtool/interface/cli/workflows/builders/row_extraction_session_builder_test.rb +21 -0
  96. data/test/csvtool/interface/cli/workflows/builders/row_randomization_session_builder_test.rb +26 -0
  97. data/test/csvtool/interface/cli/workflows/presenters/column_extraction_presenter_test.rb +24 -0
  98. data/test/csvtool/interface/cli/workflows/presenters/cross_csv_dedupe_presenter_test.rb +30 -0
  99. data/test/csvtool/interface/cli/workflows/presenters/row_extraction_presenter_test.rb +33 -0
  100. data/test/csvtool/interface/cli/workflows/presenters/row_randomization_presenter_test.rb +33 -0
  101. data/test/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow_test.rb +246 -0
  102. data/test/csvtool/interface/cli/workflows/run_extraction_workflow_test.rb +56 -0
  103. data/test/csvtool/interface/cli/workflows/run_row_extraction_workflow_test.rb +83 -0
  104. data/test/csvtool/interface/cli/workflows/run_row_randomization_workflow_test.rb +69 -0
  105. data/test/csvtool/interface/cli/workflows/steps/cross_csv_dedupe/collect_options_step_test.rb +41 -0
  106. data/test/csvtool/interface/cli/workflows/steps/extraction/collect_inputs_step_test.rb +66 -0
  107. data/test/csvtool/interface/cli/workflows/steps/row_extraction/collect_source_step_test.rb +39 -0
  108. data/test/csvtool/interface/cli/workflows/steps/row_extraction/execute_step_test.rb +91 -0
  109. data/test/csvtool/interface/cli/workflows/steps/row_extraction/read_headers_step_test.rb +57 -0
  110. data/test/csvtool/interface/cli/workflows/steps/row_randomization/collect_inputs_step_test.rb +37 -0
  111. data/test/csvtool/interface/cli/workflows/steps/workflow_step_pipeline_test.rb +30 -0
  112. data/test/csvtool/interface/cli/workflows/support/output_destination_mapper_test.rb +23 -0
  113. data/test/csvtool/interface/cli/workflows/support/result_error_handler_test.rb +34 -0
  114. data/test/fixtures/dedupe_reference.csv +3 -0
  115. data/test/fixtures/dedupe_reference.tsv +3 -0
  116. data/test/fixtures/dedupe_reference_all.csv +5 -0
  117. data/test/fixtures/dedupe_reference_no_headers.csv +2 -0
  118. data/test/fixtures/dedupe_reference_none.csv +2 -0
  119. data/test/fixtures/dedupe_reference_normalization.csv +3 -0
  120. data/test/fixtures/dedupe_source.csv +6 -0
  121. data/test/fixtures/dedupe_source.tsv +6 -0
  122. data/test/fixtures/dedupe_source_no_headers.csv +5 -0
  123. data/test/fixtures/dedupe_source_normalization.csv +4 -0
  124. metadata +93 -8
  125. data/lib/csvtool/domain/row_randomization_session/randomization_output_destination.rb +0 -31
  126. data/lib/csvtool/domain/row_session/row_output_destination.rb +0 -31
  127. data/test/csvtool/domain/column_session/output_destination_test.rb +0 -18
  128. data/test/csvtool/domain/row_randomization_session/randomization_output_destination_test.rb +0 -21
  129. data/test/csvtool/domain/row_session/row_output_destination_test.rb +0 -23
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csvtool/application/use_cases/run_row_randomization"
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/seed_prompt"
9
+ require "csvtool/interface/cli/prompts/output_destination_prompt"
10
+ require "csvtool/interface/cli/workflows/builders/row_randomization_session_builder"
11
+ require "csvtool/interface/cli/workflows/presenters/row_randomization_presenter"
12
+ require "csvtool/interface/cli/workflows/support/output_destination_mapper"
13
+ require "csvtool/interface/cli/workflows/support/result_error_handler"
14
+ require "csvtool/interface/cli/workflows/steps/workflow_step_pipeline"
15
+ require "csvtool/interface/cli/workflows/steps/row_randomization/collect_inputs_step"
16
+ require "csvtool/interface/cli/workflows/steps/row_randomization/collect_destination_step"
17
+ require "csvtool/interface/cli/workflows/steps/row_randomization/execute_step"
18
+ module Csvtool
19
+ module Interface
20
+ module CLI
21
+ module Workflows
22
+ class RunRowRandomizationWorkflow
23
+ def initialize(stdin:, stdout:, use_case: Application::UseCases::RunRowRandomization.new)
24
+ @stdin = stdin
25
+ @stdout = stdout
26
+ @use_case = use_case
27
+ @errors = Interface::CLI::Errors::Presenter.new(stdout: stdout)
28
+ @session_builder = Builders::RowRandomizationSessionBuilder.new
29
+ @output_destination_mapper = Support::OutputDestinationMapper.new
30
+ @result_error_handler = Support::ResultErrorHandler.new(errors: @errors)
31
+ end
32
+
33
+ def call
34
+ context = {
35
+ use_case: @use_case,
36
+ session_builder: @session_builder,
37
+ output_destination_mapper: @output_destination_mapper,
38
+ presenter_factory: ->(headers:, col_sep:) { Presenters::RowRandomizationPresenter.new(stdout: @stdout, headers: headers, col_sep: col_sep) },
39
+ handle_error: method(:handle_error)
40
+ }
41
+
42
+ pipeline = Steps::WorkflowStepPipeline.new(steps: [
43
+ Steps::RowRandomization::CollectInputsStep.new(
44
+ file_path_prompt: Interface::CLI::Prompts::FilePathPrompt.new(stdin: @stdin, stdout: @stdout),
45
+ separator_prompt: Interface::CLI::Prompts::SeparatorPrompt.new(stdin: @stdin, stdout: @stdout, errors: @errors),
46
+ headers_present_prompt: Interface::CLI::Prompts::HeadersPresentPrompt.new(stdin: @stdin, stdout: @stdout),
47
+ seed_prompt: Interface::CLI::Prompts::SeedPrompt.new(stdin: @stdin, stdout: @stdout, errors: @errors)
48
+ ),
49
+ Steps::RowRandomization::CollectDestinationStep.new(
50
+ output_destination_prompt: Interface::CLI::Prompts::OutputDestinationPrompt.new(
51
+ stdin: @stdin,
52
+ stdout: @stdout,
53
+ errors: @errors
54
+ )
55
+ ),
56
+ Steps::RowRandomization::ExecuteStep.new
57
+ ])
58
+ pipeline.call(context)
59
+ rescue ArgumentError => e
60
+ return @errors.empty_output_path if e.message == "file output path cannot be empty"
61
+
62
+ raise e
63
+ end
64
+
65
+ private
66
+
67
+ def handle_error(result)
68
+ @result_error_handler.call(result, {
69
+ file_not_found: ->(r, errors) { errors.file_not_found(r.data[:path]) },
70
+ no_headers: ->(_r, errors) { errors.no_headers },
71
+ could_not_parse_csv: ->(_r, errors) { errors.could_not_parse_csv },
72
+ cannot_read_file: ->(r, errors) { errors.cannot_read_file(r.data[:path]) },
73
+ cannot_write_output_file: ->(r, errors) { errors.cannot_write_output_file(r.data[:path], r.data[:error_class]) }
74
+ })
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CrossCsvDedupe
9
+ class CollectOptionsStep
10
+ def initialize(selector_prompt:, yes_no_prompt:, output_destination_prompt:, errors:)
11
+ @selector_prompt = selector_prompt
12
+ @yes_no_prompt = yes_no_prompt
13
+ @output_destination_prompt = output_destination_prompt
14
+ @errors = errors
15
+ end
16
+
17
+ def call(context)
18
+ source = context.fetch(:source)
19
+ reference = context.fetch(:reference)
20
+
21
+ source_selector = @selector_prompt.call(label: "Source", headers_present: source.headers_present?)
22
+ if source_selector.nil?
23
+ @errors.column_not_found
24
+ return :halt
25
+ end
26
+ reference_selector = @selector_prompt.call(label: "Reference", headers_present: reference.headers_present?)
27
+ if reference_selector.nil?
28
+ @errors.column_not_found
29
+ return :halt
30
+ end
31
+
32
+ trim_whitespace = @yes_no_prompt.call(label: "Trim whitespace before matching? [Y/n]: ", default: true)
33
+ case_insensitive = @yes_no_prompt.call(label: "Case-insensitive matching? [y/N]: ", default: false)
34
+
35
+ output_destination = @output_destination_prompt.call
36
+ return :halt if output_destination.nil?
37
+
38
+ context[:session] = context.fetch(:session_builder).call(
39
+ source: source,
40
+ reference: reference,
41
+ source_selector: source_selector,
42
+ reference_selector: reference_selector,
43
+ trim_whitespace: trim_whitespace,
44
+ case_insensitive: case_insensitive,
45
+ destination: context.fetch(:output_destination_mapper).call(output_destination)
46
+ )
47
+ nil
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csvtool/domain/cross_csv_dedupe_session/csv_profile"
4
+
5
+ module Csvtool
6
+ module Interface
7
+ module CLI
8
+ module Workflows
9
+ module Steps
10
+ module CrossCsvDedupe
11
+ class CollectProfilesStep
12
+ def initialize(file_path_prompt:, separator_prompt:, headers_present_prompt:, errors:)
13
+ @file_path_prompt = file_path_prompt
14
+ @separator_prompt = separator_prompt
15
+ @headers_present_prompt = headers_present_prompt
16
+ @errors = errors
17
+ end
18
+
19
+ def call(context)
20
+ source_path = @file_path_prompt.call
21
+ return @errors.file_not_found(source_path) || :halt unless File.file?(source_path)
22
+
23
+ source_col_sep = @separator_prompt.call(label: "Source CSV separator:")
24
+ return :halt if source_col_sep.nil?
25
+ source_headers_present = @headers_present_prompt.call(label: "Source headers present? [Y/n]: ")
26
+
27
+ reference_path = @file_path_prompt.call(label: "Reference CSV file path: ")
28
+ return @errors.file_not_found(reference_path) || :halt unless File.file?(reference_path)
29
+
30
+ reference_col_sep = @separator_prompt.call(label: "Reference CSV separator:")
31
+ return :halt if reference_col_sep.nil?
32
+ reference_headers_present = @headers_present_prompt.call(label: "Reference headers present? [Y/n]: ")
33
+
34
+ context[:source] = Domain::CrossCsvDedupeSession::CsvProfile.new(
35
+ path: source_path,
36
+ separator: source_col_sep,
37
+ headers_present: source_headers_present
38
+ )
39
+ context[:reference] = Domain::CrossCsvDedupeSession::CsvProfile.new(
40
+ path: reference_path,
41
+ separator: reference_col_sep,
42
+ headers_present: reference_headers_present
43
+ )
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module CrossCsvDedupe
9
+ class ExecuteStep
10
+ def call(context)
11
+ session = context.fetch(:session)
12
+ presenter = context.fetch(:presenter_factory).call(col_sep: session.source.separator)
13
+
14
+ result = context.fetch(:use_case).call(
15
+ session: session,
16
+ on_header: ->(headers) { presenter.print_header(headers) },
17
+ on_row: ->(fields) { presenter.print_row(fields) }
18
+ )
19
+ unless result.ok?
20
+ context.fetch(:handle_error).call(result)
21
+ return :halt
22
+ end
23
+
24
+ presenter.print_file_written(result.data[:output_path]) if session.output_destination.file?
25
+ presenter.print_summary(result.data[:stats])
26
+ nil
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csvtool/domain/column_session/extraction_value"
4
+ require "csvtool/domain/column_session/preview"
5
+
6
+ module Csvtool
7
+ module Interface
8
+ module CLI
9
+ module Workflows
10
+ module Steps
11
+ module Extraction
12
+ class BuildPreviewStep
13
+ def initialize(confirm_prompt:)
14
+ @confirm_prompt = confirm_prompt
15
+ end
16
+
17
+ def call(context)
18
+ preview_result = context.fetch(:use_case).preview(session: context.fetch(:session))
19
+ unless preview_result.ok?
20
+ context.fetch(:handle_error).call(preview_result)
21
+ return :halt
22
+ end
23
+
24
+ preview = Domain::ColumnSession::Preview.new(
25
+ values: preview_result.data[:preview_values].map { |value| Domain::ColumnSession::ExtractionValue.new(value) }
26
+ )
27
+ session = context.fetch(:session).with_preview(preview)
28
+ confirmed = @confirm_prompt.call(session.preview.to_strings)
29
+ return :halt unless confirmed
30
+
31
+ context[:session] = session.confirm!
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module Extraction
9
+ class CollectDestinationStep
10
+ def initialize(output_destination_prompt:)
11
+ @output_destination_prompt = output_destination_prompt
12
+ end
13
+
14
+ def call(context)
15
+ output_destination = @output_destination_prompt.call
16
+ return :halt if output_destination.nil?
17
+
18
+ destination = context.fetch(:output_destination_mapper).call(output_destination)
19
+ context[:session] = context.fetch(:session).with_output_destination(destination)
20
+ nil
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module Extraction
9
+ class CollectInputsStep
10
+ def initialize(file_path_prompt:, separator_prompt:, column_selector_prompt:, skip_blanks_prompt:)
11
+ @file_path_prompt = file_path_prompt
12
+ @separator_prompt = separator_prompt
13
+ @column_selector_prompt = column_selector_prompt
14
+ @skip_blanks_prompt = skip_blanks_prompt
15
+ end
16
+
17
+ def call(context)
18
+ file_path = @file_path_prompt.call
19
+ col_sep = @separator_prompt.call
20
+ return :halt if col_sep.nil?
21
+
22
+ header_result = context.fetch(:use_case).read_headers(file_path: file_path, col_sep: col_sep)
23
+ unless header_result.ok?
24
+ context.fetch(:handle_error).call(header_result)
25
+ return :halt
26
+ end
27
+
28
+ headers = header_result.data[:headers]
29
+ column_name = @column_selector_prompt.call(headers)
30
+ return :halt if column_name.nil?
31
+
32
+ skip_blanks = @skip_blanks_prompt.call
33
+ context[:session] = context.fetch(:session_builder).call(
34
+ file_path: file_path,
35
+ col_sep: col_sep,
36
+ column_name: column_name,
37
+ skip_blanks: skip_blanks
38
+ )
39
+ nil
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module Extraction
9
+ class ExecuteStep
10
+ def call(context)
11
+ result = context.fetch(:use_case).extract(
12
+ session: context.fetch(:session),
13
+ on_value: ->(value) { context.fetch(:presenter).print_value(value) }
14
+ )
15
+ unless result.ok?
16
+ context.fetch(:handle_error).call(result)
17
+ return :halt
18
+ end
19
+
20
+ session = context.fetch(:session)
21
+ if session.output_destination.file?
22
+ context.fetch(:presenter).print_file_written(result.data[:output_path])
23
+ end
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module RowExtraction
9
+ class CollectDestinationStep
10
+ def initialize(output_destination_prompt:)
11
+ @output_destination_prompt = output_destination_prompt
12
+ end
13
+
14
+ def call(context)
15
+ output_destination = @output_destination_prompt.call
16
+ return :halt if output_destination.nil?
17
+
18
+ destination = context.fetch(:output_destination_mapper).call(output_destination)
19
+ context[:session] = context.fetch(:session_builder).call(
20
+ file_path: context.fetch(:file_path),
21
+ col_sep: context.fetch(:col_sep),
22
+ row_range: context.fetch(:row_range),
23
+ destination: destination
24
+ )
25
+ nil
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csvtool/domain/row_session/row_range"
4
+
5
+ module Csvtool
6
+ module Interface
7
+ module CLI
8
+ module Workflows
9
+ module Steps
10
+ module RowExtraction
11
+ class CollectRangeStep
12
+ def initialize(stdin:, stdout:)
13
+ @stdin = stdin
14
+ @stdout = stdout
15
+ end
16
+
17
+ def call(context)
18
+ @stdout.print "Start row (1-based, inclusive): "
19
+ start_row_input = @stdin.gets&.strip.to_s
20
+ @stdout.print "End row (1-based, inclusive): "
21
+ end_row_input = @stdin.gets&.strip.to_s
22
+
23
+ context[:row_range] = Domain::RowSession::RowRange.from_inputs(
24
+ start_row_input: start_row_input,
25
+ end_row_input: end_row_input
26
+ )
27
+ nil
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csvtool/interface/cli/prompts/file_path_prompt"
4
+ require "csvtool/interface/cli/prompts/separator_prompt"
5
+
6
+ module Csvtool
7
+ module Interface
8
+ module CLI
9
+ module Workflows
10
+ module Steps
11
+ module RowExtraction
12
+ class CollectSourceStep
13
+ def initialize(file_path_prompt:, separator_prompt:)
14
+ @file_path_prompt = file_path_prompt
15
+ @separator_prompt = separator_prompt
16
+ end
17
+
18
+ def call(context)
19
+ context[:file_path] = @file_path_prompt.call
20
+ col_sep = @separator_prompt.call
21
+ return :halt if col_sep.nil?
22
+
23
+ context[:col_sep] = col_sep
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ 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 RowExtraction
9
+ class ExecuteStep
10
+ def initialize(stdout:, errors:, presenter_class: Presenters::RowExtractionPresenter)
11
+ @stdout = stdout
12
+ @errors = errors
13
+ @presenter_class = presenter_class
14
+ end
15
+
16
+ def call(context)
17
+ session = context.fetch(:session)
18
+ presenter = @presenter_class.new(
19
+ stdout: @stdout,
20
+ headers: context.fetch(:headers),
21
+ col_sep: ","
22
+ )
23
+ result = context.fetch(:use_case).extract(
24
+ session: session,
25
+ headers: context.fetch(:headers),
26
+ on_row: ->(fields) { presenter.print_row(fields) }
27
+ )
28
+ unless result.ok?
29
+ context.fetch(:handle_error).call(result)
30
+ return :halt
31
+ end
32
+
33
+ presenter.print_file_written(result.data[:output_path]) if result.data[:wrote_rows]
34
+ @errors.row_range_out_of_bounds(result.data[:row_count]) unless result.data[:matched]
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module RowExtraction
9
+ class ReadHeadersStep
10
+ def call(context)
11
+ result = context.fetch(:use_case).read_headers(
12
+ file_path: context.fetch(:file_path),
13
+ col_sep: context.fetch(:col_sep)
14
+ )
15
+ unless result.ok?
16
+ context.fetch(:handle_error).call(result)
17
+ return :halt
18
+ end
19
+
20
+ context[:headers] = result.data[:headers]
21
+ nil
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module RowRandomization
9
+ class CollectDestinationStep
10
+ def initialize(output_destination_prompt:)
11
+ @output_destination_prompt = output_destination_prompt
12
+ end
13
+
14
+ def call(context)
15
+ output_destination = @output_destination_prompt.call
16
+ return :halt if output_destination.nil?
17
+
18
+ destination = context.fetch(:output_destination_mapper).call(output_destination)
19
+ context[:session] = context.fetch(:session_builder).call(
20
+ file_path: context.fetch(:file_path),
21
+ col_sep: context.fetch(:col_sep),
22
+ headers_present: context.fetch(:headers_present),
23
+ seed: context.fetch(:seed),
24
+ destination: destination
25
+ )
26
+ nil
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module RowRandomization
9
+ class CollectInputsStep
10
+ def initialize(file_path_prompt:, separator_prompt:, headers_present_prompt:, seed_prompt:)
11
+ @file_path_prompt = file_path_prompt
12
+ @separator_prompt = separator_prompt
13
+ @headers_present_prompt = headers_present_prompt
14
+ @seed_prompt = seed_prompt
15
+ end
16
+
17
+ def call(context)
18
+ file_path = @file_path_prompt.call
19
+ col_sep = @separator_prompt.call
20
+ return :halt if col_sep.nil?
21
+
22
+ headers_present = @headers_present_prompt.call
23
+ header_result = context.fetch(:use_case).read_headers(
24
+ file_path: file_path,
25
+ col_sep: col_sep,
26
+ headers_present: headers_present
27
+ )
28
+ unless header_result.ok?
29
+ context.fetch(:handle_error).call(header_result)
30
+ return :halt
31
+ end
32
+
33
+ seed = @seed_prompt.call
34
+ return :halt if seed == Interface::CLI::Prompts::SeedPrompt::INVALID
35
+
36
+ context[:file_path] = file_path
37
+ context[:col_sep] = col_sep
38
+ context[:headers_present] = headers_present
39
+ context[:headers] = header_result.data[:headers]
40
+ context[:seed] = seed
41
+ nil
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Csvtool
4
+ module Interface
5
+ module CLI
6
+ module Workflows
7
+ module Steps
8
+ module RowRandomization
9
+ class ExecuteStep
10
+ def call(context)
11
+ session = context.fetch(:session)
12
+ presenter = context.fetch(:presenter_factory).call(
13
+ headers: context.fetch(:headers),
14
+ col_sep: session.source.separator
15
+ )
16
+
17
+ presenter.print_console_start unless session.output_destination.file?
18
+ result = context.fetch(:use_case).randomize(
19
+ session: session,
20
+ headers: context.fetch(:headers),
21
+ on_row: ->(fields) { presenter.print_row(fields) }
22
+ )
23
+ unless result.ok?
24
+ context.fetch(:handle_error).call(result)
25
+ return :halt
26
+ end
27
+
28
+ presenter.print_file_written(result.data[:output_path]) if session.output_destination.file?
29
+ nil
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end