iostreams 1.0.0.beta7 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -37,15 +37,15 @@ module IOStreams
37
37
  # # => "files/file.xls"
38
38
  #
39
39
  # For Files
40
- # IOStreams.path('blah.zip').option(:encode, encoding: 'BINARY').each_line { |line puts line }
41
- # IOStreams.path('blah.zip').option(:encode, encoding: 'UTF-8').each_line.first
42
- # IOStreams.path('blah.zip').option(:encode, encoding: 'UTF-8').each_record.last
43
- # IOStreams.path('blah.zip').option(:encode, encoding: 'UTF-8').each_record.size
40
+ # IOStreams.path('blah.zip').option(:encode, encoding: 'BINARY').each(:line) { |line| puts line }
41
+ # IOStreams.path('blah.zip').option(:encode, encoding: 'UTF-8').each(:line).first
42
+ # IOStreams.path('blah.zip').option(:encode, encoding: 'UTF-8').each(:hash).last
43
+ # IOStreams.path('blah.zip').option(:encode, encoding: 'UTF-8').each(:hash).size
44
44
  # IOStreams.path('blah.zip').option(:encode, encoding: 'UTF-8').reader.size
45
- # IOStreams.path('blah.csv.zip').each_line { |line puts line }
46
- # IOStreams.path('blah.zip').option(:pgp, passphrase: 'receiver_passphrase').reader(&:read)
47
- # IOStreams.path('blah.zip').stream(:zip).stream(:pgp, passphrase: 'receiver_passphrase').reader(&:read)
48
- # IOStreams.path('blah.zip').stream(:zip).stream(:encode, encoding: 'BINARY').reader(&:read)
45
+ # IOStreams.path('blah.csv.zip').each(:line) { |line| puts line }
46
+ # IOStreams.path('blah.zip').option(:pgp, passphrase: 'receiver_passphrase').read
47
+ # IOStreams.path('blah.zip').stream(:zip).stream(:pgp, passphrase: 'receiver_passphrase').read
48
+ # IOStreams.path('blah.zip').stream(:zip).stream(:encode, encoding: 'BINARY').read
49
49
  #
50
50
  def self.path(*elements, **args)
51
51
  return elements.first if (elements.size == 1) && args.empty? && elements.first.is_a?(IOStreams::Path)
@@ -59,9 +59,9 @@ module IOStreams
59
59
 
60
60
  # For an existing IO Stream
61
61
  # IOStreams.stream(io).file_name('blah.zip').encoding('BINARY').reader(&:read)
62
- # IOStreams.stream(io).file_name('blah.zip').encoding('BINARY').each_line(...)
63
- # IOStreams.stream(io).file_name('blah.csv.zip').each_line(...)
64
- # IOStreams.stream(io).stream(:zip).stream(:pgp, passphrase: 'receiver_passphrase').reader(&:read)
62
+ # IOStreams.stream(io).file_name('blah.zip').encoding('BINARY').each(:line){ ... }
63
+ # IOStreams.stream(io).file_name('blah.csv.zip').each(:line) { ... }
64
+ # IOStreams.stream(io).stream(:zip).stream(:pgp, passphrase: 'receiver_passphrase').read
65
65
  def self.stream(io_stream)
66
66
  return io_stream if io_stream.is_a?(Stream)
67
67
 
@@ -32,7 +32,7 @@ module IOStreams
32
32
  # Write a line to the output stream
33
33
  #
34
34
  # Example:
35
- # IOStreams.line_writer('a.txt') do |stream|
35
+ # IOStreams.path('a.txt').writer(:line) do |stream|
36
36
  # stream << 'first line' << 'second line'
37
37
  # end
38
38
  def <<(data)
@@ -44,7 +44,7 @@ module IOStreams
44
44
  # Returns [Integer] the number of bytes written.
45
45
  #
46
46
  # Example:
47
- # IOStreams.line_writer('a.txt') do |stream|
47
+ # IOStreams.path('a.txt').writer(:line) do |stream|
48
48
  # count = stream.write('first line')
49
49
  # puts "Wrote #{count} bytes to the output file, including the delimiter"
50
50
  # end
@@ -8,7 +8,7 @@ module IOStreams
8
8
 
9
9
  @path = path.frozen? ? path : path.dup.freeze
10
10
  @io_stream = nil
