iostreams 1.0.0.beta → 1.0.0.beta2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 158fef0e14167719cd27b7b1cb0445bfb08403a3afc4490fad74a0e0fc9ab67b
4
- data.tar.gz: b1f3db75a4773591507706befa66122204ffda1642136c79427671a4bf240605
3
+ metadata.gz: 4469de3d596bf7fd0485de78878ad8188c0a60c8a3b52cfbd2dcd207ec4e8e92
4
+ data.tar.gz: 25e2e16fa02f1d8b964cd8bdb521576a106148fd6446ebddf832735e5006d403
5
5
  SHA512:
6
- metadata.gz: 556a898a859c9447822cb0d3aeea58ce6d839536d43fe3ef46fdf94fb1794d50d596da713fd6eef4203ef84ed94790ad8d69581d54c6a167bc9cea57e4d6b976
7
- data.tar.gz: 7c38374f8ec37252a3b5428472235cf44a3a6379b5190776d766f447f339f16f3f85b69713c77592601466f82eac998c227de8136cecc64f82081e00f540314b
6
+ metadata.gz: 37456322483004e740a678a46a32bae7dc1fd02d9ad1363a0ce02c95502cb0fb68d07295280193277b5b252b3ae39162a019ba3f85f1483c3e5503011ae68419
7
+ data.tar.gz: ea9cd84e99f50b7771d7429f78da254b15ffd1b08cb7cadfa4cb7c9033bb3cc6f59064addd3824e4bcf5f1072669268916c16cd2116d7e5b6e822bd34f6f4415
@@ -7,7 +7,9 @@ module IOStreams
7
7
  # Builtin strip options to apply after encoding the read data.
8
8
  CLEANSE_RULES = {
9
9
  # Strips all non printable characters
10
- printable: ->(data) { data.gsub!(NOT_PRINTABLE, '') || data }
10
+ printable: ->(data, _) { data.gsub!(NOT_PRINTABLE, '') || data },
11
+ # Replaces non printable characters with the value specified in the `replace` option.
12
+ replace_non_printable: ->(data, replace) { data.gsub!(NOT_PRINTABLE, replace || '') || data }
11
13
  }.freeze
12
14
 
13
15
  # Read a line at a time from a file or stream
@@ -46,6 +48,7 @@ module IOStreams
46
48
  @cleaner = self.class.extract_cleaner(cleaner)
47
49
  @encoding = encoding.nil? || encoding.is_a?(Encoding) ? encoding : Encoding.find(encoding)
48
50
  @encoding_options = replace.nil? ? {} : {invalid: :replace, undef: :replace, replace: replace}
51
+ @replace = replace
49
52
 
50
53
  # More efficient read buffering only supported when the input stream `#read` method supports it.
51
54
  if replace.nil? && !@input_stream.method(:read).arity.between?(0, 1)
@@ -75,7 +78,7 @@ module IOStreams
75
78
  return unless block
76
79
 
77
80
  block = block.encode(@encoding, @encoding_options) unless block.encoding == @encoding
78
- block = @cleaner.call(block) if @cleaner
81
+ block = @cleaner.call(block, @replace) if @cleaner
79
82
  block
80
83
  end
81
84
 
@@ -40,6 +40,7 @@ module IOStreams
40
40
  @cleaner = ::IOStreams::Encode::Reader.send(:extract_cleaner, cleaner)
41
41
  @encoding = encoding.nil? || encoding.is_a?(Encoding) ? encoding : Encoding.find(encoding)
42
42
  @encoding_options = replace.nil? ? {} : {invalid: :replace, undef: :replace, replace: replace}
43
+ @replace = replace
43
44
  end
44
45
 
45
46
  # Write a line to the output stream
@@ -66,7 +67,7 @@ module IOStreams
66
67
 
67
68
  data = data.to_s
68
69
  block = data.encoding == @encoding ? data : data.encode(@encoding, @encoding_options)
69
- block = @cleaner.call(block) if @cleaner
70
+ block = @cleaner.call(block, @replace) if @cleaner
70
71
  @output_stream.write(block)
71
72
  end
72
73
  end
@@ -47,13 +47,14 @@ module IOStreams
47
47
  # IOStreams.path('blah.zip').stream(:zip).stream(:pgp, passphrase: 'receiver_passphrase').reader(&:read)
48
48
  # IOStreams.path('blah.zip').stream(:zip).stream(:encode, encoding: 'BINARY').reader(&:read)
49
49
  #
50
- def self.path(*elements)
51
- return elements.first if (elements.size == 1) && elements.first.is_a?(IOStreams::Path)
50
+ def self.path(*elements, **args)
51
+ return elements.first if (elements.size == 1) && args.empty? && elements.first.is_a?(IOStreams::Path)
52
52
 
