bin_struct 0.2.1 → 0.3.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: df710325cf6aa4414fc997d77033b8bea34d4dc80e571ceb184462b2aa047eb3
4
- data.tar.gz: aa372402e27bc5b254b7e4e82cb7d9b2178823c84adc3c1bda17bba7806ff05e
3
+ metadata.gz: 44d43c8007a5ad33899e5c7ee6a09c306a64a95012bd489ee2d14d69c7e40dcf
4
+ data.tar.gz: db0b44a68cb8a23b1a5110cb12bb587c4cf4a737b42443f72c0ae9241fffa6c9
5
5
  SHA512:
6
- metadata.gz: 6ef8207d1fc62509bda66a0caa779ec2b6b95e2a7805fb10199423bcf1300a7f05dd5c5b595f33f8b6605b93277e485a5a18f80a83b7c6954339eea0541f6073
7
- data.tar.gz: b47f00bd2497c5330eeb6a01721b3cbd4a305d624e6df7dce2fd13b8f996c61e287ab92df393ea0fcb67fece0396497354220e9a56cb50bf617fa0d9b3ccdaff
6
+ metadata.gz: 287f8dbf64a09b27b5fe376f9b6a982055713cdb2453ab7dd2b5ec0489678727441a3545427bb9f1f31d0ccc733bcc10e6af703fdc3b85b7cf539614b306bc21
7
+ data.tar.gz: cc3ba318afa6ac9706d57676256bbebeeec3b4dc52402728777b8c7a7fb89c9de8d893903b3e753d84a25aa6339b9aa919cfd4a223ff1f1a446a2d169047dee2
data/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
4
4
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
5
 
6
+ ## 0.3.0 - 2024-12-02
7
+
8
+ ### Added
9
+
10
+ - `BitAddr` class is added. This class is used as a `Structable` type to handle bitfield attributes.
11
+ - Add `Struct.define_bit_attr`, `.define_bit_attr_before` and `.define_bit_attr_before` to define bitfield attributes.
12
+
13
+ ### Changed
14
+
15
+ - `Struct.define_bit_attr_on` is removed in favor of `Struct.define_bit_attr`. Bitfield attributes are now first class attributes, and no more an onverlay on `Int`.
16
+
6
17
  ## 0.2.1 - 2024-11-25
7
18
 
8
19
  ### Added
@@ -0,0 +1,186 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of BinStruct
4
+ # see https://github.com/lemontree55/bin_struct for more informations
5
+ # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
6
+ # This program is published under MIT license.
7
+ require 'digest'
8
+
9
+ module BinStruct
10
+ # Define a bitfield attribute to embed in a {Struct}.
11
+ #
12
+ # class MyStruct < BinStruct::Struct
13
+ # # Create a 32-bit bitfield attribute, with fields a (16 bits), b and c (4 bits each) and d (8 bits).
14
+ # # a is the leftmost field in bitfield, and d the rightmost one.
15
+ # define_attr :int32, BinStruct::BitAttr.create(width: 32, a: 16, b: 4, c: 4, d:8)
16
+ # end
17
+ # @since 0.3.0
18
+ # @author LemonTree55
19
+ class BitAttr
20
+ include Structable
21
+
22
+ # @return [Integer] width in bits of bit attribute
23
+ attr_reader :width
24
+ # @return [Array[Symbol]]
25
+ attr_reader :bit_methods
26
+
27
+ # @private
28
+ Parameters = Struct.new(:width, :fields, :int)
29
+
30
+ class << self
31
+ @cache = {}
32
+
33
+ # @private
34
+ # @return [Parameters]
35
+ attr_reader :parameters
36
+
37
+ # Create a new {BitAttr} subclass with specified parameters
38
+ # @param [Integer] width size of bitfields in bits. Must be a size of an {Int} (8, 16, 24, 32 or 64 bits).
39
+ # @param [:big,:little,:native] endian endianess of bit attribute as an integer
40
+ # @param [Hash{Symbol=>Integer}] fields hash associating field names with their size. Total size MUST be equal
41
+ # to +width+.
42
+ # @return [Class]
43
+ # @raise [ArgumentError] raise if:
44
+ # * width is not a size of one of {Int} subclasses,
45
+ # * sum of bitfield sizes is not equal to +width+
46
+ def create(width:, endian: :big, **fields)
47
+ raise ArgumentError, 'with must be 8, 16, 24, 32 or 64' unless [8, 16, 24, 32, 64].include?(width)
48
+
49
+ hsh = compute_hash(width, endian, fields)
50
+ cached = cache[hsh]
51
+ return cached if cached
52
+
53
+ total_size = fields.reduce(0) { |acc, ary| acc + ary.last }
54
+ raise ArgumentError, "sum of bitfield sizes is not equal to #{width}" unless total_size == width
55
+
56
+ cache[hsh] = Class.new(self) do
57
+ int_klass = BinStruct.const_get("Int#{width}")
58
+ @parameters = Parameters.new(width, fields, int_klass.new(endian: endian)).freeze
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ # @return [Hash{::String=>Class}]
65
+ def cache
66
+ return @cache if defined? @cache
67
+
68
+ @cache = {}
69
+ end
70
+
71
+ # @param [::Array] params
72
+ # @return [::String]
73
+ def compute_hash(*params)
74
+ Digest::MD5.digest(Marshal.dump(params))
75
+ end
76
+ end
77
+
78
+ # Initialize bit attribute
79
+ # @param [Hash{Symbol=>Integer}] opts initialization values for fields, where keys are field names and values are
80
+ # initialization values
81
+ # @return [self]
82
+ def initialize(opts = {})
83
+ parameters = self.class.parameters
84
+ raise NotImplementedError, '#initialize may only be called on subclass of {self.class}' if parameters.nil?
85
+
86
+ @width = parameters.width
87
+ @fields = parameters.fields
88
+ @int = parameters.int.dup
89
+ @data = {}
90
+ @bit_methods = []
91
+
92
+ parameters.fields.each do |name, size|
93
+ @data[name] = opts[name] || 0
94
+ define_methods(name, size)
95
+ end
96
+ @bit_methods.freeze
97
+ end
98
+
99
+ # Get type name
100
+ # @return [::String]
101
+ def type_name
102
+ return @type_name if defined? @type_name
103
+
104
+ endian_suffix = case @int.endian
105
+ when :big then ''
106
+ when :little then 'le'
107
+ when :native then 'n'
108
+ end
109
+ @type_name = "BitAttr#{@width}#{endian_suffix}"
110
+ end
111
+
112
+ # Populate bit attribute from +str+
113
+ # @param [::String,nil] str
114
+ # @return [self]
115
+ def read(str)
116
+ return self if str.nil?
117
+
118
+ @int.read(str)
119
+ compute_data(@int.to_i)
120
+ end
121
+
122
+ # Give integer associated to this attribute
123
+ # @return [Integer]
124
+ def to_i
125
+ v = 0
126
+ @fields.each do |name, size|
127
+ v <<= size
128
+ v |= @data[name]
129
+ end
130
+
131
+ v
132
+ end
133
+ alias to_human to_i
134
+
135
+ # Return binary string
136
+ # @return [::String]
137
+ def to_s
138
+ @int.value = to_i
139
+ @int.to_s
140
+ end
141
+
142
+ # Set fields from associated integer
143
+ # @param [#to_i] value
144
+ # @return [self]
145
+ def from_human(value)
146
+ compute_data(value.to_i)
147
+ end
148
+
149
+ def format_inspect
150
+ str = @int.format_inspect << "\n"
151
+ str << @data.map { |name, value| "#{name}:#{value}" }.join(' ')
152
+ end
153
+
154
+ private
155
+
156
+ # @param [Integer] value
157
+ # @return [self]
158
+ def compute_data(value)
159
+ @fields.reverse_each do |name, size|
160
+ @data[name] = value & ((2**size) - 1)
161
+ value >>= size
162
+ end
163
+
164
+ self
165
+ end
166
+
167
+ # @param [Symbol] name
168
+ # @return [void]
169
+ def define_methods(name, size)
170
+ instance_eval "def #{name}; @data[#{name.inspect}]; end\n", __FILE__, __LINE__ # def name; data[:name]; end
171
+ bit_methods << name
172
+ bit_methods << :"#{name}="
173
+
174
+ # rubocop:disable Style/DocumentDynamicEvalDefinition
175
+ if size == 1
176
+ instance_eval "def #{name}?; @data[#{name.inspect}] != 0; end\n", __FILE__, __LINE__
177
+ instance_eval "def #{name}=(val); v = case val when TrueClass; 1 when FalseClass; 0 else val end; " \
178
+ "@data[#{name.inspect}] = v; end", __FILE__, __LINE__ - 2
179
+ bit_methods << :"#{name}?"
180
+ else
181
+ instance_eval "def #{name}=(val); @data[#{name.inspect}] = val; end", __FILE__, __LINE__
182
+ end
183
+ # rubocop:enable Style/DocumentDynamicEvalDefinition
184
+ end
185
+ end
186
+ end
@@ -61,7 +61,7 @@ module BinStruct
61
61
  # @return [::String]
