csvops 0.1.0.alpha → 0.2.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -10
  3. data/docs/release-v0.2.0-alpha.md +80 -0
  4. data/lib/csvtool/application/use_cases/run_extraction.rb +17 -17
  5. data/lib/csvtool/application/use_cases/run_row_extraction.rb +111 -0
  6. data/lib/csvtool/cli.rb +6 -2
  7. data/lib/csvtool/domain/{extraction_session → column_session}/column_selection.rb +1 -1
  8. data/lib/csvtool/domain/{extraction_session/extraction_session.rb → column_session/column_session.rb} +2 -2
  9. data/lib/csvtool/domain/{extraction_session → column_session}/csv_source.rb +1 -1
  10. data/lib/csvtool/domain/{extraction_session → column_session}/extraction_options.rb +1 -1
  11. data/lib/csvtool/domain/{extraction_session → column_session}/extraction_value.rb +1 -1
  12. data/lib/csvtool/domain/{extraction_session → column_session}/output_destination.rb +1 -1
  13. data/lib/csvtool/domain/{extraction_session → column_session}/preview.rb +1 -1
  14. data/lib/csvtool/domain/{extraction_session → column_session}/separator.rb +1 -1
  15. data/lib/csvtool/domain/row_session/row_output_destination.rb +31 -0
  16. data/lib/csvtool/domain/row_session/row_range.rb +39 -0
  17. data/lib/csvtool/domain/row_session/row_session.rb +25 -0
  18. data/lib/csvtool/domain/row_session/row_source.rb +16 -0
  19. data/lib/csvtool/infrastructure/csv/row_streamer.rb +27 -0
  20. data/lib/csvtool/infrastructure/output/csv_row_console_writer.rb +34 -0
  21. data/lib/csvtool/infrastructure/output/csv_row_file_writer.rb +45 -0
  22. data/lib/csvtool/interface/cli/errors/presenter.rb +16 -0
  23. data/lib/csvtool/interface/cli/menu_loop.rb +10 -5
  24. data/lib/csvtool/version.rb +1 -1
  25. data/test/csvtool/application/use_cases/run_row_extraction_test.rb +140 -0
  26. data/test/csvtool/cli_test.rb +132 -6
  27. data/test/csvtool/cli_unit_test.rb +12 -1
  28. data/test/csvtool/domain/{extraction_session → column_session}/column_selection_test.rb +2 -2
  29. data/test/csvtool/domain/column_session/column_session_test.rb +35 -0
  30. data/test/csvtool/domain/column_session/csv_source_test.rb +14 -0
  31. data/test/csvtool/domain/{extraction_session → column_session}/extraction_options_test.rb +3 -3
  32. data/test/csvtool/domain/{extraction_session → column_session}/extraction_value_test.rb +2 -2
  33. data/test/csvtool/domain/{extraction_session → column_session}/output_destination_test.rb +3 -3
  34. data/test/csvtool/domain/column_session/preview_test.rb +18 -0
  35. data/test/csvtool/domain/{extraction_session → column_session}/separator_test.rb +3 -3
  36. data/test/csvtool/domain/row_session/row_output_destination_test.rb +23 -0
  37. data/test/csvtool/domain/row_session/row_range_test.rb +30 -0
  38. data/test/csvtool/domain/row_session/row_session_test.rb +22 -0
  39. data/test/csvtool/domain/row_session/row_source_test.rb +12 -0
  40. data/test/csvtool/infrastructure/csv/row_streamer_test.rb +41 -0
  41. data/test/csvtool/infrastructure/output/csv_row_console_writer_test.rb +24 -0
  42. data/test/csvtool/infrastructure/output/csv_row_file_writer_test.rb +40 -0
  43. data/test/csvtool/interface/cli/errors/presenter_test.rb +8 -0
  44. data/test/csvtool/interface/cli/menu_loop_test.rb +37 -12
  45. data/test/fixtures/sample_people_bad_tail.csv +5 -0
  46. metadata +35 -17
  47. data/test/csvtool/domain/extraction_session/csv_source_test.rb +0 -14
  48. data/test/csvtool/domain/extraction_session/extraction_session_test.rb +0 -35
  49. data/test/csvtool/domain/extraction_session/preview_test.rb +0 -18
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Csvtool
6
+ module Infrastructure
7
+ module Output
8
+ class CsvRowConsoleWriter
9
+ def initialize(stdout:, row_streamer:)
10
+ @stdout = stdout
11
+ @row_streamer = row_streamer
12
+ end
13
+
14
+ def call(file_path:, col_sep:, headers:, start_row:, end_row:)
15
+ wrote_header = false
16
+ stats = @row_streamer.each_in_range(
17
+ file_path: file_path,
18
+ col_sep: col_sep,
19
+ start_row: start_row,
20
+ end_row: end_row
21
+ ) do |fields|
22
+ unless wrote_header
23
+ @stdout.puts ::CSV.generate_line(headers, row_sep: "").chomp
24
+ wrote_header = true
25
+ end
26
+ @stdout.puts ::CSV.generate_line(fields, row_sep: "").chomp
27
+ end
28
+
29
+ stats
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Csvtool
6
+ module Infrastructure
7
+ module Output
8
+ class CsvRowFileWriter
9
+ def initialize(stdout:, errors:, row_streamer:)
10
+ @stdout = stdout
11
+ @errors = errors
12
+ @row_streamer = row_streamer
13
+ end
14
+
15
+ def call(file_path:, col_sep:, headers:, start_row:, end_row:, output_path:)
16
+ csv = nil
17
+ wrote_rows = false
18
+
19
+ stats = @row_streamer.each_in_range(
20
+ file_path: file_path,
21
+ col_sep: col_sep,
22
+ start_row: start_row,
23
+ end_row: end_row
24
+ ) do |fields|
25
+ unless wrote_rows
26
+ csv = ::CSV.open(output_path, "w")
27
+ csv << headers
28
+ wrote_rows = true
29
+ end
30
+ csv << fields
31
+ end
32
+
33
+ csv&.close
34
+ @stdout.puts "Wrote output to #{output_path}" if wrote_rows
35
+ stats
36
+ rescue Errno::EACCES, Errno::ENOENT => e
37
+ @errors.cannot_write_output_file(output_path, e.class)
38
+ nil
39
+ ensure
40
+ csv&.close unless csv&.closed?
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -52,6 +52,22 @@ module Csvtool
52
52
  def canceled
