iostreams 1.3.1 → 1.5.1
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/lib/io_streams/bzip2/reader.rb +3 -3
- data/lib/io_streams/bzip2/writer.rb +3 -3
- data/lib/io_streams/encode/reader.rb +0 -2
- data/lib/io_streams/io_streams.rb +0 -2
- data/lib/io_streams/line/reader.rb +5 -5
- data/lib/io_streams/path.rb +2 -0
- data/lib/io_streams/paths/s3.rb +26 -7
- data/lib/io_streams/paths/sftp.rb +5 -2
- data/lib/io_streams/pgp.rb +5 -11
- data/lib/io_streams/stream.rb +1 -1
- data/lib/io_streams/tabular.rb +0 -2
- data/lib/io_streams/tabular/header.rb +10 -11
- data/lib/io_streams/tabular/parser/fixed.rb +60 -30
- data/lib/io_streams/version.rb +1 -1
- data/test/bzip2_writer_test.rb +6 -4
- data/test/paths/s3_test.rb +1 -1
- data/test/tabular_test.rb +71 -40
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c665e5c262a98de9ceccf1cf93f5bc391370d0b674f966d9e266b731a31d3b7f
|
4
|
+
data.tar.gz: 1ab8c125e49abc178ce4c1e94f36dfb9219011ece6c8a9bd65b1c5a5d2f14604
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63bec4c3602cd4ab699bcf73abe3d97b7b808c6559c378e67be99c1af1ab7ada84dd0753c91dd2fe7be0924690a1deb30b44b87f56cd4638c4954a6bfcd38796
|
7
|
+
data.tar.gz: 2b1138c5389747892a33b5213a42b0fe4ececabc421c824db03d5218d436725a95d0306c7b0a1cc2a4b0a8eb4e1b4192152f6d60f11892fb2980c418c7be1f80
|
@@ -2,11 +2,11 @@ module IOStreams
|
|
2
2
|
module Bzip2
|
3
3
|
class Reader < IOStreams::Reader
|
4
4
|
# Read from a Bzip2 stream, decompressing the contents as it is read
|
5
|
-
def self.stream(input_stream, **
|
6
|
-
Utils.load_soft_dependency("
|
5
|
+
def self.stream(input_stream, **args)
|
6
|
+
Utils.load_soft_dependency("bzip2-ffi", "Bzip2", "bzip2/ffi") unless defined?(::Bzip2::FFI)
|
7
7
|
|
8
8
|
begin
|
9
|
-
io =
|
9
|
+
io = ::Bzip2::FFI::Reader.new(input_stream, args)
|
10
10
|
yield io
|
11
11
|
ensure
|
12
12
|
io&.close
|
@@ -2,11 +2,11 @@ module IOStreams
|
|
2
2
|
module Bzip2
|
3
3
|
class Writer < IOStreams::Writer
|
4
4
|
# Write to a stream, compressing with Bzip2
|
5
|
-
def self.stream(input_stream, original_file_name: nil, **
|
6
|
-
Utils.load_soft_dependency("
|
5
|
+
def self.stream(input_stream, original_file_name: nil, **args)
|
6
|
+
Utils.load_soft_dependency("bzip2-ffi", "Bzip2", "bzip2/ffi") unless defined?(::Bzip2::FFI)
|
7
7
|
|
8
8
|
begin
|
9
|
-
io =
|
9
|
+
io = ::Bzip2::FFI::Writer.new(input_stream, args)
|
10
10
|
yield io
|
11
11
|
ensure
|
12
12
|
io&.close
|
@@ -298,8 +298,6 @@ module IOStreams
|
|
298
298
|
@schemes[scheme_name.nil? ? nil : scheme_name.to_sym] || raise(ArgumentError, "Unknown Scheme type: #{scheme_name.inspect}")
|
299
299
|
end
|
300
300
|
|
301
|
-
private
|
302
|
-
|
303
301
|
Extension = Struct.new(:reader_class, :writer_class)
|
304
302
|
|
305
303
|
# Hold root paths
|
@@ -63,11 +63,11 @@ module IOStreams
|
|
63
63
|
# Auto-detect windows/linux line endings if not supplied. \n or \r\n
|
64
64
|
@delimiter ||= auto_detect_line_endings
|
65
65
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
66
|
+
return unless @buffer
|
67
|
+
|
68
|
+
# Change the delimiters encoding to match that of the input stream
|
69
|
+
@delimiter = @delimiter.encode(@buffer.encoding)
|
70
|
+
@delimiter_size = @delimiter.size
|
71
71
|
end
|
72
72
|
|
73
73
|
# Iterate over every line in the file/stream passing each line to supplied block in turn.
|
data/lib/io_streams/path.rb
CHANGED
@@ -82,6 +82,7 @@ module IOStreams
|
|
82
82
|
end
|
83
83
|
|
84
84
|
# Cleanup an incomplete write to the target "file" if the copy fails.
|
85
|
+
# rubocop:disable Lint/SuppressedException
|
85
86
|
def copy_from(source, **args)
|
86
87
|
super(source, **args)
|
87
88
|
rescue StandardError => e
|
@@ -91,6 +92,7 @@ module IOStreams
|
|
91
92
|
end
|
92
93
|
raise(e)
|
93
94
|
end
|
95
|
+
# rubocop:enable Lint/SuppressedException
|
94
96
|
|
95
97
|
# Moves the file by copying it to the new path and then deleting the current path.
|
96
98
|
# Returns [IOStreams::Path] the target path.
|
data/lib/io_streams/paths/s3.rb
CHANGED
@@ -3,7 +3,7 @@ require "uri"
|
|
3
3
|
module IOStreams
|
4
4
|
module Paths
|
5
5
|
class S3 < IOStreams::Path
|
6
|
-
attr_reader :bucket_name, :client
|
6
|
+
attr_reader :bucket_name, :client, :options
|
7
7
|
|
8
8
|
# Arguments:
|
9
9
|
#
|
@@ -179,17 +179,36 @@ module IOStreams
|
|
179
179
|
#
|
180
180
|
# Notes:
|
181
181
|
# - Can copy across buckets.
|
182
|
+
# - No stream conversions are applied.
|
182
183
|
def move_to(target_path)
|
184
|
+
target = copy_to(target_path, convert: false)
|
185
|
+
delete
|
186
|
+
target
|
187
|
+
end
|
188
|
+
|
189
|
+
# Make S3 perform direct copies within S3 itself.
|
190
|
+
def copy_to(target_path, convert: true)
|
191
|
+
return super(target_path) if convert
|
192
|
+
|
183
193
|
target = IOStreams.new(target_path)
|
184
194
|
return super(target) unless target.is_a?(self.class)
|
185
195
|
|
186
196
|
source_name = ::File.join(bucket_name, path)
|
187
|
-
|
188
|
-
client.copy_object(bucket: target.bucket_name, key: target.path, copy_source: source_name)
|
189
|
-
delete
|
197
|
+
client.copy_object(options.merge(bucket: target.bucket_name, key: target.path, copy_source: source_name))
|
190
198
|
target
|
191
199
|
end
|
192
200
|
|
201
|
+
# Make S3 perform direct copies within S3 itself.
|
202
|
+
def copy_from(source_path, convert: true)
|
203
|
+
return super(source_path) if convert
|
204
|
+
|
205
|
+
source = IOStreams.new(source_path)
|
206
|
+
return super(source) unless source.is_a?(self.class)
|
207
|
+
|
208
|
+
source_name = ::File.join(source.bucket_name, source.path)
|
209
|
+
client.copy_object(options.merge(bucket: bucket_name, key: path, copy_source: source_name))
|
210
|
+
end
|
211
|
+
|
193
212
|
# S3 logically creates paths when a key is set.
|
194
213
|
def mkpath
|
195
214
|
self
|
@@ -220,7 +239,7 @@ module IOStreams
|
|
220
239
|
# Shortcut method if caller has a filename already with no other streams applied:
|
221
240
|
def read_file(file_name)
|
222
241
|
::File.open(file_name, "wb") do |file|
|
223
|
-
client.get_object(
|
242
|
+
client.get_object(options.merge(response_target: file, bucket: bucket_name, key: path))
|
224
243
|
end
|
225
244
|
end
|
226
245
|
|
@@ -248,10 +267,10 @@ module IOStreams
|
|
248
267
|
# Use multipart file upload
|
249
268
|
s3 = Aws::S3::Resource.new(client: client)
|
250
269
|
obj = s3.bucket(bucket_name).object(path)
|
251
|
-
obj.upload_file(file_name)
|
270
|
+
obj.upload_file(file_name, options)
|
252
271
|
else
|
253
272
|
::File.open(file_name, "rb") do |file|
|
254
|
-
client.put_object(
|
273
|
+
client.put_object(options.merge(bucket: bucket_name, key: path, body: file))
|
255
274
|
end
|
256
275
|
end
|
257
276
|
end
|
@@ -71,7 +71,9 @@ module IOStreams
|
|
71
71
|
# end
|
72
72
|
#
|
73
73
|
# # When using the sftp executable use an identity file instead of a password to authenticate:
|
74
|
-
# IOStreams.path("sftp://test.com/path/file_name.csv",
|
74
|
+
# IOStreams.path("sftp://test.com/path/file_name.csv",
|
75
|
+
# username: "jack",
|
76
|
+
# ssh_options: {IdentityFile: "~/.ssh/private_key"}).reader do |io|
|
75
77
|
# puts io.read
|
76
78
|
# end
|
77
79
|
def initialize(url, username: nil, password: nil, ssh_options: {})
|
@@ -122,7 +124,8 @@ module IOStreams
|
|
122
124
|
# end
|
123
125
|
#
|
124
126
|
# Example Output:
|
125
|
-
# sftp://sftp.example.org/a/b/c/test.txt {:type=>1, :size=>37, :owner=>"test_owner", :group=>"test_group",
|
127
|
+
# sftp://sftp.example.org/a/b/c/test.txt {:type=>1, :size=>37, :owner=>"test_owner", :group=>"test_group",
|
128
|
+
# :permissions=>420, :atime=>1572378136, :mtime=>1572378136, :link_count=>1, :extended=>{}}
|
126
129
|
def each_child(pattern = "*", case_sensitive: true, directories: false, hidden: false)
|
127
130
|
Utils.load_soft_dependency("net-sftp", "SFTP glob capability", "net/sftp") unless defined?(Net::SFTP)
|
128
131
|
|
data/lib/io_streams/pgp.rb
CHANGED
@@ -74,9 +74,10 @@ module IOStreams
|
|
74
74
|
|
75
75
|
raise(Pgp::Failure, "GPG Failed to generate key: #{err}#{out}") unless status.success?
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
match = err.match(/gpg: key ([0-9A-F]+)\s+/)
|
78
|
+
return unless match
|
79
|
+
|
80
|
+
match[1]
|
80
81
|
end
|
81
82
|
|
82
83
|
# Delete all private and public keys for a particular email.
|
@@ -110,11 +111,6 @@ module IOStreams
|
|
110
111
|
!list_keys(email: email, key_id: key_id, private: private).empty?
|
111
112
|
end
|
112
113
|
|
113
|
-
# Deprecated
|
114
|
-
def self.has_key?(**args)
|
115
|
-
key?(**args)
|
116
|
-
end
|
117
|
-
|
118
114
|
# Returns [Array<Hash>] the list of keys.
|
119
115
|
# Each Hash consists of:
|
120
116
|
# key_length: [Integer]
|
@@ -232,7 +228,7 @@ module IOStreams
|
|
232
228
|
err.each_line do |line|
|
233
229
|
if line =~ /secret key imported/
|
234
230
|
secret = true
|
235
|
-
elsif match = line.match(/key\s+(\w+):\s+(\w+).+\"(.*)<(.*)>\"/)
|
231
|
+
elsif (match = line.match(/key\s+(\w+):\s+(\w+).+\"(.*)<(.*)>\"/))
|
236
232
|
results << {
|
237
233
|
key_id: match[1].to_s.strip,
|
238
234
|
private: secret,
|
@@ -347,8 +343,6 @@ module IOStreams
|
|
347
343
|
end
|
348
344
|
end
|
349
345
|
|
350
|
-
private
|
351
|
-
|
352
346
|
@logger = nil
|
353
347
|
|
354
348
|
def self.logger
|
data/lib/io_streams/stream.rb
CHANGED
data/lib/io_streams/tabular.rb
CHANGED
@@ -129,18 +129,17 @@ module IOStreams
|
|
129
129
|
# Perform cleansing on returned Hash keys during the narrowing process.
|
130
130
|
# For example, avoids issues with case etc.
|
131
131
|
def cleanse_hash(hash)
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
132
|
+
unmatched = columns - hash.keys
|
133
|
+
unless unmatched.empty?
|
134
|
+
hash = hash.dup
|
135
|
+
unmatched.each { |name| hash[cleanse_column(name)] = hash.delete(name) }
|
136
|
+
end
|
137
|
+
# Hash#slice as of Ruby 2.5
|
138
|
+
if hash.respond_to?(:slice)
|
139
|
+
hash.slice(*columns)
|
140
|
+
else
|
141
|
+
columns.each_with_object({}) { |column, new_hash| new_hash[column] = hash[column] }
|
142
142
|
end
|
143
|
-
h
|
144
143
|
end
|
145
144
|
|
146
145
|
def cleanse_column(name)
|
@@ -37,6 +37,22 @@ module IOStreams
|
|
37
37
|
# The :size is the total size of this field including the `.` and the decimals.
|
38
38
|
# Number of :decimals
|
39
39
|
# Raises Errors::ValueTooLong when the supplied value cannot be rendered in `size` characters.
|
40
|
+
#
|
41
|
+
# In some circumstances the length of the last column is variable.
|
42
|
+
# layout: [Array<Hash>]
|
43
|
+
# [
|
44
|
+
# {size: 23, key: "name"},
|
45
|
+
# {size: :remainder, key: "rest"}
|
46
|
+
# ]
|
47
|
+
# By setting a size of `:remainder` it will take the rest of the line as the value for that column.
|
48
|
+
#
|
49
|
+
# A size of `:remainder` and no `:key` will discard the remainder of the line without validating the length.
|
50
|
+
# layout: [Array<Hash>]
|
51
|
+
# [
|
52
|
+
# {size: 23, key: "name"},
|
53
|
+
# {size: :remainder}
|
54
|
+
# ]
|
55
|
+
#
|
40
56
|
def initialize(layout:, truncate: true)
|
41
57
|
@layout = Layout.new(layout)
|
42
58
|
@truncate = truncate
|
@@ -57,12 +73,7 @@ module IOStreams
|
|
57
73
|
|
58
74
|
result = ""
|
59
75
|
layout.columns.each do |column|
|
60
|
-
|
61
|
-
if !truncate && (value.length > column.size)
|
62
|
-
raise(Errors::ValueTooLong, "Value: #{value.inspect} is too long to fit into column #{column.key} of size #{column.size}")
|
63
|
-
end
|
64
|
-
|
65
|
-
result << column.render(value)
|
76
|
+
result << column.render(hash[column.key], truncate)
|
66
77
|
end
|
67
78
|
result
|
68
79
|
end
|
@@ -74,13 +85,18 @@ module IOStreams
|
|
74
85
|
raise(Errors::TypeMismatch, "Line must be a String when format is :fixed. Actual: #{line.class.name}")
|
75
86
|
end
|
76
87
|
|
77
|
-
if line.length != layout.length
|
88
|
+
if layout.length.positive? && (line.length != layout.length)
|
78
89
|
raise(Errors::InvalidLineLength, "Expected line length: #{layout.length}, actual line length: #{line.length}")
|
79
90
|
end
|
80
91
|
|
81
92
|
hash = {}
|
82
93
|
index = 0
|
83
94
|
layout.columns.each do |column|
|
95
|
+
if column.size == -1
|
96
|
+
hash[column.key] = column.parse(line[index..-1]) if column.key
|
97
|
+
break
|
98
|
+
end
|
99
|
+
|
84
100
|
# Ignore "columns" that have no keys. E.g. Fillers
|
85
101
|
hash[column.key] = column.parse(line[index, column.size]) if column.key
|
86
102
|
index += column.size
|
@@ -93,8 +109,6 @@ module IOStreams
|
|
93
109
|
false
|
94
110
|
end
|
95
111
|
|
96
|
-
private
|
97
|
-
|
98
112
|
class Layout
|
99
113
|
attr_reader :columns, :length
|
100
114
|
|
@@ -113,7 +127,15 @@ module IOStreams
|
|
113
127
|
raise(Errors::InvalidLayout, "Missing required :size in: #{hash.inspect}") unless hash.key?(:size)
|
114
128
|
|
115
129
|
column = Column.new(**hash)
|
116
|
-
|
130
|
+
if column.size == -1
|
131
|
+
if @length == -1
|
132
|
+
raise(Errors::InvalidLayout, "Only the last :size can be '-1' or :remainder in: #{hash.inspect}")
|
133
|
+
end
|
134
|
+
|
135
|
+
@length = -1
|
136
|
+
else
|
137
|
+
@length += column.size
|
138
|
+
end
|
117
139
|
column
|
118
140
|
end
|
119
141
|
end
|
@@ -126,11 +148,13 @@ module IOStreams
|
|
126
148
|
|
127
149
|
def initialize(key: nil, size:, type: :string, decimals: 2)
|
128
150
|
@key = key
|
129
|
-
@size = size.to_i
|
151
|
+
@size = size == :remainder ? -1 : size.to_i
|
130
152
|
@type = type.to_sym
|
131
153
|
@decimals = decimals
|
132
154
|
|
133
|
-
|
155
|
+
unless @size.positive? || (@size == -1)
|
156
|
+
raise(Errors::InvalidLayout, "Size #{size.inspect} must be positive or :remainder")
|
157
|
+
end
|
134
158
|
raise(Errors::InvalidLayout, "Unknown type: #{type.inspect}") unless TYPES.include?(type)
|
135
159
|
end
|
136
160
|
|
@@ -151,27 +175,33 @@ module IOStreams
|
|
151
175
|
end
|
152
176
|
end
|
153
177
|
|
154
|
-
def render(value)
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
178
|
+
def render(value, truncate)
|
179
|
+
formatted =
|
180
|
+
case type
|
181
|
+
when :string
|
182
|
+
value = value.to_s
|
183
|
+
return value if size == -1
|
184
|
+
|
185
|
+
format(truncate ? "%-#{size}.#{size}s" : "%-#{size}s", value)
|
186
|
+
when :integer
|
187
|
+
return value.to_i.to_s if size == -1
|
188
|
+
|
189
|
+
truncate = false
|
190
|
+
format("%0#{size}d", value.to_i)
|
191
|
+
when :float
|
192
|
+
return value.to_f.to_s if size == -1
|
193
|
+
|
194
|
+
truncate = false
|
195
|
+
format("%0#{size}.#{decimals}f", value.to_f)
|
196
|
+
else
|
197
|
+
raise(Errors::InvalidLayout, "Unsupported type: #{type.inspect}")
|
162
198
|
end
|
163
199
|
|
164
|
-
|
165
|
-
|
166
|
-
formatted = format("%0#{size}.#{decimals}f", value.to_f)
|
167
|
-
if formatted.length > size
|
168
|
-
raise(Errors::ValueTooLong, "Value: #{value} is too large to fit into column:#{key} of size:#{size}")
|
169
|
-
end
|
170
|
-
|
171
|
-
formatted
|
172
|
-
else
|
173
|
-
raise(Errors::InvalidLayout, "Unsupported type: #{type.inspect}")
|
200
|
+
if !truncate && formatted.length > size
|
201
|
+
raise(Errors::ValueTooLong, "Value: #{value} is too large to fit into column:#{key} of size:#{size}")
|
174
202
|
end
|
203
|
+
|
204
|
+
formatted
|
175
205
|
end
|
176
206
|
end
|
177
207
|
end
|
data/lib/io_streams/version.rb
CHANGED
data/test/bzip2_writer_test.rb
CHANGED
@@ -22,13 +22,14 @@ class Bzip2WriterTest < Minitest::Test
|
|
22
22
|
it "file" do
|
23
23
|
IOStreams::Bzip2::Writer.file(file_name) do |io|
|
24
24
|
io.write(decompressed)
|
25
|
+
io.write(decompressed)
|
25
26
|
end
|
26
27
|
|
27
28
|
File.open(file_name, "rb") do |file|
|
28
|
-
io =
|
29
|
+
io = ::Bzip2::FFI::Reader.new(file)
|
29
30
|
result = io.read
|
30
31
|
temp_file.delete
|
31
|
-
assert_equal decompressed, result
|
32
|
+
assert_equal decompressed + decompressed, result
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
@@ -36,12 +37,13 @@ class Bzip2WriterTest < Minitest::Test
|
|
36
37
|
io_string = StringIO.new("".b)
|
37
38
|
IOStreams::Bzip2::Writer.stream(io_string) do |io|
|
38
39
|
io.write(decompressed)
|
40
|
+
io.write(decompressed)
|
39
41
|
end
|
40
42
|
|
41
43
|
io = StringIO.new(io_string.string)
|
42
|
-
rbzip2 =
|
44
|
+
rbzip2 = ::Bzip2::FFI::Reader.new(io)
|
43
45
|
data = rbzip2.read
|
44
|
-
assert_equal decompressed, data
|
46
|
+
assert_equal decompressed + decompressed, data
|
45
47
|
end
|
46
48
|
end
|
47
49
|
end
|
data/test/paths/s3_test.rb
CHANGED
@@ -138,7 +138,7 @@ module Paths
|
|
138
138
|
|
139
139
|
it "returns all the children under a sub-dir" do
|
140
140
|
write_raw_data
|
141
|
-
expected =
|
141
|
+
expected = %w[abd/test1.txt abd/test5.file].collect { |file_name| each_root.join(file_name) }
|
142
142
|
assert_equal expected.collect(&:to_s).sort, each_root.children("abd/*").collect(&:to_s).sort
|
143
143
|
end
|
144
144
|
|
data/test/tabular_test.rb
CHANGED
@@ -10,6 +10,36 @@ class TabularTest < Minitest::Test
|
|
10
10
|
IOStreams::Tabular.new(columns: %w[first_field second third], format: format)
|
11
11
|
end
|
12
12
|
|
13
|
+
let :fixed do
|
14
|
+
layout = [
|
15
|
+
{size: 23, key: :name},
|
16
|
+
{size: 40, key: :address},
|
17
|
+
{size: 2},
|
18
|
+
{size: 5, key: :zip, type: :integer},
|
19
|
+
{size: 8, key: :age, type: :integer},
|
20
|
+
{size: 10, key: :weight, type: :float, decimals: 2}
|
21
|
+
]
|
22
|
+
IOStreams::Tabular.new(format: :fixed, format_options: {layout: layout})
|
23
|
+
end
|
24
|
+
|
25
|
+
let :fixed_with_remainder do
|
26
|
+
layout = [
|
27
|
+
{size: 23, key: :name},
|
28
|
+
{size: 40, key: :address},
|
29
|
+
{size: :remainder, key: :remainder}
|
30
|
+
]
|
31
|
+
IOStreams::Tabular.new(format: :fixed, format_options: {layout: layout})
|
32
|
+
end
|
33
|
+
|
34
|
+
let :fixed_discard_remainder do
|
35
|
+
layout = [
|
36
|
+
{size: 23, key: :name},
|
37
|
+
{size: 40, key: :address},
|
38
|
+
{size: :remainder}
|
39
|
+
]
|
40
|
+
IOStreams::Tabular.new(format: :fixed, format_options: {layout: layout})
|
41
|
+
end
|
42
|
+
|
13
43
|
describe "#parse_header" do
|
14
44
|
it "parses and sets the csv header" do
|
15
45
|
tabular = IOStreams::Tabular.new(format: :csv)
|
@@ -136,57 +166,55 @@ class TabularTest < Minitest::Test
|
|
136
166
|
end
|
137
167
|
|
138
168
|
describe ":fixed format" do
|
139
|
-
let :tabular do
|
140
|
-
layout = [
|
141
|
-
{size: 23, key: :name},
|
142
|
-
{size: 40, key: :address},
|
143
|
-
{size: 2},
|
144
|
-
{size: 5, key: :zip, type: :integer},
|
145
|
-
{size: 8, key: :age, type: :integer},
|
146
|
-
{size: 10, key: :weight, type: :float, decimals: 2}
|
147
|
-
]
|
148
|
-
IOStreams::Tabular.new(format: :fixed, format_options: {layout: layout})
|
149
|
-
end
|
150
|
-
|
151
169
|
it "parses to hash" do
|
152
|
-
assert hash =
|
170
|
+
assert hash = fixed.record_parse("Jack over there XX34618012345670012345.01")
|
153
171
|
assert_equal({name: "Jack", address: "over there", zip: 34_618, age: 1_234_567, weight: 12_345.01}, hash)
|
154
172
|
end
|
155
173
|
|
156
174
|
it "parses short string" do
|
157
175
|
assert_raises IOStreams::Errors::InvalidLineLength do
|
158
|
-
|
176
|
+
fixed.record_parse("Jack over th")
|
159
177
|
end
|
160
178
|
end
|
161
179
|
|
162
180
|
it "parses longer string" do
|
163
181
|
assert_raises IOStreams::Errors::InvalidLineLength do
|
164
|
-
|
182
|
+
fixed.record_parse("Jack over there XX34618012345670012345.01............")
|
165
183
|
end
|
166
184
|
end
|
167
185
|
|
168
186
|
it "parses zero values" do
|
169
|
-
assert hash =
|
187
|
+
assert hash = fixed.record_parse(" 00000000000000000000000")
|
170
188
|
assert_equal({name: "", address: "", zip: 0, age: 0, weight: 0.0}, hash)
|
171
189
|
end
|
172
190
|
|
173
191
|
it "parses empty values" do
|
174
|
-
assert hash =
|
192
|
+
assert hash = fixed.record_parse(" XX ")
|
175
193
|
assert_equal({name: "", address: "", zip: nil, age: nil, weight: nil}, hash)
|
176
194
|
end
|
177
195
|
|
178
196
|
it "parses blank strings" do
|
179
|
-
skip "TODO: Part of
|
180
|
-
assert hash =
|
197
|
+
skip "TODO: Part of fixed refactor to get this working"
|
198
|
+
assert hash = fixed.record_parse(" ")
|
181
199
|
assert_equal({name: "", address: "", zip: nil, age: nil, weight: nil}, hash)
|
182
200
|
end
|
183
201
|
|
184
202
|
it "parses nil data as nil" do
|
185
|
-
refute
|
203
|
+
refute fixed.record_parse(nil)
|
186
204
|
end
|
187
205
|
|
188
206
|
it "parses empty string as nil" do
|
189
|
-
refute
|
207
|
+
refute fixed.record_parse("")
|
208
|
+
end
|
209
|
+
|
210
|
+
it "parses remainder" do
|
211
|
+
hash = fixed_with_remainder.record_parse("Jack over there XX34618012345670012345.01............")
|
212
|
+
assert_equal({name: "Jack", address: "over there", remainder: "XX34618012345670012345.01............"}, hash)
|
213
|
+
end
|
214
|
+
|
215
|
+
it "discards remainder" do
|
216
|
+
hash = fixed_discard_remainder.record_parse("Jack over there XX34618012345670012345.01............")
|
217
|
+
assert_equal({name: "Jack", address: "over there"}, hash)
|
190
218
|
end
|
191
219
|
end
|
192
220
|
|
@@ -236,52 +264,55 @@ class TabularTest < Minitest::Test
|
|
236
264
|
end
|
237
265
|
|
238
266
|
describe ":fixed format" do
|
239
|
-
let :tabular do
|
240
|
-
layout = [
|
241
|
-
{size: 23, key: :name},
|
242
|
-
{size: 40, key: :address},
|
243
|
-
{size: 2},
|
244
|
-
{size: 5, key: :zip, type: :integer},
|
245
|
-
{size: 8, key: :age, type: :integer},
|
246
|
-
{size: 10, key: :weight, type: :float, decimals: 2}
|
247
|
-
]
|
248
|
-
IOStreams::Tabular.new(format: :fixed, format_options: {layout: layout})
|
249
|
-
end
|
250
|
-
|
251
267
|
it "renders fixed data" do
|
252
|
-
assert string =
|
268
|
+
assert string = fixed.render(name: "Jack", address: "over there", zip: 34_618, weight: 123_456.789123, age: 21)
|
253
269
|
assert_equal "Jack over there 34618000000210123456.79", string
|
254
270
|
end
|
255
271
|
|
256
272
|
it "truncates long strings" do
|
257
|
-
assert string =
|
273
|
+
assert string = fixed.render(name: "Jack ran up the beanstalk and when jack reached the top it was truncated", address: "over there", zip: 34_618)
|
258
274
|
assert_equal "Jack ran up the beanstaover there 34618000000000000000.00", string
|
259
275
|
end
|
260
276
|
|
261
277
|
it "when integer is too large" do
|
262
278
|
assert_raises IOStreams::Errors::ValueTooLong do
|
263
|
-
|
279
|
+
fixed.render(zip: 3_461_832_653_653_265)
|
264
280
|
end
|
265
281
|
end
|
266
282
|
|
267
283
|
it "when float is too large" do
|
268
284
|
assert_raises IOStreams::Errors::ValueTooLong do
|
269
|
-
|
285
|
+
fixed.render(weight: 3_461_832_653_653_265.234)
|
270
286
|
end
|
271
287
|
end
|
272
288
|
|
273
289
|
it "renders nil as empty string" do
|
274
|
-
assert string =
|
290
|
+
assert string = fixed.render(zip: 34_618)
|
275
291
|
assert_equal " 34618000000000000000.00", string
|
276
292
|
end
|
277
293
|
|
278
294
|
it "renders boolean" do
|
279
|
-
assert string =
|
295
|
+
assert string = fixed.render(name: true, address: false)
|
280
296
|
assert_equal "true false 00000000000000000000.00", string
|
281
297
|
end
|
282
298
|
|
283
299
|
it "renders no data as nil" do
|
284
|
-
refute
|
300
|
+
refute fixed.render({})
|
301
|
+
end
|
302
|
+
|
303
|
+
it "any size last string" do
|
304
|
+
assert string = fixed_with_remainder.render(name: "Jack", address: "over there", remainder: "XX34618012345670012345.01............")
|
305
|
+
assert_equal "Jack over there XX34618012345670012345.01............", string
|
306
|
+
end
|
307
|
+
|
308
|
+
it "nil last string" do
|
309
|
+
assert string = fixed_with_remainder.render(name: "Jack", address: "over there", remainder: nil)
|
310
|
+
assert_equal "Jack over there ", string
|
311
|
+
end
|
312
|
+
|
313
|
+
it "skips last filler" do
|
314
|
+
assert string = fixed_discard_remainder.render(name: "Jack", address: "over there")
|
315
|
+
assert_equal "Jack over there ", string
|
285
316
|
end
|
286
317
|
end
|
287
318
|
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: 1.
|
4
|
+
version: 1.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -130,7 +130,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
130
|
- !ruby/object:Gem::Version
|
131
131
|
version: '0'
|
132
132
|
requirements: []
|
133
|
-
rubygems_version: 3.
|
133
|
+
rubygems_version: 3.1.2
|
134
134
|
signing_key:
|
135
135
|
specification_version: 4
|
136
136
|
summary: Input and Output streaming for Ruby.
|