62
62
  def string=(str)
63
63
  @length.value = str.to_s.size
64
- @string = str.to_s
64
+ @string.read(str)
65
65
  end
66
66
 
67
67
  # Get binary string
@@ -82,7 +82,7 @@ module BinStruct
82
82
  # Get human readable string
83
83
  # @return [::String]
84
84
  def to_human
85
- @string
85
+ @string.to_s
86
86
  end
87
87
 
88
88
  # Set length from internal string length
@@ -81,25 +81,23 @@ module BinStruct
81
81
  # define_attr :opt1, BinStruct::Int16, optional: ->(h) { h.type == 42 }
82
82
  #
83
83
  # == Generating bit attributes
84
- # {.define_bit_attr_on} creates bit attributes on a previously declared integer
85
- # attribute. For example, +frag+ attribute in IP header:
86
- # define_attr :frag, BinStruct::Int16, default: 0
87
- # define_bit_attr_on :frag, :flag_rsv, :flag_df, :flag_mf, :fragment_offset, 13
84
+ # {.define_bit_attr} creates a bit attribute. For example, +frag+ attribute in IP header:
85
+ # define_bit_attr :frag, flag_rsv: 1, flag_df: 1, flag_mf: 1, fragment_offset: 13
88
86
  #
89
87
  # This example generates methods:
90
88
  # * +#frag+ and +#frag=+ to access +frag+ attribute as a 16-bit integer,
91
89
  # * +#flag_rsv?+, +#flag_rsv=+, +#flag_df?+, +#flag_df=+, +#flag_mf?+ and +#flag_mf=+
92
90
  # to access Boolean RSV, MF and DF flags from +frag+ attribute,
91
+ # * +#flag_rsv+, +#flag_df+ and +#flag_mf# to read RSV, MF and DF flags as Integer,
93
92
  # * +#fragment_offset+ and +#fragment_offset=+ to access 13-bit integer fragment
94
93
  # offset subattribute from +frag+ attribute.
95
94
  #
96
95
  # == Creating a new Struct class from another one
97
96
  # Some methods may help in this case:
98
- # * {.define_attr_before} to define a new attribute before an existing one,
99
- # * {.define_attr_after} to define a new attribute after an existing onr,
100
- # * {.remove_attribute} to remove an existing attribute,
101
- # * {.uptade_fied} to change options of an attribute (but not its type),
102
- # * {.remove_bit_attrs_on} to remove bit attribute definition.
97
+ # * {.define_attr_before} and {.define_bit_attr_before} to define a new attribute before an existing one,
98
+ # * {.define_attr_after} and {.define_bit_attr_after} to define a new attribute after an existing onr,
99
+ # * {.remove_attr} to remove an existing attribute,
100
+ # * {.uptade_attr} to change options of an attribute (but not its type),
103
101
  #
104
102
  # @author Sylvain Daubert (2016-2024)