11
- @streams = nil
11
+ @builder = nil
12
12
  end
13
13
 
14
14
  # If elements already contains the current path then it is used as is without
@@ -20,7 +20,7 @@ module IOStreams
20
20
  relative = ::File.join(*elements)
21
21
 
22
22
  new_path = dup
23
- new_path.streams = nil
23
+ new_path.builder = nil
24
24
  new_path.path = relative.start_with?(path) ? relative : ::File.join(path, relative)
25
25
  new_path
26
26
  end
@@ -117,7 +117,7 @@ module IOStreams
117
117
  # IOStreams.path(".profile").directory #=> "."
118
118
  def directory
119
119
  new_path = dup
120
- new_path.streams = nil
120
+ new_path.builder = nil
121
121
  new_path.path = ::File.dirname(path)
122
122
  new_path
123
123
  end
@@ -188,15 +188,15 @@ module IOStreams
188
188
 
189
189
  def inspect
190
190
  str = "#<#{self.class.name}:#{path}"
191
- str << " @streams=#{streams.streams.inspect}" if streams.streams
192
- str << " @options=#{streams.options.inspect}" if streams.options
191
+ str << " @builder=#{builder.streams.inspect}" if builder.streams
192
+ str << " @options=#{builder.options.inspect}" if builder.options
193
193
  str << " pipeline=#{pipeline.inspect}>"
194
194
  end
195
195
 
196
196
  private
197
197
 
198
- def streams
199
- @streams ||= IOStreams::Streams.new(path)
198
+ def builder
199
+ @builder ||= IOStreams::Builder.new(path)
200
200
  end
201
201
  end
202
202
  end
@@ -3,6 +3,13 @@ require "fileutils"
3
3
  module IOStreams
4
4
  module Paths
5
5
  class File < IOStreams::Path
6
+ attr_accessor :create_path
7
+
8
+ def initialize(file_name, create_path: true)
9
+ @create_path = create_path
10
+ super(file_name)
11
+ end
12
+
6
13
  # Yields Paths within the current path.
7
14
  #
8
15
  # Examples:
@@ -146,9 +153,11 @@ module IOStreams
146
153
  self.class.new(::File.realpath(path))
147
154
  end
148
155
 
156
+ private
157
+
149
158
  # Read from file
150
- def reader(&block)
151
- ::File.open(path, "rb") { |io| streams.reader(io, &block) }
159
+ def stream_reader(&block)
160
+ ::File.open(path, "rb") { |io| builder.reader(io, &block) }
152
161
  end
153
162
 
154
163
  # Write to file
@@ -156,10 +165,10 @@ module IOStreams
156
165
  # Note:
157
166
  # If an exception is raised whilst the file is being written to the file is removed to
158
167
  # prevent incomplete / partial files from being created.
159
- def writer(create_path: true, &block)
168
+ def stream_writer(&block)
160
169
  mkpath if create_path
161
170
  begin
162
- ::File.open(path, "wb") { |io| streams.writer(io, &block) }
171
+ ::File.open(path, "wb") { |io| builder.writer(io, &block) }
163
172
  rescue StandardError => e
164
173
  ::File.unlink(path) if ::File.exist?(path)
165
174
  raise(e)
@@ -47,6 +47,8 @@ module IOStreams
47
47
  url
48
48
  end
49
49
 
50
+ private
51
+
50
52
  # Read a file using an http get.
51
53
  #
52
54
  # For example:
@@ -57,7 +59,7 @@ module IOStreams
57
59
  #
58
60
  # Notes:
59
61
  # * Since Net::HTTP download only supports a push stream, the data is streamed into a tempfile first.
60
- def reader(&block)
62
+ def stream_reader(&block)
61
63
  handle_redirects(url, http_redirect_count, &block)
62
64
  end
63
65
 
@@ -91,7 +93,7 @@ module IOStreams
91
93
  Utils.temp_file_name('iostreams_http') do |file_name|
92
94
  ::File.open(file_name, 'wb') { |io| response.read_body { |chunk| io.write(chunk) } }
93
95
  # Return a read stream
94
- result = ::File.open(file_name, "rb") { |io| streams.reader(io, &block) }
96
+ result = ::File.open(file_name, "rb") { |io| builder.reader(io, &block) }
95
97
  end
96
98
  end
97
99
  end
