iostreams 0.19.0 → 0.20.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: 161967be051ed1b82c87f30afd32b760a2e6627cf8e090e978578c7c35aab278
4
- data.tar.gz: abb7aab7f5aca8cc0d043ce3820603c86b3227c9db41d07420f3117010bf68af
3
+ metadata.gz: b16ce2bb98466540950ee6a40c9b3066630e99e7987c93621b3a175b5b027731
4
+ data.tar.gz: 6fb4dd208cc04a71d78ac1355f8035159a97b67f2a749dc922df3f6edf7208ac
5
5
  SHA512:
6
- metadata.gz: 2172e682359bfe1240669fee5da16f807b5a04b940529c5a7046166d5343e7c80bd20c52f05243b59c72f7cfe57ac288603ac6475e75f413f8f99031f3e1cbd0
7
- data.tar.gz: 76923520dfda93c00b4209424496c5324ae7975b59f0f8653578652b540c07a9dd6866890b72c147ab5c968e48879210e3aaa85ab8c8ffbe4b4921524352eb32
6
+ metadata.gz: e58da1ef631c0eae5574194e6586dbf7b52becc934a01a00ede0ef0f665d14f08391a5a2fee645bbe8ffe8fe8452c6d4b79eb84396f1e94e9b98ad9dea229041
7
+ data.tar.gz: d821299bb5742832125ff2b7a6826f68b734967ea7a5f5d94cf1b619ef3ad82dfd2c457455a5d62523ca52832f5d17b7c2f343f5f387f77abdbeaca6bff42dd7
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+ module IOStreams
3
+ class BasePath
4
+ attr_reader :path
5
+
6
+ def initialize(path)
7
+ @path = path.frozen? ? path : path.dup.freeze
8
+ end
9
+
10
+ # If elements already contains the current path then it is used as is without
11
+ # adding the current path for a second time
12
+ def join(*elements)
13
+ return self if elements.empty?
14
+
15
+ relative = ::File.join(*elements)
16
+ if relative.start_with?(path)
17
+ self.class.new(relative)
18
+ else
19
+ self.class.new(::File.join(path, relative))
20
+ end
21
+ end
22
+
23
+ def to_s
24
+ path
25
+ end
26
+
27
+ # Removes the last element of the path, the file name, before creating the entire path.
28
+ # Returns self
29
+ def mkpath
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # Assumes the current path does not include a file name, and creates all elements in the path.
34
+ # Returns self
35
+ #
36
+ # Note: Do not call this method if the path contains a file name, see `#mkpath`
37
+ def mkdir
38
+ raise NotImplementedError
39
+ end
40
+
41
+ # Returns [true|false] whether the file exists
42
+ def exist?
43
+ raise NotImplementedError
44
+ end
45
+
46
+ # Returns [Integer] size of the file
47
+ def size
48
+ raise NotImplementedError
49
+ end
50
+
51
+ # Delete the file.
52
+ # Returns self
53
+ #
54
+ # Notes:
55
+ # * No error is raised if the file is not present.
56
+ # * Only the file is removed, not any of the parent paths.
57
+ def delete
58
+ raise NotImplementedError
59
+ end
60
+
61
+ # Return a reader for this path
62
+ def reader(**args, &block)
63
+ IOStreams.reader(path, **args, &block)
64
+ end
65
+
66
+ # Return a writer for this path
67
+ def writer(**args, &block)
68
+ IOStreams.writer(path, **args, &block)
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+ require 'fileutils'
3
+
4
+ module IOStreams
5
+ module File
6
+ class Path < IOStreams::BasePath
7
+ # Yields the path to a temporary file_name.
8
+ #
9
+ # File is deleted upon completion if present.
10
+ def self.temp_file_name(basename, extension = '')
11
+ result = nil
12
+ ::Dir::Tmpname.create([basename, extension]) do |tmpname|
13
+ begin
14
+ result = yield(tmpname)
15
+ ensure
16
+ ::File.unlink(tmpname) if ::File.exist?(tmpname)
17
+ end
18
+ end
19
+ result
20
+ end
21
+
22
+ # Used by writers that can write directly to file to create the target path
23
+ def self.mkpath(path)
24
+ dir = ::File.dirname(path)
25
+ FileUtils.mkdir_p(dir) unless ::File.exist?(dir)
26
+ end
27
+
28
+ def mkpath
29
+ self.class.mkpath(path)
30
+ self
31
+ end
32
+
33
+ def mkdir
34
+ FileUtils.mkdir_p(path) unless ::File.exist?(path)
35
+ self
36
+ end
37
+
38
+ def exist?
39
+ ::File.exist?(path)
40
+ end
41
+
42
+ def size
43
+ ::File.size(path)
44
+ end
45
+
46
+ def delete(recursively: false)
47
+ return self unless ::File.exist?(path)
48
+
49
+ if ::File.directory?(path)
50
+ recursively ? FileUtils.remove_dir(path) : Dir.delete(path)
51
+ else
52
+ ::File.unlink(path)
53
+ end
54
+ self
55
+ end
56
+ end
57
+ end
58
+ end
@@ -9,7 +9,7 @@ module IOStreams
9
9
  def self.open(file_name, **args, &block)
10
10
  raise(ArgumentError, 'File name must be a string') unless file_name.is_a?(String)
11
11
 
12
- IOStreams.mkpath(file_name)
12
+ IOStreams::File::Path.mkpath(file_name)
13
13
  begin
14
14
  ::File.open(file_name, 'wb', &block)
15
15
  rescue StandardError => e
@@ -4,7 +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
+ IOStreams::File::Path.mkpath(file_name_or_io)
8
8
  Zlib::GzipWriter.open(file_name_or_io, &block)
9
9
  else
10
10
  begin
@@ -55,7 +55,7 @@ module IOStreams
55
55
  raise(IOStreams::Errors::CommunicationsFailure, "Invalid response code: #{response.code}") unless response.is_a?(Net::HTTPSuccess)
56
56
 
57
57
  # Since Net::HTTP download only supports a push stream, write it to a tempfile first.
58
- IOStreams::Path.temp_file_name('iostreams_http') do |file_name|
58
+ IOStreams::File::Path.temp_file_name('iostreams_http') do |file_name|
59
59
  IOStreams::File::Writer.open(file_name) do |io|
