iostreams 1.0.0.beta → 1.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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: