iostreams 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36a51f41f33a1c58f1d5d03ed857784e86de4588ca38aa5a596e479f8661faf3
4
- data.tar.gz: 23dd967c21581520675799692296bb901e2dc539f008fa30623f4bd72a2c90f3
3
+ metadata.gz: bef30882ae2eaebcfbc240858a6854f44cc54208465868e55fd6296addfc8015
4
+ data.tar.gz: e9984f367727934b96495a646aa271009681f9972568a0b51e9046edf451e321
5
5
  SHA512:
6
- metadata.gz: 81d5f7ea50b7a5b06a5d26b758eb0639b0c820735cd0cf1545c6298f244b96b0070d3f8a11deed0ef8398b5b6c461561b3c43661612e3eeda19bdf61abb549ab
7
- data.tar.gz: 19f6f2051351533029cd7fca3cd6e7be3a4e5d3ea4ff2ae512b0b98a12b84639ae63e6a4decc0ff3edf1d50d5c14ddc1b869767f2f6c8793fc392f6ad02d939f
6
+ metadata.gz: 4c30cb2085ce36904551bcfce2d0ec1d4544d79af891bf27660c35daa4081e1f2edaf193332ca7f15ed8324f4aaddef991a906b896d502b1574c53531f27b4d5
7
+ data.tar.gz: 6066bb59b519568f99121d97ec0ba2637fab650889cc898a788f71d1dedcbce7167fa435206656d3ce607bb141eee5507501825afebbbd7e37e9c2c27c915f0f
@@ -3,9 +3,7 @@ module IOStreams
3
3
  class Reader < IOStreams::Reader
4
4
  # Read from a Bzip2 stream, decompressing the contents as it is read
5
5
  def self.stream(input_stream, **args)
6
- unless defined?(::Bzip2::FFI)
7
- Utils.load_soft_dependency("bzip2-ffi", "Bzip2", "bzip2/ffi")
8
- end
6
+ Utils.load_soft_dependency("bzip2-ffi", "Bzip2", "bzip2/ffi") unless defined?(::Bzip2::FFI)
9
7
 
10
8
  begin
11
9
  io = ::Bzip2::FFI::Reader.new(input_stream, args)
@@ -3,9 +3,7 @@ module IOStreams
3
3
  class Writer < IOStreams::Writer
4
4
  # Write to a stream, compressing with Bzip2
5
5
  def self.stream(input_stream, original_file_name: nil, **args)
6
- unless defined?(::Bzip2::FFI)
7
- Utils.load_soft_dependency("bzip2-ffi", "Bzip2", "bzip2/ffi")
8
- end
6
+ Utils.load_soft_dependency("bzip2-ffi", "Bzip2", "bzip2/ffi") unless defined?(::Bzip2::FFI)
9
7
 
10
8
  begin
11
9
  io = ::Bzip2::FFI::Writer.new(input_stream, args)
@@ -78,8 +78,6 @@ module IOStreams
78
78
  block
79
79
  end
80
80
 
81
- private
82
-
83
81
  def self.extract_cleaner(cleaner)
84
82
  return if cleaner.nil?
85
83
 
@@ -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
- if @buffer
67
- # Change the delimiters encoding to match that of the input stream
68
- @delimiter = @delimiter.encode(@buffer.encoding)
69
- @delimiter_size = @delimiter.size
70
- end
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.
@@ -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.
@@ -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", username: "jack", ssh_options: {IdentityFile: "~/.ssh/private_key"}).reader do |io|
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", :permissions=>420, :atime=>1572378136, :mtime=>1572378136, :link_count=>1, :extended=>{}}
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
 
@@ -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
- if (match = err.match(/gpg: key ([0-9A-F]+)\s+/))
78
- match[1]
79
- end
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
@@ -184,8 +184,6 @@ module IOStreams
184
184
  @formats.keys
185
185
  end
186
186
 
187
- private
188
-
189
187
  # A registry to hold formats for processing files during upload or download
190
188
  @formats = {}
191
189
 
@@ -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
- value = hash[column.key].to_s
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
- @length += column.size
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
- raise(Errors::InvalidLayout, "Size #{size.inspect} must be positive") unless @size.positive?
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
- case type
156
- when :string
157
- format("%-#{size}.#{size}s", value.to_s)
158
- when :integer
159
- formatted = format("%0#{size}d", value.to_i)
160
- if formatted.length > size
161
- raise(Errors::ValueTooLong, "Value: #{value} is too large to fit into column:#{key} of size:#{size}")
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
- formatted
165
- when :float
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
@@ -1,3 +1,3 @@
1
1
  module IOStreams
2
- VERSION = "1.4.0".freeze
2
+ VERSION = "1.5.0".freeze
3
3
  end
@@ -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 = tabular.record_parse("Jack over there XX34618012345670012345.01")
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
- tabular.record_parse("Jack over th")
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
- tabular.record_parse("Jack over there XX34618012345670012345.01............")
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 = tabular.record_parse(" 00000000000000000000000")
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 = tabular.record_parse(" XX ")
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 tabular refactor to get this working"
180
- assert hash = tabular.record_parse(" ")
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 tabular.record_parse(nil)
203
+ refute fixed.record_parse(nil)
186
204
  end
187
205
 
188
206
  it "parses empty string as nil" do
189
- refute tabular.record_parse("")
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 = tabular.render(name: "Jack", address: "over there", zip: 34_618, weight: 123_456.789123, age: 21)
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 = tabular.render(name: "Jack ran up the beanstalk and when jack reached the top it was truncated", address: "over there", zip: 34_618)
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
- tabular.render(zip: 3_461_832_653_653_265)
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
- tabular.render(weight: 3_461_832_653_653_265.234)
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 = tabular.render(zip: 34_618)
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 = tabular.render(name: true, address: false)
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 tabular.render({})
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.0
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-09-04 00:00:00.000000000 Z
11
+ date: 2020-09-10 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email: