iostreams 0.20.3 → 1.0.0.beta
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/lib/io_streams/bzip2/reader.rb +9 -21
- data/lib/io_streams/bzip2/writer.rb +9 -21
- data/lib/io_streams/deprecated.rb +217 -0
- data/lib/io_streams/encode/reader.rb +12 -16
- data/lib/io_streams/encode/writer.rb +9 -13
- data/lib/io_streams/errors.rb +6 -6
- data/lib/io_streams/gzip/reader.rb +7 -14
- data/lib/io_streams/gzip/writer.rb +7 -15
- data/lib/io_streams/io_streams.rb +182 -524
- data/lib/io_streams/line/reader.rb +9 -9
- data/lib/io_streams/line/writer.rb +10 -11
- data/lib/io_streams/path.rb +190 -0
- data/lib/io_streams/paths/file.rb +176 -0
- data/lib/io_streams/paths/http.rb +92 -0
- data/lib/io_streams/paths/matcher.rb +61 -0
- data/lib/io_streams/paths/s3.rb +269 -0
- data/lib/io_streams/paths/sftp.rb +99 -0
- data/lib/io_streams/pgp.rb +47 -19
- data/lib/io_streams/pgp/reader.rb +20 -28
- data/lib/io_streams/pgp/writer.rb +24 -46
- data/lib/io_streams/reader.rb +28 -0
- data/lib/io_streams/record/reader.rb +20 -16
- data/lib/io_streams/record/writer.rb +28 -28
- data/lib/io_streams/row/reader.rb +22 -26
- data/lib/io_streams/row/writer.rb +29 -28
- data/lib/io_streams/stream.rb +400 -0
- data/lib/io_streams/streams.rb +125 -0
- data/lib/io_streams/symmetric_encryption/reader.rb +5 -13
- data/lib/io_streams/symmetric_encryption/writer.rb +16 -15
- data/lib/io_streams/tabular/header.rb +9 -3
- data/lib/io_streams/tabular/parser/array.rb +8 -3
- data/lib/io_streams/tabular/parser/csv.rb +6 -2
- data/lib/io_streams/tabular/parser/hash.rb +4 -1
- data/lib/io_streams/tabular/parser/json.rb +3 -1
- data/lib/io_streams/tabular/parser/psv.rb +3 -1
- data/lib/io_streams/tabular/utility/csv_row.rb +9 -8
- data/lib/io_streams/utils.rb +22 -0
- data/lib/io_streams/version.rb +1 -1
- data/lib/io_streams/writer.rb +28 -0
- data/lib/io_streams/xlsx/reader.rb +7 -19
- data/lib/io_streams/zip/reader.rb +7 -26
- data/lib/io_streams/zip/writer.rb +21 -38
- data/lib/iostreams.rb +15 -15
- data/test/bzip2_reader_test.rb +3 -3
- data/test/bzip2_writer_test.rb +3 -3
- data/test/deprecated_test.rb +123 -0
- data/test/encode_reader_test.rb +3 -3
- data/test/encode_writer_test.rb +6 -6
- data/test/gzip_reader_test.rb +2 -2
- data/test/gzip_writer_test.rb +3 -3
- data/test/io_streams_test.rb +43 -136
- data/test/line_reader_test.rb +20 -20
- data/test/line_writer_test.rb +3 -3
- data/test/path_test.rb +30 -28
- data/test/paths/file_test.rb +206 -0
- data/test/paths/http_test.rb +34 -0
- data/test/paths/matcher_test.rb +111 -0
- data/test/paths/s3_test.rb +207 -0
- data/test/pgp_reader_test.rb +8 -8
- data/test/pgp_writer_test.rb +13 -13
- data/test/record_reader_test.rb +5 -5
- data/test/record_writer_test.rb +4 -4
- data/test/row_reader_test.rb +5 -5
- data/test/row_writer_test.rb +6 -6
- data/test/stream_test.rb +116 -0
- data/test/streams_test.rb +255 -0
- data/test/utils_test.rb +20 -0
- data/test/xlsx_reader_test.rb +3 -3
- data/test/zip_reader_test.rb +12 -12
- data/test/zip_writer_test.rb +5 -5
- metadata +33 -45
- data/lib/io_streams/base_path.rb +0 -72
- data/lib/io_streams/file/path.rb +0 -58
- data/lib/io_streams/file/reader.rb +0 -12
- data/lib/io_streams/file/writer.rb +0 -22
- data/lib/io_streams/http/reader.rb +0 -71
- data/lib/io_streams/s3.rb +0 -26
- data/lib/io_streams/s3/path.rb +0 -40
- data/lib/io_streams/s3/reader.rb +0 -28
- data/lib/io_streams/s3/writer.rb +0 -85
- data/lib/io_streams/sftp/reader.rb +0 -67
- data/lib/io_streams/sftp/writer.rb +0 -68
- data/test/base_path_test.rb +0 -35
- data/test/file_path_test.rb +0 -97
- data/test/file_reader_test.rb +0 -33
- data/test/file_writer_test.rb +0 -50
- data/test/http_reader_test.rb +0 -38
- data/test/s3_reader_test.rb +0 -41
- data/test/s3_writer_test.rb +0 -41
@@ -2,34 +2,31 @@ require 'open3'
|
|
2
2
|
|
3
3
|
module IOStreams
|
4
4
|
module Pgp
|
5
|
-
class Reader
|
5
|
+
class Reader < IOStreams::Reader
|
6
6
|
# Passphrase to use to open the private key to decrypt the received file
|
7
|
-
|
8
|
-
|
7
|
+
class << self
|
8
|
+
attr_writer :default_passphrase
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_reader :default_passphrase
|
13
|
+
|
14
|
+
@default_passphrase = nil
|
9
15
|
end
|
10
16
|
|
11
|
-
# Read from a PGP / GPG file
|
12
|
-
#
|
17
|
+
# Read from a PGP / GPG file , decompressing the contents as it is read.
|
18
|
+
#
|
19
|
+
# file_name: [String]
|
13
20
|
# Name of file to read from
|
14
|
-
#
|
21
|
+
#
|
15
22
|
# passphrase: [String]
|
16
23
|
# Pass phrase for private key to decrypt the file with
|
17
|
-
def self.
|
24
|
+
def self.file(file_name, passphrase: self.default_passphrase)
|
18
25
|
raise(ArgumentError, 'Missing both passphrase and IOStreams::Pgp::Reader.default_passphrase') unless passphrase
|
19
26
|
|
20
|
-
return read_file(file_name_or_io, passphrase: passphrase, binary: binary, &block) if file_name_or_io.is_a?(String)
|
21
|
-
|
22
|
-
# PGP can only work against a file, not a stream, so create temp file.
|
23
|
-
IOStreams::File::Path.temp_file_name('iostreams_pgp') do |temp_file_name|
|
24
|
-
IOStreams.copy(file_name_or_io, temp_file_name, target_options: {streams: []})
|
25
|
-
read_file(temp_file_name, passphrase: passphrase, binary: binary, &block)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.read_file(file_name, passphrase: self.default_passphrase, binary: true)
|
30
27
|
loopback = IOStreams::Pgp.pgp_version.to_f >= 2.1 ? '--pinentry-mode loopback' : ''
|
31
28
|
command = "#{IOStreams::Pgp.executable} #{loopback} --batch --no-tty --yes --decrypt --passphrase-fd 0 #{file_name}"
|
32
|
-
IOStreams::Pgp.logger
|
29
|
+
IOStreams::Pgp.logger&.debug { "IOStreams::Pgp::Reader.open: #{command}" }
|
33
30
|
|
34
31
|
# Read decrypted contents from stdout
|
35
32
|
Open3.popen3(command) do |stdin, stdout, stderr, waith_thr|
|
@@ -37,24 +34,19 @@ module IOStreams
|
|
37
34
|
stdin.close
|
38
35
|
result =
|
39
36
|
begin
|
40
|
-
stdout.binmode
|
37
|
+
stdout.binmode
|
41
38
|
yield(stdout)
|
42
39
|
rescue Errno::EPIPE
|
43
40
|
# Ignore broken pipe because gpg terminates early due to an error
|
44
41
|
raise(Pgp::Failure, "GPG Failed reading from encrypted file: #{file_name}: #{stderr.read.chomp}")
|
45
42
|
end
|
46
|
-
|
43
|
+
unless waith_thr.value.success?
|
44
|
+
raise(Pgp::Failure, "GPG Failed to decrypt file: #{file_name}: #{stderr.read.chomp}")
|
45
|
+
end
|
46
|
+
|
47
47
|
result
|
48
48
|
end
|
49
49
|
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
@default_passphrase = nil
|
54
|
-
|
55
|
-
def self.default_passphrase
|
56
|
-
@default_passphrase
|
57
|
-
end
|
58
50
|
end
|
59
51
|
end
|
60
52
|
end
|
@@ -2,24 +2,29 @@ require 'open3'
|
|
2
2
|
|
3
3
|
module IOStreams
|
4
4
|
module Pgp
|
5
|
-
class Writer
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class Writer < IOStreams::Writer
|
6
|
+
class << self
|
7
|
+
# Sign all encrypted files with this users key.
|
8
|
+
# Default: Do not sign encrypted files.
|
9
|
+
attr_writer :default_signer
|
10
|
+
|
11
|
+
# Passphrase to use to open the private key when signing the file.
|
12
|
+
# Default: None.
|
13
|
+
attr_writer :default_signer_passphrase
|
14
|
+
|
15
|
+
private
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@default_signer_passphrase =
|
17
|
+
attr_reader :default_signer_passphrase
|
18
|
+
attr_reader :default_signer
|
19
|
+
|
20
|
+
@default_signer_passphrase = nil
|
21
|
+
@default_signer = nil
|
16
22
|
end
|
17
23
|
|
18
|
-
# Write to a PGP / GPG file
|
24
|
+
# Write to a PGP / GPG file, encrypting the contents as it is written.
|
19
25
|
#
|
20
|
-
#
|
26
|
+
# file_name: [String]
|
21
27
|
# Name of file to write to.
|
22
|
-
# Or, the IO stream to write the encrypted contents to.
|
23
28
|
#
|
24
29
|
# recipient: [String]
|
25
30
|
# Email of user for which to encypt the file.
|
@@ -32,10 +37,6 @@ module IOStreams
|
|
32
37
|
# Passphrase to use to open the private key when signing the file.
|
33
38
|
# Default: default_signer_passphrase
|
34
39
|
#
|
35
|
-
# binary: [true|false]
|
36
|
-
# Whether to write binary data.
|
37
|
-
# Default: true
|
38
|
-
#
|
39
40
|
# compression: [:none|:zip|:zlib|:bzip2]
|
40
41
|
# Note: Standard PGP only supports :zip.
|
41
42
|
# :zlib is better than zip.
|
@@ -45,22 +46,12 @@ module IOStreams
|
|
45
46
|
# compress_level: [Integer]
|
46
47
|
# Compression level
|
47
48
|
# Default: 6
|
48
|
-
def self.
|
49
|
-
|
50
|
-
|
51
|
-
if file_name_or_io.is_a?(String)
|
52
|
-
IOStreams::File::Path.mkpath(file_name_or_io)
|
53
|
-
return write_file(file_name_or_io, recipient: recipient, signer: signer, signer_passphrase: signer_passphrase, binary: binary, compression: compression, compress_level: compress_level, &block)
|
54
|
-
end
|
49
|
+
def self.file(file_name, recipient: nil, import_and_trust_key: nil, signer: default_signer, signer_passphrase: default_signer_passphrase, compression: :zip, compress_level: 6, original_file_name: nil)
|
50
|
+
raise(ArgumentError, "Requires either :recipient or :import_and_trust_key") unless recipient || import_and_trust_key
|
55
51
|
|
56
|
-
|
57
|
-
|
58
|
-
write_file(temp_file_name, recipient: recipient, signer: signer, signer_passphrase: signer_passphrase, binary: binary, compression: compression, compress_level: compress_level, &block)
|
59
|
-
IOStreams.copy(temp_file_name, file_name_or_io, source_options: {streams: []})
|
60
|
-
end
|
61
|
-
end
|
52
|
+
recipient = IOStreams::Pgp.import_and_trust(key: import_and_trust_key) if import_and_trust_key
|
53
|
+
compress_level = 0 if compression == :none
|
62
54
|
|
63
|
-
def self.write_file(file_name, recipient:, signer: default_signer, signer_passphrase: default_signer_passphrase, binary: true, compression: :zip, compress_level: 6)
|
64
55
|
# Write to stdin, with encrypted contents being written to the file
|
65
56
|
command = "#{IOStreams::Pgp.executable} --batch --no-tty --yes --encrypt"
|
66
57
|
command << " --sign --local-user \"#{signer}\"" if signer
|
@@ -72,11 +63,11 @@ module IOStreams
|
|
72
63
|
command << " --compress-algo #{compression}" unless compression == :none
|
73
64
|
command << " --recipient \"#{recipient}\" -o \"#{file_name}\""
|
74
65
|
|
75
|
-
IOStreams::Pgp.logger
|
66
|
+
IOStreams::Pgp.logger&.debug { "IOStreams::Pgp::Writer.open: #{command}" }
|
76
67
|
|
77
68
|
Open3.popen2e(command) do |stdin, out, waith_thr|
|
78
69
|
begin
|
79
|
-
stdin.binmode
|
70
|
+
stdin.binmode
|
80
71
|
yield(stdin)
|
81
72
|
stdin.close
|
82
73
|
rescue Errno::EPIPE
|
@@ -91,19 +82,6 @@ module IOStreams
|
|
91
82
|
end
|
92
83
|
end
|
93
84
|
|
94
|
-
private
|
95
|
-
|
96
|
-
@default_signer_passphrase = nil
|
97
|
-
@default_signer = nil
|
98
|
-
|
99
|
-
def self.default_signer_passphrase
|
100
|
-
@default_signer_passphrase
|
101
|
-
end
|
102
|
-
|
103
|
-
def self.default_signer
|
104
|
-
@default_signer
|
105
|
-
end
|
106
|
-
|
107
85
|
end
|
108
86
|
end
|
109
87
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module IOStreams
|
2
|
+
class Reader
|
3
|
+
# When a Reader does not support streams, we copy the stream to a local temp file
|
4
|
+
# and then pass that filename in for this reader.
|
5
|
+
def self.stream(input_stream, **args, &block)
|
6
|
+
Utils.temp_file_name("iostreams_reader") do |file_name|
|
7
|
+
::File.open(file_name, 'wb') { |target| ::IO.copy_stream(input_stream, target) }
|
8
|
+
file(file_name, **args, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# When a Writer supports streams, also allow it to simply support a file
|
13
|
+
def self.file(file_name, original_file_name: file_name, **args, &block)
|
14
|
+
::File.open(file_name, 'rb') { |file| stream(file, original_file_name: original_file_name, **args, &block) }
|
15
|
+
end
|
16
|
+
|
17
|
+
# For processing by either a file name or an open IO stream.
|
18
|
+
def self.open(file_name_or_io, **args, &block)
|
19
|
+
file_name_or_io.is_a?(String) ? file(file_name_or_io, **args, &block) : stream(file_name_or_io, **args, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :input_stream
|
23
|
+
|
24
|
+
def initialize(input_stream)
|
25
|
+
@input_stream = input_stream
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,23 +1,23 @@
|
|
1
1
|
module IOStreams
|
2
2
|
module Record
|
3
3
|
# Converts each line of an input stream into hash for every row
|
4
|
-
class Reader
|
4
|
+
class Reader < IOStreams::Reader
|
5
5
|
include Enumerable
|
6
6
|
|
7
|
-
# Read a record
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
yield new(
|
7
|
+
# Read a record at a time from a line stream
|
8
|
+
# Note:
|
9
|
+
# - The supplied stream _must_ already be a line stream, or a stream that responds to :each
|
10
|
+
def self.stream(line_reader, original_file_name: nil, **args, &block)
|
11
|
+
# Pass-through if already a record reader
|
12
|
+
return block.call(line_reader) if line_reader.is_a?(self.class)
|
13
|
+
|
14
|
+
yield new(line_reader, **args)
|
15
|
+
end
|
16
|
+
|
17
|
+
# When reading from a file also add the line reader stream
|
18
|
+
def self.file(file_name, original_file_name: file_name, delimiter: $/, **args)
|
19
|
+
IOStreams::Line::Reader.file(file_name, original_file_name: original_file_name, delimiter: delimiter) do |io|
|
20
|
+
yield new(io, **args)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -33,8 +33,12 @@ module IOStreams
|
|
33
33
|
#
|
34
34
|
# For all other parameters, see Tabular::Header.new
|
35
35
|
def initialize(line_reader, cleanse_header: true, **args)
|
36
|
+
unless line_reader.respond_to?(:each)
|
37
|
+
raise(ArgumentError, "Stream must be a IOStreams::Line::Reader or implement #each")
|
38
|
+
end
|
39
|
+
|
36
40
|
@tabular = IOStreams::Tabular.new(**args)
|
37
|
-
@line_reader
|
41
|
+
@line_reader = line_reader
|
38
42
|
@cleanse_header = cleanse_header
|
39
43
|
end
|
40
44
|
|
@@ -1,28 +1,25 @@
|
|
1
1
|
module IOStreams
|
2
2
|
module Record
|
3
3
|
# Example, implied header from first record:
|
4
|
-
# IOStreams.record_writer do |stream|
|
4
|
+
# IOStreams.path('file.csv').record_writer do |stream|
|
5
5
|
# stream << {name: 'Jack', address: 'Somewhere', zipcode: 12345}
|
6
6
|
# stream << {name: 'Joe', address: 'Lost', zipcode: 32443, age: 23}
|
7
7
|
# end
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
else
|
25
|
-
yield new(file_name_or_io, **args)
|
8
|
+
class Writer < IOStreams::Writer
|
9
|
+
# Write a record as a Hash at a time to a stream.
|
10
|
+
# Note:
|
11
|
+
# - The supplied stream _must_ already be a line stream, or a stream that responds to :<<
|
12
|
+
def self.stream(line_writer, original_file_name: nil, **args, &block)
|
13
|
+
# Pass-through if already a row writer
|
14
|
+
return block.call(line_writer) if line_writer.is_a?(self.class)
|
15
|
+
|
16
|
+
yield new(line_writer, **args)
|
17
|
+
end
|
18
|
+
|
19
|
+
# When writing to a file also add the line writer stream
|
20
|
+
def self.file(file_name, original_file_name: file_name, delimiter: $/, **args, &block)
|
21
|
+
IOStreams::Line::Writer.file(file_name, original_file_name: original_file_name, delimiter: delimiter) do |io|
|
22
|
+
yield new(io, **args, &block)
|
26
23
|
end
|
27
24
|
end
|
28
25
|
|
@@ -37,24 +34,27 @@ module IOStreams
|
|
37
34
|
# :csv, :hash, :array, :json, :psv, :fixed
|
38
35
|
#
|
39
36
|
# For all other parameters, see Tabular::Header.new
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
37
|
+
def initialize(line_writer, columns: nil, **args)
|
38
|
+
unless line_writer.respond_to?(:<<)
|
39
|
+
raise(ArgumentError, 'Stream must be a IOStreams::Line::Writer or implement #<<')
|
40
|
+
end
|
41
|
+
|
42
|
+
@tabular = IOStreams::Tabular.new(columns: columns, **args)
|
43
|
+
@line_writer = line_writer
|
45
44
|
|
46
45
|
# Render header line when `columns` is supplied.
|
47
|
-
@
|
46
|
+
@line_writer << @tabular.render_header if columns && @tabular.requires_header?
|
48
47
|
end
|
49
48
|
|
50
49
|
def <<(hash)
|
51
|
-
raise(ArgumentError, '
|
50
|
+
raise(ArgumentError, '#<< only accepts a Hash argument') unless hash.is_a?(Hash)
|
51
|
+
|
52
52
|
if @tabular.header?
|
53
53
|
# Extract header from the keys from the first row when not supplied above.
|
54
54
|
@tabular.header.columns = hash.keys
|
55
|
-
@
|
55
|
+
@line_writer << @tabular.render_header
|
56
56
|
end
|
57
|
-
@
|
57
|
+
@line_writer << @tabular.render(hash)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
@@ -1,29 +1,21 @@
|
|
1
1
|
module IOStreams
|
2
2
|
module Row
|
3
3
|
# Converts each line of an input stream into an array for every line
|
4
|
-
class Reader
|
5
|
-
# Read a line as an Array at a time from a
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
encoding: encoding,
|
20
|
-
encode_cleaner: encode_cleaner,
|
21
|
-
encode_replace: encode_replace
|
22
|
-
) do |io|
|
23
|
-
yield new(io, file_name: file_name, **args)
|
24
|
-
end
|
25
|
-
else
|
26
|
-
yield new(file_name_or_io, **args)
|
4
|
+
class Reader < IOStreams::Reader
|
5
|
+
# Read a line as an Array at a time from a stream.
|
6
|
+
# Note:
|
7
|
+
# - The supplied stream _must_ already be a line stream, or a stream that responds to :each
|
8
|
+
def self.stream(line_reader, original_file_name: nil, **args, &block)
|
9
|
+
# Pass-through if already a row reader
|
10
|
+
return block.call(line_reader) if line_reader.is_a?(self.class)
|
11
|
+
|
12
|
+
yield new(line_reader, **args)
|
13
|
+
end
|
14
|
+
|
15
|
+
# When reading from a file also add the line reader stream
|
16
|
+
def self.file(file_name, original_file_name: file_name, delimiter: $/, **args)
|
17
|
+
IOStreams::Line::Reader.file(file_name, original_file_name: original_file_name, delimiter: delimiter) do |io|
|
18
|
+
yield new(io, **args)
|
27
19
|
end
|
28
20
|
end
|
29
21
|
|
@@ -37,14 +29,18 @@ module IOStreams
|
|
37
29
|
# :csv, :hash, :array, :json, :psv, :fixed
|
38
30
|
#
|
39
31
|
# For all other parameters, see Tabular::Header.new
|
40
|
-
def initialize(
|
32
|
+
def initialize(line_reader, cleanse_header: true, **args)
|
33
|
+
unless line_reader.respond_to?(:each)
|
34
|
+
raise(ArgumentError, "Stream must be a IOStreams::Line::Reader or implement #each")
|
35
|
+
end
|
36
|
+
|
41
37
|
@tabular = IOStreams::Tabular.new(**args)
|
42
|
-
@
|
38
|
+
@line_reader = line_reader
|
43
39
|
@cleanse_header = cleanse_header
|
44
40
|
end
|
45
41
|
|
46
42
|
def each
|
47
|
-
@
|
43
|
+
@line_reader.each do |line|
|
48
44
|
if @tabular.header?
|
49
45
|
columns = @tabular.parse_header(line)
|
50
46
|
@tabular.cleanse_header! if @cleanse_header
|
@@ -2,61 +2,62 @@ require 'csv'
|
|
2
2
|
module IOStreams
|
3
3
|
module Row
|
4
4
|
# Example:
|
5
|
-
# IOStreams.row_writer do |stream|
|
5
|
+
# IOStreams.path("file.csv").row_writer do |stream|
|
6
6
|
# stream << ['name', 'address', 'zipcode']
|
7
7
|
# stream << ['Jack', 'Somewhere', 12345]
|
8
8
|
# stream << ['Joe', 'Lost', 32443]
|
9
9
|
# end
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
if
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
else
|
27
|
-
yield new(file_name_or_io, **args)
|
10
|
+
class Writer < IOStreams::Writer
|
11
|
+
# Write a record from an Array at a time to a stream.
|
12
|
+
#
|
13
|
+
# Note:
|
14
|
+
# - The supplied stream _must_ already be a line stream, or a stream that responds to :<<
|
15
|
+
def self.stream(line_writer, original_file_name: nil, **args)
|
16
|
+
# Pass-through if already a row writer
|
17
|
+
return block.call(line_writer) if line_writer.is_a?(self.class)
|
18
|
+
|
19
|
+
yield new(line_writer, **args)
|
20
|
+
end
|
21
|
+
|
22
|
+
# When writing to a file also add the line writer stream
|
23
|
+
def self.file(file_name, original_file_name: file_name, delimiter: $/, **args, &block)
|
24
|
+
IOStreams::Line::Writer.file(file_name, original_file_name: original_file_name, delimiter: delimiter) do |io|
|
25
|
+
yield new(io, **args, &block)
|
28
26
|
end
|
29
27
|
end
|
30
28
|
|
31
29
|
# Create a Tabular writer that takes individual rows as arrays.
|
32
30
|
#
|
33
31
|
# Parameters
|
34
|
-
#
|
32
|
+
# line_writer: [#<<]
|
35
33
|
# Anything that accepts a line / record at a time when #<< is called on it.
|
36
34
|
#
|
37
35
|
# format: [Symbol]
|
38
36
|
# :csv, :hash, :array, :json, :psv, :fixed
|
39
37
|
#
|
40
38
|
# For all other parameters, see Tabular::Header.new
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
39
|
+
def initialize(line_writer, columns: nil, **args)
|
40
|
+
unless line_writer.respond_to?(:<<)
|
41
|
+
raise(ArgumentError, 'Stream must be a IOStreams::Line::Writer or implement #<<')
|
42
|
+
end
|
43
|
+
|
44
|
+
@tabular = IOStreams::Tabular.new(columns: columns, **args)
|
45
|
+
@line_writer = line_writer
|
46
46
|
|
47
47
|
# Render header line when `columns` is supplied.
|
48
|
-
|
48
|
+
line_writer << @tabular.render_header if columns && @tabular.requires_header?
|
49
49
|
end
|
50
50
|
|
51
51
|
# Supply a hash or an array to render
|
52
52
|
def <<(array)
|
53
53
|
raise(ArgumentError, 'Must supply an Array') unless array.is_a?(Array)
|
54
|
+
|
54
55
|
if @tabular.header?
|
55
56
|
# If header (columns) was not supplied as an argument, assume first line is the header.
|
56
57
|
@tabular.header.columns = array
|
57
|
-
@
|
58
|
+
@line_writer << @tabular.render_header
|
58
59
|
else
|
59
|
-
@
|
60
|
+
@line_writer << @tabular.render(array)
|
60
61
|
end
|
61
62
|
end
|
62
63
|
end
|