60
60
  response.read_body { |chunk| io.write(chunk) }
61
61
  end
@@ -425,7 +425,52 @@ module IOStreams
425
425
  end
426
426
  end
427
427
 
428
- Extension = Struct.new(:reader_class, :writer_class)
428
+ # Return the complete path including supplied elements.
429
+ #
430
+ # Example:
431
+ # IOStreams.path('sample')
432
+ # # => "/default_root/sample"
433
+ #
434
+ # IOStreams.path('file.xls')
435
+ # # => "/default_root/file.xls"
436
+ #
437
+ # IOStreams.path('sample', root: :ftp)
438
+ # # => "/ftp_root/sample"
439
+ #
440
+ # IOStreams.path('sample', 'file.xls', root: :ftp)
441
+ # # => "/ftp_root/sample/file.xls"
442
+ #
443
+ # Notes:
444
+ # * Add the root path first against which this path is permitted to operate.
445
+ # `IOStreams.add_root_path(:default, "/usr/local/var/files")`
446
+ def self.path(*elements, root: :default)
447
+ root_path(root).join(*elements)
448
+ end
449
+
450
+ # When using a full path, without needing the root prefixed.
451
+ def self.full_path(*elements)
452
+ path = ::File.join(*elements)
453
+ uri = URI.parse(path)
454
+ IOStreams.scheme(uri.scheme).path_class.new(path)
455
+ end
456
+
457
+ # Return named root path
458
+ def self.root_path(root = :default)
459
+ @roots_paths[root.to_sym] || raise(ArgumentError, "Unknown root: #{root.inspect}")
460
+ end
461
+
462
+ # Add a named root path
463
+ def self.add_root_path(root, path)
464
+ raise(ArgumentError, "Invalid root name #{root.inspect}") unless root.to_s =~ /\A\w+\Z/
465
+
466
+ uri = URI.parse(path.to_s)
467
+ path_class = IOStreams.scheme(uri.scheme).path_class
468
+ @roots_paths[root.to_sym] = path_class.new(path)
469
+ end
470
+
471
+ def self.root_paths
472
+ @roots_paths.dup
473
+ end
429
474
 
430
475
  # Register a file extension and the reader and writer streaming classes
431
476
  #
@@ -453,34 +498,23 @@ module IOStreams
453
498
  # Example:
454
499
  # # MyXls::Reader and MyXls::Writer must implement .open
455
500
  # register_extension(:xls, MyXls::Reader, MyXls::Writer)
456
- def self.register_scheme(scheme, reader_class, writer_class)
501
+ def self.register_scheme(scheme, reader_class, writer_class, path_class = nil)
457
502
  raise(ArgumentError, "Invalid scheme #{scheme.inspect}") unless scheme.nil? || scheme.to_s =~ /\A\w+\Z/
458
- @schemes[scheme.nil? ? nil : scheme.to_sym] = Extension.new(reader_class, writer_class)
459
- end
460
-
461
- # Helper method: Returns [true|false] if a value is blank?
462
- def self.blank?(value)
463
- if value.nil?
464
- true
465
- elsif value.is_a?(String)
466
- value !~ /\S/
467
- else
468
- value.respond_to?(:empty?) ? value.empty? : !value
469
- end
470
- end
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)
503
+ @schemes[scheme.nil? ? nil : scheme.to_sym] = Scheme.new(reader_class, writer_class, path_class)
476
504
  end
477
505
 
478
506
  private
479
507
 
508
+ # Hold root paths
509
+ @roots_paths = {}
510
+
480
511
  # A registry to hold formats for processing files during upload or download
481
512
  @extensions = {}
482
513
  @schemes = {}
483
514
 
515
+ Extension = Struct.new(:reader_class, :writer_class)
516
+ Scheme = Struct.new(:reader_class, :writer_class, :path_class)
517
+
484
518
  # Struct to hold the Stream and options if any
485
519
  StreamStruct = Struct.new(:klass, :options)
486
520
 
@@ -556,9 +590,12 @@ module IOStreams
556
590
  StreamStruct.new(klass, options)
557
591
  end
558
592
 
559
- def self.stream_struct_for_scheme(type, scheme, options = {})
560
- ext = @schemes[scheme.nil? ? nil : scheme.to_sym] || raise(ArgumentError, "Unknown Scheme type: #{scheme.inspect}")
561
- klass = ext.send("#{type}_class")
593
+ def self.scheme(scheme_name)
594
+ @schemes[scheme_name.nil? ? nil : scheme_name.to_sym] || raise(ArgumentError, "Unknown Scheme type: #{scheme_name.inspect}")
595
+ end
596
+
597
+ def self.stream_struct_for_scheme(type, scheme_name, options = {})
598
+ klass = scheme(scheme_name).send("#{type}_class")
562
599
  StreamStruct.new(klass, options)
563
600
  end
564
601
 
@@ -584,9 +621,9 @@ module IOStreams
584
621
  # https://hostname/path/file_name
585
622
  # sftp://hostname/path/file_name
586
623
  # s3://bucket/key
587
- register_scheme(nil, IOStreams::File::Reader, IOStreams::File::Writer)
588
- register_scheme(:http, IOStreams::HTTP::Reader, nil)
624
+ register_scheme(nil, IOStreams::File::Reader, IOStreams::File::Writer, IOStreams::File::Path)
625
+ register_scheme(:http, IOStreams::HTTP::Reader, nil)
589
626
  register_scheme(:https, IOStreams::HTTP::Reader, nil)
590
- # register_scheme(:sftp, IOStreams::SFTP::Reader, IOStreams::SFTP::Writer)
591
- register_scheme(:s3, IOStreams::S3::Reader, IOStreams::S3::Writer)
627
+ register_scheme(:sftp, IOStreams::SFTP::Reader, IOStreams::SFTP::Writer)
628
+ register_scheme(:s3, IOStreams::S3::Reader, IOStreams::S3::Writer, IOStreams::S3::Path)
592
629
  end
@@ -57,10 +57,11 @@ module IOStreams
57
57
  @eof = false
58
58
  @read_cache_buffer = nil
