iostreams 0.17.3 → 0.18.0

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: e5c25f9652a6f8e21948dbbf18121093520dff467ea887959f9d9719311046e8
4
- data.tar.gz: 1cb168e667ade3af6b85d81661fcfe9d76c1df62a30e1e4bf520a6b9c463d448
3
+ metadata.gz: e9c9876f8e66281fabd3a26188f1e51d23a12620de461147c9125286d58950cb
4
+ data.tar.gz: 81f7b6b198f4dfa37b5349de57df74d2004e7984fd176172ac61cce2c7dac9d8
5
5
  SHA512:
6
- metadata.gz: d86ecd8059751b9080853ab3a6f96f6241af6fb8c610798a0f401b647c08469c1b1f828c08acc723669994be3debc7b2e5e76826146fb70a64594d6197ef00db
7
- data.tar.gz: 190fd0f23a41c3f8e30f2982c474601268b0e7e435f51b344ae1925ebef78fc9b03d62514d478d01c689eae74ceaff86b42258150d09ec8eefa16f1bcd3912c2
6
+ metadata.gz: 91c03cfc1c218d235b2bda1ab304ebc78a8d02dbb9902372a2b5c6b5dd0afb53fcee99ed335cc9361ff2e9db9213cced9fad901a9ecf48bd56f53043792e6096
7
+ data.tar.gz: 9773ffd737484920543182b8b6a32c4704cc42e83fb2be014e86447eedf184458aa6a570b129b3fa55fc44148c25862730c9af937578469467e4f54f8628ef60
@@ -9,7 +9,7 @@ module IOStreams
9
9
  raise(LoadError, "Please install the 'rbzip2' gem for Bzip2 streaming support. #{e.message}")
10
10
  end
11
11
 
12
- if IOStreams.reader_stream?(file_name_or_io)
12
+ if IOStreams.writer_stream?(file_name_or_io)
13
13
  begin
14
14
  io = RBzip2.default_adapter::Compressor.new(file_name_or_io)
15
15
  block.call(io)
@@ -17,13 +17,12 @@ module IOStreams
17
17
  io.close
18
18
  end
19
19
  else
20
- ::File.open(file_name_or_io, 'wb') do |file|
20
+ IOStreams::File::Writer.open(file_name_or_io) do |file|
21
21
  io = RBzip2.default_adapter::Compressor.new(file)
22
22
  block.call(io)
23
23
  io.close
24
24
  end
25
25
  end
26
-
27
26
  end
28
27
  end
29
28
  end
@@ -2,8 +2,6 @@ module IOStreams
2
2
  module File
3
3
  class Reader
4
4
  # Read from a named file
5
- # TODO: Add support for mode (text / binary)
6
- # TODO: Add encoding support: external_encoding, internal_encoding
7
5
  def self.open(file_name, **args, &block)
8
6
  raise(ArgumentError, 'File name must be a string') unless file_name.is_a?(String)
9
7
 
@@ -2,10 +2,20 @@ module IOStreams
2
2
  module File
3
3
  class Writer
4
4
  # Write to a named file
5
+ #
6
+ # Note:
7
+ # If an exception is raised whilst the file is being written to the file is removed to
8
+ # prevent incomplete / partial files from being created.
5
9
  def self.open(file_name, **args, &block)
6
10
  raise(ArgumentError, 'File name must be a string') unless file_name.is_a?(String)
7
11
 
8
- ::File.open(file_name, 'wb', &block)
12
+ IOStreams.mkpath(file_name)
13
+ begin
14
+ ::File.open(file_name, 'wb', &block)
15
+ rescue StandardError => e
16
+ File.unlink(file_name) if File.exist?(file_name)
17
+ raise(e)
18
+ end
9
19
  end
10
20
  end
11
21
  end
@@ -4,6 +4,7 @@ module IOStreams
4
4
  # Write to a file / stream, compressing with GZip
5
5
  def self.open(file_name_or_io, **args, &block)
6
6
  unless IOStreams.writer_stream?(file_name_or_io)
7
+ IOStreams.mkpath(file_name_or_io)
7
8
  Zlib::GzipWriter.open(file_name_or_io, &block)
8
9
  else
9
10
  begin
@@ -1,9 +1,5 @@
1
1
  require 'concurrent'
2
- # Load Symmetric Encryption if present so that its reader and writer can be registered
3
- begin
4
- require 'symmetric-encryption'
5
- rescue LoadError
6
- end
2
+ require 'fileutils'
7
3
 
8
4
  # Streaming library for Ruby
9
5
  #
@@ -473,6 +469,12 @@ module IOStreams
473
469
  end
474
470
  end
475
471
 
472
+ # Used by writers that can write directly to file to create the target path
473
+ def self.mkpath(file_name)
474
+ path = ::File.dirname(file_name)
475
+ FileUtils.mkdir_p(path) unless ::File.exist?(path)
476
+ end
477
+
476
478
  private
477
479
 
478
480
  # A registry to hold formats for processing files during upload or download
@@ -484,7 +486,7 @@ module IOStreams
484
486
 
485
487
  # Returns a reader or writer stream
486
488
  def self.stream(type, file_name_or_io, streams:, file_name:, encoding: nil, encode_cleaner: nil, encode_replace: nil, &block)
487
- # TODO: Add support for different schemes, such as file://, s3://, sftp://
489
+ raise(ArgumentError, 'IOStreams call is missing mandatory block') if block.nil?
488
490
 
489
491
  streams = streams_for_file_name(file_name) if streams.nil? && file_name
490
492
 
@@ -565,6 +567,7 @@ module IOStreams
565
567
 
566
568
  # Register File extensions
567
569
  register_extension(:bz2, IOStreams::Bzip2::Reader, IOStreams::Bzip2::Writer)
570
+ register_extension(:enc, IOStreams::SymmetricEncryption::Reader, IOStreams::SymmetricEncryption::Writer)
568
571
  register_extension(:gz, IOStreams::Gzip::Reader, IOStreams::Gzip::Writer)
569
572
  register_extension(:gzip, IOStreams::Gzip::Reader, IOStreams::Gzip::Writer)
570
573
  register_extension(:zip, IOStreams::Zip::Reader, IOStreams::Zip::Writer)
@@ -573,12 +576,6 @@ module IOStreams
573
576
  register_extension(:xlsx, IOStreams::Xlsx::Reader, nil)
574
577
  register_extension(:xlsm, IOStreams::Xlsx::Reader, nil)
575
578
 
576
- # Use Symmetric Encryption to encrypt of decrypt files with the `enc` extension
577
- # when the gem `symmetric-encryption` has been loaded.
578
- if defined?(SymmetricEncryption)
579
- register_extension(:enc, SymmetricEncryption::Reader, SymmetricEncryption::Writer)
580
- end
581
-
582
579
  # Support URI schemes
583
580
  #
584
581
  # Examples:
@@ -45,38 +45,39 @@ module IOStreams
45
45
  # compress_level: [Integer]
46
46
  # Compression level
47
47
  # Default: 6
48
- def self.open(file_name_or_io, recipient:, signer: default_signer, signer_passphrase: default_signer_passphrase, binary: true, compression: :zip, compress_level: 6)
48
+ def self.open(file_name, recipient:, signer: default_signer, signer_passphrase: default_signer_passphrase, binary: true, compression: :zip, compress_level: 6)
49
49
  compress_level = 0 if compression == :none
50
- if IOStreams.writer_stream?(file_name_or_io)
50
+ if IOStreams.writer_stream?(file_name)
51
51
  raise(NotImplementedError, 'Can only PGP Encrypt directly to a file name. Output to streams are not yet supported.')
52
- else
53
- # Write to stdin, with encrypted contents being written to the file
54
- command = "#{IOStreams::Pgp.executable} --batch --no-tty --yes --encrypt"
55
- command << " --sign --local-user \"#{signer}\"" if signer
56
- if signer_passphrase
57
- command << " --pinentry-mode loopback" if IOStreams::Pgp.pgp_version.to_f >= 2.1
58
- command << " --passphrase \"#{signer_passphrase}\""
59
- end
60
- command << " -z #{compress_level}" if compress_level != 6
61
- command << " --compress-algo #{compression}" unless compression == :none
62
- command << " --recipient \"#{recipient}\" -o \"#{file_name_or_io}\""
52
+ end
53
+ IOStreams.mkpath(file_name)
63
54
 
64
- IOStreams::Pgp.logger.debug { "IOStreams::Pgp::Writer.open: #{command}" } if IOStreams::Pgp.logger
55
+ # Write to stdin, with encrypted contents being written to the file
56
+ command = "#{IOStreams::Pgp.executable} --batch --no-tty --yes --encrypt"
57
+ command << " --sign --local-user \"#{signer}\"" if signer
58
+ if signer_passphrase
59
+ command << " --pinentry-mode loopback" if IOStreams::Pgp.pgp_version.to_f >= 2.1
60
+ command << " --passphrase \"#{signer_passphrase}\""
61
+ end
62
+ command << " -z #{compress_level}" if compress_level != 6
63
+ command << " --compress-algo #{compression}" unless compression == :none
64
+ command << " --recipient \"#{recipient}\" -o \"#{file_name}\""
65
65
 
