iostreams 2.0.0 → 2.1.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -22
  3. data/Rakefile +45 -0
  4. data/docs/CLAUDE.md +9 -0
  5. data/docs/config.md +157 -0
  6. data/docs/copy_files.md +75 -0
  7. data/docs/extensions.md +111 -0
  8. data/docs/formats.md +188 -0
  9. data/docs/index.md +388 -0
  10. data/docs/path.md +652 -0
  11. data/docs/pgp.md +436 -0
  12. data/docs/streams.md +337 -0
  13. data/docs/tutorial.md +483 -0
  14. data/docs/upgrading.md +217 -0
  15. data/lib/io_streams/builder.rb +62 -2
  16. data/lib/io_streams/bzip2/reader.rb +25 -2
  17. data/lib/io_streams/bzip2/writer.rb +26 -2
  18. data/lib/io_streams/encode/reader.rb +4 -0
  19. data/lib/io_streams/encode/writer.rb +4 -0
  20. data/lib/io_streams/errors.rb +4 -0
  21. data/lib/io_streams/gzip/reader.rb +4 -0
  22. data/lib/io_streams/gzip/writer.rb +11 -2
  23. data/lib/io_streams/io_streams.rb +111 -1
  24. data/lib/io_streams/line/reader.rb +7 -2
  25. data/lib/io_streams/path.rb +115 -6
  26. data/lib/io_streams/paths/file.rb +47 -1
  27. data/lib/io_streams/paths/http.rb +45 -4
  28. data/lib/io_streams/paths/s3.rb +66 -15
  29. data/lib/io_streams/paths/sftp/net_ssh.rb +104 -0
  30. data/lib/io_streams/paths/sftp.rb +97 -57
  31. data/lib/io_streams/pgp/reader.rb +42 -2
  32. data/lib/io_streams/pgp/writer.rb +26 -6
  33. data/lib/io_streams/pgp.rb +78 -21
  34. data/lib/io_streams/reader.rb +10 -1
  35. data/lib/io_streams/record/reader.rb +72 -2
  36. data/lib/io_streams/stream.rb +12 -7
  37. data/lib/io_streams/symmetric_encryption/reader.rb +4 -0
  38. data/lib/io_streams/symmetric_encryption/writer.rb +4 -0
  39. data/lib/io_streams/tabular/header.rb +31 -4
  40. data/lib/io_streams/tabular/parser/base.rb +10 -0
  41. data/lib/io_streams/tabular/parser/csv.rb +5 -0
  42. data/lib/io_streams/tabular/parser/fixed.rb +3 -1
  43. data/lib/io_streams/tabular/parser/psv.rb +6 -2
  44. data/lib/io_streams/utils.rb +31 -0
  45. data/lib/io_streams/version.rb +1 -1
  46. data/lib/io_streams/writer.rb +10 -1
  47. data/lib/io_streams/xlsx/reader.rb +5 -1
  48. data/lib/io_streams/zip/reader.rb +4 -0
  49. data/lib/io_streams/zip/writer.rb +4 -0
  50. metadata +24 -7
@@ -3,6 +3,7 @@ module IOStreams
3
3
  module Parser
4
4
  # For parsing a single line of Pipe-separated values
5
5
  class Psv < Base
6
+ LINE_BREAK = /\r\n|\r|\n/
6
7
  # Returns [Array] the parsed PSV line
7
8
  def parse(row)
8
9
  return row if row.is_a?(::Array)
@@ -12,11 +13,14 @@ module IOStreams
12
13
  row.split("|")
13
14
  end
14
15
 
15
- # Return the supplied array as a single line JSON string.
16
+ # Return the supplied array as a single line PSV string.
17
+ #
18
+ # Since PSV has no escaping, any `|` within a value is replaced with `:`,
19
+ # and any line break with a space, so that a value cannot add columns or records.
16
20
  def render(row, header)
17
21
  array = header.to_array(row)
18
22
  cleansed_array = array.collect do |i|
19
- i.is_a?(String) ? i.tr("|", ":") : i
23
+ i.is_a?(String) ? i.tr("|", ":").gsub(LINE_BREAK, " ") : i
20
24
  end