59
59
  @buffer = nil
60
+ @delimiter = delimiter
60
61
 
61
62
  read_block
62
63
  # Auto-detect windows/linux line endings if not supplied. \n or \r\n
63
- @delimiter = delimiter || auto_detect_line_endings
64
+ @delimiter ||= auto_detect_line_endings
64
65
 
65
66
  if @buffer
66
67
  # Change the delimiters encoding to match that of the input stream
@@ -14,31 +14,37 @@ module IOStreams
14
14
  # Or, the IO stream to receive the decrypted contents
15
15
  # passphrase: [String]
16
16
  # Pass phrase for private key to decrypt the file with
17
- def self.open(file_name_or_io, passphrase: self.default_passphrase, binary: true)
17
+ def self.open(file_name_or_io, passphrase: self.default_passphrase, binary: true, &block)
18
18
  raise(ArgumentError, 'Missing both passphrase and IOStreams::Pgp::Reader.default_passphrase') unless passphrase
19
19
 
20
- if IOStreams.reader_stream?(file_name_or_io)
21
- raise(NotImplementedError, 'Can only PGP Decrypt directly from a file name. Input streams are not yet supported.')
22
- else
23
- loopback = IOStreams::Pgp.pgp_version.to_f >= 2.1 ? '--pinentry-mode loopback' : ''
24
- command = "#{IOStreams::Pgp.executable} #{loopback} --batch --no-tty --yes --decrypt --passphrase-fd 0 #{file_name_or_io}"
25
- IOStreams::Pgp.logger.debug { "IOStreams::Pgp::Reader.open: #{command}" } if IOStreams::Pgp.logger
26
-
27
- # Read decrypted contents from stdout
28
- Open3.popen3(command) do |stdin, stdout, stderr, waith_thr|
29
- stdin.puts(passphrase) if passphrase
30
- stdin.close
31
- result =
32
- begin
33
- stdout.binmode if binary
34
- yield(stdout)
35
- rescue Errno::EPIPE
36
- # Ignore broken pipe because gpg terminates early due to an error
37
- raise(Pgp::Failure, "GPG Failed reading from encrypted file: #{file_name_or_io}: #{stderr.read.chomp}")
38
- end
39
- raise(Pgp::Failure, "GPG Failed to decrypt file: #{file_name_or_io}: #{stderr.read.chomp}") unless waith_thr.value.success?
40
- result
41
- end
20
+ return read_file(file_name_or_io, passphrase: passphrase, binary: binary, &block) if file_name_or_io.is_a?(String)
21
+
22
+ # PGP can only work against a file, not a stream, so create temp file.
23
+ IOStreams::File::Path.temp_file_name('iostreams_pgp') do |temp_file_name|
24
+ IOStreams.copy(file_name_or_io, temp_file_name, target_options: {streams: []})
25
+ read_file(temp_file_name, passphrase: passphrase, binary: binary, &block)
26
+ end
27
+ end
28
+
29
+ def self.read_file(file_name, passphrase: self.default_passphrase, binary: true)
30
+ loopback = IOStreams::Pgp.pgp_version.to_f >= 2.1 ? '--pinentry-mode loopback' : ''
31
+ command = "#{IOStreams::Pgp.executable} #{loopback} --batch --no-tty --yes --decrypt --passphrase-fd 0 #{file_name}"
32
+ IOStreams::Pgp.logger.debug { "IOStreams::Pgp::Reader.open: #{command}" } if IOStreams::Pgp.logger
33
+
34
+ # Read decrypted contents from stdout
35
+ Open3.popen3(command) do |stdin, stdout, stderr, waith_thr|
36
+ stdin.puts(passphrase) if passphrase
37
+ stdin.close
38
+ result =
39
+ begin
40
+ stdout.binmode if binary
41
+ yield(stdout)
42
+ rescue Errno::EPIPE
43
+ # Ignore broken pipe because gpg terminates early due to an error
44
+ raise(Pgp::Failure, "GPG Failed reading from encrypted file: #{file_name}: #{stderr.read.chomp}")
45
+ end
46
+ raise(Pgp::Failure, "GPG Failed to decrypt file: #{file_name}: #{stderr.read.chomp}") unless waith_thr.value.success?
47
+ result
42
48
  end
43
49
  end
44
50
 
@@ -45,13 +45,22 @@ module IOStreams
45
45
  # compress_level: [Integer]
46
46
  # Compression level
47
47
  # Default: 6
48
- def self.open(file_name, recipient:, signer: default_signer, signer_passphrase: default_signer_passphrase, binary: true, compression: :zip, compress_level: 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, &block)
49
49
  compress_level = 0 if compression == :none
50
- if IOStreams.writer_stream?(file_name)
51
- raise(NotImplementedError, 'Can only PGP Encrypt directly to a file name. Output to streams are not yet supported.')
50
+
51
+ if file_name_or_io.is_a?(String)
52
+ IOStreams::File::Path.mkpath(file_name_or_io)
53
+ return write_file(file_name_or_io, recipient: recipient, signer: signer, signer_passphrase: signer_passphrase, binary: binary, compression: compression, compress_level: compress_level, &block)
54
+ end
55
+
56
+ # PGP can only work against a file, not a stream, so create temp file.
57
+ IOStreams::File::Path.temp_file_name('iostreams_pgp') do |temp_file_name|
58
+ write_file(temp_file_name, recipient: recipient, signer: signer, signer_passphrase: signer_passphrase, binary: binary, compression: compression, compress_level: compress_level, &block)
59
+ IOStreams.copy(temp_file_name, file_name_or_io, source_options: {streams: []})
52
60
  end
53
- IOStreams.mkpath(file_name)
61
+ end
54
62
 
63
+ def self.write_file(file_name, recipient:, signer: default_signer, signer_passphrase: default_signer_passphrase, binary: true, compression: :zip, compress_level: 6)
55
64
  # Write to stdin, with encrypted contents being written to the file
56
65
  command = "#{IOStreams::Pgp.executable} --batch --no-tty --yes --encrypt"
57
66
  command << " --sign --local-user \"#{signer}\"" if signer
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ module IOStreams
3
+ module S3
4
+ class Path < IOStreams::BasePath
5
+ def initialize(path)
6
+ IOStreams::S3.load_dependencies
7
+ @s3 = Aws::S3::Resource.new
8
+ @options = IOStreams::S3.parse_uri(path)
9
+ @object = s3.bucket(options[:bucket]).object(options[:key])
10
+ super(path)
11
+ end
12
+
13
+ # S3 logically creates paths when a key is set.
14
+ def mkpath
15
+ self
16
+ end
17
+
18
+ def mkdir
19
+ self
20
+ end
21
+
22
+ def exist?
23
+ object.exists?
24
+ end
25
+
26
+ def size
27
+ object.size
28
+ end
29
+
30
+ def delete
31
+ object.delete
32
+ self
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :s3, :options, :object
38
+ end
39
+ end
40
+ end
@@ -7,13 +7,14 @@ module IOStreams
7
7
 
8
8
  IOStreams::S3.load_dependencies
9
9
 
10
+ # https://aws.amazon.com/blogs/developer/using-resources/
10
11
  s3 = region.nil? ? Aws::S3::Resource.new : Aws::S3::Resource.new(region: region)
11
12
  options = IOStreams::S3.parse_uri(uri)
12
13
  object = s3.bucket(options[:bucket]).object(options[:key])
13
14
 
14
15
  begin
15
16
  # Since S3 download only supports a push stream, write it to a tempfile first.
16
- IOStreams::Path.temp_file_name('iostreams_s3') do |file_name|
17
+ IOStreams::File::Path.temp_file_name('iostreams_s3') do |file_name|
17
18
  args[:response_target] = file_name
18
19
  object.get(args)
19
20
 
data/lib/io_streams/s3.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'uri'
2
2
  module IOStreams
3
3
  module S3
4
+ autoload :Path, 'io_streams/s3/path'
4
5
  autoload :Reader, 'io_streams/s3/reader'
5
6
  autoload :Writer, 'io_streams/s3/writer'
6
7
 
@@ -86,7 +86,7 @@ module IOStreams
86
86
  # Only Applies to when the hash is already a Hash.
87
87
  # Useful to turn off narrowing when the input data is already trusted.
88
88
  def to_hash(row, cleanse = true)
89
- return if IOStreams.blank?(row)
89
+ return if IOStreams::Utils.blank?(row)
90
90
 
91
91
  case row
92
92
  when Array
@@ -112,7 +112,7 @@ module IOStreams
112
112
 
113
113
  def array_to_hash(row)
114
114
  h = {}
115
- columns.each_with_index { |col, i| h[col] = row[i] unless IOStreams.blank?(col) }
115
+ columns.each_with_index { |col, i| h[col] = row[i] unless IOStreams::Utils.blank?(col) }
116
116
  h
117
117
  end
118
118
 
@@ -40,7 +40,7 @@ module IOStreams
40
40
  # About 10 times slower than the approach used in Ruby 2.5 and earlier,
41
41
  # but at least it works on Ruby 2.6 and above.
42
42
  def parse_line(line)
43
- return if IOStreams.blank?(line)
43
+ return if IOStreams::Utils.blank?(line)
44
44
 
45
45
  CSV.parse_line(line)
46
46
  end
@@ -23,7 +23,7 @@ module IOStreams
23
23
  # line [String]
24
24
  # A single line of CSV data without any line terminators
25
25
  def parse(line)
26
- return if IOStreams.blank?(line)
26
+ return if IOStreams::Utils.blank?(line)
27
27
  return if @skip_lines and @skip_lines.match line
28
28
 
29
29
  in_extended_col = false
@@ -66,7 +66,7 @@ module IOStreams
66
66
 
67
67
  # Returns [true|false] whether a header is still required in order to parse or render the current format.
68
68
  def header?
69
- parser.requires_header? && IOStreams.blank?(header.columns)
69
+ parser.requires_header? && IOStreams::Utils.blank?(header.columns)
70
70
  end
71
71
 
72
72
  # Returns [true|false] whether a header row show be rendered on output.
@@ -81,7 +81,7 @@ module IOStreams
81
81
  # * Call `header?` first to determine if the header should be parsed first.
82
82
  # * The header columns are set after parsing the row, but the header is not cleansed.
83
83
  def parse_header(line)
84
- return if IOStreams.blank?(line) || !parser.requires_header?
84
+ return if IOStreams::Utils.blank?(line) || !parser.requires_header?
85
85
 
86
86
  header.columns = parser.parse(line)
87
87
  end
@@ -96,14 +96,14 @@ module IOStreams
96
96
  # Returns [Array] the row/line as a parsed Array of values.
97
97
  # Returns nil if the row/line is blank.
98
98
  def row_parse(line)
99
- return if IOStreams.blank?(line)
99
+ return if IOStreams::Utils.blank?(line)
100
100
 
101
101
  parser.parse(line)
102
102
  end
103
103
 
104
104
  # Renders the output row
105
105
  def render(row)
106
- return if IOStreams.blank?(row)
106
+ return if IOStreams::Utils.blank?(row)
107
107
 
108
108
  parser.render(row, header)
109
109
  end
@@ -113,7 +113,7 @@ module IOStreams
113
113
  def render_header
114
114
  return unless requires_header?
115
115
 
116
- if IOStreams.blank?(header.columns)
116
+ if IOStreams::Utils.blank?(header.columns)
117
117
  raise(Errors::MissingHeader, "Header columns must be set before attempting to render a header for format: #{format.inspect}")
118
118
  end
119
119
 
@@ -0,0 +1,14 @@
1
+ module IOStreams
2
+ module Utils
3
+ # Helper method: Returns [true|false] if a value is blank?
4
+ def self.blank?(value)
5
+ if value.nil?
6
+ true
7
+ elsif value.is_a?(String)
8
+ value !~ /\S/
9
+ else
10
+ value.respond_to?(:empty?) ? value.empty? : !value
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = '0.19.0'
2
+ VERSION = '0.20.0'
3
3
  end
@@ -8,7 +8,7 @@ module IOStreams
8
8
  return extract_csv(file_name_or_io, &block) if file_name_or_io.is_a?(String)
9
9
 
10
10
  # Creek gem can only work against a file, not a stream, so create temp file.
