iostreams 1.4.0 → 1.5.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 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: