iostreams 0.8.2 → 0.9.0
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/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
|