66
- Open3.popen2e(command) do |stdin, out, waith_thr|
67
- begin
68
- stdin.binmode if binary
69
- yield(stdin)
70
- stdin.close
71
- rescue Errno::EPIPE
72
- # Ignore broken pipe because gpg terminates early due to an error
73
- ::File.delete(file_name_or_io) if ::File.exist?(file_name_or_io)
74
- raise(Pgp::Failure, "GPG Failed writing to encrypted file: #{file_name_or_io}: #{out.read.chomp}")
75
- end
76
- unless waith_thr.value.success?
77
- ::File.delete(file_name_or_io) if ::File.exist?(file_name_or_io)
78
- raise(Pgp::Failure, "GPG Failed to create encrypted file: #{file_name_or_io}: #{out.read.chomp}")
79
- end
66
+ IOStreams::Pgp.logger.debug { "IOStreams::Pgp::Writer.open: #{command}" } if IOStreams::Pgp.logger
67
+
68
+ Open3.popen2e(command) do |stdin, out, waith_thr|
69
+ begin
70
+ stdin.binmode if binary
71
+ yield(stdin)
72
+ stdin.close
73
+ rescue Errno::EPIPE
74
+ # Ignore broken pipe because gpg terminates early due to an error
75
+ ::File.delete(file_name) if ::File.exist?(file_name)
76
+ raise(Pgp::Failure, "GPG Failed writing to encrypted file: #{file_name}: #{out.read.chomp}")
77
+ end
78
+ unless waith_thr.value.success?
79
+ ::File.delete(file_name) if ::File.exist?(file_name)
80
+ raise(Pgp::Failure, "GPG Failed to create encrypted file: #{file_name}: #{out.read.chomp}")
80
81
  end
81
82
  end
82
83
  end
@@ -67,7 +67,7 @@ module IOStreams
67
67
  # aborted and this error is raised. The raised error has a `#errors`
68
68
  # method that returns the failures that caused the upload to be
69
69
  # aborted.
70
- def self.open(uri, region: nil, **args, &block)
70
+ def self.open(uri, region: nil, **args)
71
71
  raise(ArgumentError, 'file_name must be a URI string') unless uri.is_a?(String)
72
72
 
73
73
  IOStreams::S3.load_dependencies
@@ -75,7 +75,10 @@ module IOStreams
75
75
  options = IOStreams::S3.parse_uri(uri)
76
76
  s3 = region.nil? ? Aws::S3::Resource.new : Aws::S3::Resource.new(region: region)
77
77
  object = s3.bucket(options[:bucket]).object(options[:key])
78
- object.upload_stream(args, &block)
78
+ object.upload_stream(args) do |s3|
79
+ s3.binmode
80
+ yield(s3)
81
+ end
79
82
  end
80
83
  end
81
84
  end