53
53
  @stdout.puts "Canceled."
54
54
  end
55
+
56
+ def invalid_start_row
57
+ @stdout.puts "Start row must be a positive integer."
58
+ end
59
+
60
+ def invalid_end_row
61
+ @stdout.puts "End row must be a positive integer."
62
+ end
63
+
64
+ def invalid_row_range_order
65
+ @stdout.puts "End row must be greater than or equal to start row."
66
+ end
67
+
68
+ def row_range_out_of_bounds(total_rows)
69
+ @stdout.puts "Row range is out of bounds. File has #{total_rows} data rows."
70
+ end
55
71
  end
56
72
  end
57
73
  end
@@ -4,25 +4,30 @@ module Csvtool
4
4
  module Interface
5
5
  module CLI
6
6
  class MenuLoop
7
- def initialize(stdin:, stdout:, menu_options:, extract_action:)
7
+ def initialize(stdin:, stdout:, menu_options:, extract_column_action:, extract_rows_action:)
8
8
  @stdin = stdin
9
9
  @stdout = stdout
10
10
  @menu_options = menu_options
11
- @extract_action = extract_action
11
+ @extract_column_action = extract_column_action
12
+ @extract_rows_action = extract_rows_action
12
13
  end
13
14
 
14
15
  def run
15
16
  loop do
16
17
  print_menu
17
18
  @stdout.print "> "
