iostreams 1.0.0.beta2 → 1.0.0.beta3

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: 4469de3d596bf7fd0485de78878ad8188c0a60c8a3b52cfbd2dcd207ec4e8e92
4
- data.tar.gz: 25e2e16fa02f1d8b964cd8bdb521576a106148fd6446ebddf832735e5006d403
3
+ metadata.gz: 5ecd282f3e3fb3dbbb1cc912458ad80f1286a4921bc36c1797d84ef3863a9b57
4
+ data.tar.gz: 8f695a423c861f7899d0a20b0be370787486d83f2c2741c03a71fc801ae06e44
5
5
  SHA512:
6
- metadata.gz: 37456322483004e740a678a46a32bae7dc1fd02d9ad1363a0ce02c95502cb0fb68d07295280193277b5b252b3ae39162a019ba3f85f1483c3e5503011ae68419
7
- data.tar.gz: ea9cd84e99f50b7771d7429f78da254b15ffd1b08cb7cadfa4cb7c9033bb3cc6f59064addd3824e4bcf5f1072669268916c16cd2116d7e5b6e822bd34f6f4415
6
+ metadata.gz: 2f46b8cfccf5b45099465ed4d9169a124fe427d55c0168d03c22fedb73e092d3e41e1f481c955276f04b02377041a9e277bd40c9732e6521cfd368afa7f6b935
7
+ data.tar.gz: 18cc3db574ed6a61005f4743b74da70f787f740e039dda2127d62a02f150e1b90fc7555f6d64e0bb436258c8ef1d2bab64d7fb49615cb6262b1b5d41469e3211
@@ -3,7 +3,7 @@ module IOStreams
3
3
  class Reader < IOStreams::Reader
4
4
  # Read from a Bzip2 stream, decompressing the contents as it is read
5
5
  def self.stream(input_stream, **_args)
6
- Utils.load_dependency('rbzip2', 'Bzip2') unless defined?(RBzip2)
6
+ Utils.load_soft_dependency('rbzip2', 'Bzip2') unless defined?(RBzip2)
7
7
 
8
8
  begin
9
9
  io = RBzip2.default_adapter::Decompressor.new(input_stream)
@@ -3,7 +3,7 @@ module IOStreams
3
3
  class Writer < IOStreams::Writer
4
4
  # Write to a stream, compressing with Bzip2
5
5
  def self.stream(input_stream, original_file_name: nil, **_args)
6
- Utils.load_dependency('rbzip2', 'Bzip2') unless defined?(RBzip2)
6
+ Utils.load_soft_dependency('rbzip2', 'Bzip2') unless defined?(RBzip2)
7
7
 
8
8
  begin
9
9
  io = RBzip2.default_adapter::Compressor.new(input_stream)
@@ -99,10 +99,20 @@ module IOStreams
99
99
  root(root).join(*elements)
100
100
  end
101
101
 
102
- # Returns a path to a local temporary file.
103
- def self.temp_file(*args, &block)
104
- # TODO: Possible enhancement: Add a :temp root so that temp files can be stored anywhere, or the location changed.
105
- Paths::File.temp_file(*args, &block)
102
+ # Returns a path to a temporary file.
103
+ # Temporary file is deleted upon block completion if present.
104
+ #
105
+ # Parameters:
106
+ # basename: [String]
107
+ # Base file name to include in the temp file name.
108
+ #
109
+ # extension: [String]
110
+ # Optional extension to add to the tempfile.
111
+ #
112
+ # Example:
113
+ # IOStreams.temp_file
114
+ def self.temp_file(basename, extension = "", &block)
115
+ Utils.temp_file_name(basename, extension) { |file_name| yield(Paths::File.new(file_name).stream(:none)) }
106
116
  end
107
117
 
108
118
  # Returns [IOStreams::Paths::File] current or named users home path
@@ -85,7 +85,10 @@ module IOStreams
85
85
  def copy_from(source, **args)
86
86
  super(source, **args)
87
87
  rescue StandardError => exc
88
- delete
88
+ begin
89
+ delete
90
+ rescue NotImplementedError
91
+ end
89
92
  raise(exc)
90
93
  end
91
94
 
@@ -3,12 +3,6 @@ require "fileutils"
3
3
  module IOStreams
4
4
  module Paths
5
5
  class File < IOStreams::Path
6
- # Returns a path to a temporary file.
7
- # Temporary file is deleted upon block completion if present.
8
- def self.temp_file(basename, extension = "")
9
- Utils.temp_file_name(basename, extension) { |file_name| yield(new(file_name).stream(:none)) }
10
- end
11
-
12
6
  # Yields Paths within the current path.
13
7
  #
14
8
  # Examples:
@@ -125,7 +125,7 @@ module IOStreams
125
125
  # @option params [String] :object_lock_legal_hold_status
126
126
  # The Legal Hold status that you want to apply to the specified object.
127
127
  def initialize(url, client: nil, **args)
128
- Utils.load_dependency('aws-sdk-s3', 'AWS S3') unless defined?(::Aws::S3::Client)
128
+ Utils.load_soft_dependency('aws-sdk-s3', 'AWS S3') unless defined?(::Aws::S3::Client)
129
129
 
130
130
  uri = URI.parse(url)
131
131
  raise "Invalid URI. Required Format: 's3://<bucket_name>/<key>'" unless uri.scheme == 's3'
@@ -1,9 +1,19 @@
1
+ require 'open3'
2
+
1
3
  module IOStreams
2
4
  module Paths
3
5
  class SFTP < IOStreams::Path
4
6
  include SemanticLogger::Loggable if defined?(SemanticLogger)
5
7
 
6
- attr_reader :hostname, :username, :create_path, :options, :url
8
+ class << self
9
+ attr_accessor :sshpass_bin, :sftp_bin, :sshpass_wait_seconds
10
+ end
11
+
12
+ @sftp_bin = 'sftp'
13
+ @sshpass_bin = 'sshpass'
14
+ @sshpass_wait_seconds = 5
15
+
16
+ attr_reader :hostname, :username, :ssh_options, :url
7
17
 
8
18
  # Stream to a remote file over sftp.
9
19
  #
@@ -16,40 +26,46 @@ module IOStreams
16
26
  # password: [String]
17
27
  # Password for the user.
18
28
  #
19
- # host: [String]
20
- # Name of the host to connect to.
21
- #
22
- # port: [Integer]
23
- # Port to connect to at the above host.
24
- #
25
- # **args
26
- # Any other options supported by Net::SSH.start
29
+ # **ssh_options
30
+ # Any other options supported by ssh_config.
31
+ # `man ssh_config` to see all available options.
27
32
  #
28
33
  # Examples:
29
34
  #
30
- # # Sample URL
31
- # sftp://hostname/path/file_name
35
+ # # Display the contents of a remote file
36
+ # IOStreams.path("sftp://test.com/path/file_name.csv", username: "jack", password: "OpenSesame").reader do |io|
37
+ # puts io.read
38
+ # end
32
39
  #
33
- # # Full url showing all the optional elements that can be set via the url:
40
+ # # Full url showing all the optional elements that can be set via the url:
34
41
  # sftp://username:password@hostname:22/path/file_name
35
- def initialize(url, username:, password:, port: nil, max_pkt_size: 65_536, logger: nil, create_path: false, **args)
36
- Utils.load_dependency('net-sftp', 'net/sftp') unless defined?(Net::SFTP)
37
-
42
+ #
43
+ # # Display the contents of a remote file, supplying the username and password in the url:
44
+ # IOStreams.path("sftp://jack:OpenSesame@test.com:22/path/file_name.csv").reader do |io|
45
+ # puts io.read
46
+ # end
47
+ #
48
+ # # Display the contents of a remote file, supplying the username and password as arguments:
49
+ # IOStreams.path("sftp://test.com/path/file_name.csv", username: "jack", password: "OpenSesame").reader do |io|
50
+ # puts io.read
51
+ # end
52
+ #
53
+ # # When using the sftp executable use an identity file instead of a password to authenticate:
54
+ # IOStreams.path("sftp://test.com/path/file_name.csv", username: "jack", IdentityFile: "~/.ssh/private_key").reader do |io|
55
+ # puts io.read
56
+ # end
57
+ def initialize(url, username: nil, password: nil, ruby: true, ssh_options: {})
38
58
  uri = URI.parse(url)
39
59
  raise(ArgumentError, "Invalid URL. Required Format: 'sftp://<host_name>/<file_name>'") unless uri.scheme == 'sftp'
40
60
 
41
- @hostname = uri.hostname
42
- @mkdir = false
43
- @username = username || uri.user
44
- @create_path = create_path
45
-
46
- logger ||= self.logger if defined?(SemanticLogger)
47
- options = args.dup
48
- options[:logger] = logger
49
- options[:port] = port || uri.port || 22
50
- options[:max_pkt_size] = max_pkt_size
51
- options[:password] = password || uri.password
52
- @options = options
61
+ @hostname = uri.hostname
62
+ @mkdir = false
63
+ @username = username || uri.user
64
+ @url = url
65
+ @password = password || uri.password
66
+ @port = uri.port || 22
67
+ @ssh_options = ssh_options
68
+
53
69
  super(uri.path)
54
70
  end
55
71
 
@@ -57,6 +73,7 @@ module IOStreams
57
73
  url
58
74
  end
59
75
 
76
+ # Note that mkdir is delayed and only executed when the file write is performed.
60
77
  def mkdir
61
78
  @mkdir = true
62
79
  self
@@ -74,11 +91,10 @@ module IOStreams
74
91
  # Note:
75
92
  # - raises Net::SFTP::StatusException when the file could not be read.
76
93
  def reader(&block)
77
- result = nil
78
- Net::SFTP.start(hostname, username, options) do |sftp|
79
- result = sftp.file.open(path, 'rb', &block)
94
+ IOStreams.temp_file("iostreams-sftp-reader") do |temp_file|
95
+ sftp_download(path, temp_file.to_s)
96
+ temp_file.reader(&block)
80
97
  end
81
- result
82
98
  end
83
99
 
84
100
  # Write to a file on a remote sftp server.
@@ -90,12 +106,97 @@ module IOStreams
90
106
  # output.write('Hello World')
91
107
  # end
92
108
  def writer(&block)
93
- result = nil
94
- Net::SFTP.start(hostname, username, options) do |sftp|
95
- sftp.session.exec!("mkdir -p '#{::File.dirname(path)}'") if create_path
96
- result = sftp.file.open(path, 'wb', &block)
109
+ IOStreams.temp_file("iostreams-sftp-writer") do |temp_file|
110
+ temp_file.writer(&block)
111
+ sftp_upload(temp_file.to_s, path)
112
+ temp_file.size
113
+ end
114
+ end
115
+
116
+ # TODO: Add #copy_from shortcut to detect when a file is supplied that does not require conversion.
117
+
118
+ # Search for files on the remote sftp server that match the provided pattern.
119
+ #
120
+ # The pattern matching works like Net::SFTP::Operations::Dir.glob and Dir.glob
121
+ # Each child also returns attributes that contain the file size, ownership, file dates and other details.
122
+ #
123
+ # Example Code:
124
+ # IOStreams.
125
+ # path("sftp://sftp.example.org/my_files", username: username, password: password).
126
+ # each_child('**/*.{csv,txt}') do |input, attributes|
127
+ # puts "#{input.to_s} #{attributes}"
128
+ # end
129
+ #
130
+ # Example Output:
131
+ # sftp://sftp.example.org/a/b/c/test.txt {:type=>1, :size=>37, :owner=>"test_owner", :group=>"test_group", :permissions=>420, :atime=>1572378136, :mtime=>1572378136, :link_count=>1, :extended=>{}}
132
+ def each_child(pattern = "*", case_sensitive: true, directories: false, hidden: false)
133
+ Utils.load_soft_dependency("net-sftp", "SFTP glob capability", "net/sftp") unless defined?(Net::SFTP)
134
+
135
+ flags = ::File::FNM_EXTGLOB
136
+ flags |= ::File::FNM_CASEFOLD unless case_sensitive
137
+ flags |= ::File::FNM_DOTMATCH if hidden
138
+
139
+ Net::SFTP.start(hostname, username, build_ssh_options) do |sftp|
140
+ sftp.dir.glob(".", pattern, flags) do |path|
141
+ next if !directories && !path.file?
142
+ new_path = self.class.new("sftp://#{hostname}/#{path.name}", username: username, password: password, ruby: ruby, **ssh_options)
143
+ yield(new_path, path.attributes.attributes)
144
+ end
145
+ end
146
+ nil
147
+ end
148
+
149
+ private
150
+
151
+ attr_reader :password
152
+
153
+ # Use sftp and sshpass executables to download to a local file
154
+ def sftp_download(remote_file_name, local_file_name)
155
+ Open3.popen2e(*sftp_args) do |writer, reader, waith_thr|
156
+ writer.puts password
157
+ # Give time for password to be processed and stdin to be passed to sftp process.
158
+ sleep self.class.sshpass_wait_seconds
159
+ writer.puts "get #{remote_file_name} #{local_file_name}"
160
+ writer.puts 'bye'
161
+ writer.close
162
+ out = reader.read.chomp
163
+ raise(Errors::CommunicationsFailure, "Failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}") unless waith_thr.value.success?
164
+ out
165
+ end
166
+ end
167
+
168
+ def sftp_upload(local_file_name, remote_file_name)
169
+ Open3.popen2e(*sftp_args) do |writer, reader, waith_thr|
170
+ writer.puts(password) if password
171
+ # Give time for password to be processed and stdin to be passed to sftp process.
172
+ sleep self.class.sshpass_wait_seconds
173
+ writer.puts "put #{local_file_name.inspect} #{remote_file_name.inspect}"
174
+ writer.puts 'bye'
175
+ writer.close
176
+ out = reader.read.chomp
177
+ raise(Errors::CommunicationsFailure, "Failed calling #{self.class.sftp_bin} via #{self.class.sshpass_bin}: #{out}") unless waith_thr.value.success?
178
+ out
97
179
  end
98
- result
180
+ end
181
+
182
+ def sftp_args
183
+ args = [self.class.sshpass_bin, self.class.sftp_bin, '-oBatchMode=no']
184
+ # Force it to use the password when supplied.
185
+ args << "-oPubkeyAuthentication=no" if password
186
+ ssh_options.each_pair { |key, value| args << "-o#{key}=#{value}" }
187
+ args << '-b'
188
+ args << '-'
189
+ args << "#{username}@#{hostname}"
190
+ args
191
+ end
192
+
193
+ def build_ssh_options
194
+ options = ssh_options.dup
195
+ options[:logger] ||= self.logger if defined?(SemanticLogger)
196
+ options[:port] ||= @port
197
+ options[:max_pkt_size] ||= 65_536
198
+ options[:password] ||= @password
199
+ options
99
200
  end
100
201
  end
101
202
  end
@@ -239,12 +239,15 @@ module IOStreams
239
239
  # ascii: [true|false]
240
240
  # Whether to export as ASCII text instead of binary format
241
241
  # Default: true
242
- def self.export(email:, ascii: true)
242
+ def self.export(email:, ascii: true, private: false, passphrase: nil)
243
243
  version_check
244
244
 
245
- armor = ascii ? '--armor' : nil
246
- loopback = pgp_version.to_f >= 2.1 ? '--pinentry-mode loopback' : ''
247
- command = "#{executable} #{loopback} --no-tty --passphrase-fd 0 --batch #{armor} --export #{email}"
245
+ command = "#{executable} "
246
+ command << '--pinentry-mode loopback ' if pgp_version.to_f >= 2.1
247
+ command << '--armor ' if ascii
248
+ command << "--no-tty --batch --passphrase"
249
+ command << (passphrase ? " #{passphrase} " : "-fd 0 ")
250
+ command << (private ? "--export-secret-keys #{email}" : "--export #{email}")
248
251
 
249
252
  out, err, status = Open3.capture3(command, binmode: true)
250
253
  logger.debug { "IOStreams::Pgp.export: #{command}\n#{err}" } if logger
@@ -117,7 +117,7 @@ module IOStreams
117
117
  class_for_stream(type, stream).stream(io_stream, opts, &block)
118
118
  else
119
119
  # Daisy chain multiple streams together
120
- last = pipeline.keys.inject(block) { |inner, stream| ->(io) { class_for_stream(type, stream).stream(io, pipeline[stream], &inner) } }
120
+ last = pipeline.keys.inject(block) { |inner, stream_sym| ->(io) { class_for_stream(type, stream_sym).stream(io, pipeline[stream_sym], &inner) } }
121
121
  last.call(io_stream)
122
122
  end
123
123
  end
@@ -4,7 +4,7 @@ module IOStreams
4
4
  # read from a file/stream using Symmetric Encryption
5
5
  def self.stream(input_stream, **args, &block)
6
6
  unless defined?(SymmetricEncryption)
7
- Utils.load_dependency('symmetric-encryption', '.enc streaming')
7
+ Utils.load_soft_dependency('symmetric-encryption', '.enc streaming')
8
8
  end
9
9
 
10
10
  ::SymmetricEncryption::Reader.open(input_stream, **args, &block)
@@ -6,7 +6,7 @@ module IOStreams
6
6
  # If the input_stream is already compressed consider setting compress: false.
7
7
  def self.stream(input_stream, compress: true, **args, &block)
8
8
  unless defined?(SymmetricEncryption)
9
- Utils.load_dependency('symmetric-encryption', '.enc streaming')
9
+ Utils.load_soft_dependency('symmetric-encryption', '.enc streaming')
10
10
  end
11
11
 
12
12
  ::SymmetricEncryption::Writer.open(input_stream, compress: compress, **args, &block)
@@ -16,7 +16,7 @@ module IOStreams
16
16
  # By default the output stream is compressed unless the file_name extension indicates the file is already compressed.
17
17
  def self.file(file_name, compress: nil, **args, &block)
18
18
  unless defined?(SymmetricEncryption)
19
- Utils.load_dependency('symmetric-encryption', '.enc streaming')
19
+ Utils.load_soft_dependency('symmetric-encryption', '.enc streaming')
20
20
  end
21
21
 
22
22
  ::SymmetricEncryption::Writer.open(file_name, compress: compress, **args, &block)
@@ -1,7 +1,7 @@
1
1
  module IOStreams
2
2
  module Utils
3
3
  # Lazy load dependent gem so that it remains a soft dependency.
4
- def self.load_dependency(gem_name, stream_type, require_name = gem_name)
4
+ def self.load_soft_dependency(gem_name, stream_type, require_name = gem_name)
5
5
  require require_name
6
6
  rescue LoadError => e
7
7
  raise(LoadError, "Please install the gem '#{gem_name}' to support #{stream_type}. #{e.message}")
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = "1.0.0.beta2".freeze
2
+ VERSION = "1.0.0.beta3".freeze
3
3
  end
@@ -37,7 +37,7 @@ module IOStreams
37
37
  # The input stream from the first file found in the zip file is passed
38
38
  # to the supplied block
39
39
  def self.file(file_name, entry_file_name: nil)
40
- Utils.load_dependency('rubyzip', 'Zip', 'zip') unless defined?(::Zip)
40
+ Utils.load_soft_dependency('rubyzip', 'Zip', 'zip') unless defined?(::Zip)
41
41
 
42
42
  ::Zip::InputStream.open(file_name) do |zin|
43
43
  get_entry(zin, entry_file_name) ||
@@ -46,7 +46,7 @@ module IOStreams
46
46
  end
47
47
  else
48
48
  def self.write_file(file_name, entry_file_name)
49
- Utils.load_dependency('rubyzip', 'Zip', 'zip') unless defined?(::Zip)
49
+ Utils.load_soft_dependency('rubyzip', 'Zip', 'zip') unless defined?(::Zip)
50
50
 
51
51
  zos = ::Zip::OutputStream.new(file_name)
52
52
  zos.put_next_entry(entry_file_name)
@@ -61,6 +61,23 @@ module IOStreams
61
61
  assert_equal :s3, path
62
62
  end
63
63
  end
64
+
65
+ describe '.temp_file' do
66
+ it 'returns value from block' do
67
+ result = IOStreams.temp_file('base', '.ext') { |_path| 257 }
68
+ assert_equal 257, result
69
+ end
70
+
71
+ it 'supplies new temp file_name' do
72
+ path1 = nil
73
+ path2 = nil
74
+ IOStreams.temp_file('base', '.ext') { |path| path1 = path }
75
+ IOStreams.temp_file('base', '.ext') { |path| path2 = path }
76
+ refute_equal path1.to_s, path2.to_s
77
+ assert path1.is_a?(IOStreams::Paths::File), path1
78
+ assert path2.is_a?(IOStreams::Paths::File), path2
79
+ end
80
+ end
64
81
  end
65
82
  end
66
83
  end
@@ -17,23 +17,6 @@ module Paths
17
17
  path
18
18
  end
19
19
 
20
- describe '.temp_file' do
21
- it 'returns value from block' do
22
- result = IOStreams::Paths::File.temp_file('base', '.ext') { |_path| 257 }
23
- assert_equal 257, result
24
- end
25
-
26
- it 'supplies new temp file_name' do
27
- path1 = nil
28
- path2 = nil
29
- IOStreams::Paths::File.temp_file('base', '.ext') { |path| path1 = path }
30
- IOStreams::Paths::File.temp_file('base', '.ext') { |path| path2 = path }
31
- refute_equal path1.to_s, path2.to_s
32
- assert path1.is_a?(IOStreams::Paths::File), path1
33
- assert path2.is_a?(IOStreams::Paths::File), path2
34
- end
35
- end
36
-
37
20
  describe '#each_child' do
38
21
  it 'iterates an empty path' do