11
- IOStreams::Path.temp_file_name('iostreams_xlsx') do |temp_file_name|
11
+ IOStreams::File::Path.temp_file_name('iostreams_xlsx') do |temp_file_name|
12
12
  IOStreams.copy(file_name_or_io, temp_file_name, target_options: {streams: []})
13
13
  extract_csv(temp_file_name, &block)
14
14
  end
@@ -16,7 +16,7 @@ module IOStreams
16
16
 
17
17
  # Convert the spreadsheet to csv in a tempfile
18
18
  def self.extract_csv(file_name, &block)
19
- IOStreams::Path.temp_file_name('iostreams_csv') do |temp_file_name|
19
+ IOStreams::File::Path.temp_file_name('iostreams_csv') do |temp_file_name|
20
20
  IOStreams::File::Writer.open(temp_file_name) do |io|
21
21
  new(file_name).each { |lines| io << lines.to_csv }
22
22
  end
@@ -26,7 +26,7 @@ module IOStreams
26
26
  return read_file(file_name_or_io, &block) unless IOStreams.reader_stream?(file_name_or_io)
27
27
 
28
28
  # ZIP can only work against a file, not a stream, so create temp file.
29
- IOStreams::Path.temp_file_name('iostreams_zip') do |temp_file_name|
29
+ IOStreams::File::Path.temp_file_name('iostreams_zip') do |temp_file_name|
30
30
  IOStreams.copy(file_name_or_io, temp_file_name, target_options: {streams: []})
31
31
  read_file(temp_file_name, &block)
32
32
  end
@@ -39,7 +39,7 @@ module IOStreams
39
39
  return write_file(file_name_or_io, zip_file_name, &block) unless IOStreams.writer_stream?(file_name_or_io)
40
40
 
41
41
  # ZIP can only work against a file, not a stream, so create temp file.
42
- IOStreams::Path.temp_file_name('iostreams_zip') do |temp_file_name|
42
+ IOStreams::File::Path.temp_file_name('iostreams_zip') do |temp_file_name|
43
43
  write_file(temp_file_name, zip_file_name, &block)
44
44
  IOStreams.copy(temp_file_name, file_name_or_io, source_options: {streams: []})
45
45
  end
@@ -62,7 +62,7 @@ module IOStreams
62
62
 
63
63
  else
64
64
  def self.write_file(file_name, zip_file_name, &block)
65
- IOStreams.mkpath(file_name)
65
+ IOStreams::File::Path.mkpath(file_name)
66
66
  zos = ::Zip::OutputStream.new(file_name)
67
67
  zos.put_next_entry(zip_file_name)
68
68
  block.call(zos)
data/lib/iostreams.rb CHANGED
@@ -1,13 +1,23 @@
1
1
  require 'io_streams/version'
2
2
  #@formatter:off
3
3
  module IOStreams
4
- autoload :Errors, 'io_streams/errors'
4
+ autoload :BasePath, 'io_streams/base_path'
5
+ autoload :Errors, 'io_streams/errors'
6
+ autoload :Pgp, 'io_streams/pgp'
7
+ autoload :S3, 'io_streams/s3'
8
+ autoload :Tabular, 'io_streams/tabular'
9
+ autoload :Utils, 'io_streams/utils'
5
10
 
6
11
  module Bzip2
7
12
  autoload :Reader, 'io_streams/bzip2/reader'
8
13
  autoload :Writer, 'io_streams/bzip2/writer'
9
14
  end
15
+ module Encode
16
+ autoload :Reader, 'io_streams/encode/reader'
17
+ autoload :Writer, 'io_streams/encode/writer'
18
+ end
10
19
  module File
20
+ autoload :Path, 'io_streams/file/path'
11
21
  autoload :Reader, 'io_streams/file/reader'
12
22
  autoload :Writer, 'io_streams/file/writer'
13
23
  end
@@ -18,22 +28,6 @@ module IOStreams
18
28
  module HTTP
19
29
  autoload :Reader, 'io_streams/http/reader'
20
30
  end
21
- autoload :Path, 'io_streams/path'
22
- autoload :Pgp, 'io_streams/pgp'
23
- autoload :S3, 'io_streams/s3'
24
- module SFTP
25
- autoload :Reader, 'io_streams/sftp/reader'
26
- autoload :Writer, 'io_streams/sftp/writer'
27
- end
28
- module Zip
29
- autoload :Reader, 'io_streams/zip/reader'
30
- autoload :Writer, 'io_streams/zip/writer'
31
- end
32
-
33
- module Encode
34
- autoload :Reader, 'io_streams/encode/reader'
35
- autoload :Writer, 'io_streams/encode/writer'
36
- end
37
31
  module Line
38
32
  autoload :Reader, 'io_streams/line/reader'
39
33
  autoload :Writer, 'io_streams/line/writer'
@@ -46,6 +40,10 @@ module IOStreams
46
40
  autoload :Reader, 'io_streams/row/reader'
47
41
  autoload :Writer, 'io_streams/row/writer'
48
42
  end
43
+ module SFTP
44
+ autoload :Reader, 'io_streams/sftp/reader'
45
+ autoload :Writer, 'io_streams/sftp/writer'
46
+ end
49
47
  module SymmetricEncryption
50
48
  autoload :Reader, 'io_streams/symmetric_encryption/reader'
51
49
  autoload :Writer, 'io_streams/symmetric_encryption/writer'
@@ -53,7 +51,9 @@ module IOStreams
53
51
  module Xlsx
54
52
  autoload :Reader, 'io_streams/xlsx/reader'
55
53
  end
56
-
57
- autoload :Tabular, 'io_streams/tabular'
54
+ module Zip
55
+ autoload :Reader, 'io_streams/zip/reader'
56
+ autoload :Writer, 'io_streams/zip/writer'
57
+ end
58
58
  end
59
59
  require 'io_streams/io_streams'