19
+ choice = @stdin.gets
20
+ return 0 if choice.nil?
18
21
 
19
- case @stdin.gets&.strip
22
+ case choice.strip
20
23
  when "1"
21
- @extract_action.call
24
+ @extract_column_action.call
22
25
  when "2"
26
+ @extract_rows_action.call
27
+ when "3"
23
28
  return 0
24
29
  else
25
- @stdout.puts "Please choose 1 or 2."
30
+ @stdout.puts "Please choose 1, 2, or 3."
26
31
  end
27
32
  end
28
33
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Csvtool
4
- VERSION = "0.1.0.alpha"
4
+ VERSION = "0.2.0.alpha"
5
5
  end
@@ -0,0 +1,140 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/application/use_cases/run_row_extraction"
5
+ require "tmpdir"
6
+
7
+ class RunRowExtractionTest < Minitest::Test
8
+ def test_use_case_prints_selected_row_range_with_header
9
+ out = StringIO.new
10
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
11
+ input = [fixture, "", "2", "3", ""].join("\n") + "\n"
12
+
13
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
14
+ use_case.call
15
+
16
+ assert_includes out.string, "name,city"
17
+ assert_includes out.string, "Bob,Paris"
18
+ assert_includes out.string, "Cara,Berlin"
19
+ refute_includes out.string, "Alice,London"
20
+ end
21
+
22
+ def test_rejects_non_numeric_start_row
23
+ out = StringIO.new
24
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
25
+ input = [fixture, "", "abc", "3", ""].join("\n") + "\n"
26
+
27
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
28
+ use_case.call
29
+
30
+ assert_includes out.string, "Start row must be a positive integer."
31
+ refute_includes out.string, "name,city"
32
+ end
33
+
34
+ def test_rejects_non_numeric_end_row
35
+ out = StringIO.new
36
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
37
+ input = [fixture, "", "1", "xyz", ""].join("\n") + "\n"
38
+
39
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
40
+ use_case.call
41
+
42
+ assert_includes out.string, "End row must be a positive integer."
43
+ refute_includes out.string, "name,city"
44
+ end
45
+
46
+ def test_rejects_end_before_start
47
+ out = StringIO.new
48
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
49
+ input = [fixture, "", "3", "2", ""].join("\n") + "\n"
50
+
51
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
52
+ use_case.call
53
+
54
+ assert_includes out.string, "End row must be greater than or equal to start row."
55
+ refute_includes out.string, "name,city"
56
+ end
57
+
58
+ def test_handles_out_of_bounds_start_row
59
+ out = StringIO.new
60
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
61
+ input = [fixture, "", "10", "12", ""].join("\n") + "\n"
62
+
63
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
64
+ use_case.call
65
+
66
+ assert_includes out.string, "Row range is out of bounds. File has 3 data rows."
67
+ refute_includes out.string, "name,city"
68
+ end
69
+
70
+ def test_use_case_supports_tsv_separator
71
+ out = StringIO.new
72
+ fixture = File.expand_path("../../../fixtures/sample_people.tsv", __dir__)
73
+ input = [fixture, "2", "2", "3", ""].join("\n") + "\n"
74
+
75
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
76
+ use_case.call
77
+
78
+ assert_includes out.string, "name,city"
79
+ assert_includes out.string, "Bob,Paris"
80
+ assert_includes out.string, "Cara,Berlin"
81
+ end
82
+
83
+ def test_use_case_supports_custom_separator
84
+ out = StringIO.new
85
+ fixture = File.expand_path("../../../fixtures/sample_people_colon.txt", __dir__)
86
+ input = [fixture, "5", ":", "2", "3", ""].join("\n") + "\n"
87
+
88
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
89
+ use_case.call
90
+
91
+ assert_includes out.string, "name,city"
92
+ assert_includes out.string, "Bob,Paris"
93
+ assert_includes out.string, "Cara,Berlin"
94
+ end
95
+
96
+ def test_use_case_can_write_selected_rows_to_file
97
+ out = StringIO.new
98
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
99
+
100
+ Dir.mktmpdir do |dir|
101
+ output_path = File.join(dir, "rows.csv")
102
+ input = [fixture, "", "2", "3", "2", output_path].join("\n") + "\n"
103
+
104
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
105
+ use_case.call
106
+
107
+ assert_equal "name,city\nBob,Paris\nCara,Berlin\n", File.read(output_path)
108
+ assert_includes out.string, "Wrote output to #{output_path}"
109
+ end
110
+ end
111
+
112
+ def test_stops_parsing_after_end_row_for_console_output
113
+ out = StringIO.new
114
+ fixture = File.expand_path("../../../fixtures/sample_people_bad_tail.csv", __dir__)
115
+ input = [fixture, "", "1", "2", ""].join("\n") + "\n"
116
+
117
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
118
+ use_case.call
119
+
120
+ assert_includes out.string, "name,city"
121
+ assert_includes out.string, "Alice,London"
122
+ assert_includes out.string, "Bob,Paris"
123
+ refute_includes out.string, "Could not parse CSV file."
124
+ end
125
+
126
+ def test_out_of_bounds_file_mode_reports_error
127
+ out = StringIO.new
128
+ fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
129
+
130
+ Dir.mktmpdir do |dir|
131
+ output_path = File.join(dir, "rows.csv")
132
+ input = [fixture, "", "10", "12", "2", output_path].join("\n") + "\n"
133
+
134
+ use_case = Csvtool::Application::UseCases::RunRowExtraction.new(stdin: StringIO.new(input), stdout: out)
135
+ use_case.call
136
+ end
137
+
138
+ assert_includes out.string, "Row range is out of bounds. File has 3 data rows."
139
+ end
140
+ end
@@ -11,7 +11,7 @@ class TestCli < Minitest::Test
11
11
 