39
22
  none = nil
@@ -0,0 +1,78 @@
1
+ require_relative '../test_helper'
2
+
3
+ module Paths
4
+ class SFTPTest < Minitest::Test
5
+ describe IOStreams::Paths::SFTP do
6
+ before do
7
+ unless ENV["SFTP_HOSTNAME"]
8
+ skip "Supply environment variables to test SFTP paths: SFTP_HOSTNAME, SFTP_USERNAME, SFTP_PASSWORD, and optional SFTP_DIR"
9
+ end
10
+ end
11
+
12
+ let(:host_name) { ENV["SFTP_HOSTNAME"] }
13
+ let(:username) { ENV["SFTP_USERNAME"] }
14
+ let(:password) { ENV["SFTP_PASSWORD"] }
15
+ let(:ftp_dir) { ENV["SFTP_DIR"] || "iostreams_test"}
16
+ let(:url) { File.join("sftp://", host_name, ftp_dir) }
17
+
18
+ let :file_name do
19
+ File.join(File.dirname(__FILE__), '..', 'files', 'text.txt')
20
+ end
21
+
22
+ let :raw do
23
+ File.read(file_name)
24
+ end
25
+
26
+ let(:root_path) { IOStreams::Paths::SFTP.new(url, username: username, password: password, ruby: false) }
27
+
28
+ let :existing_path do
29
+ path = root_path.join('test.txt')
30
+ path.write(raw)
31
+ path
32
+ end
33
+
34
+ let :missing_path do
35
+ root_path.join("unknown_path", "test_file.txt")
36
+ end
37
+
38
+ let :missing_file_path do
39
+ root_path.join("test_file.txt")
40
+ end
41
+
42
+ let :write_path do
43
+ root_path.join("writer_test.txt")
44
+ end
45
+
46
+ describe '#reader' do
47
+ it 'reads' do
48
+ assert_equal raw, existing_path.reader { |io| io.read }
49
+ end
50
+
51
+ it 'fails when the file does not exist' do
52
+ assert_raises IOStreams::Errors::CommunicationsFailure do
53
+ missing_file_path.read
54
+ end
55
+ end
56
+
57
+ it 'fails when the directory does not exist' do
58
+ assert_raises IOStreams::Errors::CommunicationsFailure do
59
+ missing_path.read
60
+ end
61
+ end
62
+ end
63
+
64
+ describe '#writer' do
65
+ it 'writes' do
66
+ assert_equal raw.size, write_path.writer { |io| io.write(raw) }
67
+ assert_equal raw, write_path.read
68
+ end
69
+
70
+ it 'fails when the directory does not exist' do
71
+ assert_raises IOStreams::Errors::CommunicationsFailure do
72
+ missing_path.write("Bad path")
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
data/test/stream_test.rb CHANGED
@@ -47,7 +47,7 @@ class StreamTest < Minitest::Test
47
47
  result = IOStreams::Stream.new(io).
48
48
  file_name(multiple_zip_file_name).
49
49
  option(:zip, entry_file_name: 'test.json').
50
- reader { |io| io.read }
50
+ read
51
51
  assert_equal contents_test_json, result
52
52
  end
53
53
  end
@@ -56,7 +56,7 @@ class StreamTest < Minitest::Test
56
56
  File.open(zip_gz_file_name, 'rb') do |io|
57
57
  result = IOStreams::Stream.new(io).
58
58
  file_name(zip_gz_file_name).
59
- reader { |io| io.read }
59
+ read
60
60
  assert_equal contents_test_txt, result
61
61
  end
62
62
  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.beta2
4
+ version: 1.0.0.beta3
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-24 00:00:00.000000000 Z
11
+ date: 2019-11-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -94,6 +94,7 @@ files:
94
94
  - test/paths/http_test.rb
95
95
  - test/paths/matcher_test.rb
96
96
  - test/paths/s3_test.rb
97
+ - test/paths/sftp_test.rb
97
98
  - test/pgp_reader_test.rb
98
99
  - test/pgp_test.rb
99
100
  - test/pgp_writer_test.rb
@@ -147,6 +148,7 @@ test_files:
147
148
  - test/stream_test.rb
148
149
  - test/paths/matcher_test.rb
149
150
  - test/paths/s3_test.rb
151
+ - test/paths/sftp_test.rb
150
152
  - test/paths/file_test.rb
151
153
  - test/paths/http_test.rb
152
154
  - test/record_reader_test.rb