53
- elements = elements.collect(&:to_s)
54
- path = ::File.join(*elements)
55
- uri = URI.parse(path)
56
- scheme(uri.scheme).new(path)
53
+ elements = elements.collect(&:to_s)
54
+ path = ::File.join(*elements)
55
+ extracted_scheme = path.include?("://") ? URI.parse(path).scheme : nil
56
+ klass = scheme(extracted_scheme)
57
+ args.empty? ? klass.new(path) : klass.new(path, **args)
57
58
  end
58
59
 
59
60
  # For an existing IO Stream
@@ -9,9 +9,9 @@ module IOStreams
9
9
  LINEFEED_REGEXP = Regexp.compile(/\r\n|\n|\r/).freeze
10
10
 
11
11
  # Read a line at a time from a stream
12
- def self.stream(input_stream, original_file_name: nil, **args, &block)
12
+ def self.stream(input_stream, original_file_name: nil, **args)
13
13
  # Pass-through if already a line reader
14
- return block.call(input_stream) if input_stream.is_a?(self.class)
14
+ return yield(input_stream) if input_stream.is_a?(self.class)
15
15
 
16
16
  yield new(input_stream, **args)
17
17
  end
@@ -4,9 +4,9 @@ module IOStreams
4
4
  attr_reader :delimiter
5
5
 
6
6
  # Write a line at a time to a stream.
7
- def self.stream(output_stream, original_file_name: nil, **args, &block)
7
+ def self.stream(output_stream, **args)
8
8
  # Pass-through if already a line writer
9
- return block.call(output_stream) if output_stream.is_a?(self.class)
9
+ return yield(output_stream) if output_stream.is_a?(self.class)
10
10
 
11
11
  yield new(output_stream, **args)
12
12
  end
@@ -24,7 +24,7 @@ module IOStreams
24
24
  # Add the specified delimiter after every record when writing it
25
25
  # to the output stream
26
26
  # Default: OS Specific. Linux: "\n"
27
- def initialize(output_stream, delimiter: $/)
27
+ def initialize(output_stream, delimiter: $/, original_file_name: nil)
28
28
  super(output_stream)
29
29
  @delimiter = delimiter
30
30
  end
@@ -1,6 +1,6 @@
1
1
  module IOStreams
2
2
  class Path < IOStreams::Stream
3
- attr_reader :path
3
+ attr_accessor :path
4
4
 
5
5
  def initialize(path)
6
6
  raise(ArgumentError, 'Path cannot be nil') if path.nil?
@@ -18,11 +18,11 @@ module IOStreams
18
18
 
19
19
  elements = elements.collect(&:to_s)
20
20
  relative = ::File.join(*elements)
21
- if relative.start_with?(path)
22
- self.class.new(relative)
23
- else
24
- self.class.new(::File.join(path, relative))
25
- end
21
+
22
+ new_path = dup
23
+ new_path.streams = nil
24
+ new_path.path = relative.start_with?(path) ? relative : ::File.join(path, relative)
25
+ new_path
26
26
  end
27
27
 
28
28
  def relative?
@@ -103,7 +103,6 @@ module IOStreams
103
103
  end
104
104
 
105
105
  # Returns [IOStreams::Path] the directory for this file.
106
- # Returns `nil` if no `file_name` was set.
107
106
  #
108
107
  # If `path` does not include a directory name then "." is returned.
109
108
  #
@@ -114,8 +113,10 @@ module IOStreams
114
113
  # IOStreams.path("test").directory #=> "."
115
114
  # IOStreams.path(".profile").directory #=> "."
116
115
  def directory
117
- file_name = streams.file_name
118
- self.class.new(::File.dirname(file_name)) if file_name
116
+ new_path = dup
117
+ new_path.streams = nil
118
+ new_path.path = ::File.dirname(path)
119
+ new_path
119
120
  end
120
121
 
121
122
  # When path is a file, deletes this file.
@@ -166,12 +167,12 @@ module IOStreams
166
167
 
167
168
  # Paths are sortable by name
168
169
  def <=>(other)
169
- path <=> other.to_s
170
+ path <=> other.path
170
171
  end
171
172
 
172
173
  # Compare by path name, ignore streams
173
174
  def ==(other)
174
- path == other.to_s
175
+ path == other.path
175
176
  end
176
177
 
177
178
  def inspect
@@ -3,7 +3,7 @@ require 'uri'
3
3
  module IOStreams
4
4
  module Paths
5
5
  class HTTP < IOStreams::Path
6
- attr_reader :username, :password, :http_redirect_count
6
+ attr_reader :username, :password, :http_redirect_count, :url
7
7
 
8
8
  # Stream to/from a remote file over http(s).
9
9
  #
@@ -34,7 +34,12 @@ module IOStreams
34
34
  @username = username || uri.user
35
35
  @password = password || uri.password
36
36
  @http_redirect_count = http_redirect_count
37
- super(url)
37
+ @url = url
38
+ super(uri.path)
39
+ end
40
+
41
+ def to_s
42
+ url
38
43
  end