12
12
  def test_menu_can_exit_cleanly
13
13
  output = StringIO.new
14
- status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("2\n"), stdout: output, stderr: StringIO.new)
14
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("3\n"), stdout: output, stderr: StringIO.new)
15
15
  assert_equal 0, status
16
16
  assert_includes output.string, "CSV Tool Menu"
17
17
  end
@@ -26,7 +26,7 @@ class TestCli < Minitest::Test
26
26
  "",
27
27
  "y",
28
28
  "",
29
- "2"
29
+ "3"
30
30
  ].join("\n") + "\n"
31
31
 
32
32
  output = StringIO.new
@@ -49,6 +49,132 @@ class TestCli < Minitest::Test
49
49
  assert_equal "Alice\nBob\nCara\n", output.string
50
50
  end
51
51
 
52
+ def test_row_range_workflow_prints_selected_rows
53
+ output = StringIO.new
54
+ input = [
55
+ "2",
56
+ fixture_path("sample_people.csv"),
57
+ "",
58
+ "2",
59
+ "3",
60
+ "",
61
+ "3"
62
+ ].join("\n") + "\n"
63
+
64
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
65
+
66
+ assert_equal 0, status
67
+ assert_includes output.string, "name,city"
68
+ assert_includes output.string, "Bob,Paris"
69
+ assert_includes output.string, "Cara,Berlin"
70
+ refute_includes output.string, "Alice,London"
71
+ end
72
+
73
+ def test_row_range_invalid_inputs_return_to_menu
74
+ output = StringIO.new
75
+ input = [
76
+ "2",
77
+ fixture_path("sample_people.csv"),
78
+ "",
79
+ "0",
80
+ "3",
81
+ "",
82
+ "3"
83
+ ].join("\n") + "\n"
84
+
85
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
86
+
87
+ assert_equal 0, status
88
+ assert_includes output.string, "Start row must be a positive integer."
89
+ assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
90
+ end
91
+
92
+ def test_row_range_workflow_supports_tsv_separator
93
+ output = StringIO.new
94
+ input = [
95
+ "2",
96
+ fixture_path("sample_people.tsv"),
97
+ "2",
98
+ "2",
99
+ "3",
100
+ "",
101
+ "3"
102
+ ].join("\n") + "\n"
103
+
104
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
105
+
106
+ assert_equal 0, status
107
+ assert_includes output.string, "name,city"
108
+ assert_includes output.string, "Bob,Paris"
109
+ assert_includes output.string, "Cara,Berlin"
110
+ end
111
+
112
+ def test_row_range_workflow_supports_custom_separator
113
+ output = StringIO.new
114
+ input = [
115
+ "2",
116
+ fixture_path("sample_people_colon.txt"),
117
+ "5",
118
+ ":",
119
+ "2",
120
+ "3",
121
+ "",
122
+ "3"
123
+ ].join("\n") + "\n"
124
+
125
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
126
+
127
+ assert_equal 0, status
128
+ assert_includes output.string, "name,city"
129
+ assert_includes output.string, "Bob,Paris"
130
+ assert_includes output.string, "Cara,Berlin"
131
+ end
132
+
133
+ def test_row_range_workflow_can_write_selected_rows_to_file
134
+ output = StringIO.new
135
+ output_path = nil
136
+
137
+ Dir.mktmpdir do |dir|
138
+ output_path = File.join(dir, "row_range.csv")
139
+ input = [
140
+ "2",
141
+ fixture_path("sample_people.csv"),
142
+ "",
143
+ "2",
144
+ "3",
145
+ "2",
146
+ output_path,
147
+ "3"
148
+ ].join("\n") + "\n"
149
+
150
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
151
+ assert_equal 0, status
152
+ assert_equal "name,city\nBob,Paris\nCara,Berlin\n", File.read(output_path)
153
+ end
154
+
155
+ assert_includes output.string, "Wrote output to #{output_path}"
156
+ end
157
+
158
+ def test_row_range_workflow_stops_before_malformed_tail
159
+ output = StringIO.new
160
+ input = [
161
+ "2",
162
+ fixture_path("sample_people_bad_tail.csv"),
163
+ "",
164
+ "1",
165
+ "2",
166
+ "",
167
+ "3"
168
+ ].join("\n") + "\n"
169
+
170
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
171
+
172
+ assert_equal 0, status
173
+ assert_includes output.string, "Alice,London"
174
+ assert_includes output.string, "Bob,Paris"
175
+ refute_includes output.string, "Could not parse CSV file."
176
+ end
177
+
52
178
  def test_end_to_end_file_output_writes_expected_csv