@@ -0,0 +1,35 @@
1
+ require_relative 'test_helper'
2
+
3
+ module IOStreams
4
+ class BasePathTest < Minitest::Test
5
+ describe IOStreams::BasePath do
6
+ describe '.join' do
7
+ let(:path) { IOStreams::BasePath.new('some_path') }
8
+
9
+ it 'returns self when no elements' do
10
+ assert_equal path.object_id, path.join.object_id
11
+ end
12
+
13
+ it 'adds element to path' do
14
+ assert_equal ::File.join('some_path', 'test'), path.join('test').to_s
15
+ end
16
+
17
+ it 'adds paths to root' do
18
+ assert_equal ::File.join('some_path', 'test', 'second', 'third'), path.join('test', 'second', 'third').to_s
19
+ end
20
+
21
+ it 'returns path and filename' do
22
+ assert_equal ::File.join('some_path', 'file.xls'), path.join('file.xls').to_s
23
+ end
24
+
25
+ it 'adds elements to path' do
26
+ assert_equal ::File.join('some_path', 'test', 'second', 'third', 'file.xls'), path.join('test', 'second', 'third', 'file.xls').to_s
27
+ end
28
+
29
+ it 'return path as sent in when full path' do
30
+ assert_equal ::File.join('some_path', 'test', 'second', 'third', 'file.xls'), path.join('some_path', 'test', 'second', 'third', 'file.xls').to_s
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,97 @@
1
+ require_relative 'test_helper'
2
+
3
+ module IOStreams
4
+ class FilePathTest < Minitest::Test
5
+ describe IOStreams::File::Path do
6
+ let(:path) { IOStreams::File::Path.new('/tmp/iostreams/some_test_path').delete(recursively: true) }
7
+ let(:path_with_file_name) { IOStreams::File::Path.new('/tmp/iostreams/some_test_path/test_file.txt').delete }
8
+
9
+ describe '.temp_file_name' do
10
+ it 'returns value from block' do
11
+ result = IOStreams::File::Path.temp_file_name('base', '.ext') { |name| 257 }
12
+ assert_equal 257, result
13
+ end
14
+
15
+ it 'supplies new temp file_name' do
16
+ file_name = nil
17
+ file_name2 = nil
18
+ IOStreams::File::Path.temp_file_name('base', '.ext') { |name| file_name = name }
19
+ IOStreams::File::Path.temp_file_name('base', '.ext') { |name| file_name2 = name }
20
+ refute_equal file_name, file_name2
21
+ end
22
+ end
23
+
24
+ describe '.mkpath' do
25
+ it 'makes path skipping file_name' do
26
+ new_path = path.join('test_mkpath.xls')
27
+ IOStreams::File::Path.mkpath(new_path.to_s)
28
+
29
+ assert ::File.exist?(path.to_s)
30
+ refute ::File.exist?(new_path.to_s)
31
+ end
32
+ end
33
+
34
+ describe '#mkpath' do
35
+ it 'makes path skipping file_name' do
36
+ #path.writer { |io| io << "Hello World" }
37
+ new_path = path.join('test_mkpath.xls').mkpath
38
+ assert ::File.exist?(path.to_s)
39
+ refute ::File.exist?(new_path.to_s)
40
+ end
41
+ end
42
+
43
+ describe '#mkdir' do
44
+ it 'makes entire path that does not have a file name' do
45
+ new_path = path.join('more_path').mkdir
46
+ assert ::File.exist?(path.to_s)
47
+ assert ::File.exist?(new_path.to_s)
48
+ end
49
+ end
50
+
51
+ describe '#exist?' do
52
+ it 'true on existing file' do
53
+ new_path = path.join('test_exist.txt')
54
+ new_path.writer { |io| io << "Hello World" }
55
+ assert ::File.exist?(path.to_s)
56
+ assert ::File.exist?(new_path.to_s)
57
+ end
58
+ end
59
+
60
+ describe '#size' do
61
+ it 'of file' do
62
+ data = "Hello World"
63
+ path_with_file_name.writer { |io| io << data }
64
+ assert_equal data.size, path_with_file_name.size
65
+ end
66
+ end
67
+
68
+ describe '#delete' do
69
+ it 'deletes existing file' do
70
+ path_with_file_name.writer { |io| io << "Hello World" }
71
+ assert ::File.exist?(path_with_file_name.to_s)
72
+ path_with_file_name.delete
73
+ refute ::File.exist?(path_with_file_name.to_s)
74
+ end
75
+
76
+ it 'ignores missing file' do
77
+ path_with_file_name.delete
78
+ path_with_file_name.delete
79
+ end
80
+ end
81
+
82
+ describe 'reader' do
83
+ it 'reads file' do
84
+ path_with_file_name.writer { |io| io << "Hello World" }
85
+ assert_equal "Hello World", path_with_file_name.reader(&:read)
86
+ end
87
+ end
88
+
89
+ describe 'writer' do
90
+ it 'creates file' do
91
+ path_with_file_name.writer { |io| io << "Hello World" }
92
+ assert ::File.exist?(path_with_file_name.to_s)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
data/test/path_test.rb CHANGED
@@ -2,73 +2,52 @@ require_relative 'test_helper'
2
2
 
3
3
  module IOStreams
4
4
  class PathTest < Minitest::Test
5
- describe IOStreams::Path do
6
- describe '.root' do
5
+ describe IOStreams do
6
+ describe '.root_path' do
7
7
  it 'return default path' do
8
8
  path = ::File.expand_path(::File.join(__dir__, '../tmp/default'))
9
- assert_equal path, IOStreams::Path[:default]
9
+ assert_equal path, IOStreams.root_path.to_s
10
10
  end
11
11
 
12
12
  it 'return downloads path' do
13
13
  path = ::File.expand_path(::File.join(__dir__, '../tmp/downloads'))
14
- assert_equal path, IOStreams::Path[:downloads]
14
+ assert_equal path, IOStreams.root_path(:downloads).to_s
15
15
  end
16
16
  end
17
17
 
18
- describe '.to_s' do
18
+ describe '.path' do
19
19
  it 'returns path' do
20
- assert_equal IOStreams::Path[:default], IOStreams::Path.new.to_s
20
+ assert_equal IOStreams.root_path.to_s, IOStreams.path.to_s
21
21
  end
22
22
 
23
23
  it 'adds path to root' do
24
- assert_equal ::File.join(IOStreams::Path[:default], 'test'), IOStreams::Path.new('test').to_s
24
+ assert_equal ::File.join(IOStreams.root_path.to_s, 'test'), IOStreams.path('test').to_s
25
25
  end
26
26
 
27
27
  it 'adds paths to root' do
28
- assert_equal ::File.join(IOStreams::Path[:default], 'test', 'second', 'third'), IOStreams::Path.new('test', 'second', 'third').to_s
28
+ assert_equal ::File.join(IOStreams.root_path.to_s, 'test', 'second', 'third'), IOStreams.path('test', 'second', 'third').to_s
29
29
  end
30
30
 
31
31
  it 'returns path and filename' do
32
- path = ::File.join(IOStreams::Path[:default], 'file.xls')
33
- assert_equal path, IOStreams::Path.new('file.xls').to_s
32
+ path = ::File.join(IOStreams.root_path.to_s, 'file.xls')
33
+ assert_equal path, IOStreams.path('file.xls').to_s
34
34
  end
35
35
 
36
36
  it 'adds path to root and filename' do
37
- path = ::File.join(IOStreams::Path[:default], 'test', 'file.xls')
38
- assert_equal path, IOStreams::Path.new('test', 'file.xls').to_s
37
+ path = ::File.join(IOStreams.root_path.to_s, 'test', 'file.xls')
38
+ assert_equal path, IOStreams.path('test', 'file.xls').to_s
39
39
  end
40
40
 
41
41
  it 'adds paths to root' do
42
- path = ::File.join(IOStreams::Path[:default], 'test', 'second', 'third', 'file.xls')
43
- assert_equal path, IOStreams::Path.new('test', 'second', 'third', 'file.xls').to_s
42
+ path = ::File.join(IOStreams.root_path.to_s, 'test', 'second', 'third', 'file.xls')
43
+ assert_equal path, IOStreams.path('test', 'second', 'third', 'file.xls').to_s
44
44
  end
45
45
 
46
46
  it 'return path as sent in when full path' do
47
- path = ::File.join(IOStreams::Path[:default], 'file.xls')
48
- assert_equal path, IOStreams::Path.new(path).to_s
47
+ path = ::File.join(IOStreams.root_path.to_s, 'file.xls')
48
+ assert_equal path, IOStreams.path(path).to_s
49
49
  end
50
50
  end
51
-
52
- describe '.mkpath' do
53
- it 'makes root' do
54
- path = IOStreams::Path.new('test.xls')
55
- assert_equal path, path.mkpath
56
- assert ::File.exist?(IOStreams::Path.new.to_s)
57
- end
58
-
59
- it 'makes root with path' do
60
- path = IOStreams::Path.new('test', 'test.xls')
61
- assert_equal path, path.mkpath
62
- assert ::File.exist?(IOStreams::Path.new('test').to_s)
63
- end
64
-
65
- it 'makes root with paths' do
66
- path = IOStreams::Path.new('test', 'second', 'third', 'test.xls')
67
- assert_equal path, path.mkpath
68
- assert ::File.exist?(IOStreams::Path.new('test', 'second', 'third').to_s)
69
- end
70
- end
71
-
72
51
  end
73
52
  end
74
53
  end
@@ -31,11 +31,15 @@ class PgpReaderTest < Minitest::Test
31
31
  end
32
32
  end
33
33
 
34
- it 'fails with stream input' do
35
- io = StringIO.new
36
- assert_raises NotImplementedError do
37
- IOStreams::Pgp::Reader.open(io, passphrase: 'BAD') { |file| file.read }
34
+ it 'streams input' do
35
+ IOStreams::Pgp::Writer.open(temp_file.path, recipient: 'receiver@example.org') do |io|
36
+ io.write(decrypted)
38
37
  end
38
+
39
+ encrypted = IOStreams::File::Reader.open(temp_file.path, &:read)
40
+ io = StringIO.new(encrypted)
41
+ result = IOStreams::Pgp::Reader.open(io, passphrase: 'receiver_passphrase', &:read)
42
+ assert_equal decrypted, result
39
43
  end
40
44
 
41
45
  end
@@ -25,7 +25,7 @@ class PgpWriterTest < Minitest::Test
25
25
  io.write(decrypted)
26
26
  end
27
27
 
28
- result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase', binary: false) { |file| file.read }
28
+ result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase', binary: false, &:read)
29
29
  assert_equal decrypted, result
30
30
  end
31
31
 
@@ -39,7 +39,7 @@ class PgpWriterTest < Minitest::Test
39
39
  end
40
40
  end
41
41
 
42
- result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase') { |file| file.read }
42
+ result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase', &:read)
43
43
  assert_equal binary_data, result
44
44
  end
45
45
 
@@ -48,7 +48,7 @@ class PgpWriterTest < Minitest::Test
48
48
  io.write(decrypted)
49
49
  end
50
50
 
51
- result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase') { |file| file.read }
51
+ result = IOStreams::Pgp::Reader.open(file_name, passphrase: 'receiver_passphrase', &:read)
52
52
  assert_equal decrypted, result
53
53
  end
54
54
 
@@ -80,14 +80,17 @@ class PgpWriterTest < Minitest::Test
80
80
  end
81
81
  end
82
82
 
83
- it 'fails with stream output' do
84
- string_io = StringIO.new
85
- assert_raises NotImplementedError do
86
- IOStreams::Pgp::Writer.open(string_io, recipient: 'receiver@example.org') do |io|
87
- io.write(decrypted)
88
- end
83
+ it 'writes to a stream' do
84
+ io_string = StringIO.new(''.b)
85
+ IOStreams::Pgp::Writer.open(io_string, recipient: 'receiver@example.org', signer: 'sender@example.org', signer_passphrase: 'sender_passphrase') do |io|
86
+ io.write(decrypted)
89
87
  end
88
+
89
+ io = StringIO.new(io_string.string)
90
+ result = IOStreams::Pgp::Reader.open(io, passphrase: 'receiver_passphrase', &:read)
91
+ assert_equal decrypted, result
90
92
  end
93
+
91
94
  end
92
95
  end
93
96
  end
data/test/test_helper.rb CHANGED
@@ -35,5 +35,5 @@ end
35
35
 
36
36
  # Test paths
37
37
  root = File.expand_path(File.join(__dir__, '../tmp'))
