csvops 0.1.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 +7 -0
- data/Gemfile +8 -0
- data/README.md +209 -0
- data/Rakefile +10 -0
- data/bin/csvtool +8 -0
- data/bin/tool +8 -0
- data/csvops.gemspec +25 -0
- data/docs/release-v0.1.0-alpha.md +73 -0
- data/exe/csvtool +8 -0
- data/lib/csvtool/application/use_cases/run_extraction.rb +125 -0
- data/lib/csvtool/cli.rb +87 -0
- data/lib/csvtool/domain/extraction_session/column_selection.rb +17 -0
- data/lib/csvtool/domain/extraction_session/csv_source.rb +18 -0
- data/lib/csvtool/domain/extraction_session/extraction_options.rb +22 -0
- data/lib/csvtool/domain/extraction_session/extraction_session.rb +61 -0
- data/lib/csvtool/domain/extraction_session/extraction_value.rb +15 -0
- data/lib/csvtool/domain/extraction_session/output_destination.rb +35 -0
- data/lib/csvtool/domain/extraction_session/preview.rb +23 -0
- data/lib/csvtool/domain/extraction_session/separator.rb +17 -0
- data/lib/csvtool/infrastructure/csv/header_reader.rb +17 -0
- data/lib/csvtool/infrastructure/csv/value_streamer.rb +20 -0
- data/lib/csvtool/infrastructure/output/console_writer.rb +20 -0
- data/lib/csvtool/infrastructure/output/csv_file_writer.rb +30 -0
- data/lib/csvtool/interface/cli/errors/presenter.rb +59 -0
- data/lib/csvtool/interface/cli/menu_loop.rb +41 -0
- data/lib/csvtool/interface/cli/prompts/column_selector_prompt.rb +54 -0
- data/lib/csvtool/interface/cli/prompts/confirm_prompt.rb +29 -0
- data/lib/csvtool/interface/cli/prompts/file_path_prompt.rb +21 -0
- data/lib/csvtool/interface/cli/prompts/output_destination_prompt.rb +40 -0
- data/lib/csvtool/interface/cli/prompts/separator_prompt.rb +44 -0
- data/lib/csvtool/interface/cli/prompts/skip_blanks_prompt.rb +22 -0
- data/lib/csvtool/services/preview_builder.rb +20 -0
- data/lib/csvtool/version.rb +5 -0
- data/test/csvtool/application/use_cases/run_extraction_test.rb +31 -0
- data/test/csvtool/cli_test.rb +134 -0
- data/test/csvtool/cli_unit_test.rb +27 -0
- data/test/csvtool/domain/extraction_session/column_selection_test.rb +11 -0
- data/test/csvtool/domain/extraction_session/csv_source_test.rb +14 -0
- data/test/csvtool/domain/extraction_session/extraction_options_test.rb +18 -0
- data/test/csvtool/domain/extraction_session/extraction_session_test.rb +35 -0
- data/test/csvtool/domain/extraction_session/extraction_value_test.rb +11 -0
- data/test/csvtool/domain/extraction_session/output_destination_test.rb +18 -0
- data/test/csvtool/domain/extraction_session/preview_test.rb +18 -0
- data/test/csvtool/domain/extraction_session/separator_test.rb +15 -0
- data/test/csvtool/infrastructure/csv/header_reader_test.rb +16 -0
- data/test/csvtool/infrastructure/csv/value_streamer_test.rb +22 -0
- data/test/csvtool/infrastructure/output/console_writer_test.rb +19 -0
- data/test/csvtool/infrastructure/output/csv_file_writer_test.rb +35 -0
- data/test/csvtool/interface/cli/errors/presenter_test.rb +36 -0
- data/test/csvtool/interface/cli/menu_loop_test.rb +51 -0
- data/test/csvtool/interface/cli/prompts/column_selector_prompt_test.rb +23 -0
- data/test/csvtool/interface/cli/prompts/confirm_prompt_test.rb +23 -0
- data/test/csvtool/interface/cli/prompts/file_path_prompt_test.rb +11 -0
- data/test/csvtool/interface/cli/prompts/output_destination_prompt_test.rb +28 -0
- data/test/csvtool/interface/cli/prompts/separator_prompt_test.rb +31 -0
- data/test/csvtool/interface/cli/prompts/skip_blanks_prompt_test.rb +13 -0
- data/test/csvtool/services/preview_builder_test.rb +22 -0
- data/test/fixtures/empty.csv +0 -0
- data/test/fixtures/sample_people.csv +4 -0
- data/test/fixtures/sample_people.tsv +4 -0
- data/test/fixtures/sample_people_blanks.csv +6 -0
- data/test/fixtures/sample_people_colon.txt +4 -0
- data/test/fixtures/sample_people_many.csv +13 -0
- data/test/test_helper.rb +6 -0
- metadata +150 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Domain
|
|
5
|
+
module ExtractionSession
|
|
6
|
+
class OutputDestination
|
|
7
|
+
attr_reader :mode, :path
|
|
8
|
+
|
|
9
|
+
def self.console
|
|
10
|
+
new(mode: :console)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.file(path:)
|
|
14
|
+
new(mode: :file, path: path)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(mode:, path: nil)
|
|
18
|
+
raise ArgumentError, "invalid output mode" unless %i[console file].include?(mode)
|
|
19
|
+
raise ArgumentError, "file output path cannot be empty" if mode == :file && path.to_s.empty?
|
|
20
|
+
|
|
21
|
+
@mode = mode
|
|
22
|
+
@path = path
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def file?
|
|
26
|
+
@mode == :file
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def console?
|
|
30
|
+
@mode == :console
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Domain
|
|
5
|
+
module ExtractionSession
|
|
6
|
+
class Preview
|
|
7
|
+
attr_reader :values
|
|
8
|
+
|
|
9
|
+
def initialize(values:)
|
|
10
|
+
@values = values
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def size
|
|
14
|
+
@values.size
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_strings
|
|
18
|
+
@values.map(&:value)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Domain
|
|
5
|
+
module ExtractionSession
|
|
6
|
+
class Separator
|
|
7
|
+
attr_reader :value
|
|
8
|
+
|
|
9
|
+
def initialize(value)
|
|
10
|
+
raise ArgumentError, "separator cannot be empty" if value.to_s.empty?
|
|
11
|
+
|
|
12
|
+
@value = value
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
|
|
5
|
+
module Csvtool
|
|
6
|
+
module Infrastructure
|
|
7
|
+
module CSV
|
|
8
|
+
class HeaderReader
|
|
9
|
+
def call(file_path:, col_sep:)
|
|
10
|
+
first_row = ::CSV.open(file_path, "r", headers: true, col_sep: col_sep, &:first)
|
|
11
|
+
headers = first_row&.headers || []
|
|
12
|
+
headers.compact.reject(&:empty?)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
|
|
5
|
+
module Csvtool
|
|
6
|
+
module Infrastructure
|
|
7
|
+
module CSV
|
|
8
|
+
class ValueStreamer
|
|
9
|
+
def each(file_path:, column_name:, col_sep:, skip_blanks:)
|
|
10
|
+
::CSV.foreach(file_path, headers: true, col_sep: col_sep) do |row|
|
|
11
|
+
value = row[column_name].to_s
|
|
12
|
+
next if skip_blanks && value.strip.empty?
|
|
13
|
+
|
|
14
|
+
yield value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Output
|
|
6
|
+
class ConsoleWriter
|
|
7
|
+
def initialize(stdout:, value_streamer:)
|
|
8
|
+
@stdout = stdout
|
|
9
|
+
@value_streamer = value_streamer
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(file_path:, column_name:, col_sep:, skip_blanks:)
|
|
13
|
+
@value_streamer.each(file_path: file_path, column_name: column_name, col_sep: col_sep, skip_blanks: skip_blanks) do |value|
|
|
14
|
+
@stdout.puts value
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "csv"
|
|
4
|
+
|
|
5
|
+
module Csvtool
|
|
6
|
+
module Infrastructure
|
|
7
|
+
module Output
|
|
8
|
+
class CsvFileWriter
|
|
9
|
+
def initialize(stdout:, errors:, value_streamer:)
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@errors = errors
|
|
12
|
+
@value_streamer = value_streamer
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call(file_path:, column_name:, col_sep:, skip_blanks:, output_path:)
|
|
16
|
+
::CSV.open(output_path, "w") do |csv|
|
|
17
|
+
csv << [column_name]
|
|
18
|
+
@value_streamer.each(file_path: file_path, column_name: column_name, col_sep: col_sep, skip_blanks: skip_blanks) do |value|
|
|
19
|
+
csv << [value]
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@stdout.puts "Wrote output to #{output_path}"
|
|
24
|
+
rescue Errno::EACCES, Errno::ENOENT => e
|
|
25
|
+
@errors.cannot_write_output_file(output_path, e.class)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Errors
|
|
7
|
+
class Presenter
|
|
8
|
+
def initialize(stdout:)
|
|
9
|
+
@stdout = stdout
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def file_not_found(path)
|
|
13
|
+
@stdout.puts "File not found: #{path}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def no_headers
|
|
17
|
+
@stdout.puts "No headers found."
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def column_not_found
|
|
21
|
+
@stdout.puts "Column not found."
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def could_not_parse_csv
|
|
25
|
+
@stdout.puts "Could not parse CSV file."
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cannot_read_file(path)
|
|
29
|
+
@stdout.puts "Cannot read file: #{path}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def cannot_write_output_file(path, error_class)
|
|
33
|
+
@stdout.puts "Cannot write output file: #{path} (#{error_class})"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def empty_output_path
|
|
37
|
+
@stdout.puts "Output file path cannot be empty."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def invalid_output_destination
|
|
41
|
+
@stdout.puts "Invalid output destination."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def empty_custom_separator
|
|
45
|
+
@stdout.puts "Separator cannot be empty."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def invalid_separator_choice
|
|
49
|
+
@stdout.puts "Invalid separator choice."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def canceled
|
|
53
|
+
@stdout.puts "Canceled."
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
class MenuLoop
|
|
7
|
+
def initialize(stdin:, stdout:, menu_options:, extract_action:)
|
|
8
|
+
@stdin = stdin
|
|
9
|
+
@stdout = stdout
|
|
10
|
+
@menu_options = menu_options
|
|
11
|
+
@extract_action = extract_action
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
loop do
|
|
16
|
+
print_menu
|
|
17
|
+
@stdout.print "> "
|
|
18
|
+
|
|
19
|
+
case @stdin.gets&.strip
|
|
20
|
+
when "1"
|
|
21
|
+
@extract_action.call
|
|
22
|
+
when "2"
|
|
23
|
+
return 0
|
|
24
|
+
else
|
|
25
|
+
@stdout.puts "Please choose 1 or 2."
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def print_menu
|
|
33
|
+
@stdout.puts "CSV Tool Menu"
|
|
34
|
+
@menu_options.each_with_index do |option, index|
|
|
35
|
+
@stdout.puts "#{index + 1}. #{option}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class ColumnSelectorPrompt
|
|
8
|
+
def initialize(stdin:, stdout:, errors:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@errors = errors
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(headers)
|
|
15
|
+
@stdout.print "Filter columns (optional): "
|
|
16
|
+
filter = @stdin.gets&.strip.to_s
|
|
17
|
+
|
|
18
|
+
filtered_headers = select_headers(headers, filter)
|
|
19
|
+
return nil if filtered_headers.empty?
|
|
20
|
+
|
|
21
|
+
@stdout.puts "Select column:"
|
|
22
|
+
filtered_headers.each_with_index do |header, index|
|
|
23
|
+
@stdout.puts "#{index + 1}. #{header}"
|
|
24
|
+
end
|
|
25
|
+
@stdout.print "Column number: "
|
|
26
|
+
|
|
27
|
+
selected_header = filtered_headers[@stdin.gets&.strip.to_i - 1]
|
|
28
|
+
return selected_header if selected_header
|
|
29
|
+
|
|
30
|
+
@errors.column_not_found
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def select_headers(headers, filter)
|
|
37
|
+
filtered_headers =
|
|
38
|
+
if filter.empty?
|
|
39
|
+
headers
|
|
40
|
+
else
|
|
41
|
+
headers.select { |header| header.to_s.downcase.include?(filter.downcase) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if filtered_headers.empty?
|
|
45
|
+
@errors.column_not_found
|
|
46
|
+
return []
|
|
47
|
+
end
|
|
48
|
+
filtered_headers
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class ConfirmPrompt
|
|
8
|
+
def initialize(stdin:, stdout:, errors:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@errors = errors
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(preview_values)
|
|
15
|
+
@stdout.puts "Preview (first #{preview_values.length} values):"
|
|
16
|
+
preview_values.each { |value| @stdout.puts value }
|
|
17
|
+
@stdout.print "Print all values? [y/N]: "
|
|
18
|
+
|
|
19
|
+
answer = @stdin.gets&.strip.to_s.downcase
|
|
20
|
+
return true if %w[y yes].include?(answer)
|
|
21
|
+
|
|
22
|
+
@errors.canceled
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class FilePathPrompt
|
|
8
|
+
def initialize(stdin:, stdout:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
@stdout.print "CSV file path: "
|
|
15
|
+
@stdin.gets&.strip.to_s
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class OutputDestinationPrompt
|
|
8
|
+
def initialize(stdin:, stdout:, errors:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@errors = errors
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
@stdout.puts "Output destination:"
|
|
16
|
+
@stdout.puts "1. console"
|
|
17
|
+
@stdout.puts "2. file"
|
|
18
|
+
@stdout.print "Output destination [1]: "
|
|
19
|
+
choice = @stdin.gets&.strip.to_s
|
|
20
|
+
|
|
21
|
+
case choice
|
|
22
|
+
when "", "1"
|
|
23
|
+
{ mode: :console }
|
|
24
|
+
when "2"
|
|
25
|
+
@stdout.print "Output file path: "
|
|
26
|
+
output_path = @stdin.gets&.strip.to_s
|
|
27
|
+
return { mode: :file, path: output_path } unless output_path.empty?
|
|
28
|
+
|
|
29
|
+
@errors.empty_output_path
|
|
30
|
+
nil
|
|
31
|
+
else
|
|
32
|
+
@errors.invalid_output_destination
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class SeparatorPrompt
|
|
8
|
+
def initialize(stdin:, stdout:, errors:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
@errors = errors
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
@stdout.puts "Choose separator:"
|
|
16
|
+
@stdout.puts "1. comma (,)"
|
|
17
|
+
@stdout.puts "2. tab (\\t)"
|
|
18
|
+
@stdout.puts "3. semicolon (;)"
|
|
19
|
+
@stdout.puts "4. pipe (|)"
|
|
20
|
+
@stdout.puts "5. custom"
|
|
21
|
+
@stdout.print "Separator choice [1]: "
|
|
22
|
+
|
|
23
|
+
case @stdin.gets&.strip.to_s
|
|
24
|
+
when "", "1" then ","
|
|
25
|
+
when "2" then "\t"
|
|
26
|
+
when "3" then ";"
|
|
27
|
+
when "4" then "|"
|
|
28
|
+
when "5"
|
|
29
|
+
@stdout.print "Custom separator: "
|
|
30
|
+
custom = @stdin.gets&.strip.to_s
|
|
31
|
+
return custom unless custom.empty?
|
|
32
|
+
|
|
33
|
+
@errors.empty_custom_separator
|
|
34
|
+
nil
|
|
35
|
+
else
|
|
36
|
+
@errors.invalid_separator_choice
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Interface
|
|
5
|
+
module CLI
|
|
6
|
+
module Prompts
|
|
7
|
+
class SkipBlanksPrompt
|
|
8
|
+
def initialize(stdin:, stdout:)
|
|
9
|
+
@stdin = stdin
|
|
10
|
+
@stdout = stdout
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
@stdout.print "Skip blank values? [Y/n]: "
|
|
15
|
+
answer = @stdin.gets&.strip.to_s.downcase
|
|
16
|
+
!%w[n no].include?(answer)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Csvtool
|
|
4
|
+
module Services
|
|
5
|
+
class PreviewBuilder
|
|
6
|
+
def initialize(value_streamer:)
|
|
7
|
+
@value_streamer = value_streamer
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def call(file_path:, column_name:, col_sep:, skip_blanks:, limit:)
|
|
11
|
+
preview_values = []
|
|
12
|
+
@value_streamer.each(file_path: file_path, column_name: column_name, col_sep: col_sep, skip_blanks: skip_blanks) do |value|
|
|
13
|
+
preview_values << value
|
|
14
|
+
break if preview_values.length >= limit
|
|
15
|
+
end
|
|
16
|
+
preview_values
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../test_helper"
|
|
4
|
+
require "csvtool/application/use_cases/run_extraction"
|
|
5
|
+
|
|
6
|
+
class RunExtractionTest < Minitest::Test
|
|
7
|
+
def test_missing_file_path_reports_error
|
|
8
|
+
out = StringIO.new
|
|
9
|
+
use_case = Csvtool::Application::UseCases::RunExtraction.new(
|
|
10
|
+
stdin: StringIO.new("/tmp/not-present.csv\n"),
|
|
11
|
+
stdout: out
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
use_case.call
|
|
15
|
+
|
|
16
|
+
assert_includes out.string, "File not found: /tmp/not-present.csv"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def test_use_case_can_run_console_happy_path
|
|
20
|
+
out = StringIO.new
|
|
21
|
+
fixture = File.expand_path("../../../fixtures/sample_people.csv", __dir__)
|
|
22
|
+
input = ["#{fixture}", "1", "", "1", "", "y", ""].join("\n") + "\n"
|
|
23
|
+
|
|
24
|
+
use_case = Csvtool::Application::UseCases::RunExtraction.new(stdin: StringIO.new(input), stdout: out)
|
|
25
|
+
use_case.call
|
|
26
|
+
|
|
27
|
+
assert_includes out.string, "Alice"
|
|
28
|
+
assert_includes out.string, "Bob"
|
|
29
|
+
assert_includes out.string, "Cara"
|
|
30
|
+
end
|
|
31
|
+
end
|