53
179
  output = StringIO.new
54
180
  output_path = nil
@@ -65,7 +191,7 @@ class TestCli < Minitest::Test
65
191
  "y",
66
192
  "2",
67
193
  output_path,
68
- "2"
194
+ "3"
69
195
  ].join("\n") + "\n"
70
196
 
71
197
  status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: StringIO.new)
@@ -85,7 +211,7 @@ class TestCli < Minitest::Test
85
211
  "1",
86
212
  "",
87
213
  "n",
88
- "2"
214
+ "3"
89
215
  ].join("\n") + "\n"
90
216
 
91
217
  output = StringIO.new
@@ -100,7 +226,7 @@ class TestCli < Minitest::Test
100
226
  output = StringIO.new
101
227
  status = Csvtool::CLI.start(
102
228
  ["menu"],
103
- stdin: StringIO.new("1\n/tmp/does-not-exist.csv\n2\n"),
229
+ stdin: StringIO.new("1\n/tmp/does-not-exist.csv\n3\n"),
104
230
  stdout: output,
105
231
  stderr: StringIO.new
106
232
  )
@@ -121,7 +247,7 @@ class TestCli < Minitest::Test
121
247
  "y",
122
248
  "2",
123
249
  "/tmp/not-a-dir/out.csv",
124
- "2"
250
+ "3"
125
251
  ].join("\n") + "\n"