@@ -208,12 +208,12 @@ module IOStreams
208
208
  # TODO: delete_all
209
209
 
210
210
  # Read from AWS S3 file.
211
- def reader(&block)
211
+ def stream_reader(&block)
212
212
  # Since S3 download only supports a push stream, write it to a tempfile first.
213
213
  Utils.temp_file_name("iostreams_s3") do |file_name|
214
214
  read_file(file_name)
215
215
 
216
- ::File.open(file_name, "rb") { |io| streams.reader(io, &block) }
216
+ ::File.open(file_name, "rb") { |io| builder.reader(io, &block) }
217
217
  end
218
218
  end
219
219
 
@@ -231,10 +231,10 @@ module IOStreams
231
231
  # aborted and this error is raised. The raised error has a `#errors`
232
232
  # method that returns the failures that caused the upload to be
233
233
  # aborted.
234
- def writer(&block)
234
+ def stream_writer(&block)
235
235
  # Since S3 upload only supports a pull stream, write it to a tempfile first.
236
236
  Utils.temp_file_name("iostreams_s3") do |file_name|
237
- result = ::File.open(file_name, "wb") { |io| streams.writer(io, &block) }
237
+ result = ::File.open(file_name, "wb") { |io| builder.writer(io, &block) }
238
238
 
239
239
  # Upload file only once all data has been written to it
240
240
  write_file(file_name)
@@ -2,6 +2,26 @@ require 'open3'
2
2
 
3
3
  module IOStreams
4
4
  module Paths
5
+ # Read a file from a remote sftp server.
6
+ #
7
+ # Example:
8
+ # IOStreams.
9
+ # path("sftp://example.org/path/file.txt", username: "jbloggs", password: "secret", compression: false).
10
+ # reader do |input|
11
+ # puts input.read
12
+ # end
13
+ #
14
+ # Note:
15
+ # - raises Net::SFTP::StatusException when the file could not be read.
16
+ #
17
+ # Write to a file on a remote sftp server.
18
+ #
19
+ # Example:
20
+ # IOStreams.
21
+ # path("sftp://example.org/path/file.txt", username: "jbloggs", password: "secret", compression: false).
22
+ # writer do |output|
23
+ # output.write('Hello World')
24
+ # end
5
25
  class SFTP < IOStreams::Path
6
26
  include SemanticLogger::Loggable if defined?(SemanticLogger)
7
27
 
@@ -54,7 +74,7 @@ module IOStreams
54
74
  # IOStreams.path("sftp://test.com/path/file_name.csv", username: "jack", IdentityFile: "~/.ssh/private_key").reader do |io|
55
75
  # puts io.read
56
76
  # end
57
- def initialize(url, username: nil, password: nil, ruby: true, ssh_options: {})
77
+ def initialize(url, username: nil, password: nil, ssh_options: {})
58
78
  uri = Utils::URI.new(url)
59
79
  raise(ArgumentError, "Invalid URL. Required Format: 'sftp://<host_name>/<file_name>'") unless uri.scheme == 'sftp'
60
80
 
@@ -87,40 +107,6 @@ module IOStreams
87
107
  self
88
108
  end
89
109
 
90
- # Read a file from a remote sftp server.
91
- #
92
- # Example:
93
- # IOStreams.
94
- # path("sftp://example.org/path/file.txt", username: "jbloggs", password: "secret", compression: false).
95
- # reader do |input|
96
- # puts input.read
97
- # end
98
- #
99
- # Note:
100
- # - raises Net::SFTP::StatusException when the file could not be read.
101
- def reader(&block)
102
- IOStreams.temp_file("iostreams-sftp-reader") do |temp_file|
103
- sftp_download(path, temp_file.to_s)
104
- ::File.open(temp_file.to_s, "rb") { |io| streams.reader(io, &block) }
105
- end
106
- end
107
-
108
- # Write to a file on a remote sftp server.
109
- #
110
- # Example:
111
- # IOStreams.
112
- # path("sftp://example.org/path/file.txt", username: "jbloggs", password: "secret", compression: false).
113
- # writer do |output|
114
- # output.write('Hello World')
115
- # end
116
- def writer(&block)
117
- IOStreams.temp_file("iostreams-sftp-writer") do |temp_file|
118
- ::File.open(temp_file.to_s, "wb") { |io| streams.writer(io, &block) }
119
- sftp_upload(temp_file.to_s, path)
120
- temp_file.size
121
- end
122
- end
123
-
124
110
  # TODO: Add #copy_from shortcut to detect when a file is supplied that does not require conversion.
125
111
 
126
112
  # Search for files on the remote sftp server that match the provided pattern.
@@ -147,7 +133,7 @@ module IOStreams
147
133
  Net::SFTP.start(hostname, username, build_ssh_options) do |sftp|
148
134
  sftp.dir.glob(".", pattern, flags) do |path|
149
135
  next if !directories && !path.file?
150
- new_path = self.class.new("sftp://#{hostname}/#{path.name}", username: username, password: password, ruby: ruby, **ssh_options)
136
+ new_path = self.class.new("sftp://#{hostname}/#{path.name}", username: username, password: password, **ssh_options)
151
137
  yield(new_path, path.attributes.attributes)
152
138
  end
153
139
  end
@@ -158,6 +144,21 @@ module IOStreams
158
144
 
159
145
  attr_reader :password
160
146
 
147
+ def stream_reader(&block)
148
+ IOStreams.temp_file("iostreams-sftp-reader") do |temp_file|
149
+ sftp_download(path, temp_file.to_s)
150
+ ::File.open(temp_file.to_s, "rb") { |io| builder.reader(io, &block) }
151
+ end
152
+ end
153
+
154
+ def stream_writer(&block)
155
+ IOStreams.temp_file("iostreams-sftp-writer") do |temp_file|
156
+ ::File.open(temp_file.to_s, "wb") { |io| builder.writer(io, &block) }
157
+ sftp_upload(temp_file.to_s, path)
158
+ temp_file.size
159
+ end
160
+ end
161
+
161
162
  # Use sftp and sshpass executables to download to a local file
162
163
  def sftp_download(remote_file_name, local_file_name)
163
164
  with_sftp_args do |args|
@@ -21,7 +21,9 @@ module IOStreams
21
21
  #
22
22
  # passphrase: [String]
23
23
  # Pass phrase for private key to decrypt the file with
24
- def self.file(file_name, passphrase: self.default_passphrase)
24
+ def self.file(file_name, passphrase: nil)
25
+ # Cannot use `passphrase: self.default_passphrase` since it is considered private
26
+ passphrase ||= default_passphrase
25
27
  raise(ArgumentError, 'Missing both passphrase and IOStreams::Pgp::Reader.default_passphrase') unless passphrase
26
28
 
27
29
  loopback = IOStreams::Pgp.pgp_version.to_f >= 2.1 ? '--pinentry-mode loopback' : ''
@@ -1,7 +1,7 @@
1
1
  module IOStreams
2
2
  module Record
3
3
  # Example, implied header from first record:
4
- # IOStreams.path('file.csv').record_writer do |stream|
4
+ # IOStreams.path('file.csv').writer(:hash) do |stream|
5
5
  # stream << {name: 'Jack', address: 'Somewhere', zipcode: 12345}
6
6
  # stream << {name: 'Joe', address: 'Lost', zipcode: 32443, age: 23}
7
7
  # end
@@ -2,7 +2,7 @@ require 'csv'
2
2
  module IOStreams
3
3
  module Row
4
4
  # Example:
5
- # IOStreams.path("file.csv").row_writer do |stream|
5
+ # IOStreams.path("file.csv").writer(:array) do |stream|
6
6
  # stream << ['name', 'address', 'zipcode']
7
7
  # stream << ['Jack', 'Somewhere', 12345]
8
8
  # stream << ['Joe', 'Lost', 32443]
@@ -1,14 +1,14 @@
1
1
  module IOStreams
2
2
  class Stream
3
3
  attr_reader :io_stream
4
- attr_writer :streams
4
+ attr_writer :builder
5
5
 
6
6
  def initialize(io_stream)
7
7
  raise(ArgumentError, 'io_stream cannot be nil') if io_stream.nil?
8
8
  raise(ArgumentError, "io_stream must not be a string: #{io_stream.inspect}") if io_stream.is_a?(String)
9
9
 
10
10
  @io_stream = io_stream
11
- @streams = nil
11
+ @builder = nil
12
12
  end
13
13
 
14
14
  # Ignore the filename and use only the supplied streams.
@@ -19,7 +19,7 @@ module IOStreams
19
19
  #