38
- IOStreams::Path.add_root(:default, File.join(root, 'default'))
39
- IOStreams::Path.add_root(:downloads, File.join(root, 'downloads'))
38
+ IOStreams.add_root_path(:default, File.join(root, 'default'))
39
+ IOStreams.add_root_path(:downloads, File.join(root, 'downloads'))
@@ -17,7 +17,6 @@ class XlsxReaderTest
17
17
  describe '.open' do
18
18
  describe 'with a file path' do
19
19
  it 'returns the contents of the file' do
20
- rows = []
21
20
  csv = IOStreams::Xlsx::Reader.open(file_name, &:read)
22
21
  assert_equal xlsx_contents, CSV.parse(csv)
23
22
  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.19.0
4
+ version: 0.20.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-08-22 00:00:00.000000000 Z
11
+ date: 2019-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -34,11 +34,13 @@ files:
34
34
  - LICENSE
35
35
  - README.md
36
36
  - Rakefile
37
+ - lib/io_streams/base_path.rb
37
38
  - lib/io_streams/bzip2/reader.rb
38
39
  - lib/io_streams/bzip2/writer.rb
39
40
  - lib/io_streams/encode/reader.rb
40
41
  - lib/io_streams/encode/writer.rb
41
42
  - lib/io_streams/errors.rb
43
+ - lib/io_streams/file/path.rb
42
44
  - lib/io_streams/file/reader.rb
43
45
  - lib/io_streams/file/writer.rb
44
46
  - lib/io_streams/gzip/reader.rb
@@ -47,7 +49,6 @@ files:
47
49
  - lib/io_streams/io_streams.rb
48
50
  - lib/io_streams/line/reader.rb
49
51
  - lib/io_streams/line/writer.rb
50
- - lib/io_streams/path.rb
51
52
  - lib/io_streams/pgp.rb
52
53
  - lib/io_streams/pgp/reader.rb
53
54
  - lib/io_streams/pgp/writer.rb
@@ -56,6 +57,7 @@ files:
56
57
  - lib/io_streams/row/reader.rb
57
58
  - lib/io_streams/row/writer.rb
58
59
  - lib/io_streams/s3.rb
60
+ - lib/io_streams/s3/path.rb
59
61
  - lib/io_streams/s3/reader.rb
60
62
  - lib/io_streams/s3/writer.rb
61
63
  - lib/io_streams/sftp/reader.rb
@@ -72,15 +74,18 @@ files:
72
74
  - lib/io_streams/tabular/parser/json.rb
73
75
  - lib/io_streams/tabular/parser/psv.rb
74
76
  - lib/io_streams/tabular/utility/csv_row.rb
77
+ - lib/io_streams/utils.rb
75
78
  - lib/io_streams/version.rb
76
79
  - lib/io_streams/xlsx/reader.rb
77
80
  - lib/io_streams/zip/reader.rb
78
81
  - lib/io_streams/zip/writer.rb
79
82
  - lib/iostreams.rb
83
+ - test/base_path_test.rb
80
84
  - test/bzip2_reader_test.rb
81
85
  - test/bzip2_writer_test.rb
82
86
  - test/encode_reader_test.rb
83
87
  - test/encode_writer_test.rb
88
+ - test/file_path_test.rb
84
89
  - test/file_reader_test.rb
85
90
  - test/file_writer_test.rb
86
91
  - test/files/embedded_lines_test.csv
@@ -139,6 +144,7 @@ specification_version: 4
139
144
  summary: Input and Output streaming for Ruby.
140
145
  test_files:
141
146
  - test/pgp_reader_test.rb
147
+ - test/base_path_test.rb
142
148
  - test/line_reader_test.rb
143
149
  - test/xlsx_reader_test.rb
144
150
  - test/row_writer_test.rb
@@ -175,3 +181,4 @@ test_files:
175
181
  - test/io_streams_test.rb
176
182
  - test/record_writer_test.rb
177
183
  - test/s3_reader_test.rb
184
+ - test/file_path_test.rb
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'fileutils'
3
-
4
- module IOStreams
5
- #
6
- # NOTE: This is a proof of concept class and will change significantly.
7
- # I.e. Dont use it yet.
8
- #
9
- class Path
10
- attr_reader :root, :relative
11
-
12
- # Return named root path
13
- def self.[](root)
14
- @roots[root.to_sym] || raise(ArgumentError, "Unknown root: #{root.inspect}")
15
- end
16
-
17
- # Add a named root path
18
- def self.add_root(root, path)
19
- @roots[root.to_sym] = path.dup.freeze
20
- end
21
-
22
- def self.roots
23
- @roots.dup
24
- end
25
-
26
- # Yields the path to a temporary file_name.
27
- #
28
- # File is deleted upon completion if present.
29
- def self.temp_file_name(basename, extension = '')
30
- result = nil
31
- ::Dir::Tmpname.create([basename, extension]) do |tmpname|
32
- begin
33
- result = yield(tmpname)
34
- ensure
35
- ::File.unlink(tmpname) if ::File.exist?(tmpname)
36
- end
37
- end
38
- result
39
- end
40
-
41
- def initialize(*elements, root: :default)
42
- @root = root.to_sym
43
- root_path = self.class[@root]
44
- if elements.empty?
45
- @relative = ''
46
- @path = root_path
47
- else
48
- @relative = ::File.join(*elements).freeze
49
- if @relative.start_with?(root_path)
50
- @path = @relative
51
- @relative = @path[root_path.size + 1..-1].freeze
52
- else
53
- @path = ::File.join(root_path, @relative).freeze
54
- end
55
- end
56
- end
57
-
58
- def to_s
59
- @path
60
- end
61
-
62
- # Creates the entire path excluding the file_name.
63
- def mkpath
64
- path = ::File.dirname(@path)
65
- FileUtils.mkdir_p(path) unless ::File.exist?(path)
66
- self
67
- end
68
-
69
- def exist?
70
- ::File.exist?(@path)
71
- end
72
-
73
- # Delete the file.
74
- #
75
- # Note: Only the file is removed, not any of the parent paths.
76
- def delete
77
- ::File.unlink(@path)
78
- self
79
- end
80
-
81
- private
82
-
83
- @roots = {}
84
- end
85
- end