iostreams 2.1.0 → 2.2.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 +4 -4
- data/docs/config.md +13 -9
- data/docs/copy_files.md +20 -2
- data/docs/extensions.md +18 -4
- data/docs/formats.md +644 -16
- data/docs/path.md +18 -2
- data/docs/streams.md +54 -9
- data/docs/tutorial.md +14 -2
- data/docs/upgrading.md +470 -23
- data/lib/io_streams/builder/deprecations.rb +250 -0
- data/lib/io_streams/builder.rb +256 -39
- data/lib/io_streams/bzip2/reader.rb +18 -2
- data/lib/io_streams/bzip2/writer.rb +11 -2
- data/lib/io_streams/deprecation/methods.rb +51 -0
- data/lib/io_streams/deprecation/once.rb +27 -0
- data/lib/io_streams/deprecation/option_names.rb +26 -0
- data/lib/io_streams/deprecation/words.rb +42 -0
- data/lib/io_streams/deprecation.rb +259 -0
- data/lib/io_streams/encode/reader.rb +54 -3
- data/lib/io_streams/encode/writer.rb +19 -2
- data/lib/io_streams/encode.rb +99 -0
- data/lib/io_streams/errors.rb +51 -0
- data/lib/io_streams/gzip/reader.rb +11 -0
- data/lib/io_streams/io_streams.rb +365 -35
- data/lib/io_streams/line/reader.rb +2 -1
- data/lib/io_streams/line/utf8_reader.rb +23 -0
- data/lib/io_streams/path.rb +125 -35
- data/lib/io_streams/paths/file.rb +72 -9
- data/lib/io_streams/paths/http.rb +48 -2
- data/lib/io_streams/paths/matcher.rb +6 -3
- data/lib/io_streams/paths/s3.rb +180 -9
- data/lib/io_streams/paths/sftp.rb +47 -3
- data/lib/io_streams/paths.rb +23 -0
- data/lib/io_streams/pgp/reader.rb +18 -0
- data/lib/io_streams/pgp/writer.rb +16 -0
- data/lib/io_streams/pgp.rb +16 -2
- data/lib/io_streams/reader.rb +44 -0
- data/lib/io_streams/record/reader.rb +59 -68
- data/lib/io_streams/record/writer.rb +17 -10
- data/lib/io_streams/row/header_row.rb +21 -0
- data/lib/io_streams/row/reader.rb +52 -15
- data/lib/io_streams/row/writer.rb +64 -13
- data/lib/io_streams/schema/columns.rb +456 -0
- data/lib/io_streams/schema/constraints.rb +99 -0
- data/lib/io_streams/schema/field.rb +967 -0
- data/lib/io_streams/schema/json_number.rb +56 -0
- data/lib/io_streams/schema/keys.rb +78 -0
- data/lib/io_streams/schema/pattern.rb +257 -0
- data/lib/io_streams/schema/source.rb +31 -0
- data/lib/io_streams/schema.rb +616 -0
- data/lib/io_streams/stream.rb +836 -67
- data/lib/io_streams/symmetric_encryption/reader.rb +8 -0
- data/lib/io_streams/tabular/header.rb +29 -0
- data/lib/io_streams/tabular/header_mapping.rb +332 -0
- data/lib/io_streams/tabular/mapping.rb +165 -0
- data/lib/io_streams/tabular/parser/array.rb +5 -0
- data/lib/io_streams/tabular/parser/base.rb +24 -0
- data/lib/io_streams/tabular/parser/fixed.rb +197 -0
- data/lib/io_streams/tabular/parser/fixed_fields.rb +160 -0
- data/lib/io_streams/tabular/parser/hash.rb +10 -0
- data/lib/io_streams/tabular/parser/json.rb +17 -0
- data/lib/io_streams/tabular/parser/json_fields.rb +132 -0
- data/lib/io_streams/tabular/schema_mapping.rb +361 -0
- data/lib/io_streams/tabular.rb +344 -30
- data/lib/io_streams/utils.rb +441 -6
- data/lib/io_streams/version.rb +1 -1
- data/lib/io_streams/writer.rb +27 -0
- data/lib/io_streams/xlsx/cells.rb +127 -0
- data/lib/io_streams/xlsx/reader.rb +56 -0
- data/lib/io_streams/xlsx/schema_reader.rb +90 -0
- data/lib/io_streams/xlsx/v3_writer.rb +16 -0
- data/lib/io_streams/zip/reader.rb +7 -0
- data/lib/io_streams/zip/writer.rb +24 -2
- data/lib/iostreams.rb +21 -17
- metadata +28 -2
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IOStreams
|
|
4
|
+
# Converts the text that the application reads or writes to an encoding, and optionally cleanses it, see
|
|
5
|
+
# `IOStreams::Encode::Reader` and `IOStreams::Encode::Writer`.
|
|
6
|
+
module Encode
|
|
7
|
+
autoload :Reader, "io_streams/encode/reader"
|
|
8
|
+
autoload :Writer, "io_streams/encode/writer"
|
|
9
|
+
|
|
10
|
+
# What the application uses in place of the #encoding and #cleaner of the reader and writer of the encode stream,
|
|
11
|
+
# which v3.0 removes: the settings of the path, which return what was set in both versions, see
|
|
12
|
+
# `IOStreams::Reader.v3_stream_advice`.
|
|
13
|
+
V3_STREAM_ADVICE = {
|
|
14
|
+
encoding: "use the #encoding of the path instead, which returns the encoding that was set, such as \"UTF-8\", or " \
|
|
15
|
+
"Encoding.find(path.encoding) for its Encoding",
|
|
16
|
+
cleaner: "use path.setting(:encode)[:cleaner] instead, which returns the cleaner that was set, such as :printable"
|
|
17
|
+
}.freeze
|
|
18
|
+
private_constant :V3_STREAM_ADVICE
|
|
19
|
+
|
|
20
|
+
# Returns [Array<Symbol>] the names of the options of the encode stream, which its reader and writer both take.
|
|
21
|
+
def self.option_names
|
|
22
|
+
Reader.option_names | Writer.option_names
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Raises ArgumentError for the options of the encode stream that its reader or writer would raise for once the text
|
|
26
|
+
# is read or written: an option that it does not take, an encoding that Ruby does not know, or that is neither a
|
|
27
|
+
# String nor an Encoding, one that Ruby cannot convert text to, see .check_convertible, a `replace` that is not a
|
|
28
|
+
# String, or a cleaner that is not the name of a built-in rule. Also for a cleaner that is neither a Symbol nor a
|
|
29
|
+
# Proc, which the reader and writer ignore, so that the text would not be cleansed.
|
|
30
|
+
#
|
|
31
|
+
# So that `IOStreams::Builder#encoding` raises when the options are set, rather than once a writer has replaced the
|
|
32
|
+
# file that it writes.
|
|
33
|
+
def self.check(**options)
|
|
34
|
+
unknown = options.keys - option_names
|
|
35
|
+
if unknown.any?
|
|
36
|
+
raise(ArgumentError, "Unknown encoding #{unknown.size == 1 ? 'option' : 'options'} #{list(unknown)}. " \
|
|
37
|
+
"Valid options: #{list(option_names)}.")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
check_encoding(options[:encoding]) if options.key?(:encoding)
|
|
41
|
+
check_replace(options[:replace])
|
|
42
|
+
check_cleaner(options[:cleaner])
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.check_encoding(encoding)
|
|
47
|
+
unless encoding.is_a?(String) || encoding.is_a?(Encoding)
|
|
48
|
+
raise(ArgumentError, "Invalid encoding #{encoding.inspect}: supply the name of an encoding, such as " \
|
|
49
|
+
"\"UTF-8\" or \"Windows-1252\", or an Encoding")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
check_convertible(encoding, encoding.is_a?(String) ? Encoding.find(encoding) : encoding)
|
|
53
|
+
end
|
|
54
|
+
private_class_method :check_encoding
|
|
55
|
+
|
|
56
|
+
# Raises ArgumentError for an encoding that Ruby has no converter for, such as "UTF-7", so that it raises when it is
|
|
57
|
+
# set, rather than Encoding::ConverterNotFoundError once the text is read or written. The text that is read and
|
|
58
|
+
# written is converted to the encoding, such as from the UTF-8 that the application writes. An ASCII compatible
|
|
59
|
+
# encoding needs no converter for ASCII text, so the text of one that Ruby cannot convert, such as "Windows-1258",
|
|
60
|
+
# is still read and written as it is.
|
|
61
|
+
def self.check_convertible(option, encoding)
|
|
62
|
+
return if encoding.ascii_compatible? || converter?(Encoding::UTF_8, encoding)
|
|
63
|
+
|
|
64
|
+
raise(ArgumentError, "Invalid encoding #{option.inspect}: Ruby cannot convert text to or from #{encoding}")
|
|
65
|
+
end
|
|
66
|
+
private_class_method :check_convertible
|
|
67
|
+
|
|
68
|
+
# Returns [true|false] whether Ruby has a converter of text from the source encoding to the destination encoding.
|
|
69
|
+
def self.converter?(source, destination)
|
|
70
|
+
Encoding::Converter.new(source, destination)
|
|
71
|
+
true
|
|
72
|
+
rescue Encoding::ConverterNotFoundError
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
private_class_method :converter?
|
|
76
|
+
|
|
77
|
+
def self.check_replace(replace)
|
|
78
|
+
return if replace.nil? || replace.is_a?(String)
|
|
79
|
+
|
|
80
|
+
raise(ArgumentError, "Invalid replace #{replace.inspect}: supply the String that replaces each invalid character")
|
|
81
|
+
end
|
|
82
|
+
private_class_method :check_replace
|
|
83
|
+
|
|
84
|
+
def self.check_cleaner(cleaner)
|
|
85
|
+
unless cleaner.nil? || cleaner.is_a?(Symbol) || cleaner.is_a?(Proc)
|
|
86
|
+
raise(ArgumentError,
|
|
87
|
+
"Invalid cleaner #{cleaner.inspect}: supply the name of a built-in rule, such as :printable, or a Proc")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
Reader.extract_cleaner(cleaner)
|
|
91
|
+
end
|
|
92
|
+
private_class_method :check_cleaner
|
|
93
|
+
|
|
94
|
+
def self.list(names)
|
|
95
|
+
names.map(&:inspect).join(", ")
|
|
96
|
+
end
|
|
97
|
+
private_class_method :list
|
|
98
|
+
end
|
|
99
|
+
end
|
data/lib/io_streams/errors.rb
CHANGED
|
@@ -33,6 +33,57 @@ module IOStreams
|
|
|
33
33
|
class ValueTooLong < Error
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# A value that is not one of the type of its field in the schema, such as `"abc"` for an integer,
|
|
37
|
+
# see `IOStreams::Stream#schema`.
|
|
38
|
+
class InvalidValue < Error
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Text that is not valid in the encoding that it is read or written in, such as bytes that are not valid UTF-8 in
|
|
42
|
+
# a file read with a schema, see `IOStreams::Stream#schema`.
|
|
43
|
+
#
|
|
44
|
+
# It is an `Encoding::UndefinedConversionError`, as the encode stream raises, so that it can be rescued as one.
|
|
45
|
+
#
|
|
46
|
+
# Example:
|
|
47
|
+
# IOStreams.path("data.csv").schema("people.json").each(:hash) { |hash| p hash }
|
|
48
|
+
# # => IOStreams::Errors::InvalidEncoding: "\xFF" is not valid UTF-8
|
|
49
|
+
class InvalidEncoding < ::Encoding::UndefinedConversionError
|
|
50
|
+
# Returns [Integer] the offset of the first invalid byte within the data, which IOStreams 3.0 gives, or [nil] in
|
|
51
|
+
# IOStreams 2.2, which does not count the bytes of the data read.
|
|
52
|
+
attr_reader :byte_offset
|
|
53
|
+
|
|
54
|
+
# Returns [Integer] the number of the line that the invalid data is on, counting from 1,
|
|
55
|
+
# or [nil] when the data is not read a line at a time.
|
|
56
|
+
attr_reader :line_number
|
|
57
|
+
|
|
58
|
+
# Returns [String] the invalid bytes, as binary, like `Encoding::UndefinedConversionError#error_char`.
|
|
59
|
+
attr_reader :error_char
|
|
60
|
+
|
|
61
|
+
# Returns [Encoding] the encoding that the invalid bytes were read or written as, binary for data read from a file,
|
|
62
|
+
# and the encoding that they are not valid in, like `Encoding::UndefinedConversionError#source_encoding` and
|
|
63
|
+
# `#destination_encoding`.
|
|
64
|
+
attr_reader :source_encoding, :destination_encoding
|
|
65
|
+
|
|
66
|
+
def initialize(message, byte_offset: nil, line_number: nil, error_char: nil, source_encoding: nil,
|
|
67
|
+
destination_encoding: nil)
|
|
68
|
+
@byte_offset = byte_offset
|
|
69
|
+
@line_number = line_number
|
|
70
|
+
@error_char = error_char
|
|
71
|
+
@source_encoding = source_encoding
|
|
72
|
+
@destination_encoding = destination_encoding
|
|
73
|
+
super(message)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns [String] the name of the #source_encoding, such as "ASCII-8BIT", or nil.
|
|
77
|
+
def source_encoding_name
|
|
78
|
+
source_encoding&.name
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns [String] the name of the #destination_encoding, such as "UTF-8", or nil.
|
|
82
|
+
def destination_encoding_name
|
|
83
|
+
destination_encoding&.name
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
36
87
|
class MalformedDataError < RuntimeError
|
|
37
88
|
attr_reader :line_number
|
|
38
89
|
|
|
@@ -5,6 +5,17 @@ module IOStreams
|
|
|
5
5
|
[]
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
# The methods that the stream that v3.0 supplies to a reader block for gzip answers: those of an IO that read, and
|
|
9
|
+
# #close, see .v3_stream_methods.
|
|
10
|
+
V3_METHODS = [*V3_READ_METHODS, :close].freeze
|
|
11
|
+
private_constant :V3_METHODS
|
|
12
|
+
|
|
13
|
+
# Returns [Array<Symbol>] the methods that the stream that v3.0 supplies to a reader block, in place of the
|
|
14
|
+
# `Zlib::GzipReader` that this reader yields, answers, see `IOStreams::Reader.v3_stream_methods`.
|
|
15
|
+
def self.v3_stream_methods(_options)
|
|
16
|
+
V3_METHODS
|
|
17
|
+
end
|
|
18
|
+
|
|
8
19
|
# Read from a gzip stream, decompressing the contents as it is read
|
|
9
20
|
def self.stream(input_stream)
|
|
10
21
|
io = ::Zlib::GzipReader.new(input_stream)
|