20
20
  # IOStreams.path('tempfile2527').stream(:zip).stream(:pgp, passphrase: 'receiver_passphrase').reader(&:read)
21
21
  def stream(stream, **options)
22
- streams.stream(stream, **options)
22
+ builder.stream(stream, **options)
23
23
  self
24
24
  end
25
25
 
@@ -34,32 +34,76 @@ module IOStreams
34
34
  #
35
35
  # IOStreams.path(output_file_name).option(:pgp, passphrase: 'receiver_passphrase').reader(&:read)
36
36
  def option(stream, **options)
37
- streams.option(stream, **options)
37
+ builder.option(stream, **options)
38
38
  self
39
39
  end
40
40
 
41
41
  # Adds the options for the specified stream as an option,
42
42
  # but if streams have already been added it is instead added as a stream.
43
43
  def option_or_stream(stream, **options)
44
- streams.option_or_stream(stream, **options)
44
+ builder.option_or_stream(stream, **options)
45
45
  self
46
46
  end
47
47
 
48
48
  # Return the options already set for either a stream or option.
49
49
  def setting(stream)
50
- streams.setting(stream)
50
+ builder.setting(stream)
51
51
  self
52
52
  end
53
53
 
54
54
  # Returns [Hash<Symbol:Hash>] the pipeline of streams
55
55
  # with their options that will be applied when the reader or writer is invoked.
56
56
  def pipeline
57
- streams.pipeline
57
+ builder.pipeline
58
+ end
59
+
60
+ # Iterate over a file / stream returning one line at a time.
61
+ #
62
+ # Example: Read a line at a time
63
+ # IOStreams.path("file.txt").each(:line) do |line|
64
+ # puts line
65
+ # end
66
+ #
67
+ # Example: Read a line at a time with custom options
68
+ # IOStreams.path("file.csv").each(:line, embedded_within: '"') do |line|
69
+ # puts line
70
+ # end
71
+ #
72
+ # Example: Read a row at a time
73
+ # IOStreams.path("file.csv").each(:array) do |array|
74
+ # p array
75
+ # end
76
+ #
77
+ # Example: Read a record at a time
78
+ # IOStreams.path("file.csv").each(:hash) do |hash|
79
+ # p hash
80
+ # end
81
+ #
82
+ # Notes:
83
+ # - Embedded lines (within double quotes) will be skipped if
84
+ # 1. The file name contains .csv
85
+ # 2. Or the embedded_within argument is set
86
+ def each(mode = :line, **args, &block)
87
+ raise(ArgumentError, "Invalid mode: #{mode.inspect}") if mode == :stream
88
+
89
+ # return enum_for __method__ unless block_given?
90
+ reader(mode, **args) { |stream| stream.each(&block) }
58
91
  end
59
92
 
60
93
  # Returns a Reader for reading a file / stream
61
- def reader(&block)
62
- streams.reader(io_stream, &block)
94
+ def reader(mode = :stream, **args, &block)
95
+ case mode
96
+ when :stream
97
+ stream_reader(&block)
98
+ when :line
99
+ line_reader(**args, &block)
100
+ when :array
101
+ row_reader(**args, &block)
102
+ when :hash
103
+ record_reader(**args, &block)
104
+ else
105
+ raise(ArgumentError, "Invalid mode: #{mode.inspect}")
106
+ end
63
107
  end
64
108
 
65
109
  # Read an entire file into memory.
@@ -67,12 +111,37 @@ module IOStreams
67
111
  # Notes:
68
112
  # - Use with caution since large files can cause a denial of service since
69
113
  # this method will load the entire file into memory.
70
- # - Recommend using instead `#reader`, `#each_line`, or `#each_record` to read a
71
- # block into memory at a time.
114
+ # - Recommend using instead `#reader` to read a block into memory at a time.
72
115
  def read(*args)
73
116
  reader { |stream| stream.read(*args) }
74
117
  end
75
118
 