21
25
  cleansed_array.join("|")
22
26
  end
@@ -12,6 +12,17 @@ module IOStreams
12
12
  raise(LoadError, "Please install the gem '#{gem_name}' to support #{stream_type}. #{e.message}")
13
13
  end
14
14
 
15
+ # Log a warning for options that a stream ignores, until they raise `ArgumentError` in v3.0.
16
+ def self.warn_unknown_options(unknown, stream, direction, valid)
17
+ return if unknown.empty?
18
+
19
+ names = unknown.keys.map(&:inspect).join(", ")
20
+ IOStreams.logger&.warn(
21
+ "Ignoring unknown #{unknown.size == 1 ? 'option' : 'options'} #{names} when #{direction} a #{stream.inspect} " \
22
+ "stream. Valid options: #{valid.map(&:inspect).join(', ')}. In v3.0 this will raise ArgumentError."
23
+ )
24
+ end
25
+
15
26
  # Helper method: Returns [true|false] if a value is blank?
16
27
  def self.blank?(value)
17
28
  return true if value.nil?
@@ -33,6 +44,26 @@ module IOStreams
33
44
  result
34
45
  end
35
46
 
47
+ # Yields the name of a new, empty temporary file that only the current user can read or write.
48
+ #
49
+ # The file is created exclusively, so that an existing file, or a link planted in a shared
50
+ # temp directory, is never written to. Only a name collision when creating the file is retried,
51
+ # and the file is only deleted once it was created here, so that another process's file is never removed.
52
+ #
53
+ # Returns the value from the block.
54
+ def self.private_temp_file(basename, extension = "")
55
+ file_name = ::Dir::Tmpname.create([basename, extension], IOStreams.temp_dir,
56
+ max_try: MAX_TEMP_FILE_NAME_ATTEMPTS) do |tmpname|
57
+ ::File.open(tmpname, ::File::WRONLY | ::File::CREAT | ::File::EXCL, 0o600, &:close)
58
+ end
59
+
60
+ begin
61
+ yield(file_name)
62
+ ensure
63
+ ::FileUtils.rm_f(file_name)
64
+ end
65
+ end
66
+
36
67
  class URI
37
68
  attr_reader :scheme, :hostname, :path, :user, :password, :port, :query
38
69
 
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = "2.0.0".freeze
2
+ VERSION = "2.1.0".freeze
3
3
  end
@@ -1,9 +1,18 @@
1
1
  module IOStreams
2
2
  class Writer
3
+ # Returns [Array<Symbol>] the names of the options this writer accepts,
4
+ # or [nil] when the writer does not declare them.
5
+ #
6
+ # When declared, `IOStreams::Builder` rejects any other option before the writer is opened,
7
+ # naming the direction an option belongs to when it is only valid for the other direction.
8
+ def self.option_names
9
+ nil
10
+ end
11
+
3
12
  # When a Writer does not support streams, we copy the stream to a local temp file
4
13
  # and then pass that filename in for this reader.
5
14
  def self.stream(output_stream, **args, &block)
6
- Utils.temp_file_name("iostreams_writer") do |file_name|
15
+ Utils.private_temp_file("iostreams_writer") do |file_name|
7
16
  count = file(file_name, **args, &block)
8
17
  ::File.open(file_name, "rb") { |source| ::IO.copy_stream(source, output_stream) }
9
18
  count
@@ -3,10 +3,14 @@ require "csv"
3
3
  module IOStreams
4
4
  module Xlsx
5
5
  class Reader < IOStreams::Reader
6
+ def self.option_names
7
+ []
8
+ end
9
+
6
10
  # Convert a xlsx, or xlsm file into CSV format.
7
11
  def self.file(file_name, &block)
8
12
  # Stream into a temp file as csv
9
- Utils.temp_file_name("iostreams_csv") do |temp_file_name|
13
+ Utils.private_temp_file("iostreams_csv") do |temp_file_name|
10
14
  ::File.open(temp_file_name, "wb") { |io| new(file_name).each { |lines| io << lines.to_csv } }
11
15
  ::File.open(temp_file_name, "rb", &block)
12
16
  end
@@ -1,6 +1,10 @@
1
1
  module IOStreams
2
2
  module Zip
3
3
  class Reader < IOStreams::Reader
4
+ def self.option_names
5
+ %i[entry_file_name]
6
+ end
7
+
4
8
  # Read from a zip file or stream, decompressing the contents as it is read
5
9
  # The input stream from the first file found in the zip file is passed
6
10
  # to the supplied block.
@@ -1,6 +1,10 @@
1
1
  module IOStreams
2
2
  module Zip
3
3
  class Writer < IOStreams::Writer
4
+ def self.option_names
5
+ %i[zip_file_name entry_file_name]
6
+ end
7
+
4
8
  # When writing to a file, default the entry name within the zip to the file name
5
9
  # without the `.zip` extension, unless an entry name was explicitly supplied.
6
10
  def self.file(file_name, zip_file_name: nil, entry_file_name: zip_file_name, &)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iostreams
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reid Morrison
@@ -23,6 +23,10 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ description: IOStreams makes file formats, compression (gzip, zip, bzip2), encryption
27
+ (PGP, symmetric), and storage location (local file, S3, SFTP, HTTP) transparent
28
+ to your application code. Files of any size are read and written one block at a
29
+ time, without loading the entire file into memory.
26
30
  executables: []
27
31
  extensions: []
28
32
  extra_rdoc_files: []
@@ -30,6 +34,17 @@ files:
30
34
  - LICENSE
31
35
  - README.md
32
36
  - Rakefile
37
+ - docs/CLAUDE.md
38
+ - docs/config.md
39
+ - docs/copy_files.md
40
+ - docs/extensions.md
41
+ - docs/formats.md
42
+ - docs/index.md
43
+ - docs/path.md
44
+ - docs/pgp.md
45
+ - docs/streams.md
46
+ - docs/tutorial.md
47
+ - docs/upgrading.md
33
48
  - lib/io_streams/builder.rb
34
49
  - lib/io_streams/bzip2/reader.rb
35
50
  - lib/io_streams/bzip2/writer.rb
@@ -47,6 +62,7 @@ files:
47
62
  - lib/io_streams/paths/matcher.rb
48
63
  - lib/io_streams/paths/s3.rb
49
64
  - lib/io_streams/paths/sftp.rb
65
+ - lib/io_streams/paths/sftp/net_ssh.rb
50
66
  - lib/io_streams/pgp.rb
51
67
  - lib/io_streams/pgp/reader.rb
52
68
  - lib/io_streams/pgp/writer.rb
@@ -74,14 +90,15 @@ files:
74
90
  - lib/io_streams/zip/reader.rb
75
91
  - lib/io_streams/zip/writer.rb
76
92
  - lib/iostreams.rb
77
- homepage: https://iostreams.rocketjob.io
93
+ homepage: https://iostreams.reidmorrison.com
78
94
  licenses:
79
95
  - Apache-2.0
80
96
  metadata:
81
97
  bug_tracker_uri: https://github.com/reidmorrison/iostreams/issues
82
- changelog_uri: https://github.com/reidmorrison/iostreams/blob/v2.0.0/CHANGELOG.md
83
- documentation_uri: https://iostreams.rocketjob.io
84
- source_code_uri: https://github.com/reidmorrison/iostreams/tree/v2.0.0
98
+ changelog_uri: https://github.com/reidmorrison/iostreams/blob/main/CHANGELOG.md
99
+ documentation_uri: https://iostreams.reidmorrison.com
100
+ homepage_uri: https://iostreams.reidmorrison.com
101
+ source_code_uri: https://github.com/reidmorrison/iostreams/tree/v2.1.0
85
102
  rubygems_mfa_required: 'true'
86
103
  rdoc_options: []
87
104
  require_paths:
@@ -99,6 +116,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
99
116
  requirements: []
100
117
  rubygems_version: 3.6.9
101
118
  specification_version: 4
102
- summary: 'Streaming I/O for Ruby: compression, encryption, format, and storage transparent
103
- to your code.'
119
+ summary: 'Streaming I/O for Ruby: compression, encryption, file format, and storage
120
+ location transparent to your code.'
104
121
  test_files: []