iostreams 1.3.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +64 -29
- 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: bef30882ae2eaebcfbc240858a6854f44cc54208465868e55fd6296addfc8015
|
4
|
+
data.tar.gz: e9984f367727934b96495a646aa271009681f9972568a0b51e9046edf451e321
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4c30cb2085ce36904551bcfce2d0ec1d4544d79af891bf27660c35daa4081e1f2edaf193332ca7f15ed8324f4aaddef991a906b896d502b1574c53531f27b4d5
|
7
|
+
data.tar.gz: 6066bb59b519568f99121d97ec0ba2637fab650889cc898a788f71d1dedcbce7167fa435206656d3ce607bb141eee5507501825afebbbd7e37e9c2c27c915f0f
|
@@ -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, **args) 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
|
@@ -88,7 +104,10 @@ module IOStreams
|
|
88
104
|
hash
|
89
105
|
end
|
90
106
|
|
91
|
-
|
107
|
+
# The header is required as an argument and cannot be supplied in the file itself.
|
108
|
+
def requires_header?
|
109
|
+
false
|
110
|
+
end
|
92
111
|
|
93
112
|
class Layout
|
94
113
|
attr_reader :columns, :length
|
@@ -108,7 +127,15 @@ module IOStreams
|
|
108
127
|
raise(Errors::InvalidLayout, "Missing required :size in: #{hash.inspect}") unless hash.key?(:size)
|
109
128
|
|
110
129
|
column = Column.new(**hash)
|
111
|
-
|
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
|
112
139
|
column
|
113
140
|
end
|
114
141
|
end
|
@@ -121,11 +148,13 @@ module IOStreams
|
|
121
148
|
|
122
149
|
def initialize(key: nil, size:, type: :string, decimals: 2)
|
123
150
|
@key = key
|
124
|
-
@size = size.to_i
|
151
|
+
@size = size == :remainder ? -1 : size.to_i
|
125
152
|
@type = type.to_sym
|
126
153
|
@decimals = decimals
|
127
154
|
|
128
|
-
|
155
|
+
unless @size.positive? || (@size == -1)
|
156
|
+
raise(Errors::InvalidLayout, "Size #{size.inspect} must be positive or :remainder")
|
157
|
+
end
|
129
158
|
raise(Errors::InvalidLayout, "Unknown type: #{type.inspect}") unless TYPES.include?(type)
|
130
159
|
end
|
131
160
|
|
@@ -146,27 +175,33 @@ module IOStreams
|
|
146
175
|
end
|
147
176
|
end
|
148
177
|
|
149
|
-
def render(value)
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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}")
|
157
198
|
end
|
158
199
|
|
159
|
-
|
160
|
-
|
161
|
-
formatted = format("%0#{size}.#{decimals}f", value.to_f)
|
162
|
-
if formatted.length > size
|
163
|
-
raise(Errors::ValueTooLong, "Value: #{value} is too large to fit into column:#{key} of size:#{size}")
|
164
|
-
end
|
165
|
-
|
166
|
-
formatted
|
167
|
-
else
|
168
|
-
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}")
|
169
202
|
end
|
203
|
+
|
204
|
+
formatted
|
170
205
|
end
|
171
206
|
end
|
172
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.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: 2020-
|
11
|
+
date: 2020-09-10 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.
|