iostruct 0.4.0 → 0.6.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.
@@ -2,24 +2,206 @@ require 'spec_helper'
2
2
  require 'stringio'
3
3
 
4
4
  describe IOStruct do
5
+ describe "#pack" do
6
+ it "serializes struct back to binary" do
7
+ struct = described_class.new('L S C', :a, :b, :c)
8
+ obj = struct.new(a: 0x12345678, b: 0xABCD, c: 0xEF)
9
+ expect(obj.pack).to eq [0x12345678, 0xABCD, 0xEF].pack('L S C')
10
+ end
11
+
12
+ it "round-trips correctly" do
13
+ struct = described_class.new('L S C', :a, :b, :c)
14
+ data = [123, 456, 78].pack('L S C')
15
+ obj = struct.read(data)
16
+ expect(obj.pack).to eq data
17
+ end
18
+ end
19
+
20
+ describe "#empty?" do
21
+ let(:struct) { described_class.new('L S', :a, :b) }
22
+
23
+ it "returns true when all fields are zero" do
24
+ expect(struct.new(a: 0, b: 0)).to be_empty
25
+ end
26
+
27
+ it "returns true when all fields are nil" do
28
+ expect(struct.new).to be_empty
29
+ end
30
+
31
+ it "returns false when any field is non-zero" do
32
+ expect(struct.new(a: 1, b: 0)).not_to be_empty
33
+ end
34
+
35
+ it "returns true for null-filled strings" do
36
+ str_struct = described_class.new('a4', :name)
37
+ expect(str_struct.new(name: "\x00\x00\x00\x00")).to be_empty
38
+ end
39
+
40
+ it "returns false for non-empty strings" do
41
+ str_struct = described_class.new('a4', :name)
42
+ expect(str_struct.new(name: "test")).not_to be_empty
43
+ end
44
+ end
45
+
46
+ describe "hash-style initialization" do
47
+ let(:struct) { described_class.new('L S C', :a, :b, :c) }
48
+
49
+ it "initializes fields by name" do
50
+ obj = struct.new(a: 100, c: 50)
51
+ expect(obj.a).to eq 100
52
+ expect(obj.b).to be_nil
53
+ expect(obj.c).to eq 50
54
+ end
55
+ end
56
+
57
+ describe "auto-generated field names" do
58
+ it "generates names based on offset" do
59
+ struct = described_class.new('C S L')
60
+ expect(struct.members).to eq [:f0, :f1, :f3]
61
+ end
62
+ end
63
+
64
+ describe "field renaming" do
65
+ it "renames auto-generated fields via kwargs" do
66
+ struct = described_class.new('C S L', f0: :byte_field, f3: :long_field)
67
+ expect(struct.members).to eq [:byte_field, :f1, :long_field]
68
+ end
69
+
70
+ it "renames named fields via kwargs" do
71
+ struct = described_class.new('C S L', :a, :b, :c, a: :first, c: :last)
72
+ expect(struct.members).to eq [:first, :b, :last]
73
+ end
74
+ end
75
+
76
+ describe "float types" do
77
+ it "unpacks single-precision float (f/F)" do
78
+ data = [3.14159].pack('f')
79
+ struct = described_class.new('f', :val)
80
+ expect(struct.read(data).val).to be_within(0.0001).of(3.14159)
81
+ expect(struct::SIZE).to eq 4
82
+ end
83
+
84
+ it "unpacks double-precision float (d/D)" do
85
+ data = [3.141592653589793].pack('d')
86
+ struct = described_class.new('d', :val)
87
+ expect(struct.read(data).val).to be_within(0.0000001).of(3.141592653589793)
88
+ expect(struct::SIZE).to eq 8
89
+ end
90
+
91
+ it "unpacks little-endian floats (e/E)" do
92
+ data = [2.5].pack('e')
93
+ struct = described_class.new('e', :val)
94
+ expect(struct.read(data).val).to be_within(0.0001).of(2.5)
95
+ end
96
+
97
+ it "unpacks big-endian floats (g/G)" do
98
+ data = [2.5].pack('G')
99
+ struct = described_class.new('G', :val)
100
+ expect(struct.read(data).val).to be_within(0.0001).of(2.5)
101
+ end
102
+
103
+ it "formats floats in to_table" do
104
+ struct = described_class.new('f', :val)
105
+ obj = struct.new(val: 3.14159)
106
+ expect(obj.to_table).to match(/val=\s*3\.142/)
107
+ end
108
+ end
109
+
110
+ describe "signed integer types" do
111
+ it "reads signed 8-bit (c)" do
112
+ struct = described_class.new('c', :val)
113
+ expect(struct.read("\x80").val).to eq(-128)
114
+ expect(struct.read("\x7f").val).to eq(127)
115
+ end
116
+
117
+ it "reads signed 16-bit (s)" do
118
+ struct = described_class.new('s', :val)
119
+ expect(struct.read("\x00\x80").val).to eq(-32768)
120
+ end
121
+
122
+ it "reads signed 32-bit (i)" do
123
+ struct = described_class.new('i', :val)
124
+ expect(struct.read("\x00\x00\x00\x80").val).to eq(-2147483648)
125
+ end
126
+
127
+ it "reads signed 64-bit (q)" do
128
+ struct = described_class.new('q', :val)
129
+ expect(struct.read("\x00\x00\x00\x00\x00\x00\x00\x80").val).to eq(-9223372036854775808)
130
+ end
131
+ end
132
+
133
+ describe "string types" do
134
+ it "unpacks space-padded string (A)" do
135
+ struct = described_class.new('A8', :name)
136
+ obj = struct.read("hello ")
137
+ expect(obj.name).to eq "hello"
138
+ end
139
+
140
+ it "unpacks null-terminated string (Z)" do
141
+ struct = described_class.new('Z8', :name)
142
+ obj = struct.read("hello\x00\x00\x00")
143
+ expect(obj.name).to eq "hello"
144
+ end
145
+
146
+ it "unpacks raw string (a)" do
147
+ struct = described_class.new('a8', :name)
148
+ obj = struct.read("hello\x00\x00\x00")
149
+ expect(obj.name).to eq "hello\x00\x00\x00"
150
+ end
151
+ end
152
+
153
+ describe "error handling" do
154
+ it "raises when no fmt and no :fields" do
155
+ expect { described_class.new }.to raise_error(/no fmt and no :fields/)
156
+ end
157
+
158
+ it "raises when reading from unsupported source" do
159
+ struct = described_class.new('L', :val)
160
+ expect { struct.read(12345) }.to raise_error(/don't know how to read/)
161
+ end
162
+ end
163
+
164
+ describe "inspect_name_override (deprecated)" do
165
+ it "works as alias for struct_name" do
166
+ struct = described_class.new('L', :val, inspect_name_override: 'MyStruct')
167
+ expect(struct.new.inspect).to match(/<MyStruct/)
168
+ end
169
+ end
170
+
171
+ describe ":name" do
172
+ context "when set" do
173
+ it "uses the custom name" do
174
+ x = described_class.new('LL', :x, :y, struct_name: 'Point')
175
+ expect(x.new.inspect).to match(/<Point x=nil y=nil>/)
176
+ end
177
+ end
178
+
179
+ context "when not set" do
180
+ it "has default name" do
181
+ x = described_class.new('LL', :x, :y)
182
+ expect(x.new.inspect).to match(/<struct x=nil y=nil>/)
183
+ end
184
+ end
185
+ end
186
+
5
187
  describe "#read" do
6
188
  let(:a) { [12345, 56789] }
7
189
  let(:data) { a.pack('L2') }
8
190
 
9
191
  it "reads from IO" do
10
- x = IOStruct.new('LL', :x, :y).read(StringIO.new(data))
192
+ x = described_class.new('LL', :x, :y).read(StringIO.new(data))
11
193
  expect(x.x).to eq a[0]
12
194
  expect(x.y).to eq a[1]
13
195
  end
14
196
 
15
197
  it "reads from String" do
16
- x = IOStruct.new('LL', :x, :y).read(data)
198
+ x = described_class.new('LL', :x, :y).read(data)
17
199
  expect(x.x).to eq a[0]
18
200
  expect(x.y).to eq a[1]
19
201
  end
20
202
 
21
203
  it "creates a new instance of a subclass" do
22
- klass = Class.new( IOStruct.new('LL', :x, :y) )
204
+ klass = Class.new( described_class.new('LL', :x, :y) )
23
205
  x = klass.read(data)
24
206
  expect(x).to be_a klass
25
207
  end
@@ -27,7 +209,7 @@ describe IOStruct do
27
209
 
28
210
  context "zero-length strings" do
29
211
  let(:data) { [1, 2].pack('CC') }
30
- let(:struct) { IOStruct.new('C a0 C', :a, :b, :c) }
212
+ let(:struct) { described_class.new('C a0 C', :a, :b, :c) }
31
213
 
32
214
  it "deserializes" do
33
215
  x = struct.read(data)
@@ -45,8 +227,8 @@ describe IOStruct do
45
227
  end
46
228
 
47
229
  it "reads correct number of bytes from IO" do
48
- io = StringIO.new(data*2)
49
- x = struct.read(io)
230
+ io = StringIO.new(data * 2)
231
+ struct.read(io)
50
232
  expect(io.pos).to eq 2
51
233
  end
52
234
 
@@ -59,29 +241,30 @@ describe IOStruct do
59
241
  it "skips on 'x'" do
60
242
  a = [12345, 56789]
61
243
  data = a.pack('L2')
62
- x = IOStruct.new('x4L', :y).read(data)
244
+ x = described_class.new('x4L', :y).read(data)
63
245
  expect(x.y).to eq a[1]
64
246
  end
65
247
 
66
248
  it "unpacks hex-string (H)" do
67
249
  data = "1234"
68
- struct = IOStruct.new('H8', :x).read(data)
250
+ struct = described_class.new('H8', :x).read(data)
69
251
  expect(struct.x).to eq "31323334"
70
252
  expect(struct.pack).to eq data
71
253
  end
72
254
 
73
255
  it "unpacks reverse-nibbled hex-string (h)" do
74
256
  data = "1234"
75
- struct = IOStruct.new('h8', :x).read(data)
257
+ struct = described_class.new('h8', :x).read(data)
76
258
  expect(struct.x).to eq "13233343"
77
259
  expect(struct.pack).to eq data
78
260
  end
79
261
 
262
+ # rubocop:disable RSpec/RepeatedExample
80
263
  ['n', 'N', 'S>', 'L>', 'I>'].each do |fmt|
81
264
  it "unpacks unsigned big-endian '#{fmt}'" do
82
265
  a = [12345]
83
266
  data = a.pack(fmt)
84
- x = IOStruct.new(fmt, :x).read(data)
267
+ x = described_class.new(fmt, :x).read(data)
85
268
  expect(x.x).to eq a[0]
86
269
  expect(x.pack).to eq data
87
270
  end
@@ -91,27 +274,26 @@ describe IOStruct do
91
274
  it "unpacks unsigned little-endian '#{fmt}'" do
92
275
  a = [12345]
93
276
  data = a.pack(fmt)
94
- x = IOStruct.new(fmt, :x).read(data)
277
+ x = described_class.new(fmt, :x).read(data)
95
278
  expect(x.x).to eq a[0]
96
279
  expect(x.pack).to eq data
97
280
  end
98
281
  end
282
+ # rubocop:enable RSpec/RepeatedExample
99
283
 
100
284
  it "throws exception on unknown format" do
101
- expect { IOStruct.new('K', :x) }.to raise_error('Unknown field type "K"')
285
+ expect { described_class.new('K', :x) }.to raise_error('Unknown field type "K"')
102
286
  end
103
287
 
104
288
  context '__offset field' do
105
289
  let(:data) { 0x100.times.to_a.pack('L*') }
106
290
  let(:io) { StringIO.new(data) }
107
- let(:struct) { IOStruct.new('LLLL', :a, :b, :c, :d) }
291
+ let(:struct) { described_class.new('LLLL', :a, :b, :c, :d) }
108
292
 
109
293
  context 'when src is an IO' do
110
294
  it 'is set to the current IO position' do
111
295
  a = []
112
- while !io.eof?
113
- a << struct.read(io)
114
- end
296
+ a << struct.read(io) until io.eof?
115
297
  expect(a.map(&:__offset)).to eq (0...0x400).step(0x10).to_a
116
298
  end
117
299
  end
@@ -123,4 +305,77 @@ describe IOStruct do
123
305
  end
124
306
  end
125
307
  end
308
+
309
+ describe "to_table" do
310
+ context "when inspect is :hex" do
311
+ it "formats signed struct as table" do
312
+ signed_struct = described_class.new('c s i q', inspect: :hex)
313
+ expect(signed_struct.new.to_table).to eq(
314
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
315
+ )
316
+ expect(signed_struct.read("\xff" * 16).to_table).to eq(
317
+ "<struct f0=ff f1=ffff f3=ffffffff f7=ffffffffffffffff>"
318
+ )
319
+ end
320
+
321
+ it "formats unsigned struct as table" do
322
+ unsigned_struct = described_class.new('C S I Q', inspect: :hex)
323
+ expect(unsigned_struct.new.to_table).to eq(
324
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
325
+ )
326
+ expect(unsigned_struct.read("\xff" * 16).to_table).to eq(
327
+ "<struct f0=ff f1=ffff f3=ffffffff f7=ffffffffffffffff>"
328
+ )
329
+ end
330
+
331
+ it "works with unknown types" do
332
+ struct = described_class.new('C', :a, :name, inspect: :hex)
333
+ s = struct.new
334
+ expect(s.to_table).to eq(
335
+ "<struct a= 0 name=nil>"
336
+ )
337
+ s.name = "test"
338
+ expect(s.to_table).to eq(
339
+ '<struct a= 0 name="test">'
340
+ )
341
+ end
342
+ end
343
+
344
+ context "when inspect is not :hex" do
345
+ it "formats signed struct as table" do
346
+ signed_struct = described_class.new('c s i q', inspect: :dec)
347
+ expect(signed_struct.new.to_table).to eq(
348
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
349
+ )
350
+ expect(signed_struct.read("\xff" * 16).to_table).to eq(
351
+ "<struct f0= -1 f1= -1 f3= -1 f7= -1>"
352
+ )
353
+ expect(signed_struct.read("\x80\x00\x80\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x80").to_table).to eq(
354
+ "<struct f0=-128 f1=-32768 f3=-2147483648 f7=-9223372036854775808>"
355
+ )
356
+ end
357
+
358
+ it "formats unsigned struct as table" do
359
+ unsigned_struct = described_class.new('C S I Q', inspect: :dec)
360
+ expect(unsigned_struct.new.to_table).to eq(
361
+ "<struct f0= 0 f1= 0 f3= 0 f7= 0>"
362
+ )
363
+ expect(unsigned_struct.read("\xff" * 16).to_table).to eq(
364
+ "<struct f0= 255 f1= 65535 f3= 4294967295 f7=18446744073709551615>"
365
+ )
366
+ end
367
+
368
+ it "works with unknown types" do
369
+ struct = described_class.new('C', :a, :name, inspect: :dec)
370
+ s = struct.new
371
+ expect(s.to_table).to eq(
372
+ "<struct a= 0 name=nil>"
373
+ )
374
+ s.name = "test"
375
+ expect(s.to_table).to eq(
376
+ '<struct a= 0 name="test">'
377
+ )
378
+ end
379
+ end
380
+ end
126
381
  end
metadata CHANGED
@@ -1,58 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iostruct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey "Zed" Zaikin
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2013-01-08 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- description:
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
56
12
  email: zed.0xff@gmail.com
57
13
  executables: []
58
14
  extensions: []
@@ -60,6 +16,8 @@ extra_rdoc_files: []
60
16
  files:
61
17
  - ".gitignore"
62
18
  - ".rspec"
19
+ - ".rubocop.yml"
20
+ - ".ruby-version"
63
21
  - CHANGELOG.md
64
22
  - Gemfile
65
23
  - Gemfile.lock
@@ -68,14 +26,18 @@ files:
68
26
  - Rakefile
69
27
  - iostruct.gemspec
70
28
  - lib/iostruct.rb
29
+ - lib/iostruct/hash_fmt.rb
30
+ - lib/iostruct/pack_fmt.rb
71
31
  - lib/iostruct/version.rb
32
+ - spec/.rubocop.yml
33
+ - spec/hash_fmt_spec.rb
72
34
  - spec/iostruct_spec.rb
73
35
  - spec/spec_helper.rb
74
36
  homepage: http://github.com/zed-0xff/iostruct
75
37
  licenses:
76
38
  - MIT
77
- metadata: {}
78
- post_install_message:
39
+ metadata:
40
+ rubygems_mfa_required: 'true'
79
41
  rdoc_options: []
80
42
  require_paths:
81
43
  - lib
@@ -90,8 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
52
  - !ruby/object:Gem::Version
91
53
  version: '0'
92
54
  requirements: []
93
- rubygems_version: 3.5.22
94
- signing_key:
55
+ rubygems_version: 3.6.9
95
56
  specification_version: 4
96
57
  summary: A Struct that can read/write itself from/to IO-like objects
97
58
  test_files: []