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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/io_streams/bzip2/reader.rb +1 -1
  3. data/lib/io_streams/bzip2/writer.rb +1 -1
  4. data/lib/io_streams/encode/reader.rb +102 -0
  5. data/lib/io_streams/encode/writer.rb +78 -0
  6. data/lib/io_streams/errors.rb +19 -0
  7. data/lib/io_streams/file/reader.rb +1 -1
  8. data/lib/io_streams/file/writer.rb +1 -3
  9. data/lib/io_streams/gzip/reader.rb +1 -1
  10. data/lib/io_streams/gzip/writer.rb +1 -1
  11. data/lib/io_streams/io_streams.rb +57 -38
  12. data/lib/io_streams/line/reader.rb +125 -69
  13. data/lib/io_streams/line/writer.rb +11 -35
  14. data/lib/io_streams/pgp.rb +1 -1
  15. data/lib/io_streams/record/reader.rb +12 -14
  16. data/lib/io_streams/record/writer.rb +12 -14
  17. data/lib/io_streams/row/reader.rb +15 -16
  18. data/lib/io_streams/row/writer.rb +14 -12
  19. data/lib/io_streams/tabular.rb +50 -30
  20. data/lib/io_streams/tabular/header.rb +6 -6
  21. data/lib/io_streams/tabular/parser/array.rb +2 -2
  22. data/lib/io_streams/tabular/parser/csv.rb +6 -2
  23. data/lib/io_streams/tabular/parser/fixed.rb +18 -37
  24. data/lib/io_streams/tabular/parser/hash.rb +1 -1
  25. data/lib/io_streams/tabular/parser/json.rb +3 -1
  26. data/lib/io_streams/tabular/parser/psv.rb +6 -2
  27. data/lib/io_streams/version.rb +1 -1
  28. data/lib/io_streams/xlsx/reader.rb +22 -32
  29. data/lib/iostreams.rb +6 -0
  30. data/test/encode_reader_test.rb +54 -0
  31. data/test/encode_writer_test.rb +82 -0
  32. data/test/io_streams_test.rb +0 -65
  33. data/test/line_reader_test.rb +180 -37
  34. data/test/tabular_test.rb +79 -3
  35. data/test/test_helper.rb +1 -1
  36. data/test/xlsx_reader_test.rb +7 -10
  37. metadata +10 -4
  38. 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: UTF8_ENCODING, strip_non_printable: false, **args)
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: delimiter,
20
- encoding: encoding,
21
- strip_non_printable: strip_non_printable) do |io|
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.render(columns) if columns && @tabular.requires_header?
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
- # If header (columns) was not supplied as an argument, assume first line is the header.
53
- tabular.header.columns = array if tabular.requires_header?
54
- delimited << tabular.render(array)
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
@@ -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 row needs to be read first.
69
- def requires_header?
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 `parse_header?` first to determine if the header should be parsed first.
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 file extension and the reader and writer classes to use to format it
129
+ # Register a format and the parser class for it.
114
130
  #
115
131
  # Example:
116
- # # MyXls::Reader and MyXls::Writer must implement .open
117
- # register_extension(:xls, MyXls::Reader, MyXls::Writer)
118
- def self.register_extension(extension, parser)
119
- raise(ArgumentError, "Invalid extension #{extension.inspect}") unless extension.nil? || extension.to_s =~ /\A\w+\Z/
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 extension
138
+ # De-Register a file format
124
139
  #
125
- # Returns [Symbol] the extension removed, or nil if the extension was not registered
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.deregister_extension(extension)
130
- raise(ArgumentError, "Invalid extension #{extension.inspect}") unless extension.to_s =~ /\A\w+\Z/
131
- @extensions.delete(extension.to_sym)
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
- @extensions = {}
157
+ @formats = {}
138
158
 
139
159
  def self.parser_class(format)
140
- @extensions[format.nil? ? nil : format.to_sym] || raise(ArgumentError, "Unknown Tabular Format: #{format.inspect}")
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
- extension = nil
165
+ format = nil
146
166
  file_name.to_s.split('.').reverse_each do |ext|
147
- if @extensions.include?(ext.to_sym)
148
- extension = ext.to_sym
167
+ if @formats.include?(ext.to_sym)
168
+ format = ext.to_sym
149
169
  break