39
44
 
40
45
  # Read a file using an http get.
@@ -48,7 +53,7 @@ module IOStreams
48
53
  # Notes:
49
54
  # * Since Net::HTTP download only supports a push stream, the data is streamed into a tempfile first.
50
55
  def reader(&block)
51
- handle_redirects(path, http_redirect_count, &block)
56
+ handle_redirects(url, http_redirect_count, &block)
52
57
  end
53
58
 
54
59
  def handle_redirects(uri, http_redirect_count, &block)
@@ -3,14 +3,14 @@ require "uri"
3
3
  module IOStreams
4
4
  module Paths
5
5
  class S3 < IOStreams::Path
6
- attr_reader :bucket_name, :key, :client
6
+ attr_reader :bucket_name, :client
7
7
 
8
8
  # Arguments:
9
9
  #
10
10
  # url: [String]
11
11
  # Prefix must be: `s3://`
12
12
  # followed by bucket name,
13
- # followed by path and file_name (key).
13
+ # followed by key.
14
14
  # Examples:
15
15
  # s3://my-bucket-name/file_name.txt
16
16
  # s3://my-bucket-name/some_path/file_name.csv
@@ -62,9 +62,6 @@ module IOStreams
62
62
  # @option params [String] :grant_write_acp
63
63
  # Allows grantee to write the ACL for the applicable object.
64
64
  #
65
- # @option params [required, String] :key
66
- # Object key for which the PUT operation was initiated.
67
- #
68
65
  # @option params [Hash<String,String>] :metadata
69
66
  # A map of metadata to store with the object in S3.
70
67
  #
@@ -134,21 +131,25 @@ module IOStreams
134
131
  raise "Invalid URI. Required Format: 's3://<bucket_name>/<key>'" unless uri.scheme == 's3'
135
132
 
136
133
  @bucket_name = uri.host
137
- @key = uri.path.sub(%r{\A/}, '')
134
+ key = uri.path.sub(%r{\A/}, '')
138
135
  @client = client || ::Aws::S3::Client.new
139
136
  @options = args
140
- super(url)
137
+ super(key)
138
+ end
139
+
140
+ def to_s
141
+ ::File.join("s3://", bucket_name, path)
141
142
  end
142
143
 
143
144
  def delete
144
- client.delete_object(bucket: bucket_name, key: key)
145
+ client.delete_object(bucket: bucket_name, key: path)
145
146
  self
146
147
  rescue Aws::S3::Errors::NotFound
147
148
  self
148
149
  end
149
150
 
150
151
  def exist?
151
- client.head_object(bucket: bucket_name, key: key)
152
+ client.head_object(bucket: bucket_name, key: path)
152
153
  true
153
154
  rescue Aws::S3::Errors::NotFound
154
155
  false
@@ -162,9 +163,9 @@ module IOStreams
162
163
  target = IOStreams.new(target_path)
163
164
  return super(target) unless target.is_a?(self.class)
164
165
 
165
- source_name = ::File.join(bucket_name, key)
166
+ source_name = ::File.join(bucket_name, path)
166
167
  # TODO: Does/should it also copy metadata?
167
- client.copy_object(bucket: target.bucket_name, key: target.key, copy_source: source_name)
168
+ client.copy_object(bucket: target.bucket_name, key: target.path, copy_source: source_name)
168
169
  delete
169
170
  target
170
171
  end
@@ -179,7 +180,7 @@ module IOStreams
179
180
  end
180
181
 
181
182
  def size
182
- client.head_object(bucket: bucket_name, key: key).content_length
183
+ client.head_object(bucket: bucket_name, key: path).content_length
183
184
  rescue Aws::S3::Errors::NotFound
184
185
  nil
185
186
  end
@@ -199,7 +200,7 @@ module IOStreams
199
200
  # Shortcut method if caller has a filename already with no other streams applied:
200
201
  def read_file(file_name)
201
202
  ::File.open(file_name, 'wb') do |file|
202
- client.get_object(@options.merge(response_target: file, bucket: bucket_name, key: key))
203
+ client.get_object(@options.merge(response_target: file, bucket: bucket_name, key: path))
203
204
  end
204
205
  end
205
206
 
@@ -226,11 +227,11 @@ module IOStreams
226
227
  if ::File.size(file_name) > 5 * 1024 * 1024
227
228
  # Use multipart file upload
228
229
  s3 = Aws::S3::Resource.new(client: client)
229
- obj = s3.bucket(bucket_name).object(key)
230
+ obj = s3.bucket(bucket_name).object(path)
230
231
  obj.upload_file(file_name)
231
232
  else
232
233
  ::File.open(file_name, 'rb') do |file|
233
- client.put_object(@options.merge(bucket: bucket_name, key: key, body: file))
234
+ client.put_object(@options.merge(bucket: bucket_name, key: path, body: file))
234
235
  end
