iostreams 0.17.3 → 0.18.0

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