csvops 0.8.0.alpha → 0.9.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 +23 -3
- data/docs/architecture.md +3 -0
- data/docs/cli-output-conventions.md +49 -0
- data/docs/release-v0.9.0-alpha.md +80 -0
- data/lib/csvtool/cli.rb +132 -12
- data/lib/csvtool/interface/cli/menu_loop.rb +6 -5
- data/lib/csvtool/interface/cli/output/color_policy.rb +25 -0
- data/lib/csvtool/interface/cli/output/colorizer.rb +27 -0
- data/lib/csvtool/interface/cli/output/formatters/csv_row_formatter.rb +19 -0
- data/lib/csvtool/interface/cli/output/formatters/stats_formatter.rb +57 -0
- data/lib/csvtool/interface/cli/output/streams.rb +22 -0
- data/lib/csvtool/interface/cli/output/table_renderer.rb +70 -0
- data/lib/csvtool/interface/cli/workflows/presenters/cross_csv_dedupe_presenter.rb +17 -5
- data/lib/csvtool/interface/cli/workflows/presenters/csv_parity_presenter.rb +15 -4
- data/lib/csvtool/interface/cli/workflows/presenters/csv_split_presenter.rb +15 -6
- data/lib/csvtool/interface/cli/workflows/presenters/csv_stats_presenter.rb +18 -9
- data/lib/csvtool/interface/cli/workflows/presenters/row_extraction_presenter.rb +5 -4
- data/lib/csvtool/interface/cli/workflows/presenters/row_randomization_presenter.rb +5 -4
- data/lib/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow.rb +9 -8
- data/lib/csvtool/interface/cli/workflows/run_csv_parity_workflow.rb +6 -5
- data/lib/csvtool/interface/cli/workflows/run_csv_split_workflow.rb +11 -10
- data/lib/csvtool/interface/cli/workflows/run_csv_stats_workflow.rb +7 -6
- data/lib/csvtool/interface/cli/workflows/run_extraction_workflow.rb +9 -8
- data/lib/csvtool/interface/cli/workflows/run_row_extraction_workflow.rb +7 -6
- data/lib/csvtool/interface/cli/workflows/run_row_randomization_workflow.rb +8 -7
- data/lib/csvtool/version.rb +1 -1
- data/test/csvtool/cli_test.rb +289 -44
- data/test/csvtool/cli_unit_test.rb +5 -5
- data/test/csvtool/interface/cli/output/color_policy_test.rb +40 -0
- data/test/csvtool/interface/cli/output/colorizer_test.rb +28 -0
- data/test/csvtool/interface/cli/output/formatters/csv_row_formatter_test.rb +22 -0
- data/test/csvtool/interface/cli/output/formatters/stats_formatter_test.rb +51 -0
- data/test/csvtool/interface/cli/output/streams_test.rb +25 -0
- data/test/csvtool/interface/cli/output/table_renderer_test.rb +36 -0
- data/test/csvtool/interface/cli/workflows/presenters/cross_csv_dedupe_presenter_test.rb +4 -1
- data/test/csvtool/interface/cli/workflows/presenters/csv_parity_presenter_test.rb +5 -1
- data/test/csvtool/interface/cli/workflows/presenters/csv_split_presenter_test.rb +22 -4
- data/test/csvtool/interface/cli/workflows/presenters/csv_stats_presenter_test.rb +7 -5
- data/test/csvtool/interface/cli/workflows/run_cross_csv_dedupe_workflow_test.rb +10 -7
- data/test/csvtool/interface/cli/workflows/run_csv_parity_workflow_test.rb +3 -1
- data/test/csvtool/interface/cli/workflows/run_csv_split_workflow_test.rb +5 -3
- data/test/csvtool/interface/cli/workflows/run_csv_stats_workflow_test.rb +23 -18
- metadata +15 -1
data/test/csvtool/cli_test.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../test_helper"
|
|
4
4
|
require "csvtool/cli"
|
|
5
|
+
require "json"
|
|
5
6
|
require "tmpdir"
|
|
6
7
|
require "fileutils"
|
|
7
8
|
|
|
@@ -12,7 +13,7 @@ class TestCli < Minitest::Test
|
|
|
12
13
|
|
|
13
14
|
def test_menu_can_exit_cleanly
|
|
14
15
|
output = StringIO.new
|
|
15
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("8\n"), stdout: output, stderr:
|
|
16
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("8\n"), stdout: output, stderr: output)
|
|
16
17
|
assert_equal 0, status
|
|
17
18
|
assert_includes output.string, "CSV Tool Menu"
|
|
18
19
|
end
|
|
@@ -28,12 +29,13 @@ class TestCli < Minitest::Test
|
|
|
28
29
|
"8"
|
|
29
30
|
].join("\n") + "\n"
|
|
30
31
|
|
|
31
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
32
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
32
33
|
|
|
33
34
|
assert_equal 0, status
|
|
34
35
|
assert_includes output.string, "CSV Stats Summary"
|
|
35
|
-
assert_includes output.string, "
|
|
36
|
-
assert_includes output.string, "
|
|
36
|
+
assert_includes output.string, "Metric"
|
|
37
|
+
assert_includes output.string, "Rows"
|
|
38
|
+
assert_includes output.string, "Columns"
|
|
37
39
|
assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
|
|
38
40
|
end
|
|
39
41
|
|
|
@@ -48,7 +50,7 @@ class TestCli < Minitest::Test
|
|
|
48
50
|
"8"
|
|
49
51
|
].join("\n") + "\n"
|
|
50
52
|
|
|
51
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
53
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
52
54
|
|
|
53
55
|
assert_equal 0, status
|
|
54
56
|
assert_includes output.string, "File not found: /tmp/does-not-exist.csv"
|
|
@@ -70,7 +72,7 @@ class TestCli < Minitest::Test
|
|
|
70
72
|
"8"
|
|
71
73
|
].join("\n") + "\n"
|
|
72
74
|
|
|
73
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
75
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
74
76
|
|
|
75
77
|
assert_equal 0, status
|
|
76
78
|
assert_includes output.string, "Wrote output to #{output_path}"
|
|
@@ -98,10 +100,10 @@ class TestCli < Minitest::Test
|
|
|
98
100
|
"8"
|
|
99
101
|
].join("\n") + "\n"
|
|
100
102
|
|
|
101
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
103
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
102
104
|
|
|
103
105
|
assert_equal 0, status
|
|
104
|
-
assert_includes output.string, "Chunks written
|
|
106
|
+
assert_includes output.string, "Chunks written"
|
|
105
107
|
assert File.file?(File.join(dir, "people_part_001.csv"))
|
|
106
108
|
assert File.file?(File.join(dir, "people_part_002.csv"))
|
|
107
109
|
assert File.file?(File.join(dir, "people_part_003.csv"))
|
|
@@ -119,7 +121,7 @@ class TestCli < Minitest::Test
|
|
|
119
121
|
"8"
|
|
120
122
|
].join("\n") + "\n"
|
|
121
123
|
|
|
122
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
124
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
123
125
|
|
|
124
126
|
assert_equal 0, status
|
|
125
127
|
assert_includes output.string, "Chunk size must be a positive integer."
|
|
@@ -140,7 +142,7 @@ class TestCli < Minitest::Test
|
|
|
140
142
|
].join("\n") + "\n"
|
|
141
143
|
|
|
142
144
|
output = StringIO.new
|
|
143
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
145
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
144
146
|
|
|
145
147
|
assert_equal 0, status
|
|
146
148
|
assert_match(/\nAlice\nBob\nCara\n/, output.string)
|
|
@@ -159,6 +161,244 @@ class TestCli < Minitest::Test
|
|
|
159
161
|
assert_equal "Alice\nBob\nCara\n", output.string
|
|
160
162
|
end
|
|
161
163
|
|
|
164
|
+
def test_stats_command_writes_summary_to_stdout_only
|
|
165
|
+
stdout = StringIO.new
|
|
166
|
+
stderr = StringIO.new
|
|
167
|
+
|
|
168
|
+
status = Csvtool::CLI.start(
|
|
169
|
+
["stats", fixture_path("sample_people.csv")],
|
|
170
|
+
stdin: StringIO.new,
|
|
171
|
+
stdout: stdout,
|
|
172
|
+
stderr: stderr
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
assert_equal 0, status
|
|
176
|
+
assert_includes stdout.string, "CSV Stats Summary"
|
|
177
|
+
assert_includes stdout.string, "Metric"
|
|
178
|
+
assert_includes stdout.string, "Rows"
|
|
179
|
+
assert_includes stdout.string, "Columns"
|
|
180
|
+
assert_includes stdout.string, "Headers"
|
|
181
|
+
assert_equal "", stderr.string
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def test_stats_command_writes_errors_to_stderr_only
|
|
185
|
+
stdout = StringIO.new
|
|
186
|
+
stderr = StringIO.new
|
|
187
|
+
|
|
188
|
+
status = Csvtool::CLI.start(
|
|
189
|
+
["stats", "/tmp/not-there.csv"],
|
|
190
|
+
stdin: StringIO.new,
|
|
191
|
+
stdout: stdout,
|
|
192
|
+
stderr: stderr
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
assert_equal 1, status
|
|
196
|
+
assert_equal "", stdout.string
|
|
197
|
+
assert_includes stderr.string, "File not found: /tmp/not-there.csv"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def test_stats_command_supports_json_format
|
|
201
|
+
stdout = StringIO.new
|
|
202
|
+
stderr = StringIO.new
|
|
203
|
+
|
|
204
|
+
status = Csvtool::CLI.start(
|
|
205
|
+
["stats", fixture_path("sample_people.csv"), "--format", "json"],
|
|
206
|
+
stdin: StringIO.new,
|
|
207
|
+
stdout: stdout,
|
|
208
|
+
stderr: stderr
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
assert_equal 0, status
|
|
212
|
+
data = JSON.parse(stdout.string, symbolize_names: true)
|
|
213
|
+
assert_equal 3, data[:row_count]
|
|
214
|
+
assert_equal 2, data[:column_count]
|
|
215
|
+
assert_equal ["name", "city"], data[:headers]
|
|
216
|
+
assert_equal "", stderr.string
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def test_stats_command_supports_csv_format
|
|
220
|
+
stdout = StringIO.new
|
|
221
|
+
stderr = StringIO.new
|
|
222
|
+
|
|
223
|
+
status = Csvtool::CLI.start(
|
|
224
|
+
["stats", fixture_path("sample_people.csv"), "--format=csv"],
|
|
225
|
+
stdin: StringIO.new,
|
|
226
|
+
stdout: stdout,
|
|
227
|
+
stderr: stderr
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
assert_equal 0, status
|
|
231
|
+
lines = stdout.string.lines.map(&:strip)
|
|
232
|
+
assert_equal "metric,value", lines[0]
|
|
233
|
+
assert_includes lines, "row_count,3"
|
|
234
|
+
assert_includes lines, "column_count,2"
|
|
235
|
+
assert_includes lines, "headers,name|city"
|
|
236
|
+
assert_equal "", stderr.string
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def test_stats_command_rejects_unknown_format
|
|
240
|
+
stdout = StringIO.new
|
|
241
|
+
stderr = StringIO.new
|
|
242
|
+
|
|
243
|
+
status = Csvtool::CLI.start(
|
|
244
|
+
["stats", fixture_path("sample_people.csv"), "--format", "yaml"],
|
|
245
|
+
stdin: StringIO.new,
|
|
246
|
+
stdout: stdout,
|
|
247
|
+
stderr: stderr
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
assert_equal 1, status
|
|
251
|
+
assert_equal "", stdout.string
|
|
252
|
+
assert_includes stderr.string, "Invalid format: yaml"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def test_stats_command_color_auto_without_tty_has_no_ansi
|
|
256
|
+
stdout = StringIO.new
|
|
257
|
+
stderr = StringIO.new
|
|
258
|
+
|
|
259
|
+
status = Csvtool::CLI.start(
|
|
260
|
+
["stats", fixture_path("sample_people.csv"), "--color", "auto"],
|
|
261
|
+
stdin: StringIO.new,
|
|
262
|
+
stdout: stdout,
|
|
263
|
+
stderr: stderr
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
assert_equal 0, status
|
|
267
|
+
refute_match(/\e\[[0-9;]*m/, stdout.string)
|
|
268
|
+
assert_equal "", stderr.string
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def test_stats_command_color_always_adds_ansi
|
|
272
|
+
stdout = StringIO.new
|
|
273
|
+
stderr = StringIO.new
|
|
274
|
+
|
|
275
|
+
status = Csvtool::CLI.start(
|
|
276
|
+
["stats", fixture_path("sample_people.csv"), "--color=always"],
|
|
277
|
+
stdin: StringIO.new,
|
|
278
|
+
stdout: stdout,
|
|
279
|
+
stderr: stderr
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
assert_equal 0, status
|
|
283
|
+
assert_match(/\e\[[0-9;]*m/, stdout.string)
|
|
284
|
+
assert_equal "", stderr.string
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def test_stats_command_color_never_disables_ansi
|
|
288
|
+
stdout = StringIO.new
|
|
289
|
+
stderr = StringIO.new
|
|
290
|
+
|
|
291
|
+
status = Csvtool::CLI.start(
|
|
292
|
+
["stats", fixture_path("sample_people.csv"), "--color", "never"],
|
|
293
|
+
stdin: StringIO.new,
|
|
294
|
+
stdout: stdout,
|
|
295
|
+
stderr: stderr
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
assert_equal 0, status
|
|
299
|
+
refute_match(/\e\[[0-9;]*m/, stdout.string)
|
|
300
|
+
assert_equal "", stderr.string
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def test_stats_command_no_color_env_disables_auto
|
|
304
|
+
stdout = StringIO.new
|
|
305
|
+
stderr = StringIO.new
|
|
306
|
+
|
|
307
|
+
status = Csvtool::CLI.start(
|
|
308
|
+
["stats", fixture_path("sample_people.csv"), "--color", "auto"],
|
|
309
|
+
stdin: StringIO.new,
|
|
310
|
+
stdout: stdout,
|
|
311
|
+
stderr: stderr,
|
|
312
|
+
env: { "NO_COLOR" => "1" }
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
assert_equal 0, status
|
|
316
|
+
refute_match(/\e\[[0-9;]*m/, stdout.string)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def test_stats_command_color_always_overrides_no_color_env
|
|
320
|
+
stdout = StringIO.new
|
|
321
|
+
stderr = StringIO.new
|
|
322
|
+
|
|
323
|
+
status = Csvtool::CLI.start(
|
|
324
|
+
["stats", fixture_path("sample_people.csv"), "--color", "always"],
|
|
325
|
+
stdin: StringIO.new,
|
|
326
|
+
stdout: stdout,
|
|
327
|
+
stderr: stderr,
|
|
328
|
+
env: { "NO_COLOR" => "1" }
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
assert_equal 0, status
|
|
332
|
+
assert_match(/\e\[[0-9;]*m/, stdout.string)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def test_stats_command_rejects_unknown_color_mode
|
|
336
|
+
stdout = StringIO.new
|
|
337
|
+
stderr = StringIO.new
|
|
338
|
+
|
|
339
|
+
status = Csvtool::CLI.start(
|
|
340
|
+
["stats", fixture_path("sample_people.csv"), "--color", "sometimes"],
|
|
341
|
+
stdin: StringIO.new,
|
|
342
|
+
stdout: stdout,
|
|
343
|
+
stderr: stderr
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
assert_equal 1, status
|
|
347
|
+
assert_equal "", stdout.string
|
|
348
|
+
assert_includes stderr.string, "Invalid color mode: sometimes"
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def test_menu_workflow_routes_ui_to_stderr_and_data_to_stdout
|
|
352
|
+
stdout = StringIO.new
|
|
353
|
+
stderr = StringIO.new
|
|
354
|
+
input = [
|
|
355
|
+
"7",
|
|
356
|
+
fixture_path("sample_people.csv"),
|
|
357
|
+
"",
|
|
358
|
+
"",
|
|
359
|
+
"",
|
|
360
|
+
"8"
|
|
361
|
+
].join("\n") + "\n"
|
|
362
|
+
|
|
363
|
+
status = Csvtool::CLI.start(
|
|
364
|
+
["menu"],
|
|
365
|
+
stdin: StringIO.new(input),
|
|
366
|
+
stdout: stdout,
|
|
367
|
+
stderr: stderr
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
assert_equal 0, status
|
|
371
|
+
assert_includes stderr.string, "CSV Tool Menu"
|
|
372
|
+
assert_includes stderr.string, "CSV file path:"
|
|
373
|
+
assert_includes stdout.string, "CSV Stats Summary"
|
|
374
|
+
refute_includes stdout.string, "CSV Tool Menu"
|
|
375
|
+
refute_includes stdout.string, "CSV file path:"
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def test_stats_command_table_respects_narrow_terminal_width
|
|
379
|
+
stdout = StringIO.new
|
|
380
|
+
stderr = StringIO.new
|
|
381
|
+
|
|
382
|
+
Dir.mktmpdir do |dir|
|
|
383
|
+
path = File.join(dir, "long_headers.csv")
|
|
384
|
+
File.write(path, "very_long_column_name,another_really_long_column_name\nvalue_a,value_b\n")
|
|
385
|
+
|
|
386
|
+
status = Csvtool::CLI.start(
|
|
387
|
+
["stats", path],
|
|
388
|
+
stdin: StringIO.new,
|
|
389
|
+
stdout: stdout,
|
|
390
|
+
stderr: stderr,
|
|
391
|
+
env: { "COLUMNS" => "32" }
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
assert_equal 0, status
|
|
395
|
+
lines = stdout.string.lines.map(&:chomp)
|
|
396
|
+
assert lines.all? { |line| line.length <= 32 }
|
|
397
|
+
assert_includes stdout.string, "..."
|
|
398
|
+
assert_equal "", stderr.string
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
162
402
|
def test_row_range_workflow_prints_selected_rows
|
|
163
403
|
output = StringIO.new
|
|
164
404
|
input = [
|
|
@@ -171,7 +411,7 @@ class TestCli < Minitest::Test
|
|
|
171
411
|
"8"
|
|
172
412
|
].join("\n") + "\n"
|
|
173
413
|
|
|
174
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
414
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
175
415
|
|
|
176
416
|
assert_equal 0, status
|
|
177
417
|
assert_includes output.string, "name,city"
|
|
@@ -192,7 +432,7 @@ class TestCli < Minitest::Test
|
|
|
192
432
|
"8"
|
|
193
433
|
].join("\n") + "\n"
|
|
194
434
|
|
|
195
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
435
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
196
436
|
|
|
197
437
|
assert_equal 0, status
|
|
198
438
|
assert_includes output.string, "Start row must be a positive integer."
|
|
@@ -211,7 +451,7 @@ class TestCli < Minitest::Test
|
|
|
211
451
|
"8"
|
|
212
452
|
].join("\n") + "\n"
|
|
213
453
|
|
|
214
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
454
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
215
455
|
|
|
216
456
|
assert_equal 0, status
|
|
217
457
|
assert_includes output.string, "name,city"
|
|
@@ -232,7 +472,7 @@ class TestCli < Minitest::Test
|
|
|
232
472
|
"8"
|
|
233
473
|
].join("\n") + "\n"
|
|
234
474
|
|
|
235
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
475
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
236
476
|
|
|
237
477
|
assert_equal 0, status
|
|
238
478
|
assert_includes output.string, "name,city"
|
|
@@ -257,7 +497,7 @@ class TestCli < Minitest::Test
|
|
|
257
497
|
"8"
|
|
258
498
|
].join("\n") + "\n"
|
|
259
499
|
|
|
260
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
500
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
261
501
|
assert_equal 0, status
|
|
262
502
|
assert_equal "name,city\nBob,Paris\nCara,Berlin\n", File.read(output_path)
|
|
263
503
|
end
|
|
@@ -277,7 +517,7 @@ class TestCli < Minitest::Test
|
|
|
277
517
|
"8"
|
|
278
518
|
].join("\n") + "\n"
|
|
279
519
|
|
|
280
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
520
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
281
521
|
|
|
282
522
|
assert_equal 0, status
|
|
283
523
|
assert_includes output.string, "Alice,London"
|
|
@@ -297,7 +537,7 @@ class TestCli < Minitest::Test
|
|
|
297
537
|
"8"
|
|
298
538
|
].join("\n") + "\n"
|
|
299
539
|
|
|
300
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
540
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
301
541
|
|
|
302
542
|
assert_equal 0, status
|
|
303
543
|
assert_includes output.string, "name,city"
|
|
@@ -322,7 +562,7 @@ class TestCli < Minitest::Test
|
|
|
322
562
|
"8"
|
|
323
563
|
].join("\n") + "\n"
|
|
324
564
|
|
|
325
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
565
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
326
566
|
|
|
327
567
|
assert_equal 0, status
|
|
328
568
|
assert_includes output.string, "Wrote output to #{output_path}"
|
|
@@ -344,7 +584,7 @@ class TestCli < Minitest::Test
|
|
|
344
584
|
"8"
|
|
345
585
|
].join("\n") + "\n"
|
|
346
586
|
|
|
347
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
587
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
348
588
|
|
|
349
589
|
assert_equal 0, status
|
|
350
590
|
assert_includes output.string, "name\tcity"
|
|
@@ -363,7 +603,7 @@ class TestCli < Minitest::Test
|
|
|
363
603
|
"8"
|
|
364
604
|
].join("\n") + "\n"
|
|
365
605
|
|
|
366
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
606
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
367
607
|
|
|
368
608
|
assert_equal 0, status
|
|
369
609
|
refute_includes output.string, "name,city"
|
|
@@ -383,7 +623,7 @@ class TestCli < Minitest::Test
|
|
|
383
623
|
"8"
|
|
384
624
|
].join("\n") + "\n"
|
|
385
625
|
|
|
386
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
626
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
387
627
|
|
|
388
628
|
assert_equal 0, status
|
|
389
629
|
assert_includes output.string, "Seed must be an integer."
|
|
@@ -408,7 +648,7 @@ class TestCli < Minitest::Test
|
|
|
408
648
|
"8"
|
|
409
649
|
].join("\n") + "\n"
|
|
410
650
|
|
|
411
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
651
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
412
652
|
|
|
413
653
|
assert_equal 0, status
|
|
414
654
|
assert_includes output.string, "Reference CSV file path:"
|
|
@@ -417,7 +657,7 @@ class TestCli < Minitest::Test
|
|
|
417
657
|
assert_includes output.string, "customer_id,name"
|
|
418
658
|
assert_includes output.string, "1,Alice"
|
|
419
659
|
assert_includes output.string, "3,Cara"
|
|
420
|
-
assert_includes output.string, "Summary
|
|
660
|
+
assert_includes output.string, "Summary"
|
|
421
661
|
end
|
|
422
662
|
|
|
423
663
|
def test_dedupe_workflow_can_write_to_file
|
|
@@ -442,12 +682,12 @@ class TestCli < Minitest::Test
|
|
|
442
682
|
"8"
|
|
443
683
|
].join("\n") + "\n"
|
|
444
684
|
|
|
445
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
685
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
446
686
|
|
|
447
687
|
assert_equal 0, status
|
|
448
688
|
assert_includes output.string, "Wrote output to #{output_path}"
|
|
449
689
|
assert_equal "customer_id,name\n1,Alice\n3,Cara\n", File.read(output_path)
|
|
450
|
-
assert_includes output.string, "Summary
|
|
690
|
+
assert_includes output.string, "Summary"
|
|
451
691
|
end
|
|
452
692
|
end
|
|
453
693
|
|
|
@@ -469,7 +709,7 @@ class TestCli < Minitest::Test
|
|
|
469
709
|
"8"
|
|
470
710
|
].join("\n") + "\n"
|
|
471
711
|
|
|
472
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
712
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
473
713
|
|
|
474
714
|
assert_equal 0, status
|
|
475
715
|
assert_includes output.string, "customer_id\tname"
|
|
@@ -495,13 +735,13 @@ class TestCli < Minitest::Test
|
|
|
495
735
|
"8"
|
|
496
736
|
].join("\n") + "\n"
|
|
497
737
|
|
|
498
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
738
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
499
739
|
|
|
500
740
|
assert_equal 0, status
|
|
501
741
|
refute_includes output.string, "customer_id,name"
|
|
502
742
|
assert_includes output.string, "1,Alice"
|
|
503
743
|
assert_includes output.string, "3,Cara"
|
|
504
|
-
assert_includes output.string, "Summary
|
|
744
|
+
assert_includes output.string, "Summary"
|
|
505
745
|
end
|
|
506
746
|
|
|
507
747
|
def test_parity_workflow_reports_match_and_returns_to_menu
|
|
@@ -515,13 +755,15 @@ class TestCli < Minitest::Test
|
|
|
515
755
|
"8"
|
|
516
756
|
].join("\n") + "\n"
|
|
517
757
|
|
|
518
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
758
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
519
759
|
|
|
520
760
|
assert_equal 0, status
|
|
521
761
|
assert_includes output.string, "Left CSV file path:"
|
|
522
762
|
assert_includes output.string, "Right CSV file path:"
|
|
523
763
|
assert_includes output.string, "MATCH"
|
|
524
|
-
assert_includes output.string, "
|
|
764
|
+
assert_includes output.string, "Metric"
|
|
765
|
+
assert_includes output.string, "Left rows"
|
|
766
|
+
assert_includes output.string, "Right rows"
|
|
525
767
|
assert_operator output.string.scan("CSV Tool Menu").length, :>=, 2
|
|
526
768
|
end
|
|
527
769
|
|
|
@@ -536,11 +778,12 @@ class TestCli < Minitest::Test
|
|
|
536
778
|
"8"
|
|
537
779
|
].join("\n") + "\n"
|
|
538
780
|
|
|
539
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
781
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
540
782
|
|
|
541
783
|
assert_equal 0, status
|
|
542
784
|
assert_includes output.string, "MATCH"
|
|
543
|
-
assert_includes output.string, "
|
|
785
|
+
assert_includes output.string, "Left rows"
|
|
786
|
+
assert_includes output.string, "Right rows"
|
|
544
787
|
end
|
|
545
788
|
|
|
546
789
|
def test_parity_workflow_headerless_mode_compares_all_rows
|
|
@@ -554,11 +797,12 @@ class TestCli < Minitest::Test
|
|
|
554
797
|
"8"
|
|
555
798
|
].join("\n") + "\n"
|
|
556
799
|
|
|
557
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
800
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
558
801
|
|
|
559
802
|
assert_equal 0, status
|
|
560
803
|
assert_includes output.string, "MATCH"
|
|
561
|
-
assert_includes output.string, "
|
|
804
|
+
assert_includes output.string, "Left rows"
|
|
805
|
+
assert_includes output.string, "Right rows"
|
|
562
806
|
end
|
|
563
807
|
|
|
564
808
|
def test_parity_workflow_reports_header_mismatch_in_headered_mode
|
|
@@ -572,7 +816,7 @@ class TestCli < Minitest::Test
|
|
|
572
816
|
"8"
|
|
573
817
|
].join("\n") + "\n"
|
|
574
818
|
|
|
575
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
819
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
576
820
|
|
|
577
821
|
assert_equal 0, status
|
|
578
822
|
assert_includes output.string, "CSV headers do not match."
|
|
@@ -590,11 +834,12 @@ class TestCli < Minitest::Test
|
|
|
590
834
|
"8"
|
|
591
835
|
].join("\n") + "\n"
|
|
592
836
|
|
|
593
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
837
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
594
838
|
|
|
595
839
|
assert_equal 0, status
|
|
596
840
|
assert_includes output.string, "MISMATCH"
|
|
597
|
-
assert_includes output.string, "
|
|
841
|
+
assert_includes output.string, "Left only"
|
|
842
|
+
assert_includes output.string, "Right only"
|
|
598
843
|
assert_includes output.string, "Left-only examples:"
|
|
599
844
|
assert_includes output.string, "Cara,Berlin (count +1)"
|
|
600
845
|
assert_includes output.string, "Right-only examples:"
|
|
@@ -612,7 +857,7 @@ class TestCli < Minitest::Test
|
|
|
612
857
|
"8"
|
|
613
858
|
].join("\n") + "\n"
|
|
614
859
|
|
|
615
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
860
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
616
861
|
|
|
617
862
|
assert_equal 0, status
|
|
618
863
|
assert_includes output.string, "File not found: /tmp/not-there-left.csv"
|
|
@@ -631,7 +876,7 @@ class TestCli < Minitest::Test
|
|
|
631
876
|
"8"
|
|
632
877
|
].join("\n") + "\n"
|
|
633
878
|
|
|
634
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
879
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
635
880
|
|
|
636
881
|
assert_equal 0, status
|
|
637
882
|
assert_includes output.string, "File not found: /tmp/not-there-right.csv"
|
|
@@ -650,7 +895,7 @@ class TestCli < Minitest::Test
|
|
|
650
895
|
"8"
|
|
651
896
|
].join("\n") + "\n"
|
|
652
897
|
|
|
653
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
898
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
654
899
|
|
|
655
900
|
assert_equal 0, status
|
|
656
901
|
assert_includes output.string, "Could not parse CSV file."
|
|
@@ -677,7 +922,7 @@ class TestCli < Minitest::Test
|
|
|
677
922
|
"8"
|
|
678
923
|
].join("\n") + "\n"
|
|
679
924
|
|
|
680
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
925
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
681
926
|
assert_equal 0, status
|
|
682
927
|
assert_equal "name\nAlice\nBob\nCara\n", File.read(output_path)
|
|
683
928
|
end
|
|
@@ -698,7 +943,7 @@ class TestCli < Minitest::Test
|
|
|
698
943
|
].join("\n") + "\n"
|
|
699
944
|
|
|
700
945
|
output = StringIO.new
|
|
701
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
946
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
702
947
|
|
|
703
948
|
assert_equal 0, status
|
|
704
949
|
assert_includes output.string, "Canceled."
|
|
@@ -711,7 +956,7 @@ class TestCli < Minitest::Test
|
|
|
711
956
|
["menu"],
|
|
712
957
|
stdin: StringIO.new("1\n/tmp/does-not-exist.csv\n4\n7\n"),
|
|
713
958
|
stdout: output,
|
|
714
|
-
stderr:
|
|
959
|
+
stderr: output
|
|
715
960
|
)
|
|
716
961
|
|
|
717
962
|
assert_equal 0, status
|
|
@@ -734,7 +979,7 @@ class TestCli < Minitest::Test
|
|
|
734
979
|
].join("\n") + "\n"
|
|
735
980
|
|
|
736
981
|
output = StringIO.new
|
|
737
|
-
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr:
|
|
982
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: output, stderr: output)
|
|
738
983
|
|
|
739
984
|
assert_equal 0, status
|
|
740
985
|
assert_includes output.string, "Cannot write output file: /tmp/not-a-dir/out.csv"
|
|
@@ -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("
|
|
19
|
+
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new("8\n"), stdout: StringIO.new, stderr: StringIO.new)
|
|
20
20
|
assert_equal 0, status
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -28,7 +28,7 @@ class CliUnitTest < Minitest::Test
|
|
|
28
28
|
def test_menu_routes_to_row_range_shell
|
|
29
29
|
stdout = StringIO.new
|
|
30
30
|
fixture = File.expand_path("../fixtures/sample_people.csv", __dir__)
|
|
31
|
-
input = ["2", fixture, "", "2", "3", "", "
|
|
31
|
+
input = ["2", fixture, "", "2", "3", "", "8"].join("\n") + "\n"
|
|
32
32
|
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: stdout, stderr: StringIO.new)
|
|
33
33
|
assert_equal 0, status
|
|
34
34
|
assert_includes stdout.string, "name,city"
|
|
@@ -39,7 +39,7 @@ class CliUnitTest < Minitest::Test
|
|
|
39
39
|
def test_menu_routes_to_randomize_rows_shell
|
|
40
40
|
stdout = StringIO.new
|
|
41
41
|
fixture = File.expand_path("../fixtures/sample_people.csv", __dir__)
|
|
42
|
-
input = ["3", fixture, "", "", "", "", "
|
|
42
|
+
input = ["3", fixture, "", "", "", "", "8"].join("\n") + "\n"
|
|
43
43
|
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: stdout, stderr: StringIO.new)
|
|
44
44
|
assert_equal 0, status
|
|
45
45
|
assert_includes stdout.string, "name,city"
|
|
@@ -52,12 +52,12 @@ class CliUnitTest < Minitest::Test
|
|
|
52
52
|
stdout = StringIO.new
|
|
53
53
|
source_fixture = File.expand_path("../fixtures/dedupe_source.csv", __dir__)
|
|
54
54
|
reference_fixture = File.expand_path("../fixtures/dedupe_reference.csv", __dir__)
|
|
55
|
-
input = ["4", source_fixture, "", "", reference_fixture, "", "", "customer_id", "external_id", "", "", "", "
|
|
55
|
+
input = ["4", source_fixture, "", "", reference_fixture, "", "", "customer_id", "external_id", "", "", "", "8"].join("\n") + "\n"
|
|
56
56
|
status = Csvtool::CLI.start(["menu"], stdin: StringIO.new(input), stdout: stdout, stderr: StringIO.new)
|
|
57
57
|
assert_equal 0, status
|
|
58
58
|
assert_includes stdout.string, "customer_id,name"
|
|
59
59
|
assert_includes stdout.string, "1,Alice"
|
|
60
60
|
assert_includes stdout.string, "3,Cara"
|
|
61
|
-
assert_includes stdout.string, "Summary
|
|
61
|
+
assert_includes stdout.string, "Summary"
|
|
62
62
|
end
|
|
63
63
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../../test_helper"
|
|
4
|
+
require "csvtool/interface/cli/output/color_policy"
|
|
5
|
+
|
|
6
|
+
class ColorPolicyTest < Minitest::Test
|
|
7
|
+
class TtyIO
|
|
8
|
+
def tty? = true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class NonTtyIO
|
|
12
|
+
def tty? = false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_auto_uses_tty
|
|
16
|
+
enabled = Csvtool::Interface::CLI::Output::ColorPolicy.new(mode: "auto", io: TtyIO.new, env: {}).enabled?
|
|
17
|
+
disabled = Csvtool::Interface::CLI::Output::ColorPolicy.new(mode: "auto", io: NonTtyIO.new, env: {}).enabled?
|
|
18
|
+
|
|
19
|
+
assert_equal true, enabled
|
|
20
|
+
assert_equal false, disabled
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_never_disables_color
|
|
24
|
+
policy = Csvtool::Interface::CLI::Output::ColorPolicy.new(mode: "never", io: TtyIO.new, env: {})
|
|
25
|
+
|
|
26
|
+
assert_equal false, policy.enabled?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_always_enables_color_even_with_no_color
|
|
30
|
+
policy = Csvtool::Interface::CLI::Output::ColorPolicy.new(mode: "always", io: NonTtyIO.new, env: { "NO_COLOR" => "1" })
|
|
31
|
+
|
|
32
|
+
assert_equal true, policy.enabled?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_no_color_disables_auto
|
|
36
|
+
policy = Csvtool::Interface::CLI::Output::ColorPolicy.new(mode: "auto", io: TtyIO.new, env: { "NO_COLOR" => "1" })
|
|
37
|
+
|
|
38
|
+
assert_equal false, policy.enabled?
|
|
39
|
+
end
|
|
40
|
+
end
|