235
236
  end
236
237
  end
@@ -3,12 +3,12 @@ module IOStreams
3
3
  class SFTP < IOStreams::Path
4
4
  include SemanticLogger::Loggable if defined?(SemanticLogger)
5
5
 
6
- attr_reader :hostname, :username, :file_name, :create_path, :options
6
+ attr_reader :hostname, :username, :create_path, :options, :url
7
7
 
8
8
  # Stream to a remote file over sftp.
9
9
  #
10
- # file_name: [String]
11
- # Name of file to write to.
10
+ # url: [String]
11
+ # "sftp://<host_name>/<file_name>"
12
12
  #
13
13
  # username: [String]
14
14
  # Name of user to login with.
@@ -39,7 +39,6 @@ module IOStreams
39
39
  raise(ArgumentError, "Invalid URL. Required Format: 'sftp://<host_name>/<file_name>'") unless uri.scheme == 'sftp'
40
40
 
41
41
  @hostname = uri.hostname
42
- @file_name = uri.path
43
42
  @mkdir = false
44
43
  @username = username || uri.user
45
44
  @create_path = create_path
@@ -51,7 +50,11 @@ module IOStreams
51
50
  options[:max_pkt_size] = max_pkt_size
52
51
  options[:password] = password || uri.password
53
52
  @options = options
54
- super(file_name)
53
+ super(uri.path)
54
+ end
55
+
56
+ def to_s
57
+ url
55
58
  end
56
59
 
57
60
  def mkdir
@@ -73,7 +76,7 @@ module IOStreams
73
76
  def reader(&block)
74
77
  result = nil
75
78
  Net::SFTP.start(hostname, username, options) do |sftp|
76
- result = sftp.file.open(file_name, 'rb', &block)
79
+ result = sftp.file.open(path, 'rb', &block)
77
80
  end
78
81
  result
79
82
  end
@@ -89,8 +92,8 @@ module IOStreams
89
92
  def writer(&block)
90
93
  result = nil
91
94
  Net::SFTP.start(hostname, username, options) do |sftp|
92
- sftp.session.exec!("mkdir -p '#{::File.dirname(file_name)}'") if create_path
93
- result = sftp.file.open(file_name, 'wb', &block)
95
+ sftp.session.exec!("mkdir -p '#{::File.dirname(path)}'") if create_path
96
+ result = sftp.file.open(path, 'wb', &block)
94
97
  end
95
98
  result
96
99
  end
@@ -7,9 +7,9 @@ module IOStreams
7
7
  # Read a record at a time from a line stream
8
8
  # Note:
9
9
  # - The supplied stream _must_ already be a line stream, or a stream that responds to :each
10
- def self.stream(line_reader, original_file_name: nil, **args, &block)
10
+ def self.stream(line_reader, original_file_name: nil, **args)
11
11
  # Pass-through if already a record reader
12
- return block.call(line_reader) if line_reader.is_a?(self.class)
12
+ return yield(line_reader) if line_reader.is_a?(self.class)
13
13
 
14
14
  yield new(line_reader, **args)
15
15
  end
@@ -9,9 +9,9 @@ module IOStreams
9
9
  # Write a record as a Hash at a time to a stream.
10
10
  # Note:
11
11
  # - The supplied stream _must_ already be a line stream, or a stream that responds to :<<
12
- def self.stream(line_writer, original_file_name: nil, **args, &block)
13
- # Pass-through if already a row writer
14
- return block.call(line_writer) if line_writer.is_a?(self.class)
12
+ def self.stream(line_writer, original_file_name: nil, **args)
13
+ # Pass-through if already a record writer
14
+ return yield(line_writer) if line_writer.is_a?(self.class)
15
15
 
16
16
  yield new(line_writer, **args)
17
17
  end
@@ -5,9 +5,9 @@ module IOStreams
5
5
  # Read a line as an Array at a time from a stream.
6
6
  # Note:
7
7
  # - The supplied stream _must_ already be a line stream, or a stream that responds to :each
8
- def self.stream(line_reader, original_file_name: nil, **args, &block)
8
+ def self.stream(line_reader, original_file_name: nil, **args)
9
9
  # Pass-through if already a row reader
10
- return block.call(line_reader) if line_reader.is_a?(self.class)
10
+ return yield(line_reader) if line_reader.is_a?(self.class)
11
11
 
12
12
  yield new(line_reader, **args)
13
13
  end
@@ -14,7 +14,7 @@ module IOStreams
14
14
  # - The supplied stream _must_ already be a line stream, or a stream that responds to :<<
15
15
  def self.stream(line_writer, original_file_name: nil, **args)
16
16
  # Pass-through if already a row writer
17
- return block.call(line_writer) if line_writer.is_a?(self.class)
17
+ return yield(line_writer) if line_writer.is_a?(self.class)
18
18
 