119
+ # Returns a Writer for writing to a file / stream
120
+ def writer(mode = :stream, **args, &block)
121
+ case mode
122
+ when :stream
123
+ stream_writer(&block)
124
+ when :line
125
+ line_writer(**args, &block)
126
+ when :array
127
+ row_writer(**args, &block)
128
+ when :hash
129
+ record_writer(**args, &block)
130
+ else
131
+ raise(ArgumentError, "Invalid mode: #{mode.inspect}")
132
+ end
133
+ end
134
+
135
+ # Write entire string to file.
136
+ #
137
+ # Notes:
138
+ # - Use with caution since preparing large amounts of data in memory can cause a denial of service
139
+ # since all the data for the file needs to be resident in memory before writing.
140
+ # - Recommend using instead `#writer` to write a block of memory at a time.
141
+ def write(data)
142
+ writer { |stream| stream.write(data) }
143
+ end
144
+
76
145
  # Copy from another stream, path, file_name or IO instance.
77
146
  #
78
147
  # Parameters:
@@ -113,109 +182,19 @@ module IOStreams
113
182
  target.copy_from(self, convert: convert)
114
183
  end
115
184
 
116
- # Iterate over a file / stream returning one line at a time.
117
- # Embedded lines (within double quotes) will be skipped if
118
- # 1. The file name contains .csv
119
- # 2. Or the embedded_within argument is set
120
- #
121
- # Example: Supply custom options
122
- # IOStreams.each_line(file_name, embedded_within: '"') do |line|
123
- # puts line
124
- # end
125
- #
126
- def each_line(**args, &block)
127
- # return enum_for __method__ unless block_given?
128
- line_reader(**args) { |line_stream| line_stream.each(&block) }
129
- end
130
-
131
- # Iterate over a file / stream returning one line at a time.
132
- # Embedded lines (within double quotes) will be skipped if
133
- # 1. The file name contains .csv
134
- # 2. Or the embedded_within argument is set
135
- #
136
- # Example: Supply custom options
137
- # IOStreams.each_row(file_name, embedded_within: '"') do |line|
138
- # puts line
139
- # end
140
- #
141
- def each_row(**args, &block)
142
- row_reader(**args) { |row_stream| row_stream.each(&block) }
143
- end
144
-
145
- # Returns [Hash] of every record in a file or stream with support for headers.
146
- def each_record(**args, &block)
147
- record_reader(**args) { |record_stream| record_stream.each(&block) }
148
- end
149
-
150
- # Iterate over a file / stream returning each record/line one at a time.
151
- # It will apply the embedded_within argument if the file or input_stream contain .csv in its name.
152
- def line_reader(embedded_within: nil, **args)
153
- embedded_within = '"' if embedded_within.nil? && streams.file_name&.include?('.csv')
154
-
155
- reader { |io| yield IOStreams::Line::Reader.new(io, embedded_within: embedded_within, **args) }
156
- end
157
-
158
- # Iterate over a file / stream returning each line as an array, one at a time.
159
- def row_reader(delimiter: nil, embedded_within: nil, **args)
160
- line_reader(delimiter: delimiter, embedded_within: embedded_within) do |io|
161
- yield IOStreams::Row::Reader.new(io, **args)
162
- end
163
- end
164
-
165
- # Iterate over a file / stream returning each line as a hash, one at a time.
166
- def record_reader(delimiter: nil, embedded_within: nil, **args)
167
- line_reader(delimiter: delimiter, embedded_within: embedded_within) do |io|
168
- yield IOStreams::Record::Reader.new(io, **args)
169
- end
170
- end
171
-
172
- # Returns a Writer for writing to a file / stream
173
- def writer(&block)
174
- streams.writer(io_stream, &block)
175
- end
176
-
177
- # Write entire string to file.
178
- #
179
- # Notes:
180
- # - Use with caution since preparing large amounts of data in memory can cause a denial of service
181
- # since all the data for the file needs to be resident in memory before writing.
182
- # - Recommend using instead `#writer`, `#line_writer`, or `#row_writer` to write a
183
- # block of memory at a time.
184
- def write(data)
185
- writer { |stream| stream.write(data) }
186
- end
187
-
188
- def line_writer(**args, &block)
189
- return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Line::Writer)
190
-
191
- writer { |io| IOStreams::Line::Writer.stream(io, **args, &block) }
192
- end
193
-
194
- def row_writer(delimiter: $/, **args, &block)
195
- return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Row::Writer)
196
-
197
- line_writer(delimiter: delimiter) { |io| IOStreams::Row::Writer.stream(io, **args, &block) }
198
- end
199
-
200
- def record_writer(delimiter: $/, **args, &block)
201
- return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Record::Writer)
202
-
203
- line_writer(delimiter: delimiter) { |io| IOStreams::Record::Writer.stream(io, **args, &block) }
204
- end
205
-
206
185
  # Set/get the original file_name