105
103
  # @author LemonTree55
@@ -121,7 +119,7 @@ module BinStruct
121
119
  # @return [Hash]
122
120
  attr_reader :attr_defs
123
121
  # Get bit attribute defintions for this class
124
- # @return [Hash]
122
+ # @return [Hash{Symbol=>Array[Symbol]}]
125
123
  attr_reader :bit_attrs
126
124
 
127
125
  # On inheritage, create +@attr_defs+ class variable
@@ -198,11 +196,7 @@ module BinStruct
198
196
  define_attr name, type, options
199
197
  return if other.nil?
200
198
 
201
- attributes.delete name
202
- idx = attributes.index(other)
203
- raise ArgumentError, "unknown #{other} attribute" if idx.nil?
204
-
205
- attributes[idx, 0] = name
199
+ move_attr(name, before: other)
206
200
  end
207
201
 
208
202
  # Define an attribute, after another one
@@ -217,11 +211,7 @@ module BinStruct
217
211
  define_attr name, type, options
218
212
  return if other.nil?
219
213
 
220
- attributes.delete name
221
- idx = attributes.index(other)
222
- raise ArgumentError, "unknown #{other} attribute" if idx.nil?
223
-
224
- attributes[idx + 1, 0] = name
214
+ move_attr(name, after: other)
225
215
  end
226
216
 
227
217
  # Remove a previously defined attribute
@@ -229,9 +219,12 @@ module BinStruct
229
219
  # @return [void]
230
220
  def remove_attr(name)
231
221
  attributes.delete(name)
232
- @attr_defs.delete(name)
222
+ attr_def = attr_defs.delete(name)
233
223
  undef_method name if method_defined?(name)
234
224
  undef_method :"#{name}=" if method_defined?(:"#{name}=")
225
+ return unless bit_attrs[name]
226
+
227
+ attr_def.type.new.bit_methods.each { |meth| undef_method(meth) }
235
228
  end
236
229
 
237
230
  # Update a previously defined attribute
@@ -250,64 +243,95 @@ module BinStruct
250
243
  attr_defs[name].options.merge!(options)
251
244
  end
252
245
 
253
- # Define a bit attribute on given attribute
246
+ # Define a bit attribute
254
247
  # class MyHeader < BinStruct::Struct
255
- # define_attr :flags, BinStruct::Int16
256
- # # define a bit attribute on :flag attribute
248
+ # # define a 16-bit attribute named :flag
257
249
  # # flag1, flag2 and flag3 are 1-bit attributes
258
- # # type and stype are 3-bit attributes. reserved is a 6-bit attribute
259
- # define_bit_attributes_on :flags, :flag1, :flag2, :flag3, :type, 3, :stype, 3, :reserved, 7
250
+ # # type and stype are 3-bit attributes. reserved is a 7-bit attribute
251
+ # define_bit_attr :flags, flag1: 1, flag2: 1, flag3: 1, type: 3, stype: 3, reserved: 7
260
252
  # end
261
- # A bit attribute of size 1 bit defines 2 methods:
262
- # * +#attr+ which returns a Boolean,
263
- # * +#attr=+ which takes and returns a Boolean.
264
- # A bit attribute of more bits defines 2 methods:
253
+ # A bit attribute of size 1 bit defines 3 methods:
254
+ # * +#attr+ which returns an Integer,
255
+ # * +#attr?+ which returns a Boolean,
256
+ # * +#attr=+ which accepts an Integer or a Boolean.
257
+ # A bit attribute of more bits defines only 2 methods:
265
258
  # * +#attr+ which returns an Integer,
266
- # * +#attr=+ which takes and returns an Integer.
267
- # @param [Symbol] attr attribute name (attribute should be a {BinStruct::Int}
268
- # subclass)
269
- # @param [Array] args list of bit attribute names. Name may be followed
270
- # by bit size. If no size is given, 1 bit is assumed.
271
- # @raise [ArgumentError] unknown +attr+
259
+ # * +#attr=+ which takes an Integer.
260
+ # @param [Symbol] attr attribute name
261
+ # @param [:big,:little,:native] endian endianess of Integer
262
+ # @param [Integer] default default value for whole attribute
263
+ # @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
272
264
  # @return [void]
273
- def define_bit_attrs_on(attr, *args)
274
- check_existence_of(attr)
275
-
276
- type = attr_defs[attr].type
277
- raise TypeError, "#{attr} is not a BinStruct::Int" unless type < Int
278
-
279
- total_size = type.new.nbits
280
- idx = total_size - 1
265
+ # @since 0.3.0
266
+ def define_bit_attr(attr, endian: :big, default: 0, **fields)
267
+ width = fields.reduce(0) { |acc, ary| acc + ary.last }
268
+ bit_attr_klass = BitAttr.create(width: width, endian: endian, **fields)
269
+ define_attr(attr, bit_attr_klass, default: default)
270
+ fields.each_key { |field| register_bit_attr_field(attr, field) }
271
+ bit_attr_klass.new.bit_methods.each do |meth|
272
+ if meth.to_s.end_with?('=')
273
+ define_method(meth) { |value| self[attr].send(meth, value) }
274
+ else
275
+ define_method(meth) { self[attr].send(meth) }
276
+ end
277
+ end
278
+ end
281
279
 
282
- until args.empty?
283
- arg = args.shift
284
- next unless arg.is_a? Symbol
280
+ # Define a bit attribute, before another attribute
281
+ # @param [Symbol,nil] other attribute name to create a new one before.
282
+ # If +nil+, new attribute is appended.
283
+ # @param [Symbol] name attribute name to create
284
+ # @param [:big,:little,:native] endian endianess of Integer
285
+ # @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
286
+ # @return [void]
287
+ # @since 0.3.0
288
+ # @see .define_bit_attr
289
+ def define_bit_attr_before(other, name, endian: :big, **fields)
290
+ define_bit_attr(name, endian: endian, **fields)
291
+ return if other.nil?
285
292
 
286
- size = size_from(args)
293
+ move_attr(name, before: other)
294
+ end
287
295
 
288
- unless attr == :_
289
- add_bit_methods(attr, arg, size, total_size, idx)
290
- register_bit_attr_size(attr, arg, size)
291
- end
296
+ # Define a bit attribute after another attribute
297
+ # @param [Symbol,nil] other attribute name to create a new one after.
298
+ # If +nil+, new attribute is appended.
299
+ # @param [Symbol] name attribute name to create
300
+ # @param [:big,:little,:native] endian endianess of Integer
301
+ # @param [Hash{Symbol=>Integer}] fields Hash defining fields. Keys are field names, values are field sizes.
302
+ # @return [void]
303
+ # @since 0.3.0
304
+ # @see .define_bit_attr
305
+ def define_bit_attr_after(other, name, endian: :big, **fields)
306
+ define_bit_attr(name, endian: endian, **fields)
307
+ return if other.nil?
292
308
 
293
- idx -= size
294
- end
309
+ move_attr(name, after: other)
295
310
  end
296
311
 
297
- # Remove all bit attributes defined on +attr+
298
- # @param [Symbol] attr attribute defining bit attributes
312
+ private
313
+
314
+ # @param [Symbol] name
315
+ # @param [Symbol,nil] before
316
+ # @param [Symbol,nil] after
299
317
  # @return [void]
300
- def remove_bit_attrs_on(attr)
301
- bits = bit_attrs.delete(attr)
302
- return if bits.nil?
318
+ # @raise [ArgumentError] Both +before+ and +after+ are nil, or both are set.
319
+ def move_attr(name, before: nil, after: nil)
320
+ move_check_destination(before, after)
303
321
 
304
- bits.each do |bit, size|
305
- undef_method :"#{bit}="
306
- undef_method(size == 1 ? "#{bit}?" : bit)
307
- end
322
+ other = before || after
323
+ attributes.delete(name)
324
+ idx = attributes.index(other)
325
+ raise ArgumentError, "unknown #{other} attribute" if idx.nil?
326
+
327
+ idx += 1 unless after.nil?
328
+ attributes[idx, 0] = name
308
329
  end
309
330
 
310
- private
331
+ def move_check_destination(before, after)
332
+ raise ArgumentError 'one of before: and after: arguments MUST be set' if before.nil? && after.nil?
333
+ raise ArgumentError 'only one of before and after argument MUST be set' if !before.nil? && !after.nil?
334
+ end
311
335
 
312
336
  def add_methods(name, type)
313
337
  define = []
@@ -328,73 +352,15 @@ module BinStruct
328
352
  class_eval define.join("\n")
329
353
  end
330
354
 
331
- def add_bit_methods(attr, name, size, total_size, idx)
332
- shift = idx - (size - 1)
333
-
334
- if size == 1
335
- add_single_bit_methods(attr, name, size, total_size, shift)
336
- else
337
- add_multibit_methods(attr, name, size, total_size, shift)
338
- end
339
- end
340
-
341
- def compute_mask(size, shift)
342
- ((2**size) - 1) << shift
343
- end
344
-
345
- def compute_clear_mask(total_size, mask)
346
- ((2**total_size) - 1) & (~mask & ((2**total_size) - 1))
347
- end
348
-
349
- def add_single_bit_methods(attr, name, size, total_size, shift)
350
- mask = compute_mask(size, shift)
351
- clear_mask = compute_clear_mask(total_size, mask)
352
-
353
- class_eval <<-METHODS, __FILE__, __LINE__ + 1
354
- def #{name}? # def bit?
355
- val = (self[:#{attr}].to_i & #{mask}) >> #{shift} # val = (self[:attr}].to_i & 1}) >> 1
356
- val != 0 # val != 0
357
- end # end
358
- def #{name}=(v) # def bit=(v)
359
- val = v ? 1 : 0 # val = v ? 1 : 0
360
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfffd
361
- self[:#{attr}].value |= val << #{shift} # self[:attr].value |= val << 1
362
- end # end
363
- METHODS
364
- end
365
-
366
- def add_multibit_methods(attr, name, size, total_size, shift)
367
- mask = compute_mask(size, shift)
368
- clear_mask = compute_clear_mask(total_size, mask)
369
-
370
- class_eval <<-METHODS, __FILE__, __LINE__ + 1
371
- def #{name} # def multibit
372
- (self[:#{attr}].to_i & #{mask}) >> #{shift} # (self[:attr].to_i & 6) >> 1
373
- end # end
374
- def #{name}=(v) # def multibit=(v)
375
- self[:#{attr}].value = self[:#{attr}].to_i & #{clear_mask} # self[:attr].value = self[:attr].to_i & 0xfff9
376
- self[:#{attr}].value |= (v & #{(2**size) - 1}) << #{shift} # self[:attr].value |= (v & 3) << 1
377
- end # end
378
- METHODS
379
- end
380
-
381
- def register_bit_attr_size(attr, name, size)
382
- bit_attrs[attr] = {} if bit_attrs[attr].nil?
383
- bit_attrs[attr][name] = size
355
+ def register_bit_attr_field(attr, field)
356
+ bit_attrs[attr] ||= []
357
+ bit_attrs[attr] << field
384
358
  end
385
359
 
386
360
  def attr_defs_property_from(attr, property, options)
387
361
  attr_defs[attr].send(:"#{property}=", options.delete(property)) if options.key?(property)
388
362
  end
389
363
 
390
- def size_from(args)
391
- if args.first.is_a? Integer
392
- args.shift
393
- else
394
- 1
395
- end
396
- end
397
-
398
364
  def check_existence_of(attr)
399
365
  raise ArgumentError, "unknown #{attr} attribute for #{self}" unless attr_defs.key?(attr)
400
366
  end
@@ -402,7 +368,7 @@ module BinStruct
402
368
 
403
369
  # Create a new Struct object
404
370
  # @param [Hash] options Keys are symbols. They should have name of object
405
- # attributes, as defined by {.define_attr} and by {.define_bit_attrs_on}.
371
+ # attributes, as defined by {.define_attr} and by {.define_bit_attr}.
406
372
  def initialize(options = {})
407
373
  @attributes = {}
408
374
  @optional_attributes = {}
@@ -413,8 +379,8 @@ module BinStruct
413
379
  initialize_optional(attr)
414
380
  end
415
381
 
416
- self.class.bit_attrs.each_value do |hsh|
417
- hsh.each_key do |bit|
382
+ self.class.bit_attrs.each_value do |bit_fields|
383
+ bit_fields.each do |bit|
418
384
  send(:"#{bit}=", options[bit]) if options[bit]
419
385
  end
420
386
  end
@@ -599,26 +565,39 @@ module BinStruct
599
565
  end
600
566
  end
601
567
 
568
+ # @param [Symbol] attr
569
+ # @return [void]
602
570
  def initialize_optional(attr)
603
571
  optional = attr_defs[attr].optional
604
572
  @optional_attributes[attr] = optional if optional
605
573
  end
606
574
 
575
+ # @return [String]
607
576
  def inspect_titleize
608
577
  title = self.class.to_s
609
578
  +"-- #{title} #{'-' * (66 - title.length)}\n"
610
579
  end
611
580
 
581
+ # @param [:Symbol] attr
582
+ # @param [Structable] value
583
+ # @param [Integer] level
584
+ # @return [::String]
612
585
  def inspect_attribute(attr, value, level = 1)
613
- type = value.class.to_s.sub(/.*::/, '')
614
- inspect_format(type, attr, value.format_inspect, level)
615
- end
616
-
617
- def inspect_format(type, attr, value, level = 1)
618
586
  str = inspect_shift_level(level)
619
- str << (FMT_ATTR % [type, attr, value])
587
+ value_lines = value.format_inspect.split("\n")
588
+ str << (FMT_ATTR % [value.type_name, attr, value_lines.shift])
589
+ return str if value_lines.empty?
590
+
591
+ shift = (FMT_ATTR % ['', '', 'START']).index('START')
592
+ value_lines.each do |l|
593
+ str << inspect_shift_level(level)
594
+ str << (' ' * shift) << l << "\n"
595
+ end
596
+ str
620
597
  end
621
598
 
599
+ # @param [Integer] level
600
+ # @return [String]
622
601
  def inspect_shift_level(level = 1)
623
602
  ' ' * (level + 1)
624
603
  end
@@ -58,7 +58,7 @@ module BinStruct
58
58
  # Format object when inspecting a {Struct} object
59
59
  # @return [::String]
60
60
  def format_inspect
61
- to_human
61
+ to_human.to_s
62
62
  end
63
63
  end
64
64
  end
@@ -8,5 +8,5 @@
8
8
 
9
9
  module BinStruct
10
10
  # BinStruct version
11
- VERSION = '0.2.1'
11
+ VERSION = '0.3.0'
12
12
  end
data/lib/bin_struct.rb CHANGED
@@ -25,6 +25,7 @@ end
25
25
  require_relative 'bin_struct/structable'
26
26
  require_relative 'bin_struct/int'
27
27
  require_relative 'bin_struct/enum'
28
+ require_relative 'bin_struct/bit_attr'
28
29
  require_relative 'bin_struct/struct'
29
30
  require_relative 'bin_struct/length_from'
30
31
  require_relative 'bin_struct/abstract_tlv'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bin_struct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LemonTree55
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-25 00:00:00.000000000 Z
11
+ date: 2024-12-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'BinStruct is a binary dissector and generator. It eases manipulating
14
14
  complex binary data.
@@ -26,6 +26,7 @@ files:
26
26
  - lib/bin_struct.rb
27
27
  - lib/bin_struct/abstract_tlv.rb
28
28
  - lib/bin_struct/array.rb
29
+ - lib/bin_struct/bit_attr.rb
29
30
  - lib/bin_struct/cstring.rb
30
31
  - lib/bin_struct/enum.rb
31
32
  - lib/bin_struct/int.rb