19
19
  yield new(line_writer, **args)
20
20
  end
@@ -58,50 +58,6 @@ module IOStreams
58
58
  end
59
59
 
60
60
  # Returns a Reader for reading a file / stream
61
- #
62
- # Parameters
63
- # file_name_or_io [String|IO]
64
- # The file_name of the file to write to, or an IO Stream that implements
65
- # #read.
66
- #
67
- # streams [Symbol|Array]
68
- # The formats/streams that be used to convert the data whilst it is
69
- # being read.
70
- # When nil, the file_name will be inspected to try and determine what
71
- # streams should be applied.
72
- # Default: nil
73
- #
74
- # file_name [String]
75
- # When `streams` is not supplied, `file_name` can be used for determining the streams
76
- # to apply to read the file/stream.
77
- # This is particularly useful when `file_name_or_io` is a stream, or a temporary file name.
78
- # Default: nil
79
- #
80
- # Example: Zip
81
- # IOStreams.reader('myfile.zip') do |stream|
82
- # puts stream.read
83
- # end
84
- #
85
- # Example: Encrypted Zip
86
- # IOStreams.reader('myfile.zip.enc') do |stream|
87
- # puts stream.read
88
- # end
89
- #
90
- # Example: Explicitly set the streams
91
- # IOStreams.reader('myfile.zip.enc', [:zip, :enc]) do |stream|
92
- # puts stream.read
93
- # end
94
- #
95
- # Example: Supply custom options
96
- # # Encrypt the file and get Symmetric Encryption to also compress it
97
- # IOStreams.reader('myfile.csv.enc', streams: enc: {compress: true}) do |stream|
98
- # puts stream.read
99
- # end
100
- #
101
- # Note:
102
- # * Passes the file_name_or_io as-is into the block if it is already a reader stream AND
103
- # no streams are passed in.
104
- #
105
61
  def reader(&block)
106
62
  streams.reader(io_stream, &block)
107
63
  end
@@ -141,12 +97,12 @@ module IOStreams
141
97
  def copy_from(source, convert: true)
142
98
  if convert
143
99
  stream = IOStreams.new(source)
144
- streams.writer(io_stream) do |target|
100
+ writer do |target|
145
101
  stream.reader { |src| IO.copy_stream(src, target) }
146
102
  end
147
103
  else
148
104
  stream = source.is_a?(Stream) ? source.dup : IOStreams.new(source)
149
- streams.dup.stream(:none).writer(io_stream) do |target|
105
+ dup.stream(:none).writer do |target|
150
106
  stream.stream(:none).reader { |src| IO.copy_stream(src, target) }
151
107
  end
152
108
  end
@@ -182,26 +138,6 @@ module IOStreams
182
138
  end
183
139
 
184
140
  # Returns [Hash] of every record in a file or stream with support for headers.
185
- #
186
- # Reading a delimited stream and converting to tabular form.
187
- #
188
- # Each record / line is returned one at a time so that very large files
189
- # can be read without having to load the entire file into memory.
190
- #
191
- # Embedded lines (within double quotes) will be skipped if
192
- # 1. The file name contains .csv
193
- # 2. Or the embedded_within argument is set
194
- #
195
- # Example: Supply custom options
196
- # IOStreams.each_record(file_name, embedded_within: '"') do |line|
197
- # puts line
198
- # end
199
- #
200
- # Example:
201
- # file_name = 'customer_data.csv.pgp'
202
- # IOStreams.each_record(file_name) do |hash|
203
- # p hash
204
- # end
205
141
  def each_record(**args, &block)
206
142
  record_reader(**args) { |record_stream| record_stream.each(&block) }
207
143
  end
@@ -229,57 +165,6 @@ module IOStreams
229
165
  end
230
166
 
231
167
  # Returns a Writer for writing to a file / stream
232
- #
233
- # Parameters
234
- # file_name_or_io [String|IO]
235
- # The file_name of the file to write to, or an IO Stream that implements
236
- # #write.
237
- #
238
- # streams [Symbol|Array]
239
- # The formats/streams that be used to convert the data whilst it is
240
- # being written.
241
- # When nil, the file_name will be inspected to try and determine what
242
- # streams should be applied.
243
- # Default: nil
244
- #
245
- # Stream types / extensions supported:
246
- # .zip Zip File [ :zip ]
247
- # .gz, .gzip GZip File [ :gzip ]
248
- # .enc File Encrypted using symmetric encryption [ :enc ]
249
- # other All other extensions will be returned as: [ :file ]
250
- #
251
- # When a file is encrypted, it may also be compressed:
252
- # .zip.enc [ :zip, :enc ]
253
- # .gz.enc [ :gz, :enc ]
254
- #
255
- # Example: Zip
256
- # IOStreams.writer('myfile.zip') do |stream|
257
- # stream.write(data)
258
- # end
259
- #
260
- # Example: Encrypted Zip
261
- # IOStreams.writer('myfile.zip.enc') do |stream|
262
- # stream.write(data)
263
- # end
264
- #
265
- # Example: Explicitly set the streams
266
- # IOStreams.writer('myfile.zip.enc', [:zip, :enc]) do |stream|
267
- # stream.write(data)
268
- # end
269
- #
270
- # Example: Supply custom options
271
- # IOStreams.writer('myfile.csv.enc', [enc: { compress: true }]) do |stream|
272
- # stream.write(data)
273
- # end
274
- #
275
- # Example: Set internal filename when creating a zip file
276
- # IOStreams.writer('myfile.csv.zip', zip: { zip_file_name: 'myfile.csv' }) do |stream|
277
- # stream.write(data)
278
- # end
279
- #
280
- # Note:
281
- # * Passes the file_name_or_io as-is into the block if it is already a writer stream AND
282
- # no streams are passed in.
283
168
  def writer(&block)
