iostreams 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/io_streams/csv/reader.rb +21 -0
- data/lib/io_streams/csv/writer.rb +20 -0
- data/lib/io_streams/delimited/reader.rb +30 -12
- data/lib/io_streams/delimited/writer.rb +71 -0
- data/lib/io_streams/file/reader.rb +2 -2
- data/lib/io_streams/file/writer.rb +2 -2
- data/lib/io_streams/gzip/reader.rb +2 -2
- data/lib/io_streams/gzip/writer.rb +2 -2
- data/lib/io_streams/io_streams.rb +50 -10
- data/lib/io_streams/streams.rb +109 -0
- data/lib/io_streams/version.rb +1 -1
- data/lib/io_streams/xlsx/reader.rb +19 -18
- data/lib/io_streams/zip/reader.rb +1 -1
- data/lib/io_streams/zip/writer.rb +6 -5
- data/lib/iostreams.rb +4 -0
- data/test/csv_reader_test.rb +34 -0
- data/test/csv_writer_test.rb +35 -0
- data/test/delimited_reader_test.rb +19 -9
- data/test/delimited_writer_test.rb +44 -0
- data/test/file_reader_test.rb +1 -0
- data/test/file_writer_test.rb +1 -0
- data/test/files/test.csv +4 -0
- data/test/gzip_reader_test.rb +1 -0
- data/test/gzip_writer_test.rb +1 -0
- data/test/xlsx_reader_test.rb +10 -8
- data/test/zip_reader_test.rb +1 -0
- metadata +15 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48ec90a51a7475ae218a9758190fe4916d3af4a6
|
4
|
+
data.tar.gz: 6d5d33b93ab5b17884fdd79ae8e7d00adbe8adc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 99d8654a734acc8af11acaaba49c61904f71d055695279ebb912d0501c20ad812e8e8ba1a906f84586e8c72e67dcae590922fb5d9df100487e1c87ac72d533f7
|
7
|
+
data.tar.gz: 708bcf9a33706391aa38a3314e668149bd14c86964dd235281c1ec512f4ac1ff11a80a232ba996789d7d04dae08cf70541637837965ec10ac92b8152f8b87539
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'csv'
|
2
|
+
module IOStreams
|
3
|
+
module CSV
|
4
|
+
class Reader
|
5
|
+
# Read from a file or stream
|
6
|
+
def self.open(file_name_or_io, options = Hash.new, &block)
|
7
|
+
unless IOStreams.reader_stream?(file_name_or_io)
|
8
|
+
::CSV.open(file_name_or_io, options, &block)
|
9
|
+
else
|
10
|
+
begin
|
11
|
+
csv = ::CSV.new(file_name_or_io, options)
|
12
|
+
block.call(csv)
|
13
|
+
ensure
|
14
|
+
csv.close if csv
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module IOStreams
|
2
|
+
module CSV
|
3
|
+
class Writer
|
4
|
+
# Write to a file / stream, compressing with GZip
|
5
|
+
def self.open(file_name_or_io, options = {}, &block)
|
6
|
+
unless IOStreams.writer_stream?(file_name_or_io)
|
7
|
+
::CSV.open(file_name_or_io, 'wb', options, &block)
|
8
|
+
else
|
9
|
+
begin
|
10
|
+
csv = ::CSV.new(file_name_or_io, options)
|
11
|
+
block.call(csv)
|
12
|
+
ensure
|
13
|
+
csv.close if csv
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -5,7 +5,7 @@ module IOStreams
|
|
5
5
|
|
6
6
|
# Read from a file or stream
|
7
7
|
def self.open(file_name_or_io, options={}, &block)
|
8
|
-
if
|
8
|
+
if IOStreams.reader_stream?(file_name_or_io)
|
9
9
|
block.call(new(file_name_or_io, options))
|
10
10
|
else
|
11
11
|
::File.open(file_name_or_io, 'rb') do |io|
|
@@ -25,16 +25,14 @@ module IOStreams
|
|
25
25
|
# The input stream that implements #read
|
26
26
|
#
|
27
27
|
# options
|
28
|
-
# :delimiter[
|
28
|
+
# :delimiter[String]
|
29
29
|
# Line / Record delimiter to use to break the stream up into records
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# Searches for the first "\r\n" or "\n" and then uses that as the
|
33
|
-
# delimiter for all subsequent records
|
34
|
-
# String:
|
35
|
-
# Any string to break the stream up by
|
36
|
-
# The records when saved will not include this delimiter
|
30
|
+
# Any string to break the stream up by
|
31
|
+
# The records when saved will not include this delimiter
|
37
32
|
# Default: nil
|
33
|
+
# Automatically detect line endings and break up by line
|
34
|
+
# Searches for the first "\r\n" or "\n" and then uses that as the
|
35
|
+
# delimiter for all subsequent records
|
38
36
|
#
|
39
37
|
# :buffer_size [Integer]
|
40
38
|
# Maximum size of the buffer into which to read the stream into for
|
@@ -44,7 +42,7 @@ module IOStreams
|
|
44
42
|
#
|
45
43
|
# :strip_non_printable [true|false]
|
46
44
|
# Strip all non-printable characters read from the file
|
47
|
-
# Default:
|
45
|
+
# Default: false
|
48
46
|
#
|
49
47
|
# :encoding
|
50
48
|
# Force encoding to this encoding for all data being read
|
@@ -65,7 +63,7 @@ module IOStreams
|
|
65
63
|
end
|
66
64
|
|
67
65
|
# Returns each line at a time to to the supplied block
|
68
|
-
def
|
66
|
+
def each(&block)
|
69
67
|
partial = nil
|
70
68
|
loop do
|
71
69
|
if read_chunk == 0
|
@@ -89,9 +87,29 @@ module IOStreams
|
|
89
87
|
end
|
90
88
|
end
|
91
89
|
|
90
|
+
alias_method :each_line, :each
|
91
|
+
|
92
|
+
# Reads length bytes from the I/O stream.
|
93
|
+
# Not recommended, but available if someone calls #read on this delimited reader
|
94
|
+
def read(length = nil, outbuf = nil)
|
95
|
+
if length
|
96
|
+
while (@buffer.size < length) && (read_chunk > 0)
|
97
|
+
end
|
98
|
+
data = @buffer.slice!(0, length)
|
99
|
+
outbuf << data if outbuf
|
100
|
+
data
|
101
|
+
else
|
102
|
+
while read_chunk > 0
|
103
|
+
end
|
104
|
+
@buffer
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
92
108
|
##########################################################################
|
93
109
|
private
|
94
110
|
|
111
|
+
NOT_PRINTABLE = Regexp.compile(/[^[:print:]|\r|\n]/)
|
112
|
+
|
95
113
|
# Returns [Integer] the number of bytes read into the internal buffer
|
96
114
|
# Returns 0 on EOF
|
97
115
|
def read_chunk
|
@@ -100,7 +118,7 @@ module IOStreams
|
|
100
118
|
return 0 unless chunk
|
101
119
|
|
102
120
|
# Strip out non-printable characters before converting to UTF-8
|
103
|
-
chunk
|
121
|
+
chunk.gsub!(NOT_PRINTABLE, '') if @strip_non_printable
|
104
122
|
|
105
123
|
@buffer << (@encoding ? chunk.force_encoding(@encoding) : chunk)
|
106
124
|
chunk.size
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module IOStreams
|
2
|
+
module Delimited
|
3
|
+
class Writer
|
4
|
+
attr_accessor :delimiter
|
5
|
+
|
6
|
+
# Write delimited records/lines to a file or stream
|
7
|
+
def self.open(file_name_or_io, options={}, &block)
|
8
|
+
if IOStreams.writer_stream?(file_name_or_io)
|
9
|
+
block.call(new(file_name_or_io, options))
|
10
|
+
else
|
11
|
+
::File.open(file_name_or_io, 'wb') do |io|
|
12
|
+
block.call(new(io, options))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
NOT_PRINTABLE = Regexp.compile(/[^[:print:]]/)
|
18
|
+
|
19
|
+
# A delimited stream writer that will write to the supplied output stream
|
20
|
+
#
|
21
|
+
# The output stream should be binary with no text conversions performed
|
22
|
+
# since `strip_non_printable` will be applied to the binary stream before
|
23
|
+
# converting to UTF-8
|
24
|
+
#
|
25
|
+
# Parameters
|
26
|
+
# output_stream
|
27
|
+
# The output stream that implements #write
|
28
|
+
#
|
29
|
+
# options
|
30
|
+
# delimiter: [String]
|
31
|
+
# Add the specified delimiter after every record when writing it
|
32
|
+
# to the output stream
|
33
|
+
# Default: OS Specific. Linux: "\n"
|
34
|
+
#
|
35
|
+
# :encoding
|
36
|
+
# Force encoding to this encoding for all data being read
|
37
|
+
# Default: UTF8_ENCODING
|
38
|
+
# Set to nil to disable encoding
|
39
|
+
#
|
40
|
+
# :strip_non_printable [true|false]
|
41
|
+
# Strip all non-printable characters read from the file
|
42
|
+
# Default: false
|
43
|
+
def initialize(output_stream, options={})
|
44
|
+
@output_stream = output_stream
|
45
|
+
options = options.dup
|
46
|
+
@delimiter = options.has_key?(:delimiter) ? options.delete(:delimiter) : $/.dup
|
47
|
+
@encoding = options.has_key?(:encoding) ? options.delete(:encoding) : UTF8_ENCODING
|
48
|
+
@strip_non_printable = options.delete(:strip_non_printable)
|
49
|
+
@strip_non_printable = @strip_non_printable.nil? && (@encoding == UTF8_ENCODING)
|
50
|
+
raise ArgumentError.new("Unknown IOStreams::Delimited::Writer#initialize options: #{options.inspect}") if options.size > 0
|
51
|
+
@delimiter.force_encoding(UTF8_ENCODING) if @delimiter
|
52
|
+
end
|
53
|
+
|
54
|
+
# Write a record or line to the output stream
|
55
|
+
def <<(record)
|
56
|
+
chunk = record.to_s
|
57
|
+
# Strip out non-printable characters before converting to UTF-8
|
58
|
+
chunk = chunk.gsub(NOT_PRINTABLE, '') if @strip_non_printable
|
59
|
+
@output_stream.write((@encoding ? chunk.force_encoding(@encoding) : chunk))
|
60
|
+
@output_stream.write(@delimiter)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Write the given string to the underlying stream
|
64
|
+
# Note: Use of this method not recommended
|
65
|
+
def write(string)
|
66
|
+
@output_stream.write(string)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -3,7 +3,7 @@ module IOStreams
|
|
3
3
|
class Reader
|
4
4
|
# Read from a file or stream
|
5
5
|
def self.open(file_name_or_io, _=nil, &block)
|
6
|
-
unless
|
6
|
+
unless IOStreams.reader_stream?(file_name_or_io)
|
7
7
|
::File.open(file_name_or_io, 'rb', &block)
|
8
8
|
else
|
9
9
|
block.call(file_name_or_io)
|
@@ -12,4 +12,4 @@ module IOStreams
|
|
12
12
|
|
13
13
|
end
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
@@ -3,7 +3,7 @@ module IOStreams
|
|
3
3
|
class Writer
|
4
4
|
# Write to a file or stream
|
5
5
|
def self.open(file_name_or_io, _=nil, &block)
|
6
|
-
unless
|
6
|
+
unless IOStreams.writer_stream?(file_name_or_io)
|
7
7
|
::File.open(file_name_or_io, 'wb', &block)
|
8
8
|
else
|
9
9
|
block.call(file_name_or_io)
|
@@ -12,4 +12,4 @@ module IOStreams
|
|
12
12
|
|
13
13
|
end
|
14
14
|
end
|
15
|
-
end
|
15
|
+
end
|
@@ -3,7 +3,7 @@ module IOStreams
|
|
3
3
|
class Reader
|
4
4
|
# Read from a gzip file or stream, decompressing the contents as it is read
|
5
5
|
def self.open(file_name_or_io, _=nil, &block)
|
6
|
-
unless
|
6
|
+
unless IOStreams.reader_stream?(file_name_or_io)
|
7
7
|
::Zlib::GzipReader.open(file_name_or_io, &block)
|
8
8
|
else
|
9
9
|
begin
|
@@ -17,4 +17,4 @@ module IOStreams
|
|
17
17
|
|
18
18
|
end
|
19
19
|
end
|
20
|
-
end
|
20
|
+
end
|
@@ -3,7 +3,7 @@ module IOStreams
|
|
3
3
|
class Writer
|
4
4
|
# Write to a file / stream, compressing with GZip
|
5
5
|
def self.open(file_name_or_io, _=nil, &block)
|
6
|
-
unless
|
6
|
+
unless IOStreams.writer_stream?(file_name_or_io)
|
7
7
|
Zlib::GzipWriter.open(file_name_or_io, &block)
|
8
8
|
else
|
9
9
|
begin
|
@@ -17,4 +17,4 @@ module IOStreams
|
|
17
17
|
|
18
18
|
end
|
19
19
|
end
|
20
|
-
end
|
20
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require '
|
1
|
+
require 'concurrent'
|
2
2
|
module IOStreams
|
3
3
|
# A registry to hold formats for processing files during upload or download
|
4
|
-
@@extensions =
|
4
|
+
@@extensions = Concurrent::Hash.new
|
5
5
|
|
6
6
|
UTF8_ENCODING = Encoding.find('UTF-8').freeze
|
7
7
|
BINARY_ENCODING = Encoding.find('BINARY').freeze
|
@@ -110,7 +110,7 @@ module IOStreams
|
|
110
110
|
# IOStreams.reader('myfile.csv.enc', [:enc]) do |stream|
|
111
111
|
# puts stream.read
|
112
112
|
# end
|
113
|
-
def self.reader(file_name_or_io, streams=nil, &block)
|
113
|
+
def self.reader(file_name_or_io, streams = nil, &block)
|
114
114
|
stream(:reader, file_name_or_io, streams, &block)
|
115
115
|
end
|
116
116
|
|
@@ -162,7 +162,7 @@ module IOStreams
|
|
162
162
|
# IOStreams.writer('myfile.csv.zip', zip: { zip_file_name: 'myfile.csv' }) do |stream|
|
163
163
|
# stream.write(data)
|
164
164
|
# end
|
165
|
-
def self.writer(file_name_or_io, streams=nil, &block)
|
165
|
+
def self.writer(file_name_or_io, streams = nil, &block)
|
166
166
|
stream(:writer, file_name_or_io, streams, &block)
|
167
167
|
end
|
168
168
|
|
@@ -185,6 +185,42 @@ module IOStreams
|
|
185
185
|
bytes
|
186
186
|
end
|
187
187
|
|
188
|
+
# Returns [true|false] whether the supplied file_name_or_io is a reader stream
|
189
|
+
def self.reader_stream?(file_name_or_io)
|
190
|
+
file_name_or_io.respond_to?(:read)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns [true|false] whether the supplied file_name_or_io is a reader stream
|
194
|
+
def self.writer_stream?(file_name_or_io)
|
195
|
+
file_name_or_io.respond_to?(:write)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Returns [true|false] whether the file is compressed
|
199
|
+
# Note: Currently only looks at the file name extension
|
200
|
+
def self.compressed?(file_name)
|
201
|
+
!(file_name =~ /\.(zip|gz|gzip|xls.|)\z/i).nil?
|
202
|
+
end
|
203
|
+
|
204
|
+
# Deletes the specified stream from the supplied streams if present
|
205
|
+
# Returns deleted stream, or nil if not found
|
206
|
+
def self.delete_stream(stream, streams)
|
207
|
+
raise(ArgumentError, "Argument :stream must be a symbol: #{stream.inspect}") unless stream.is_a?(Symbol)
|
208
|
+
|
209
|
+
Array(streams).delete_if do |_stream|
|
210
|
+
stream_key = _stream.is_a?(Symbol) ? _stream : _stream.keys.first
|
211
|
+
stream == stream_key
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Returns [true|false] whether the stream starts with a delimited reader or writer
|
216
|
+
def self.delimited_stream?(streams)
|
217
|
+
stream = Array(streams).first
|
218
|
+
return false unless stream
|
219
|
+
|
220
|
+
# TODO Need to figure out a way so that this is not hard-coded
|
221
|
+
[:xlsx, :xlsm, :delimited].include?(stream.is_a?(Symbol) ? stream : stream.keys.first)
|
222
|
+
end
|
223
|
+
|
188
224
|
##########################################################################
|
189
225
|
private
|
190
226
|
|
@@ -192,7 +228,7 @@ module IOStreams
|
|
192
228
|
StreamStruct = Struct.new(:klass, :options)
|
193
229
|
|
194
230
|
# Returns a reader or writer stream
|
195
|
-
def self.stream(type, file_name_or_io, streams=nil, &block)
|
231
|
+
def self.stream(type, file_name_or_io, streams = nil, &block)
|
196
232
|
unless streams
|
197
233
|
respond_to = type == :reader ? :read : :write
|
198
234
|
streams = file_name_or_io.respond_to?(respond_to) ? [:file] : streams_for_file_name(file_name_or_io)
|
@@ -239,9 +275,13 @@ module IOStreams
|
|
239
275
|
|
240
276
|
# Register File extensions
|
241
277
|
# @formatter:off
|
242
|
-
register_extension(:enc,
|
243
|
-
register_extension(:file,
|
244
|
-
register_extension(:gz,
|
245
|
-
register_extension(:gzip,
|
246
|
-
register_extension(:zip,
|
278
|
+
register_extension(:enc, SymmetricEncryption::Reader, SymmetricEncryption::Writer) if defined?(SymmetricEncryption)
|
279
|
+
register_extension(:file, IOStreams::File::Reader, IOStreams::File::Writer)
|
280
|
+
register_extension(:gz, IOStreams::Gzip::Reader, IOStreams::Gzip::Writer)
|
281
|
+
register_extension(:gzip, IOStreams::Gzip::Reader, IOStreams::Gzip::Writer)
|
282
|
+
register_extension(:zip, IOStreams::Zip::Reader, IOStreams::Zip::Writer)
|
283
|
+
register_extension(:delimited, IOStreams::Delimited::Reader, IOStreams::Delimited::Writer)
|
284
|
+
register_extension(:xlsx, IOStreams::Xlsx::Reader, nil)
|
285
|
+
register_extension(:xlsm, IOStreams::Xlsx::Reader, nil)
|
286
|
+
#register_extension(:csv, IOStreams::CSV::Reader, IOStreams::CSV::Writer)
|
247
287
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module IOStreams
|
2
|
+
# Contains behavior for streams
|
3
|
+
#
|
4
|
+
# When a file is being read the streams are processed from right to left.
|
5
|
+
# When writing a file streams are processed left to right.
|
6
|
+
# For example:
|
7
|
+
# file.gz.enc ==> [:gz, :enc]
|
8
|
+
# Read: Unencrypt, then Gunzip
|
9
|
+
# Write: GZip, then Encrypt
|
10
|
+
class Streams
|
11
|
+
|
12
|
+
# Returns [Streams] collection of streams to process against the file
|
13
|
+
#
|
14
|
+
def self.streams_for_file_name(file_name)
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create a processing stream given:
|
19
|
+
# - No stream. Defaults to :file
|
20
|
+
# - A single String implies a file_name and the streams will be created based on the file_name
|
21
|
+
# - One or more symbols or hashes for a stream
|
22
|
+
# - One or more arrays for streams
|
23
|
+
def initialize(*args)
|
24
|
+
if args.size == 0
|
25
|
+
@streams = [:file]
|
26
|
+
elsif args.size == 1
|
27
|
+
stream = args.first
|
28
|
+
if stream
|
29
|
+
@stream = stream.is_a?(String) ? streams_for_file_name(stream) : Array(stream)
|
30
|
+
else
|
31
|
+
@streams = [:file]
|
32
|
+
end
|
33
|
+
else
|
34
|
+
@streams = streams
|
35
|
+
end
|
36
|
+
@streams.flatten!
|
37
|
+
end
|
38
|
+
|
39
|
+
def delimited?
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete(stream)
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
# Add another stream for processing
|
48
|
+
def <<(stream)
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# Add a stream for processing
|
53
|
+
def unshift(stream)
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Return the Stream klass for the specified hash or symbol
|
60
|
+
# Parameters
|
61
|
+
# stream [Hash|Symbol]
|
62
|
+
def stream_for(stream)
|
63
|
+
if stream.is_a?(Symbol)
|
64
|
+
registered_klass(stream, {})
|
65
|
+
else
|
66
|
+
registered_klass(@stream.first)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns [Array] the formats required to process the file by looking at
|
71
|
+
# its extension(s)
|
72
|
+
#
|
73
|
+
# Extensions supported:
|
74
|
+
# .zip Zip File [ :zip ]
|
75
|
+
# .gz, .gzip GZip File [ :gzip ]
|
76
|
+
# .enc File Encrypted using symmetric encryption [ :enc ]
|
77
|
+
# other All other extensions will be returned as: [ :file ]
|
78
|
+
#
|
79
|
+
# When a file is encrypted, it may also be compressed:
|
80
|
+
# .zip.enc [ :zip, :enc ]
|
81
|
+
# .gz.enc [ :gz, :enc ]
|
82
|
+
#
|
83
|
+
# Example Zip file:
|
84
|
+
# RocketJob::Formatter::Formats.streams_for_file_name('myfile.zip')
|
85
|
+
# => [ :zip ]
|
86
|
+
#
|
87
|
+
# Example Encrypted Gzip file:
|
88
|
+
# RocketJob::Formatter::Formats.streams_for_file_name('myfile.csv.gz.enc')
|
89
|
+
# => [ :gz, :enc ]
|
90
|
+
#
|
91
|
+
# Example plain text / binary file:
|
92
|
+
# RocketJob::Formatter::Formats.streams_for_file_name('myfile.csv')
|
93
|
+
# => [ :file ]
|
94
|
+
def streams_for_file_name(file_name)
|
95
|
+
raise ArgumentError.new("RocketJob Cannot detect file format when uploading to stream: #{file_name.inspect}") if file_name.respond_to?(:read)
|
96
|
+
|
97
|
+
parts = file_name.split('.')
|
98
|
+
extensions = []
|
99
|
+
while extension = parts.pop
|
100
|
+
break unless @@extensions[extension.to_sym]
|
101
|
+
extensions.unshift(extension.to_sym)
|
102
|
+
end
|
103
|
+
extensions << :file if extensions.size == 0
|
104
|
+
extensions
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
data/lib/io_streams/version.rb
CHANGED
@@ -1,9 +1,3 @@
|
|
1
|
-
begin
|
2
|
-
require 'creek'
|
3
|
-
rescue LoadError => e
|
4
|
-
puts "Install the 'creek' gem for xlsx streaming support"
|
5
|
-
raise(e)
|
6
|
-
end
|
7
1
|
require 'csv'
|
8
2
|
|
9
3
|
module IOStreams
|
@@ -11,17 +5,7 @@ module IOStreams
|
|
11
5
|
class Reader
|
12
6
|
attr_reader :worksheet
|
13
7
|
|
14
|
-
|
15
|
-
@worksheet = workbook.sheets[0]
|
16
|
-
end
|
17
|
-
|
18
|
-
def each_line(&block)
|
19
|
-
worksheet.rows.each do |row|
|
20
|
-
block.call(row.values.to_csv(row_sep: nil))
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# Read from a xlsx file or stream.
|
8
|
+
# Read from a xlsx, or xlsm file or stream.
|
25
9
|
#
|
26
10
|
# Example:
|
27
11
|
# IOStreams::Xlsx::Reader.open('spreadsheet.xlsx') do |spreadsheet_stream|
|
@@ -30,11 +14,17 @@ module IOStreams
|
|
30
14
|
# end
|
31
15
|
# end
|
32
16
|
def self.open(file_name_or_io, options={}, &block)
|
17
|
+
begin
|
18
|
+
require 'creek' unless defined?(Creek::Book)
|
19
|
+
rescue LoadError => e
|
20
|
+
raise(LoadError, "Please install the 'creek' gem for xlsx streaming support. #{e.message}")
|
21
|
+
end
|
22
|
+
|
33
23
|
options = options.dup
|
34
24
|
buffer_size = options.delete(:buffer_size) || 65536
|
35
25
|
raise(ArgumentError, "Unknown IOStreams::Xlsx::Reader option: #{options.inspect}") if options.size > 0
|
36
26
|
|
37
|
-
if
|
27
|
+
if IOStreams.reader_stream?(file_name_or_io)
|
38
28
|
temp_file = Tempfile.new('rocket_job_xlsx')
|
39
29
|
file_name = temp_file.to_path
|
40
30
|
|
@@ -50,6 +40,17 @@ module IOStreams
|
|
50
40
|
temp_file.delete if temp_file
|
51
41
|
end
|
52
42
|
|
43
|
+
def initialize(workbook)
|
44
|
+
@worksheet = workbook.sheets[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns each [Array] row from the spreadsheet
|
48
|
+
def each(&block)
|
49
|
+
worksheet.rows.each { |row| block.call(row.values) }
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :each_line, :each
|
53
|
+
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
@@ -18,7 +18,7 @@ module IOStreams
|
|
18
18
|
raise(ArgumentError, "Unknown IOStreams::Zip::Reader option: #{options.inspect}") if options.size > 0
|
19
19
|
|
20
20
|
# File name supplied
|
21
|
-
return read_file(file_name_or_io, &block) unless
|
21
|
+
return read_file(file_name_or_io, &block) unless IOStreams.reader_stream?(file_name_or_io)
|
22
22
|
|
23
23
|
# Stream supplied
|
24
24
|
begin
|
@@ -4,10 +4,11 @@ module IOStreams
|
|
4
4
|
# Write a single file in Zip format to the supplied output file name
|
5
5
|
#
|
6
6
|
# Parameters
|
7
|
-
#
|
7
|
+
# file_name_or_io [String]
|
8
8
|
# Full path and filename for the output zip file
|
9
9
|
#
|
10
|
-
#
|
10
|
+
# Options
|
11
|
+
# :file_name [String]
|
11
12
|
# Name of the file within the Zip Stream
|
12
13
|
#
|
13
14
|
# The stream supplied to the block only responds to #write
|
@@ -23,7 +24,7 @@ module IOStreams
|
|
23
24
|
# is automatically created under the covers
|
24
25
|
def self.open(file_name_or_io, options={}, &block)
|
25
26
|
options = options.dup
|
26
|
-
zip_file_name = options.delete(:zip_file_name)
|
27
|
+
zip_file_name = options.delete(:file_name) || options.delete(:zip_file_name)
|
27
28
|
buffer_size = options.delete(:buffer_size) || 65536
|
28
29
|
raise(ArgumentError, "Unknown IOStreams::Zip::Writer option: #{options.inspect}") if options.size > 0
|
29
30
|
|
@@ -32,7 +33,7 @@ module IOStreams
|
|
32
33
|
zip_file_name ||= 'file'
|
33
34
|
|
34
35
|
# File name supplied
|
35
|
-
return write_file(file_name_or_io, zip_file_name, &block) unless
|
36
|
+
return write_file(file_name_or_io, zip_file_name, &block) unless IOStreams.writer_stream?(file_name_or_io)
|
36
37
|
|
37
38
|
# Stream supplied
|
38
39
|
begin
|
@@ -83,4 +84,4 @@ module IOStreams
|
|
83
84
|
|
84
85
|
end
|
85
86
|
end
|
86
|
-
end
|
87
|
+
end
|
data/lib/iostreams.rb
CHANGED
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
# Unit Test for IOStreams::File
|
4
|
+
module Streams
|
5
|
+
class CSVReaderTest < Minitest::Test
|
6
|
+
describe IOStreams::CSV::Reader do
|
7
|
+
before do
|
8
|
+
@file_name = File.join(File.dirname(__FILE__), 'files', 'test.csv')
|
9
|
+
@data = CSV.read(@file_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.open' do
|
13
|
+
it 'file' do
|
14
|
+
rows = []
|
15
|
+
IOStreams::CSV::Reader.open(@file_name) do |io|
|
16
|
+
io.each { |row| rows << row }
|
17
|
+
end
|
18
|
+
assert_equal @data, rows
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'stream' do
|
22
|
+
rows = []
|
23
|
+
File.open(@file_name) do |file|
|
24
|
+
IOStreams::CSV::Reader.open(file) do |io|
|
25
|
+
io.each { |row| rows << row }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
assert_equal @data, rows
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
require 'csv'
|
3
|
+
|
4
|
+
module Streams
|
5
|
+
class CSVWriterTest < Minitest::Test
|
6
|
+
describe IOStreams::CSV::Writer do
|
7
|
+
before do
|
8
|
+
@file_name = File.join(File.dirname(__FILE__), 'files', 'test.csv')
|
9
|
+
@data = ::CSV.read(@file_name)
|
10
|
+
@raw_csv_data = ::File.read(@file_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '.open' do
|
14
|
+
it 'file' do
|
15
|
+
temp_file = Tempfile.new('rocket_job')
|
16
|
+
file_name = temp_file.to_path
|
17
|
+
IOStreams::CSV::Writer.open(file_name) do |io|
|
18
|
+
@data.each { |row| io << row }
|
19
|
+
end
|
20
|
+
result = File.read(file_name)
|
21
|
+
assert_equal @raw_csv_data, result
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'stream' do
|
25
|
+
io_string = StringIO.new
|
26
|
+
IOStreams::CSV::Writer.open(io_string) do |io|
|
27
|
+
@data.each { |row| io << row }
|
28
|
+
end
|
29
|
+
assert_equal @raw_csv_data, io_string.string
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -3,7 +3,7 @@ require_relative 'test_helper'
|
|
3
3
|
# Unit Test for IOStreams::File
|
4
4
|
module Streams
|
5
5
|
class DelimitedReaderTest < Minitest::Test
|
6
|
-
describe IOStreams::
|
6
|
+
describe IOStreams::Delimited::Reader do
|
7
7
|
before do
|
8
8
|
@file_name = File.join(File.dirname(__FILE__), 'files', 'text.txt')
|
9
9
|
@data = []
|
@@ -14,11 +14,11 @@ module Streams
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
describe '
|
17
|
+
describe '#each' do
|
18
18
|
it 'each_line file' do
|
19
19
|
lines = []
|
20
20
|
IOStreams::Delimited::Reader.open(@file_name) do |io|
|
21
|
-
io.
|
21
|
+
io.each { |line| lines << line }
|
22
22
|
end
|
23
23
|
assert_equal @data, lines
|
24
24
|
end
|
@@ -27,7 +27,7 @@ module Streams
|
|
27
27
|
lines = []
|
28
28
|
File.open(@file_name) do |file|
|
29
29
|
IOStreams::Delimited::Reader.open(file) do |io|
|
30
|
-
io.
|
30
|
+
io.each { |line| lines << line }
|
31
31
|
end
|
32
32
|
end
|
33
33
|
assert_equal @data, lines
|
@@ -38,34 +38,44 @@ module Streams
|
|
38
38
|
lines = []
|
39
39
|
stream = StringIO.new(@data.join(delimiter))
|
40
40
|
IOStreams::Delimited::Reader.open(stream, buffer_size: 15) do |io|
|
41
|
-
io.
|
41
|
+
io.each { |line| lines << line }
|
42
42
|
end
|
43
43
|
assert_equal @data, lines
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
47
|
['@', 'BLAH'].each do |delimiter|
|
48
|
-
it "
|
48
|
+
it "reads delimited #{delimiter.inspect}" do
|
49
49
|
lines = []
|
50
50
|
stream = StringIO.new(@data.join(delimiter))
|
51
51
|
IOStreams::Delimited::Reader.open(stream, buffer_size: 15, delimiter: delimiter) do |io|
|
52
|
-
io.
|
52
|
+
io.each { |line| lines << line }
|
53
53
|
end
|
54
54
|
assert_equal @data, lines
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
it
|
58
|
+
it 'reads binary delimited' do
|
59
59
|
delimiter = "\x01"
|
60
60
|
lines = []
|
61
61
|
stream = StringIO.new(@data.join(delimiter))
|
62
62
|
IOStreams::Delimited::Reader.open(stream, buffer_size: 15, delimiter: delimiter, encoding: IOStreams::BINARY_ENCODING) do |io|
|
63
|
-
io.
|
63
|
+
io.each { |line| lines << line }
|
64
64
|
end
|
65
65
|
assert_equal @data, lines
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
describe '.read' do
|
70
|
+
it 'reads without delimiter' do
|
71
|
+
result = IOStreams::Delimited::Reader.open(@file_name) do |io|
|
72
|
+
io.read
|
73
|
+
end
|
74
|
+
file = File.read(@file_name)
|
75
|
+
assert_equal file, result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
69
79
|
end
|
70
80
|
end
|
71
81
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
module Streams
|
4
|
+
class DelimitedWriterTest < Minitest::Test
|
5
|
+
describe IOStreams::Delimited::Writer do
|
6
|
+
before do
|
7
|
+
@file_name = File.join(File.dirname(__FILE__), 'files', 'text.txt')
|
8
|
+
@file = File.read(@file_name)
|
9
|
+
@data = @file.lines
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#<<' do
|
13
|
+
it 'file' do
|
14
|
+
temp_file = Tempfile.new('rocket_job')
|
15
|
+
file_name = temp_file.to_path
|
16
|
+
IOStreams::Delimited::Writer.open(file_name) do |io|
|
17
|
+
@data.each { |line| io << line.strip }
|
18
|
+
end
|
19
|
+
result = File.read(file_name)
|
20
|
+
assert_equal @file, result
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'stream' do
|
24
|
+
io_string = StringIO.new
|
25
|
+
IOStreams::Delimited::Writer.open(io_string) do |io|
|
26
|
+
@data.each { |line| io << line.strip }
|
27
|
+
end
|
28
|
+
assert_equal @file, io_string.string
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '.write' do
|
33
|
+
it 'writes without delimiter' do
|
34
|
+
io_string = StringIO.new
|
35
|
+
IOStreams::File::Writer.open(io_string) do |io|
|
36
|
+
io.write(@file)
|
37
|
+
end
|
38
|
+
assert_equal @file, io_string.string
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/test/file_reader_test.rb
CHANGED
data/test/file_writer_test.rb
CHANGED
data/test/files/test.csv
ADDED
data/test/gzip_reader_test.rb
CHANGED
data/test/gzip_writer_test.rb
CHANGED
data/test/xlsx_reader_test.rb
CHANGED
@@ -2,10 +2,12 @@ require_relative 'test_helper'
|
|
2
2
|
|
3
3
|
module Streams
|
4
4
|
describe IOStreams::Xlsx::Reader do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
before do
|
6
|
+
@xlsx_contents = [
|
7
|
+
['first column', 'second column', 'third column'],
|
8
|
+
['data 1', 'data 2', 'more data']
|
9
|
+
]
|
10
|
+
end
|
9
11
|
|
10
12
|
describe '.open' do
|
11
13
|
let(:file_name) { File.join(File.dirname(__FILE__), 'files', 'spreadsheet.xlsx') }
|
@@ -18,9 +20,9 @@ module Streams
|
|
18
20
|
it 'returns the contents of the file' do
|
19
21
|
rows = []
|
20
22
|
IOStreams::Xlsx::Reader.open(@file) do |spreadsheet|
|
21
|
-
spreadsheet.
|
23
|
+
spreadsheet.each { |row| rows << row }
|
22
24
|
end
|
23
|
-
assert_equal(
|
25
|
+
assert_equal(@xlsx_contents, rows)
|
24
26
|
end
|
25
27
|
end
|
26
28
|
|
@@ -30,11 +32,11 @@ module Streams
|
|
30
32
|
rows = []
|
31
33
|
File.open(file_name) do |file|
|
32
34
|
IOStreams::Xlsx::Reader.open(file) do |spreadsheet|
|
33
|
-
spreadsheet.
|
35
|
+
spreadsheet.each { |row| rows << row }
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
assert_equal(
|
39
|
+
assert_equal(@xlsx_contents, rows)
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
data/test/zip_reader_test.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iostreams
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: symmetric-encryption
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '3.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: concurrent-ruby
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -47,21 +47,29 @@ extra_rdoc_files: []
|
|
47
47
|
files:
|
48
48
|
- README.md
|
49
49
|
- Rakefile
|
50
|
+
- lib/io_streams/csv/reader.rb
|
51
|
+
- lib/io_streams/csv/writer.rb
|
50
52
|
- lib/io_streams/delimited/reader.rb
|
53
|
+
- lib/io_streams/delimited/writer.rb
|
51
54
|
- lib/io_streams/file/reader.rb
|
52
55
|
- lib/io_streams/file/writer.rb
|
53
56
|
- lib/io_streams/gzip/reader.rb
|
54
57
|
- lib/io_streams/gzip/writer.rb
|
55
58
|
- lib/io_streams/io_streams.rb
|
59
|
+
- lib/io_streams/streams.rb
|
56
60
|
- lib/io_streams/version.rb
|
57
61
|
- lib/io_streams/xlsx/reader.rb
|
58
62
|
- lib/io_streams/zip/reader.rb
|
59
63
|
- lib/io_streams/zip/writer.rb
|
60
64
|
- lib/iostreams.rb
|
65
|
+
- test/csv_reader_test.rb
|
66
|
+
- test/csv_writer_test.rb
|
61
67
|
- test/delimited_reader_test.rb
|
68
|
+
- test/delimited_writer_test.rb
|
62
69
|
- test/file_reader_test.rb
|
63
70
|
- test/file_writer_test.rb
|
64
71
|
- test/files/spreadsheet.xlsx
|
72
|
+
- test/files/test.csv
|
65
73
|
- test/files/text.txt
|
66
74
|
- test/files/text.txt.gz
|
67
75
|
- test/files/text.txt.gz.zip
|
@@ -97,10 +105,14 @@ signing_key:
|
|
97
105
|
specification_version: 4
|
98
106
|
summary: Ruby Input and Output streaming with support for Zip, Gzip, and Encryption.
|
99
107
|
test_files:
|
108
|
+
- test/csv_reader_test.rb
|
109
|
+
- test/csv_writer_test.rb
|
100
110
|
- test/delimited_reader_test.rb
|
111
|
+
- test/delimited_writer_test.rb
|
101
112
|
- test/file_reader_test.rb
|
102
113
|
- test/file_writer_test.rb
|
103
114
|
- test/files/spreadsheet.xlsx
|
115
|
+
- test/files/test.csv
|
104
116
|
- test/files/text.txt
|
105
117
|
- test/files/text.txt.gz
|
106
118
|
- test/files/text.txt.gz.zip
|
@@ -111,4 +123,3 @@ test_files:
|
|
111
123
|
- test/xlsx_reader_test.rb
|
112
124
|
- test/zip_reader_test.rb
|
113
125
|
- test/zip_writer_test.rb
|
114
|
-
has_rdoc: true
|