iostreams 1.0.0.beta2 → 1.0.0.beta3

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: 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