284
169
  streams.writer(io_stream, &block)
285
170
  end
@@ -295,22 +180,32 @@ module IOStreams
295
180
  writer { |stream| stream.write(data) }
296
181
  end
297
182
 
298
- def line_writer(**args)
299
- writer { |io| yield IOStreams::Line::Writer.new(io, **args) }
183
+ def line_writer(**args, &block)
184
+ return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Line::Writer)
185
+
186
+ writer { |io| IOStreams::Line::Writer.stream(io, **args, &block) }
300
187
  end
301
188
 
302
- def row_writer(delimiter: $/, **args)
303
- line_writer(delimiter: delimiter) { |io| yield IOStreams::Row::Writer.new(io, **args) }
189
+ def row_writer(delimiter: $/, **args, &block)
190
+ return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Row::Writer)
191
+
192
+ line_writer(delimiter: delimiter) { |io| IOStreams::Row::Writer.stream(io, **args, &block) }
304
193
  end
305
194
 
306
- def record_writer(delimiter: $/, **args)
307
- line_writer(delimiter: delimiter) { |io| yield IOStreams::Record::Writer.new(io, **args) }
195
+ def record_writer(delimiter: $/, **args, &block)
196
+ return block.call(io_stream) if io_stream && io_stream.is_a?(IOStreams::Record::Writer)
197
+
198
+ line_writer(delimiter: delimiter) { |io| IOStreams::Record::Writer.stream(io, **args, &block) }
308
199
  end
309
200
 
310
201
  # Set/get the original file_name
311
202
  def file_name(file_name = :none)
312
- file_name == :none ? streams.file_name : streams.file_name = file_name
313
- self
203
+ if file_name == :none
204
+ streams.file_name
205
+ else
206
+ streams.file_name = file_name
207
+ self
208
+ end
314
209
  end
315
210
 
316
211
  # Set/get the original file_name
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = "1.0.0.beta".freeze
2
+ VERSION = "1.0.0.beta2".freeze
3
3
  end
@@ -97,19 +97,313 @@ class StreamTest < Minitest::Test
97
97
  describe '.each_record' do
98
98
  end
99
99
 
100
- describe '.writer' do
101
- end
100
+ describe '#writer' do
101
+ describe "#write" do
102
+ it 'one block' do
103
+ io = StringIO.new
104
+ IOStreams::Stream.new(io).writer do |stream|
105
+ stream.write("Hello World")
106
+ end
107
+ assert_equal "Hello World", io.string
108
+ end
109
+
110
+ it 'multiple blocks' do
111
+ io = StringIO.new
112
+ IOStreams::Stream.new(io).writer do |stream|
113
+ stream.write("He")
114
+ stream.write("l")
115
+ stream.write("lo ")
116
+ stream.write("World")
117
+ end
118
+ assert_equal "Hello World", io.string
119
+ end
120
+
121
+ it 'empty blocks' do
122
+ io = StringIO.new
123
+ IOStreams::Stream.new(io).writer do |stream|
124
+ stream.write("")
125
+ stream.write("He")
126
+ stream.write("")
127
+ stream.write("l")
128
+ stream.write("")
129
+ stream.write("lo ")
130
+ stream.write("World")
131
+ stream.write("")
132
+ end
133
+ assert_equal "Hello World", io.string
134
+ end
135
+
136
+ it 'nil blocks' do
137
+ io = StringIO.new
138
+ IOStreams::Stream.new(io).writer do |stream|
139
+ stream.write(nil)
140
+ stream.write("He")
141
+ stream.write(nil)
142
+ stream.write("l")
143
+ stream.write(nil)
144
+ stream.write("lo ")
145
+ stream.write("World")
146
+ stream.write(nil)
147
+ end
148
+ assert_equal "Hello World", io.string
149
+ end
150
+ end
151
+
152
+ describe "#<<" do
153
+ it 'one block' do
154
+ io = StringIO.new
155
+ IOStreams::Stream.new(io).writer do |stream|
156
+ stream << "Hello World"
157
+ end
158
+ assert_equal "Hello World", io.string
159
+ end
160
+
161
+ it 'multiple blocks' do
162
+ io = StringIO.new
163
+ IOStreams::Stream.new(io).writer do |stream|
164
+ stream << "He"
165
+ stream << "l" << "lo " << "World"
166
+ end
167
+ assert_equal "Hello World", io.string
168
+ end
169
+
170
+ it 'empty blocks' do
171
+ io = StringIO.new
172
+ IOStreams::Stream.new(io).writer do |stream|
173
+ stream << ""
174
+ stream << "He" << "" << "l" << ""
175
+ stream << "lo " << "World"
176
+ stream << ""
177
+ end
178
+ assert_equal "Hello World", io.string
179
+ end
102
180
 
