iostreams 0.15.0 → 0.16.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/bzip2/reader.rb +1 -1
- data/lib/io_streams/bzip2/writer.rb +1 -1
- data/lib/io_streams/encode/reader.rb +102 -0
- data/lib/io_streams/encode/writer.rb +78 -0
- data/lib/io_streams/errors.rb +19 -0
- data/lib/io_streams/file/reader.rb +1 -1
- data/lib/io_streams/file/writer.rb +1 -3
- data/lib/io_streams/gzip/reader.rb +1 -1
- data/lib/io_streams/gzip/writer.rb +1 -1
- data/lib/io_streams/io_streams.rb +57 -38
- data/lib/io_streams/line/reader.rb +125 -69
- data/lib/io_streams/line/writer.rb +11 -35
- data/lib/io_streams/pgp.rb +1 -1
- data/lib/io_streams/record/reader.rb +12 -14
- data/lib/io_streams/record/writer.rb +12 -14
- data/lib/io_streams/row/reader.rb +15 -16
- data/lib/io_streams/row/writer.rb +14 -12
- data/lib/io_streams/tabular.rb +50 -30
- data/lib/io_streams/tabular/header.rb +6 -6
- data/lib/io_streams/tabular/parser/array.rb +2 -2
- data/lib/io_streams/tabular/parser/csv.rb +6 -2
- data/lib/io_streams/tabular/parser/fixed.rb +18 -37
- data/lib/io_streams/tabular/parser/hash.rb +1 -1
- data/lib/io_streams/tabular/parser/json.rb +3 -1
- data/lib/io_streams/tabular/parser/psv.rb +6 -2
- data/lib/io_streams/version.rb +1 -1
- data/lib/io_streams/xlsx/reader.rb +22 -32
- data/lib/iostreams.rb +6 -0
- data/test/encode_reader_test.rb +54 -0
- data/test/encode_writer_test.rb +82 -0
- data/test/io_streams_test.rb +0 -65
- data/test/line_reader_test.rb +180 -37
- data/test/tabular_test.rb +79 -3
- data/test/test_helper.rb +1 -1
- data/test/xlsx_reader_test.rb +7 -10
- metadata +10 -4
- data/lib/io_streams/tabular/errors.rb +0 -14
@@ -13,12 +13,14 @@ module IOStreams
|
|
13
13
|
#
|
14
14
|
class Writer
|
15
15
|
# Write a record as a Hash at a time to a file or stream.
|
16
|
-
def self.open(file_name_or_io, delimiter: $/, encoding:
|
16
|
+
def self.open(file_name_or_io, delimiter: $/, encoding: nil, encode_cleaner: nil, encode_replace: nil, **args)
|
17
17
|
if file_name_or_io.is_a?(String)
|
18
18
|
IOStreams.line_writer(file_name_or_io,
|
19
|
-
delimiter:
|
20
|
-
encoding:
|
21
|
-
|
19
|
+
delimiter: delimiter,
|
20
|
+
encoding: encoding,
|
21
|
+
encode_cleaner: encode_cleaner,
|
22
|
+
encode_replace: encode_replace
|
23
|
+
) do |io|
|
22
24
|
yield new(io, **args)
|
23
25
|
end
|
24
26
|
else
|
@@ -43,20 +45,20 @@ module IOStreams
|
|
43
45
|
@delimited = delimited
|
44
46
|
|
45
47
|
# Render header line when `columns` is supplied.
|
46
|
-
delimited << @tabular.
|
48
|
+
delimited << @tabular.render_header if columns && @tabular.requires_header?
|
47
49
|
end
|
48
50
|
|
49
51
|
# Supply a hash or an array to render
|
50
52
|
def <<(array)
|
51
53
|
raise(ArgumentError, 'Must supply an Array') unless array.is_a?(Array)
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
if @tabular.header?
|
55
|
+
# If header (columns) was not supplied as an argument, assume first line is the header.
|
56
|
+
@tabular.header.columns = array
|
57
|
+
@delimited << @tabular.render_header
|
58
|
+
else
|
59
|
+
@delimited << @tabular.render(array)
|
60
|
+
end
|
55
61
|
end
|
56
|
-
|
57
|
-
private
|
58
|
-
|
59
|
-
attr_reader :tabular, :delimited
|
60
62
|
end
|
61
63
|
end
|
62
64
|
end
|
data/lib/io_streams/tabular.rb
CHANGED
@@ -28,7 +28,6 @@ module IOStreams
|
|
28
28
|
# tabular.render({"third"=>"3", "first_field"=>"1" })
|
29
29
|
# # => "1,,3"
|
30
30
|
class Tabular
|
31
|
-
autoload :Errors, 'io_streams/tabular/errors'
|
32
31
|
autoload :Header, 'io_streams/tabular/header'
|
33
32
|
|
34
33
|
module Parser
|
@@ -54,7 +53,7 @@ module IOStreams
|
|
54
53
|
# :csv, :hash, :array, :json, :psv, :fixed
|
55
54
|
#
|
56
55
|
# For all other parameters, see Tabular::Header.new
|
57
|
-
def initialize(format: nil, file_name: nil, **args)
|
56
|
+
def initialize(format: nil, file_name: nil, format_options: nil, **args)
|
58
57
|
@header = Header.new(**args)
|
59
58
|
klass =
|
60
59
|
if file_name && format.nil?
|
@@ -62,19 +61,24 @@ module IOStreams
|
|
62
61
|
else
|
63
62
|
self.class.parser_class(format)
|
64
63
|
end
|
65
|
-
@parser = klass.new
|
64
|
+
@parser = format_options ? klass.new(format_options) : klass.new
|
66
65
|
end
|
67
66
|
|
68
|
-
# Returns [true|false] whether a header
|
69
|
-
def
|
67
|
+
# Returns [true|false] whether a header is still required in order to parse or render the current format.
|
68
|
+
def header?
|
70
69
|
parser.requires_header? && IOStreams.blank?(header.columns)
|
71
70
|
end
|
72
71
|
|
72
|
+
# Returns [true|false] whether a header row show be rendered on output.
|
73
|
+
def requires_header?
|
74
|
+
parser.requires_header?
|
75
|
+
end
|
76
|
+
|
73
77
|
# Returns [Array] the header row/line after parsing and cleansing.
|
74
78
|
# Returns `nil` if the row/line is blank, or a header is not required for the supplied format (:json, :hash).
|
75
79
|
#
|
76
80
|
# Notes:
|
77
|
-
# * Call `
|
81
|
+
# * Call `header?` first to determine if the header should be parsed first.
|
78
82
|
# * The header columns are set after parsing the row, but the header is not cleansed.
|
79
83
|
def parse_header(line)
|
80
84
|
return if IOStreams.blank?(line) || !parser.requires_header?
|
@@ -104,60 +108,76 @@ module IOStreams
|
|
104
108
|
parser.render(row, header)
|
105
109
|
end
|
106
110
|
|
111
|
+
# Returns [String] the header rendered for the output format
|
112
|
+
# Return nil if no header is required.
|
113
|
+
def render_header
|
114
|
+
return unless requires_header?
|
115
|
+
|
116
|
+
if IOStreams.blank?(header.columns)
|
117
|
+
raise(Errors::MissingHeader, "Header columns must be set before attempting to render a header for format: #{format.inspect}")
|
118
|
+
end
|
119
|
+
|
120
|
+
parser.render(header.columns, header)
|
121
|
+
end
|
122
|
+
|
107
123
|
# Returns [Array<String>] the cleansed columns
|
108
124
|
def cleanse_header!
|
109
125
|
header.cleanse!
|
110
126
|
header.columns
|
111
127
|
end
|
112
128
|
|
113
|
-
# Register a
|
129
|
+
# Register a format and the parser class for it.
|
114
130
|
#
|
115
131
|
# Example:
|
116
|
-
#
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
@extensions[extension.nil? ? nil : extension.to_sym] = parser
|
132
|
+
# register_format(:csv, IOStreams::Tabular::Parser::Csv)
|
133
|
+
def self.register_format(format, parser)
|
134
|
+
raise(ArgumentError, "Invalid format #{format.inspect}") unless format.nil? || format.to_s =~ /\A\w+\Z/
|
135
|
+
@formats[format.nil? ? nil : format.to_sym] = parser
|
121
136
|
end
|
122
137
|
|
123
|
-
# De-Register a file
|
138
|
+
# De-Register a file format
|
124
139
|
#
|
125
|
-
# Returns [Symbol] the
|
140
|
+
# Returns [Symbol] the format removed, or nil if the format was not registered
|
126
141
|
#
|
127
142
|
# Example:
|
128
143
|
# register_extension(:xls)
|
129
|
-
def self.
|
130
|
-
raise(ArgumentError, "Invalid
|
131
|
-
@
|
144
|
+
def self.deregister_format(format)
|
145
|
+
raise(ArgumentError, "Invalid format #{format.inspect}") unless format.to_s =~ /\A\w+\Z/
|
146
|
+
@formats.delete(format.to_sym)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns [Array<Symbol>] the list of registered formats
|
150
|
+
def self.registered_formats
|
151
|
+
@formats.keys
|
132
152
|
end
|
133
153
|
|
134
154
|
private
|
135
155
|
|
136
156
|
# A registry to hold formats for processing files during upload or download
|
137
|
-
@
|
157
|
+
@formats = {}
|
138
158
|
|
139
159
|
def self.parser_class(format)
|
140
|
-
@
|
160
|
+
@formats[format.nil? ? nil : format.to_sym] || raise(ArgumentError, "Unknown Tabular Format: #{format.inspect}")
|
141
161
|
end
|
142
162
|
|
143
163
|
# Returns the parser to use with tabular for the supplied file_name
|
144
164
|
def self.parser_class_for_file_name(file_name)
|
145
|
-
|
165
|
+
format = nil
|
146
166
|
file_name.to_s.split('.').reverse_each do |ext|
|
147
|
-
if @
|
148
|
-
|
167
|
+
if @formats.include?(ext.to_sym)
|
168
|
+
format = ext.to_sym
|
149
169
|
break
|
150
170
|
end
|
151
171
|
end
|
152
|
-
parser_class(
|
172
|
+
parser_class(format)
|
153
173
|
end
|
154
174
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
175
|
+
register_format(nil, IOStreams::Tabular::Parser::Csv)
|
176
|
+
register_format(:array, IOStreams::Tabular::Parser::Array)
|
177
|
+
register_format(:csv, IOStreams::Tabular::Parser::Csv)
|
178
|
+
register_format(:fixed, IOStreams::Tabular::Parser::Fixed)
|
179
|
+
register_format(:hash, IOStreams::Tabular::Parser::Hash)
|
180
|
+
register_format(:json, IOStreams::Tabular::Parser::Json)
|
181
|
+
register_format(:psv, IOStreams::Tabular::Parser::Psv)
|
162
182
|
end
|
163
183
|
end
|
@@ -61,17 +61,17 @@ module IOStreams
|
|
61
61
|
end
|
62
62
|
|
63
63
|
if !skip_unknown && !ignored_columns.empty?
|
64
|
-
raise(IOStreams::
|
64
|
+
raise(IOStreams::Errors::InvalidHeader, "Unknown columns after cleansing: #{ignored_columns.join(',')}")
|
65
65
|
end
|
66
66
|
|
67
67
|
if ignored_columns.size == columns.size
|
68
|
-
raise(IOStreams::
|
68
|
+
raise(IOStreams::Errors::InvalidHeader, "All columns are unknown after cleansing: #{ignored_columns.join(',')}")
|
69
69
|
end
|
70
70
|
|
71
71
|
if required_columns
|
72
72
|
missing_columns = required_columns - columns
|
73
73
|
unless missing_columns.empty?
|
74
|
-
raise(IOStreams::
|
74
|
+
raise(IOStreams::Errors::InvalidHeader, "Missing columns after cleansing: #{missing_columns.join(',')}")
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
@@ -90,12 +90,12 @@ module IOStreams
|
|
90
90
|
|
91
91
|
case row
|
92
92
|
when Array
|
93
|
-
raise(
|
93
|
+
raise(IOStreams::Errors::InvalidHeader, "Missing mandatory header when trying to convert a row into a hash") unless columns
|
94
94
|
array_to_hash(row)
|
95
95
|
when Hash
|
96
96
|
cleanse && columns ? cleanse_hash(row) : row
|
97
97
|
else
|
98
|
-
raise(
|
98
|
+
raise(IOStreams::Errors::TypeMismatch, "Don't know how to convert #{row.class.name} to a Hash")
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
@@ -104,7 +104,7 @@ module IOStreams
|
|
104
104
|
row = cleanse_hash(row) if cleanse
|
105
105
|
row = columns.collect { |column| row[column] }
|
106
106
|
end
|
107
|
-
raise(
|
107
|
+
raise(IOStreams::Errors::TypeMismatch, "Don't know how to convert #{row.class.name} to an Array without the header columns being set.") unless row.is_a?(Array)
|
108
108
|
row
|
109
109
|
end
|
110
110
|
|
@@ -6,13 +6,13 @@ module IOStreams
|
|
6
6
|
# Returns [Array<String>] the header row.
|
7
7
|
# Returns nil if the row is blank.
|
8
8
|
def parse_header(row)
|
9
|
-
raise(
|
9
|
+
raise(IOStreams::Errors::InvalidHeader, "Format is :array. Invalid input header: #{row.class.name}") unless row.is_a?(::Array)
|
10
10
|
row
|
11
11
|
end
|
12
12
|
|
13
13
|
# Returns Array
|
14
14
|
def parse(row)
|
15
|
-
raise(
|
15
|
+
raise(IOStreams::Errors::TypeMismatch, "Format is :array. Invalid input: #{row.class.name}") unless row.is_a?(::Array)
|
16
16
|
row
|
17
17
|
end
|
18
18
|
|
@@ -11,14 +11,18 @@ module IOStreams
|
|
11
11
|
# Returns [Array<String>] the header row.
|
12
12
|
# Returns nil if the row is blank.
|
13
13
|
def parse_header(row)
|
14
|
-
|
14
|
+
return row if row.is_a?(::Array)
|
15
|
+
|
16
|
+
raise(IOStreams::Errors::InvalidHeader, "Format is :csv. Invalid input header: #{row.class.name}") unless row.is_a?(String)
|
15
17
|
|
16
18
|
csv_parser.parse(row)
|
17
19
|
end
|
18
20
|
|
19
21
|
# Returns [Array] the parsed CSV line
|
20
22
|
def parse(row)
|
21
|
-
|
23
|
+
return row if row.is_a?(::Array)
|
24
|
+
|
25
|
+
raise(IOStreams::Errors::TypeMismatch, "Format is :csv. Invalid input: #{row.class.name}") unless row.is_a?(String)
|
22
26
|
|
23
27
|
csv_parser.parse(row)
|
24
28
|
end
|
@@ -3,83 +3,64 @@ module IOStreams
|
|
3
3
|
module Parser
|
4
4
|
# Parsing and rendering fixed length data
|
5
5
|
class Fixed < Base
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :fixed_layout
|
7
7
|
|
8
8
|
# Returns [IOStreams::Tabular::Parser]
|
9
9
|
#
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# Parameters:
|
11
|
+
# layout: [Array<Hash>]
|
12
12
|
# [
|
13
13
|
# {key: 'name', size: 23 },
|
14
14
|
# {key: 'address', size: 40 },
|
15
15
|
# {key: 'zip', size: 5 }
|
16
16
|
# ]
|
17
|
-
|
18
|
-
|
19
|
-
# nil: Don't perform any encoding conversion
|
20
|
-
# 'ASCII': ASCII Format
|
21
|
-
# 'UTF-8': UTF-8 Format
|
22
|
-
# Etc.
|
23
|
-
# Default: nil
|
24
|
-
#
|
25
|
-
# replacement: [String]
|
26
|
-
# The character to replace with when a character cannot be converted to the target encoding.
|
27
|
-
# nil: Don't replace any invalid characters. Encoding::UndefinedConversionError is raised.
|
28
|
-
# Default: nil
|
29
|
-
def initialize(format:, encoding: nil, replacement: nil)
|
30
|
-
@encoding = encoding.nil? || encoding.is_a?(Encoding) ? encoding : Encoding.find(encoding)
|
31
|
-
@encoding_options = replacement.nil? ? {} : {invalid: :replace, undef: :replace, replace: replacement}
|
32
|
-
@fixed_format = parse_format(format)
|
17
|
+
def initialize(layout:)
|
18
|
+
@fixed_layout = parse_layout(layout)
|
33
19
|
end
|
34
20
|
|
35
|
-
# Returns [String] fixed
|
21
|
+
# Returns [String] fixed layout values extracted from the supplied hash.
|
36
22
|
# String will be encoded to `encoding`
|
37
23
|
def render(row, header)
|
38
24
|
hash = header.to_hash(row)
|
39
25
|
|
40
|
-
result =
|
41
|
-
|
26
|
+
result = ''
|
27
|
+
fixed_layout.each do |map|
|
42
28
|
# A nil value is considered an empty string
|
43
29
|
value = hash[map.key].to_s
|
44
|
-
result <<
|
45
|
-
if encoding
|
46
|
-
format("%-#{map.size}.#{map.size}s".encode(encoding), value.encode(encoding, encoding_options))
|
47
|
-
else
|
48
|
-
format("%-#{map.size}.#{map.size}s", value)
|
49
|
-
end
|
30
|
+
result << format("%-#{map.size}.#{map.size}s", value)
|
50
31
|
end
|
51
32
|
result
|
52
33
|
end
|
53
34
|
|
54
|
-
# Returns [Hash<Symbol, String>] fixed
|
35
|
+
# Returns [Hash<Symbol, String>] fixed layout values extracted from the supplied line.
|
55
36
|
# String will be encoded to `encoding`
|
56
37
|
def parse(line)
|
57
38
|
unless line.is_a?(String)
|
58
|
-
raise(
|
39
|
+
raise(IOStreams::Errors::TypeMismatch, "Format is :fixed. Invalid parse input: #{line.class.name}")
|
59
40
|
end
|
60
41
|
|
61
42
|
hash = {}
|
62
43
|
index = 0
|
63
|
-
|
44
|
+
fixed_layout.each do |map|
|
64
45
|
value = line[index..(index + map.size - 1)]
|
65
46
|
index += map.size
|
66
|
-
hash[map.key] =
|
47
|
+
hash[map.key] = value.to_s.strip
|
67
48
|
end
|
68
49
|
hash
|
69
50
|
end
|
70
51
|
|
71
52
|
private
|
72
53
|
|
73
|
-
|
54
|
+
FixedLayout = Struct.new(:key, :size)
|
74
55
|
|
75
|
-
# Returns [Array<
|
56
|
+
# Returns [Array<FixedLayout>] the layout for this fixed width file.
|
76
57
|
# Also validates values
|
77
|
-
def
|
78
|
-
|
58
|
+
def parse_layout(layout)
|
59
|
+
layout.collect do |map|
|
79
60
|
size = map[:size]
|
80
61
|
key = map[:key]
|
81
62
|
raise(ArgumentError, "Missing required :key and :size in: #{map.inspect}") unless size && key
|
82
|
-
|
63
|
+
FixedLayout.new(key, size)
|
83
64
|
end
|
84
65
|
end
|
85
66
|
end
|
@@ -4,7 +4,7 @@ module IOStreams
|
|
4
4
|
module Parser
|
5
5
|
class Hash < Base
|
6
6
|
def parse(row)
|
7
|
-
raise(
|
7
|
+
raise(IOStreams::Errors::TypeMismatch, "Format is :hash. Invalid input: #{row.class.name}") unless row.is_a?(::Hash)
|
8
8
|
row
|
9
9
|
end
|
10
10
|
|
@@ -5,7 +5,9 @@ module IOStreams
|
|
5
5
|
# For parsing a single line of JSON at a time
|
6
6
|
class Json < Base
|
7
7
|
def parse(row)
|
8
|
-
|
8
|
+
return row if row.is_a?(::Hash)
|
9
|
+
|
10
|
+
raise(IOStreams::Errors::TypeMismatch, "Format is :json. Invalid input: #{row.class.name}") unless row.is_a?(String)
|
9
11
|
|
10
12
|
JSON.parse(row)
|
11
13
|
end
|
@@ -6,8 +6,10 @@ module IOStreams
|
|
6
6
|
# Returns [Array<String>] the header row.
|
7
7
|
# Returns nil if the row is blank.
|
8
8
|
def parse_header(row)
|
9
|
+
return row if row.is_a?(::Array)
|
10
|
+
|
9
11
|
unless row.is_a?(String)
|
10
|
-
raise(
|
12
|
+
raise(IOStreams::Errors::InvalidHeader, "Format is :psv. Invalid input header: #{row.class.name}")
|
11
13
|
end
|
12
14
|
|
13
15
|
row.split('|')
|
@@ -15,7 +17,9 @@ module IOStreams
|
|
15
17
|
|
16
18
|
# Returns [Array] the parsed PSV line
|
17
19
|
def parse(row)
|
18
|
-
|
20
|
+
return row if row.is_a?(::Array)
|
21
|
+
|
22
|
+
raise(IOStreams::Errors::TypeMismatch, "Format is :psv. Invalid input: #{row.class.name}") unless row.is_a?(String)
|
19
23
|
|
20
24
|
row.split('|')
|
21
25
|
end
|
data/lib/io_streams/version.rb
CHANGED
@@ -3,50 +3,40 @@ require 'csv'
|
|
3
3
|
module IOStreams
|
4
4
|
module Xlsx
|
5
5
|
class Reader
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
#
|
10
|
-
# Example:
|
11
|
-
# IOStreams::Xlsx::Reader.open('spreadsheet.xlsx') do |spreadsheet_stream|
|
12
|
-
# spreadsheet_stream.each_line do |line|
|
13
|
-
# puts line
|
14
|
-
# end
|
15
|
-
# end
|
16
|
-
def self.open(file_name_or_io, buffer_size: 65536, &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
|
-
|
23
|
-
if IOStreams.reader_stream?(file_name_or_io)
|
24
|
-
temp_file = Tempfile.new('rocket_job_xlsx')
|
25
|
-
file_name = temp_file.to_path
|
26
|
-
|
27
|
-
::File.open(file_name, 'wb') do |file|
|
28
|
-
IOStreams.copy(file_name_or_io, file, buffer_size: buffer_size)
|
29
|
-
end
|
30
|
-
else
|
6
|
+
# Convert a xlsx, or xlsm file or stream into CSV format.
|
7
|
+
def self.open(file_name_or_io, _ = nil)
|
8
|
+
if file_name_or_io.is_a?(String)
|
31
9
|
file_name = file_name_or_io
|
10
|
+
else
|
11
|
+
temp_file = Tempfile.new('iostreams_xlsx')
|
12
|
+
IOStreams.copy(file_name_or_io, temp_file)
|
13
|
+
file_name = temp_file.to_path
|
32
14
|
end
|
33
15
|
|
34
|
-
|
16
|
+
csv_temp_file = Tempfile.new('iostreams_csv')
|
17
|
+
new(file_name).each { |lines| csv_temp_file << lines.to_csv }
|
18
|
+
csv_temp_file.rewind
|
19
|
+
yield csv_temp_file
|
35
20
|
ensure
|
36
21
|
temp_file.delete if temp_file
|
22
|
+
csv_temp_file.delete if csv_temp_file
|
37
23
|
end
|
38
24
|
|
39
|
-
def initialize(
|
25
|
+
def initialize(file_name)
|
26
|
+
begin
|
27
|
+
require 'creek' unless defined?(Creek::Book)
|
28
|
+
rescue LoadError => e
|
29
|
+
raise(LoadError, "Please install the 'creek' gem for xlsx streaming support. #{e.message}")
|
30
|
+
end
|
31
|
+
|
32
|
+
workbook = Creek::Book.new(file_name, check_file_extension: false)
|
40
33
|
@worksheet = workbook.sheets[0]
|
41
34
|
end
|
42
35
|
|
43
36
|
# Returns each [Array] row from the spreadsheet
|
44
|
-
def each
|
45
|
-
worksheet.rows.each { |row|
|
37
|
+
def each
|
38
|
+
@worksheet.rows.each { |row| yield row.values }
|
46
39
|
end
|
47
|
-
|
48
|
-
alias_method :each_line, :each
|
49
|
-
|
50
40
|
end
|
51
41
|
end
|
52
42
|
end
|