@@ -0,0 +1,22 @@
1
+ module IOStreams
2
+ module SymmetricEncryption
3
+ class Reader
4
+ # read from a file/stream using Symmetric Encryption
5
+ def self.open(file_name_or_io, **args, &block)
6
+ begin
7
+ require 'symmetric-encryption' unless defined?(SymmetricEncryption)
8
+ rescue LoadError => e
9
+ raise(LoadError, "Please install the 'symmetric-encryption' gem for .enc streaming support. #{e.message}")
10
+ end
11
+
12
+ if IOStreams.reader_stream?(file_name_or_io)
13
+ ::SymmetricEncryption::Reader.open(file_name_or_io, **args, &block)
14
+ else
15
+ IOStreams::File::Reader.open(file_name_or_io) do |file|
16
+ ::SymmetricEncryption::Reader.open(file, **args, &block)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ module IOStreams
2
+ module SymmetricEncryption
3
+ class Writer
4
+ # Write to file/stream using Symmetric Encryption
5
+ def self.open(file_name_or_io, compress: nil, **args, &block)
6
+ begin
7
+ require 'symmetric-encryption' unless defined?(SymmetricEncryption)
8
+ rescue LoadError => e
9
+ raise(LoadError, "Please install the 'symmetric-encryption' gem for .enc streaming support. #{e.message}")
10
+ end
11
+
12
+ if IOStreams.writer_stream?(file_name_or_io)
13
+ compress = true if compress.nil?
14
+ ::SymmetricEncryption::Writer.open(file_name_or_io, compress: compress, **args, &block)
15
+ else
16
+ compress = !IOStreams.compressed?(file_name_or_io) if compress.nil?
17
+
18
+ IOStreams::File::Writer.open(file_name_or_io) do |file|
19
+ ::SymmetricEncryption::Writer.open(file, compress: compress, **args, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = '0.17.3'
2
+ VERSION = '0.18.0'
3
3
  end
@@ -69,6 +69,7 @@ module IOStreams
69
69
 
70
70
  else
71
71
  def self.write_file(file_name, zip_file_name, &block)
72
+ IOStreams.mkpath(file_name)
72
73
  zos = ::Zip::OutputStream.new(file_name)
73
74
  zos.put_next_entry(zip_file_name)
74
75
  block.call(zos)
@@ -42,6 +42,10 @@ module IOStreams
42
42
  autoload :Reader, 'io_streams/row/reader'
43
43
  autoload :Writer, 'io_streams/row/writer'
44
44
  end
45
+ module SymmetricEncryption
46
+ autoload :Reader, 'io_streams/symmetric_encryption/reader'
47
+ autoload :Writer, 'io_streams/symmetric_encryption/writer'
48
+ end
45
49
  module Xlsx
46
50
  autoload :Reader, 'io_streams/xlsx/reader'
47
51
  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: 0.17.3
4
+ version: 0.18.0
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-07-22 00:00:00.000000000 Z
11
+ date: 2019-08-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -58,7 +58,8 @@ files:
58
58
  - lib/io_streams/s3/writer.rb
59
59
  - lib/io_streams/sftp/reader.rb
60
60
  - lib/io_streams/sftp/writer.rb
61
- - lib/io_streams/streams.rb
61
+ - lib/io_streams/symmetric_encryption/reader.rb
62
+ - lib/io_streams/symmetric_encryption/writer.rb
62
63
  - lib/io_streams/tabular.rb
63
64
  - lib/io_streams/tabular/header.rb
64
65
  - lib/io_streams/tabular/parser/array.rb
@@ -1,109 +0,0 @@
1
- module IOStreams
2
- # Contains behavior for streams
3
- #
4
- # When a file is being read the streams are processed from right to left.
5
- # When writing a file streams are processed left to right.
6
- # For example:
7
- # file.gz.enc ==> [:gz, :enc]
8
- # Read: Unencrypt, then Gunzip
9
- # Write: GZip, then Encrypt
10
- class Streams
11
-
12
- # Returns [Streams] collection of streams to process against the file
13
- #
14
- def self.streams_for_file_name(file_name)
15
-
16
- end
17
-
18
- # Create a processing stream given:
19
- # - No stream. Defaults to :file
20
- # - A single String implies a file_name and the streams will be created based on the file_name
21
- # - One or more symbols or hashes for a stream
22
- # - One or more arrays for streams
23
- def initialize(*args)
24
- if args.size == 0
25
- @streams = [:file]
26
- elsif args.size == 1
27
- stream = args.first
28
- if stream
29
- @stream = stream.is_a?(String) ? streams_for_file_name(stream) : Array(stream)
30
- else
31
- @streams = [:file]
32
- end
33
- else
34
- @streams = streams
35
- end
36
- @streams.flatten!
37
- end
38
-
39
- def delimited?
40
-
41
- end
42
-
43
- def delete(stream)
44
-
45
- end
46
-
47
- # Add another stream for processing
48
- def <<(stream)
49
-
50
- end
51
-
52
- # Add a stream for processing
53
- def unshift(stream)
54
-
55
- end
56
-
57
- private
58
-
59
- # Return the Stream klass for the specified hash or symbol
60
- # Parameters
61
- # stream [Hash|Symbol]
62
- def stream_for(stream)
63
- if stream.is_a?(Symbol)
64
- registered_klass(stream, {})
65
- else
66
- registered_klass(@stream.first)
67
- end
68
- end
69
-
70
- # Returns [Array] the formats required to process the file by looking at
71
- # its extension(s)
72
- #
73
- # Extensions supported:
74
- # .zip Zip File [ :zip ]
75
- # .gz, .gzip GZip File [ :gzip ]
76
- # .enc File Encrypted using symmetric encryption [ :enc ]
77
- # other All other extensions will be returned as: [ :file ]
78
- #
79
- # When a file is encrypted, it may also be compressed:
80
- # .zip.enc [ :zip, :enc ]
81
- # .gz.enc [ :gz, :enc ]
82
- #
83
- # Example Zip file:
84
- # IOStreams.streams_for_file_name('myfile.zip')
85
- # => [ :zip ]
86
- #
87
- # Example Encrypted Gzip file:
88
- # IOStreams.streams_for_file_name('myfile.csv.gz.enc')
89
- # => [ :gz, :enc ]
90
- #
91
- # Example plain text / binary file:
92
- # IOStreams.streams_for_file_name('myfile.csv')
93
- # => [ :file ]
94
- def streams_for_file_name(file_name)
95
- raise ArgumentError.new("Cannot auto-detect streams when already a stream: #{file_name.inspect}") if reader_stream?(file_name)
96
-
97
- parts = file_name.split('.')
98
- extensions = []
99
- while extension = parts.pop
100
- break unless @extensions[extension.to_sym]
101
- extensions.unshift(extension.to_sym)
102
- end
103
- extensions << :file if extensions.size == 0
104
- extensions
105
- end
106
-
107
-
108
- end
109
- end