103
- describe '.writer' do
181
+ it 'nil blocks' do
182
+ io = StringIO.new
183
+ IOStreams::Stream.new(io).writer do |stream|
184
+ stream << nil
185
+ stream << "He" << nil << "l" << nil
186
+ stream << "lo " << "World"
187
+ stream << nil
188
+ end
189
+ assert_equal "Hello World", io.string
190
+ end
191
+ end
104
192
  end
105
193
 
106
- describe '.line_writer' do
194
+ describe '#line_writer' do
195
+ describe "#write" do
196
+ it 'one block' do
197
+ io = StringIO.new
198
+ IOStreams::Stream.new(io).line_writer do |stream|
199
+ stream.write("Hello World")
200
+ end
201
+ assert_equal "Hello World\n", io.string
202
+ end
203
+
204
+ it 'multiple blocks' do
205
+ io = StringIO.new
206
+ IOStreams::Stream.new(io).line_writer do |stream|
207
+ stream.write("He")
208
+ stream.write("l")
209
+ stream.write("lo ")
210
+ stream.write("World")
211
+ end
212
+ assert_equal "He\nl\nlo \nWorld\n", io.string
213
+ end
214
+
215
+ it 'empty blocks' do
216
+ io = StringIO.new
217
+ IOStreams::Stream.new(io).line_writer do |stream|
218
+ stream.write("")
219
+ stream.write("He")
220
+ stream.write("")
221
+ stream.write("l")
222
+ stream.write("")
223
+ stream.write("lo ")
224
+ stream.write("World")
225
+ stream.write("")
226
+ end
227
+ assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string, io.string.inspect
228
+ end
229
+
230
+ it 'nil blocks' do
231
+ io = StringIO.new
232
+ IOStreams::Stream.new(io).line_writer do |stream|
233
+ stream.write(nil)
234
+ stream.write("He")
235
+ stream.write(nil)
236
+ stream.write("l")
237
+ stream.write(nil)
238
+ stream.write("lo ")
239
+ stream.write("World")
240
+ stream.write(nil)
241
+ end
242
+ assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string, io.string.inspect
243
+ end
244
+ end
245
+
246
+ describe "#<<" do
247
+ it 'one block' do
248
+ io = StringIO.new
249
+ IOStreams::Stream.new(io).line_writer do |stream|
250
+ stream << "Hello World"
251
+ end
252
+ assert_equal "Hello World\n", io.string
253
+ end
254
+
255
+ it 'multiple blocks' do
256
+ io = StringIO.new
257
+ IOStreams::Stream.new(io).line_writer do |stream|
258
+ stream << "He"
259
+ stream << "l" << "lo " << "World"
260
+ end
261
+ assert_equal "He\nl\nlo \nWorld\n", io.string
262
+ end
263
+
264
+ it 'empty blocks' do
265
+ io = StringIO.new
266
+ IOStreams::Stream.new(io).line_writer do |stream|
267
+ stream << ""
268
+ stream << "He" << "" << "l" << ""
269
+ stream << "lo " << "World"
270
+ stream << ""
271
+ end
272
+ assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string
273
+ end
274
+
275
+ it 'nil blocks' do
276
+ io = StringIO.new
277
+ IOStreams::Stream.new(io).line_writer do |stream|
278
+ stream << nil
279
+ stream << "He" << nil << "l" << nil
280
+ stream << "lo " << "World"
281
+ stream << nil
282
+ end
283
+ assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string
284
+ end
285
+ end
286
+
287
+ describe "line writers within line writers" do
288
+ it 'uses existing line writer' do
289
+ io = StringIO.new
290
+ IOStreams::Stream.new(io).line_writer do |stream|
291
+ stream.write("Before")
292
+ IOStreams::Stream.new(stream).line_writer do |inner|
293
+ stream.write("Inner")
294
+ assert_equal inner.object_id, stream.object_id
295
+ end
296
+ stream.write("After")
297
+ end
298
+ assert_equal "Before\nInner\nAfter\n", io.string, io.string.inspect
299
+ end
300
+ end
107
301
  end