126
252
 
127
253
  output = StringIO.new
@@ -16,7 +16,7 @@ class CliUnitTest < Minitest::Test
16
16
  end
17
17
 
18
18
  def test_menu_command_can_exit_zero
19
- status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("2\n"), stdout: StringIO.new, stderr: StringIO.new)
19
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("3\n"), stdout: StringIO.new, stderr: StringIO.new)
20
20
  assert_equal 0, status
21
21
  end
22
22
 
@@ -24,4 +24,15 @@ class CliUnitTest < Minitest::Test
24
24
  status = Csvtool::CLI.start(["column"], stdin: StringIO.new, stdout: StringIO.new, stderr: StringIO.new)
25
25
  assert_equal 1, status
26
26
  end
27
+
28
+ def test_menu_routes_to_row_range_shell
29
+ stdout = StringIO.new
30
+ fixture = File.expand_path("../fixtures/sample_people.csv", __dir__)
31
+ input = ["2", fixture, "", "2", "3", "", "3"].join("\n") + "\n"
32
+ status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: stdout, stderr: StringIO.new)
33
+ assert_equal 0, status
34
+ assert_includes stdout.string, "name,city"
35
+ assert_includes stdout.string, "Bob,Paris"
36
+ assert_includes stdout.string, "Cara,Berlin"
37
+ end
27
38
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../../test_helper"
4
- require "csvtool/domain/extraction_session/column_selection"
4
+ require "csvtool/domain/column_session/column_selection"
5
5
 
6
6
  class ColumnSelectionTest < Minitest::Test
7
7
  def test_stores_name
8
- selection = Csvtool::Domain::ExtractionSession::ColumnSelection.new(name: "city")
8
+ selection = Csvtool::Domain::ColumnSession::ColumnSelection.new(name: "city")
9
9
  assert_equal "city", selection.name
10
10
  end
11
11
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/column_session/column_session"
5
+ require "csvtool/domain/column_session/csv_source"
6
+ require "csvtool/domain/column_session/separator"
7
+ require "csvtool/domain/column_session/column_selection"
8
+ require "csvtool/domain/column_session/extraction_options"
9
+ require "csvtool/domain/column_session/preview"
10
+ require "csvtool/domain/column_session/extraction_value"
11
+ require "csvtool/domain/column_session/output_destination"
12
+
13
+ class ColumnSessionTest < Minitest::Test
14
+ def test_state_transitions
15
+ session = Csvtool::Domain::ColumnSession::ColumnSession.start(
16
+ source: Csvtool::Domain::ColumnSession::CsvSource.new(
17
+ path: "/tmp/in.csv",
18
+ separator: Csvtool::Domain::ColumnSession::Separator.new(",")
19
+ ),
20
+ column_selection: Csvtool::Domain::ColumnSession::ColumnSelection.new(name: "name"),
21
+ options: Csvtool::Domain::ColumnSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 10)
22
+ )
23
+
24
+ preview = Csvtool::Domain::ColumnSession::Preview.new(
25
+ values: [Csvtool::Domain::ColumnSession::ExtractionValue.new("Alice")]
26
+ )
27
+ session = session.with_preview(preview).confirm!.with_output_destination(
28
+ Csvtool::Domain::ColumnSession::OutputDestination.console
29
+ )
30
+
31
+ assert_equal true, session.confirmed?
32
+ assert_equal preview, session.preview
33
+ assert_equal true, session.output_destination.console?
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/column_session/csv_source"
5
+ require "csvtool/domain/column_session/separator"
6
+
7
+ class CsvSourceTest < Minitest::Test
8
+ def test_stores_path_and_separator
9
+ separator = Csvtool::Domain::ColumnSession::Separator.new(",")
10
+ source = Csvtool::Domain::ColumnSession::CsvSource.new(path: "/tmp/a.csv", separator: separator)
11
+ assert_equal "/tmp/a.csv", source.path
12
+ assert_equal separator, source.separator
13
+ end
14
+ end
@@ -1,18 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../../test_helper"
4
- require "csvtool/domain/extraction_session/extraction_options"
4
+ require "csvtool/domain/column_session/extraction_options"
5
5
 
6
6
  class ExtractionOptionsTest < Minitest::Test
7
7
  def test_exposes_options
8
- options = Csvtool::Domain::ExtractionSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 10)
8
+ options = Csvtool::Domain::ColumnSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 10)
9
9
  assert_equal true, options.skip_blanks?
10
10
  assert_equal 10, options.preview_limit
11
11
  end
12
12
 
13
13
  def test_non_positive_preview_limit_raises
14
14
  assert_raises(ArgumentError) do
15
- Csvtool::Domain::ExtractionSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 0)
15
+ Csvtool::Domain::ColumnSession::ExtractionOptions.new(skip_blanks: true, preview_limit: 0)
16
16
  end
17
17
  end
18
18
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../../test_helper"
4
- require "csvtool/domain/extraction_session/extraction_value"
4
+ require "csvtool/domain/column_session/extraction_value"
5
5
 
6
6
  class ExtractionValueTest < Minitest::Test
7
7
  def test_stringifies_value
8
- value = Csvtool::Domain::ExtractionSession::ExtractionValue.new(123)
8
+ value = Csvtool::Domain::ColumnSession::ExtractionValue.new(123)
9
9
  assert_equal "123", value.value
10
10
  end
11
11
  end
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../../test_helper"
4
- require "csvtool/domain/extraction_session/output_destination"
4
+ require "csvtool/domain/column_session/output_destination"
5
5
 
6
6
  class OutputDestinationTest < Minitest::Test
7
7
  def test_console_factory
8
- destination = Csvtool::Domain::ExtractionSession::OutputDestination.console
8
+ destination = Csvtool::Domain::ColumnSession::OutputDestination.console
9
9
  assert_equal true, destination.console?
10
10
  assert_equal false, destination.file?
11
11
  end
12
12
 
13
13
  def test_file_factory
14
- destination = Csvtool::Domain::ExtractionSession::OutputDestination.file(path: "/tmp/out.csv")
14
+ destination = Csvtool::Domain::ColumnSession::OutputDestination.file(path: "/tmp/out.csv")
15
15
  assert_equal true, destination.file?
16
16
  assert_equal "/tmp/out.csv", destination.path
17
17
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../../test_helper"
4
+ require "csvtool/domain/column_session/preview"
5
+ require "csvtool/domain/column_session/extraction_value"
6
+
7
+ class PreviewTest < Minitest::Test
8
+ def test_exposes_size_and_string_values
9
+ values = [
10
+ Csvtool::Domain::ColumnSession::ExtractionValue.new("Alice"),
11
+ Csvtool::Domain::ColumnSession::ExtractionValue.new("Bob")
12
+ ]
13
+ preview = Csvtool::Domain::ColumnSession::Preview.new(values: values)
14
+
15
+ assert_equal 2, preview.size
16
+ assert_equal %w[Alice Bob], preview.to_strings
17
+ end
18
+ end
@@ -1,15 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../../../test_helper"
4
- require "csvtool/domain/extraction_session/separator"
4
+ require "csvtool/domain/column_session/separator"
5
5
 
6
6
  class SeparatorTest < Minitest::Test
7
7
  def test_stores_value
8
- separator = Csvtool::Domain::ExtractionSession::Separator.new(",")
8
+ separator = Csvtool::Domain::ColumnSession::Separator.new(",")
9
9
  assert_equal ",", separator.value
10
10
  end
11
11
 
12
12
  def test_empty_value_raises
13
- assert_raises(ArgumentError) { Csvtool::Domain::ExtractionSession::Separator.new("") }
13
+ assert_raises(ArgumentError) { Csvtool::Domain::ColumnSession::Separator.new("") }
14
14
  end
15
15
  end