207
186
  def file_name(file_name = :none)
208
187
  if file_name == :none
209
- streams.file_name
188
+ builder.file_name
210
189
  else
211
- streams.file_name = file_name
190
+ builder.file_name = file_name
212
191
  self
213
192
  end
214
193
  end
215
194
 
216
195
  # Set/get the original file_name
217
196
  def file_name=(file_name)
218
- streams.file_name = file_name
197
+ builder.file_name = file_name
219
198
  end
220
199
 
221
200
  # Returns [String] the last component of this path.
@@ -230,7 +209,7 @@ module IOStreams
230
209
  # IOStreams.path("/home/gumby/work/ruby.rb").basename(".rb") #=> "ruby"
231
210
  # IOStreams.path("/home/gumby/work/ruby.rb").basename(".*") #=> "ruby"
232
211
  def basename(suffix = nil)
233
- file_name = streams.file_name
212
+ file_name = builder.file_name
234
213
  return unless file_name
235
214
 
236
215
  suffix.nil? ? ::File.basename(file_name) : ::File.basename(file_name, suffix)
@@ -248,7 +227,7 @@ module IOStreams
248
227
  # IOStreams.path("test").dirname #=> "."
249
228
  # IOStreams.path(".profile").dirname #=> "."
250
229
  def dirname
251
- file_name = streams.file_name
230
+ file_name = builder.file_name
252
231
  ::File.dirname(file_name) if file_name
253
232
  end
254
233
 
@@ -268,7 +247,7 @@ module IOStreams
268
247
  # IOStreams.path(".profile").extname #=> ""
269
248
  # IOStreams.path(".profile.sh").extname #=> ".sh"
270
249
  def extname
271
- file_name = streams.file_name
250
+ file_name = builder.file_name
272
251
  ::File.extname(file_name) if file_name
273
252
  end
274
253
 
@@ -293,8 +272,54 @@ module IOStreams
293
272
 
294
273
  private
295
274
 
296
- def streams
297
- @streams ||= IOStreams::Streams.new
275
+ def builder
276
+ @builder ||= IOStreams::Builder.new
277
+ end
278
+
279
+ def stream_reader(&block)
280
+ builder.reader(io_stream, &block)
281
+ end
282
+
283
+ def line_reader(embedded_within: nil, **args)
284
+ embedded_within = '"' if embedded_within.nil? && builder.file_name&.include?('.csv')
285
+
286
+ stream_reader { |io| yield IOStreams::Line::Reader.new(io, embedded_within: embedded_within, **args) }
287
+ end
288
+
289
+ # Iterate over a file / stream returning each line as an array, one at a time.
290
+ def row_reader(delimiter: nil, embedded_within: nil, **args)
291
+ line_reader(delimiter: delimiter, embedded_within: embedded_within) do |io|
292
+ yield IOStreams::Row::Reader.new(io, **args)
293
+ end
294
+ end
295
+
296
+ # Iterate over a file / stream returning each line as a hash, one at a time.
297
+ def record_reader(delimiter: nil, embedded_within: nil, **args)
298
+ line_reader(delimiter: delimiter, embedded_within: embedded_within) do |io|
299
+ yield IOStreams::Record::Reader.new(io, **args)
300
+ end
301
+ end
302
+
303
+ def stream_writer(&block)
304
+ builder.writer(io_stream, &block)
305
+ end
306
+
307
+ def line_writer(**args, &block)
308
+ return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Line::Writer)
309
+
310
+ writer { |io| IOStreams::Line::Writer.stream(io, **args, &block) }
311
+ end
312
+
313
+ def row_writer(delimiter: $/, **args, &block)
314
+ return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Row::Writer)
315
+
316
+ line_writer(delimiter: delimiter) { |io| IOStreams::Row::Writer.stream(io, **args, &block) }
317
+ end
318
+
319
+ def record_writer(delimiter: $/, **args, &block)
320
+ return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Record::Writer)
321
+
322
+ line_writer(delimiter: delimiter) { |io| IOStreams::Record::Writer.stream(io, **args, &block) }
298
323
  end
299
324
  end
300
325
  end