150
170
  end
151
171
  end
152
- parser_class(extension)
172
+ parser_class(format)
153
173
  end
154
174
 
155
- register_extension(nil, IOStreams::Tabular::Parser::Csv)
156
- register_extension(:array, IOStreams::Tabular::Parser::Array)
157
- register_extension(:csv, IOStreams::Tabular::Parser::Csv)
158
- register_extension(:fixed, IOStreams::Tabular::Parser::Fixed)
159
- register_extension(:hash, IOStreams::Tabular::Parser::Hash)
160
- register_extension(:json, IOStreams::Tabular::Parser::Json)
161
- register_extension(:psv, IOStreams::Tabular::Parser::Psv)
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::Tabular::Errors::InvalidHeader, "Unknown columns after cleansing: #{ignored_columns.join(',')}")
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::Tabular::Errors::InvalidHeader, "All columns are unknown after cleansing: #{ignored_columns.join(',')}")
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::Tabular::Errors::InvalidHeader, "Missing columns after cleansing: #{missing_columns.join(',')}")
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(Tabular::Errors::InvalidHeader, "Missing mandatory header when trying to convert a row into a hash") unless columns
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(Tabular::Errors::TypeMismatch, "Don't know how to convert #{row.class.name} to a Hash")
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(Tabular::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)
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(Tabular::Errors::InvalidHeader, "Format is :array. Invalid input header: #{row.class.name}") unless row.is_a?(::Array)
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(Tabular::Errors::TypeMismatch, "Format is :array. Invalid input: #{row.class.name}") unless row.is_a?(::Array)
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
- raise(Tabular::Errors::InvalidHeader, "Format is :csv. Invalid input header: #{row.class.name}") unless row.is_a?(String)
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
- raise(Tabular::Errors::TypeMismatch, "Format is :csv. Invalid input: #{row.class.name}") unless row.is_a?(String)
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 :encoding, :encoding_options, :fixed_format
6
+ attr_reader :fixed_layout
7
7
 
8
8
  # Returns [IOStreams::Tabular::Parser]
9
9
  #
10
- # Arguments:
11
- # format: [Array<Hash>]
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
- # encoding: [String|Encoding]
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 format values extracted from the supplied hash.
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 = encoding.nil? ? '' : ''.encode(encoding)
41
- fixed_format.each do |map|
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 format values extracted from the supplied line.
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(Tabular::Errors::TypeMismatch, "Format is :fixed. Invalid parse input: #{line.class.name}")
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
- fixed_format.each do |map|
44
+ fixed_layout.each do |map|
64
45
  value = line[index..(index + map.size - 1)]
65
46
  index += map.size
66
- hash[map.key] = encoding.nil? ? value.strip : value.strip.encode(encoding, encoding_options)
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
- FixedFormat = Struct.new(:key, :size)
54
+ FixedLayout = Struct.new(:key, :size)
74
55
 
75
- # Returns [Array<FixedFormat>] the format for this fixed width file.
56
+ # Returns [Array<FixedLayout>] the layout for this fixed width file.
76
57
  # Also validates values
77
- def parse_format(format)
78
- format.collect do |map|
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
- FixedFormat.new(key, size)
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(Tabular::Errors::TypeMismatch, "Format is :hash. Invalid input: #{row.class.name}") unless row.is_a?(::Hash)
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
- raise(Tabular::Errors::TypeMismatch, "Format is :json. Invalid input: #{row.class.name}") unless row.is_a?(String)
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(Tabular::Errors::InvalidHeader, "Format is :psv. Invalid input header: #{row.class.name}")
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
- raise(Tabular::Errors::TypeMismatch, "Format is :psv. Invalid input: #{row.class.name}") unless row.is_a?(String)
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
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = '0.15.0'
2
+ VERSION = '0.16.0'
3
3
  end
@@ -3,50 +3,40 @@ require 'csv'
3
3
  module IOStreams
4
4
  module Xlsx
5
5
  class Reader
6
- attr_reader :worksheet
7
-
8
- # Read from a xlsx, or xlsm file or stream.
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
- block.call(self.new(Creek::Book.new(file_name, check_file_extension: false)))
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(workbook)
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(&block)
45
- worksheet.rows.each { |row| block.call(row.values) }
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