108
302
 
109
- describe '.row_writer' do
303
+ describe '#row_writer' do
304
+ describe "#write" do
305
+ it 'one block' do
306
+ io = StringIO.new
307
+ IOStreams::Stream.new(io).row_writer do |stream|
308
+ stream << %w[Hello World]
309
+ end
310
+ assert_equal "Hello,World\n", io.string
311
+ end
312
+
313
+ it 'multiple blocks' do
314
+ io = StringIO.new
315
+ IOStreams::Stream.new(io).row_writer do |stream|
316
+ stream << %w[He]
317
+ stream << %w[l lo\ World]
318
+ stream << ["He", "", "l", ""]
319
+ stream << ["lo ", "World"]
320
+ end
321
+ assert_equal "He\nl,lo ,World\nHe,\"\",l,\"\"\nlo ,World\n", io.string, io.string.inspect
322
+ end
323
+
324
+ it 'empty blocks' do
325
+ # skip "TODO"
326
+ io = StringIO.new
327
+ IOStreams::Stream.new(io).row_writer do |stream|
328
+ stream << %w[He]
329
+ stream << []
330
+ stream << %w[l lo\ World]
331
+ stream << ["He", "", "l", ""]
332
+ stream << ["lo ", "World"]
333
+ stream << []
334
+ end
335
+ assert_equal "He\n\nl,lo ,World\nHe,\"\",l,\"\"\nlo ,World\n\n", io.string, io.string.inspect
336
+ end
337
+
338
+ it 'nil values' do
339
+ io = StringIO.new
340
+ IOStreams::Stream.new(io).row_writer do |stream|
341
+ stream << %w[He]
342
+ stream << %w[l lo\ World]
343
+ stream << ["He", nil, "l", nil]
344
+ stream << ["lo ", "World"]
345
+ end
346
+ assert_equal "He\nl,lo ,World\nHe,,l,\nlo ,World\n", io.string, io.string.inspect
347
+ end
348
+
349
+ it 'empty leading array' do
350
+ skip "TODO"
351
+ io = StringIO.new
352
+ IOStreams::Stream.new(io).row_writer do |stream|
353
+ stream << []
354
+ stream << %w[He]
355
+ stream << %w[l lo\ World]
356
+ stream << ["He", "", "l", ""]
357
+ stream << ["lo ", "World"]
358
+ stream << []
359
+ end
360
+ assert_equal "\nHe\n\nl\n\nlo \nWorld\n\n", io.string, io.string.inspect
361
+ end
362
+ end
110
363
  end
111
364
 
112
- describe '.record_writer' do
365
+ describe '#record_writer' do
366
+ describe "#write" do
367
+ it 'one block' do
368
+ io = StringIO.new
369
+ IOStreams::Stream.new(io).record_writer do |stream|
370
+ stream << {first_name: "Jack", last_name: "Johnson"}
371
+ end
372
+ assert_equal "first_name,last_name\nJack,Johnson\n", io.string, io.string.inspect
373
+ end
374
+
375
+ it 'multiple blocks' do
376
+ io = StringIO.new
377
+ IOStreams::Stream.new(io).record_writer do |stream|
378
+ stream << {first_name: "Jack", last_name: "Johnson"}
379
+ stream << {first_name: "Able", last_name: "Smith"}
380
+ end
381
+ assert_equal "first_name,last_name\nJack,Johnson\nAble,Smith\n", io.string, io.string.inspect
382
+ end
383
+
384
+ it 'empty hashes' do
385
+ io = StringIO.new
386
+ IOStreams::Stream.new(io).record_writer do |stream|
387
+ stream << {first_name: "Jack", last_name: "Johnson"}
388
+ stream << {} << {first_name: "Able", last_name: "Smith"}
389
+ stream << {}
390
+ end
391
+ assert_equal "first_name,last_name\nJack,Johnson\n\n{:first_name=>\"Able\", :last_name=>\"Smith\"}\n\n", io.string, io.string.inspect
392
+ end
393
+
394
+ it 'nil values' do
395
+ skip "TODO"
396
+ io = StringIO.new
397
+ IOStreams::Stream.new(io).record_writer do |stream|
398
+ stream << {first_name: "Jack", last_name: "Johnson"}
399
+ stream << {} << {first_name: "Able", last_name: "Smith"}
400
+ stream << {first_name: "Able", last_name: nil}
401
+ stream << {}
402
+ end
403
+ assert_equal "first_name,last_name\nJack,Johnson\n\n{:first_name=>\"Able\", :last_name=>\"Smith\"}\n\n", io.string, io.string.inspect
404
+ end
405
+
406
+ end
113
407
  end
114
408
 
115
409
  end
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: 1.0.0.beta
4
+ version: 1.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-10-22 00:00:00.000000000 Z
11
+ date: 2019-10-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: