iostruct 0.5.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,18 +2,184 @@ require 'spec_helper'
2
2
  require 'stringio'
3
3
 
4
4
  describe IOStruct do
5
- describe ":inspect_name_override" 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
6
172
  context "when set" do
7
173
  it "uses the custom name" do
8
- x = IOStruct.new('LL', :x, :y, inspect_name_override: 'Point')
9
- expect(x.new.inspect).to match /<Point x=nil y=nil>/
174
+ x = described_class.new('LL', :x, :y, struct_name: 'Point')
175
+ expect(x.new.inspect).to match(/<Point x=nil y=nil>/)
10
176
  end
11
177
  end
12
178
 
13
179
  context "when not set" do
14
180
  it "has default name" do
15
- x = IOStruct.new('LL', :x, :y)
16
- expect(x.new.inspect).to match /<#<Class:0x\h+> x=nil y=nil>/
181
+ x = described_class.new('LL', :x, :y)
182
+ expect(x.new.inspect).to match(/<struct x=nil y=nil>/)
17
183
  end
18
184
  end
19
185
  end
@@ -23,19 +189,19 @@ describe IOStruct do
23
189
  let(:data) { a.pack('L2') }
24
190
 
25
191
  it "reads from IO" do
26
- x = IOStruct.new('LL', :x, :y).read(StringIO.new(data))
192
+ x = described_class.new('LL', :x, :y).read(StringIO.new(data))
27
193
  expect(x.x).to eq a[0]
28
194
  expect(x.y).to eq a[1]
29
195
  end
30
196
 
31
197
  it "reads from String" do
32
- x = IOStruct.new('LL', :x, :y).read(data)
198
+ x = described_class.new('LL', :x, :y).read(data)
33
199
  expect(x.x).to eq a[0]
34
200
  expect(x.y).to eq a[1]
35
201
  end
36
202
 
37
203
  it "creates a new instance of a subclass" do
38
- klass = Class.new( IOStruct.new('LL', :x, :y) )
204
+ klass = Class.new( described_class.new('LL', :x, :y) )
39
205
  x = klass.read(data)
40
206
  expect(x).to be_a klass
41
207
  end
@@ -43,7 +209,7 @@ describe IOStruct do
43
209
 
44
210
  context "zero-length strings" do
45
211
  let(:data) { [1, 2].pack('CC') }
46
- let(:struct) { IOStruct.new('C a0 C', :a, :b, :c) }
212
+ let(:struct) { described_class.new('C a0 C', :a, :b, :c) }
47
213
 
48
214
  it "deserializes" do
49
215
  x = struct.read(data)
@@ -61,8 +227,8 @@ describe IOStruct do
61
227
  end
62
228
 
63
229
  it "reads correct number of bytes from IO" do
64
- io = StringIO.new(data*2)
65
- x = struct.read(io)
230
+ io = StringIO.new(data * 2)
231
+ struct.read(io)
66
232
  expect(io.pos).to eq 2
67
233
  end
68
234
 
@@ -75,29 +241,30 @@ describe IOStruct do
75
241
  it "skips on 'x'" do
76
242
  a = [12345, 56789]
77
243
  data = a.pack('L2')
78
- x = IOStruct.new('x4L', :y).read(data)
244
+ x = described_class.new('x4L', :y).read(data)
79
245
  expect(x.y).to eq a[1]
80
246
  end
81
247
 
82
248
  it "unpacks hex-string (H)" do
83
249
  data = "1234"
84
- struct = IOStruct.new('H8', :x).read(data)
250
+ struct = described_class.new('H8', :x).read(data)
85
251
  expect(struct.x).to eq "31323334"
86
252
  expect(struct.pack).to eq data
87
253
  end
88
254
 
89
255
  it "unpacks reverse-nibbled hex-string (h)" do
90
256
  data = "1234"
91
- struct = IOStruct.new('h8', :x).read(data)
257
+ struct = described_class.new('h8', :x).read(data)
92
258
  expect(struct.x).to eq "13233343"
93
259
  expect(struct.pack).to eq data
94
260
  end
95
261
 
262
+ # rubocop:disable RSpec/RepeatedExample
96
263
  ['n', 'N', 'S>', 'L>', 'I>'].each do |fmt|
97
264
  it "unpacks unsigned big-endian '#{fmt}'" do
98
265
  a = [12345]
99
266
  data = a.pack(fmt)
100
- x = IOStruct.new(fmt, :x).read(data)
267
+ x = described_class.new(fmt, :x).read(data)
101
268
  expect(x.x).to eq a[0]
102
269
  expect(x.pack).to eq data
103
270
  end
@@ -107,27 +274,26 @@ describe IOStruct do
107
274
  it "unpacks unsigned little-endian '#{fmt}'" do
108
275
  a = [12345]
109
276
  data = a.pack(fmt)
110
- x = IOStruct.new(fmt, :x).read(data)
277
+ x = described_class.new(fmt, :x).read(data)
111
278
  expect(x.x).to eq a[0]
112
279
  expect(x.pack).to eq data
113
280
  end
114
281
  end
282
+ # rubocop:enable RSpec/RepeatedExample
115
283
 
116
284
  it "throws exception on unknown format" do
117
- 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"')
118
286
  end
119
287
 
120
288
  context '__offset field' do
121
289
  let(:data) { 0x100.times.to_a.pack('L*') }
122
290
  let(:io) { StringIO.new(data) }
123
- let(:struct) { IOStruct.new('LLLL', :a, :b, :c, :d) }
291
+ let(:struct) { described_class.new('LLLL', :a, :b, :c, :d) }
124
292
 
125
293
  context 'when src is an IO' do
126
294
  it 'is set to the current IO position' do
127
295
  a = []
128
- while !io.eof?
129
- a << struct.read(io)
130
- end
296
+ a << struct.read(io) until io.eof?
131
297
  expect(a.map(&:__offset)).to eq (0...0x400).step(0x10).to_a
132
298
  end
133
299
  end
@@ -139,4 +305,77 @@ describe IOStruct do
139
305
  end
140
306
  end
141
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
142
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.5.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: []