bin_struct 0.1.0 → 0.2.1

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.
@@ -0,0 +1,628 @@
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) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
6
+ # Copyright (C) 2024 LemonTree55 <lenontree@proton.me>
7
+ # This program is published under MIT license.
8
+
9
+ # rubocop:disable Metrics/ClassLength
10
+
11
+ module BinStruct
12
+ # @abstract Set of attributes
13
+ # This class is a base class to define headers or anything else with a binary
14
+ # format containing multiple attributes.
15
+ #
16
+ # == Basics
17
+ # A {Struct} subclass is generaly composed of multiple binary attributes. These attributes
18
+ # have each a given type. All {Structable} types are supported.
19
+ #
20
+ # To define a new subclass, it has to inherit from {Struct}. And some class
21
+ # methods have to be used to declare attributes:
22
+ # class MyBinaryStructure < BinStruct::Struct
23
+ # # define a first Int8 attribute, with default value: 1
24
+ # define_attr :attr1, BinStruct::Int8, default: 1
25
+ # #define a second attribute, of kind Int32
26
+ # define_attr :attr2, BinStruct::Int32
27
+ # end
28
+ #
29
+ # These defintions create 4 methods: +#attr1+, +#attr1=+, +#attr2+ and +#attr2=+.
30
+ # All these methods take and/or return Integers.
31
+ #
32
+ # Attributes may also be accessed through {#[]} ans {#[]=}. These methods give access
33
+ # to type object:
34
+ # mybs = MyBinaryStructure.new
35
+ # mybs.attr1 # => Integer
36
+ # mybs[:attr1] # => BinStruct::Int8
37
+ #
38
+ # {#initialize} accepts an option hash to populate attributes. Keys are attribute
39
+ # name symbols, and values are those expected by writer accessor.
40
+ #
41
+ # {#read} is able to populate object from a binary string.
42
+ #
43
+ # {#to_s} returns binary string from object.
44
+ #
45
+ # == Add attributes
46
+ # {.define_attr} adds an attribute to Struct subclass. A lot of attribute types may be
47
+ # defined: integer types, string types (to handle a stream of bytes). More
48
+ # complex attribute types may be defined using others Struct subclasses:
49
+ # # define a 16-bit little-endian integer attribute, named type
50
+ # define_attr :type, BinStruct::Int16le
51
+ # # define a string attribute
52
+ # define_attr :body, BinStruct::String
53
+ # # define a attribute using a complex type (Struct subclass)
54
+ # define_attr :oui, BinStruct::OUI
55
+ #
56
+ # This example creates six methods on our Struct subclass: +#type+, +#type=+,
57
+ # +#body+, +#body=+, +#mac_addr+ and +#mac_addr=+.
58
+ #
59
+ # {.define_attr} has many options (third optional Hash argument):
60
+ # * +:default+ gives default attribute value. It may be a simple value (an Integer
61
+ # for an Int attribute, for example) or a lambda,
62
+ # * +:builder+ to give a builder/constructor lambda to create attribute. The lambda
63
+ # takes 2 arguments: {Struct} subclass object owning attribute, and type class as passes
64
+ # as second argument to {.define_attr},
65
+ # * +:optional+ to define this attribute as optional. This option takes a lambda
66
+ # parameter used to say if this attribute is present or not. The lambda takes an argument
67
+ # ({Struct} subclass object owning attribute),
68
+ # For example:
69
+ # # 32-bit integer attribute defaulting to 1
70
+ # define_attr :type, BinStruct::Int32, default: 1
71
+ # # 16-bit integer attribute, created with a random value. Each instance of this
72
+ # # object will have a different value.
73
+ # define_attr :id, BinStruct::Int16, default: ->(obj) { rand(65535) }
74
+ # # a size attribute
75
+ # define_attr :body_size, BinStruct::Int16
76
+ # # String attribute which length is taken from body_size attribute
77
+ # define_attr :body, BinStruct::String, builder: ->(obj, type) { type.new(length_from: obj[:body_size]) }
78
+ # # 16-bit enumeration type. As :default not specified, default to first value of enum
79
+ # define_attr :type_class, BinStruct::Int16Enum, enum: { 'class1' => 1, 'class2' => 2}
80
+ # # optional attribute. Only present if another attribute has a certain value
81
+ # define_attr :opt1, BinStruct::Int16, optional: ->(h) { h.type == 42 }
82
+ #
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
88
+ #
89
+ # This example generates methods:
90
+ # * +#frag+ and +#frag=+ to access +frag+ attribute as a 16-bit integer,
91
+ # * +#flag_rsv?+, +#flag_rsv=+, +#flag_df?+, +#flag_df=+, +#flag_mf?+ and +#flag_mf=+
92
+ # to access Boolean RSV, MF and DF flags from +frag+ attribute,
93
+ # * +#fragment_offset+ and +#fragment_offset=+ to access 13-bit integer fragment
94
+ # offset subattribute from +frag+ attribute.
95
+ #
96
+ # == Creating a new Struct class from another one
97
+ # 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.
103
+ #
104
+ # @author Sylvain Daubert (2016-2024)
105
+ # @author LemonTree55
106
+ class Struct
107
+ # @private
108
+ StructDef = ::Struct.new(:type, :default, :builder, :optional, :options)
109
+ # @private attribute names, ordered as they were declared
110
+ @ordered_attrs = []
111
+ # @private attribute definitions
112
+ @attr_defs = {}
113
+ # @private bit attribute definitions
114
+ @bit_attrs = {}
115
+
116
+ # Format to inspect attribute
117
+ FMT_ATTR = "%14s %16s: %s\n"
118
+
119
+ class << self
120
+ # Get attribute definitions for this class.
121
+ # @return [Hash]
122
+ attr_reader :attr_defs
123
+ # Get bit attribute defintions for this class
124
+ # @return [Hash]
125
+ attr_reader :bit_attrs
126
+
127
+ # On inheritage, create +@attr_defs+ class variable
128
+ # @param [Class] klass
129
+ # @return [void]
130
+ def inherited(klass)
131
+ super
132
+
133
+ attr_defs = {}
134
+ @attr_defs.each do |k, v|
135
+ attr_defs[k] = v.clone
136
+ end
137
+ ordered = @ordered_attrs.clone
138
+ bf = bit_attrs.clone
139
+
140
+ klass.class_eval do
141
+ @ordered_attrs = ordered
142
+ @attr_defs = attr_defs
143
+ @bit_attrs = bf
144
+ end
145
+ end
146
+
147
+ # Get attribute names
148
+ # @return [Array<Symbol>]
149
+ def attributes
150
+ @ordered_attrs
151
+ end
152
+
153
+ # Define an attribute in class
154
+ # class BinaryStruct < BinStruct::Struct
155
+ # # 8-bit value
156
+ # define_attr :value1, BinStruct::Int8
157
+ # # 16-bit value
158
+ # define_attr :value2, BinStruct::Int16
159
+ # # specific class, may use a specific constructor
160
+ # define_attr :value3, MyClass, builder: ->(obj, type) { type.new(obj) }
161
+ # end
162
+ #
163
+ # bs = BinaryStruct.new
164
+ # bs[value1] # => BinStruct::Int8
165
+ # bs.value1 # => Integer
166
+ # @param [Symbol] name attribute name
167
+ # @param [Structable] type class or instance
168
+ # @param [Hash] options Unrecognized options are passed to object builder if
169
+ # +:builder+ option is not set.
170
+ # @option options [Object] :default default value. May be a proc. This lambda
171
+ # take one argument: the caller object.
172
+ # @option options [Lambda] :builder lambda to construct this attribute.
173
+ # Parameters to this lambda is the caller object and the attribute type class.
174
+ # @option options [Lambda] :optional define this attribute as optional. Given lambda
175
+ # is used to known if this attribute is present or not. Parameter to this lambda is
176
+ # the being defined Struct object.
177
+ # @return [void]
178
+ def define_attr(name, type, options = {})
179
+ attributes << name
180
+ attr_defs[name] = StructDef.new(type,
181
+ options.delete(:default),
182
+ options.delete(:builder),
183
+ options.delete(:optional),
184
+ options)
185
+
186
+ add_methods(name, type)
187
+ end
188
+
189
+ # Define a attribute, before another one
190
+ # @param [Symbol,nil] other attribute name to create a new one before. If +nil+,
191
+ # new attribute is appended.
192
+ # @param [Symbol] name attribute name to create
193
+ # @param [Structable] type class or instance
194
+ # @param [Hash] options See {.define_attr}.
195
+ # @return [void]
196
+ # @see .define_attr
197
+ def define_attr_before(other, name, type, options = {})
198
+ define_attr name, type, options
199
+ return if other.nil?
200
+
201
+ attributes.delete name
202
+ idx = attributes.index(other)
203
+ raise ArgumentError, "unknown #{other} attribute" if idx.nil?
204
+
205
+ attributes[idx, 0] = name
206
+ end
207
+
208
+ # Define an attribute, after another one
209
+ # @param [Symbol,nil] other attribute name to create a new one after. If +nil+,
210
+ # new attribute is appended.
211
+ # @param [Symbol] name attribute name to create
212
+ # @param [Structable] type class or instance
213
+ # @param [Hash] options See {.define_attr}.
214
+ # @return [void]
215
+ # @see .define_attr
216
+ def define_attr_after(other, name, type, options = {})
217
+ define_attr name, type, options
218
+ return if other.nil?
219
+
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
225
+ end
226
+
227
+ # Remove a previously defined attribute
228
+ # @param [Symbol] name
229
+ # @return [void]
230
+ def remove_attr(name)
231
+ attributes.delete(name)
232
+ @attr_defs.delete(name)
233
+ undef_method name if method_defined?(name)
234
+ undef_method :"#{name}=" if method_defined?(:"#{name}=")
235
+ end
236
+
237
+ # Update a previously defined attribute
238
+ # @param [Symbol] name attribute name to create
239
+ # @param [Hash] options See {.define_attr}.
240
+ # @return [void]
241
+ # @see .define_attr
242
+ # @raise [ArgumentError] unknown attribute
243
+ def update_attr(name, options)
244
+ check_existence_of(name)
245
+
246
+ %i[default builder optional].each do |property|
247
+ attr_defs_property_from(name, property, options)
248
+ end
249
+
250
+ attr_defs[name].options.merge!(options)
251
+ end
252
+
253
+ # Define a bit attribute on given attribute
254
+ # class MyHeader < BinStruct::Struct
255
+ # define_attr :flags, BinStruct::Int16
256
+ # # define a bit attribute on :flag attribute
257
+ # # 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
260
+ # 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:
265
+ # * +#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+
272
+ # @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
281
+
282
+ until args.empty?
283
+ arg = args.shift
284
+ next unless arg.is_a? Symbol
285
+
286
+ size = size_from(args)
287
+
288
+ unless attr == :_
289
+ add_bit_methods(attr, arg, size, total_size, idx)
290
+ register_bit_attr_size(attr, arg, size)
291
+ end
292
+
293
+ idx -= size
294
+ end
295
+ end
296
+
297
+ # Remove all bit attributes defined on +attr+
298
+ # @param [Symbol] attr attribute defining bit attributes
299
+ # @return [void]
300
+ def remove_bit_attrs_on(attr)
301
+ bits = bit_attrs.delete(attr)
302
+ return if bits.nil?
303
+
304
+ bits.each do |bit, size|
305
+ undef_method :"#{bit}="
306
+ undef_method(size == 1 ? "#{bit}?" : bit)
307
+ end
308
+ end
309
+
310
+ private
311
+
312
+ def add_methods(name, type)
313
+ define = []
314
+ if type < Enum
315
+ define << "def #{name}; self[:#{name}].to_i; end"
316
+ define << "def #{name}=(val) self[:#{name}].value = val; end"
317
+ else
318
+ define << "def #{name}\n " \
319
+ "to_and_from_human?(:#{name}) ? self[:#{name}].to_human : self[:#{name}]\n" \
320
+ 'end'
321
+ define << "def #{name}=(val)\n " \
322
+ "to_and_from_human?(:#{name}) ? self[:#{name}].from_human(val) : self[:#{name}].read(val)\n" \
323
+ 'end'
324
+ end
325
+
326
+ define.delete_at(1) if instance_methods.include?(:"#{name}=")
327
+ define.delete_at(0) if instance_methods.include?(name)
328
+ class_eval define.join("\n")
329
+ end
330
+
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
384
+ end
385
+
386
+ def attr_defs_property_from(attr, property, options)
387
+ attr_defs[attr].send(:"#{property}=", options.delete(property)) if options.key?(property)
388
+ end
389
+
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
+ def check_existence_of(attr)
399
+ raise ArgumentError, "unknown #{attr} attribute for #{self}" unless attr_defs.key?(attr)
400
+ end
401
+ end
402
+
403
+ # Create a new Struct object
404
+ # @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}.
406
+ def initialize(options = {})
407
+ @attributes = {}
408
+ @optional_attributes = {}
409
+
410
+ self.class.attributes.each do |attr|
411
+ build_attribute(attr)
412
+ initialize_value(attr, options[attr])
413
+ initialize_optional(attr)
414
+ end
415
+
416
+ self.class.bit_attrs.each_value do |hsh|
417
+ hsh.each_key do |bit|
418
+ send(:"#{bit}=", options[bit]) if options[bit]
419
+ end
420
+ end
421
+ end
422
+
423
+ # Get attribute object
424
+ # @param [Symbol] attr attribute name
425
+ # @return [Structable]
426
+ def [](attr)
427
+ @attributes[attr]
428
+ end
429
+
430
+ # Set attribute object
431
+ # @param [Symbol] attr attribute name
432
+ # @param [Object] obj
433
+ # @return [Object]
434
+ def []=(attr, obj)
435
+ @attributes[attr] = obj
436
+ end
437
+
438
+ # Get all attribute names
439
+ # @return [Array<Symbol>]
440
+ def attributes
441
+ self.class.attributes
442
+ end
443
+
444
+ # Get all optional attribute names
445
+ # @return[Array<Symbol>,nil]
446
+ def optional_attributes
447
+ @optional_attributes.keys
448
+ end
449
+
450
+ # Say if this attribue is optional
451
+ # @param [Symbol] attr attribute name
452
+ # @return [Boolean]
453
+ def optional?(attr)
454
+ @optional_attributes.key?(attr)
455
+ end
456
+
457
+ # Say if an optional attribute is present
458
+ # @return [Boolean]
459
+ def present?(attr)
460
+ return true unless optional?(attr)
461
+
462
+ @optional_attributes[attr].call(self)
463
+ end
464
+
465
+ # Populate object from a binary string
466
+ # @param [String] str
467
+ # @return [Struct] self
468
+ def read(str)
469
+ return self if str.nil?
470
+
471
+ force_binary(str)
472
+ start = 0
473
+ attributes.each do |attr|
474
+ next unless present?(attr)
475
+
476
+ obj = self[attr].read(str[start..])
477
+ start += self[attr].sz
478
+ self[attr] = obj unless obj == self[attr]
479
+ end
480
+
481
+ self
482
+ end
483
+
484
+ # Common inspect method for structs.
485
+ #
486
+ # A block may be given to differently format some attributes. This
487
+ # may be used by subclasses to handle specific attributes.
488
+ # @yieldparam attr [Symbol] attribute to inspect
489
+ # @yieldreturn [String,nil] the string to print for +attr+, or +nil+
490
+ # to let +inspect+ generate it
491
+ # @return [String]
492
+ def inspect
493
+ str = inspect_titleize
494
+ attributes.each do |attr|
495
+ next if attr == :body
496
+ next unless present?(attr)
497
+
498
+ result = yield(attr) if block_given?
499
+ str << (result || inspect_attribute(attr, self[attr], 1))
500
+ end
501
+ str
502
+ end
503
+
504
+ # Return object as a binary string
505
+ # @return [String]
506
+ def to_s
507
+ attributes.select { |attr| present?(attr) }
508
+ .map! { |attr| force_binary @attributes[attr].to_s }.join
509
+ end
510
+
511
+ # Size of object as binary string
512
+ # @return [nteger]
513
+ def sz
514
+ to_s.size
515
+ end
516
+
517
+ # Return object as a hash
518
+ # @return [Hash] keys: attributes, values: attribute values
519
+ def to_h
520
+ attributes.to_h { |attr| [attr, @attributes[attr].to_human] }
521
+ end
522
+
523
+ # Get offset of given attribute in {Struct}.
524
+ # @param [Symbol] attr attribute name
525
+ # @return [Integer]
526
+ # @raise [ArgumentError] unknown attribute
527
+ def offset_of(attr)
528
+ raise ArgumentError, "#{attr} is an unknown attribute of #{self.class}" unless @attributes.include?(attr)
529
+
530
+ offset = 0
531
+ attributes.each do |a|
532
+ break offset if a == attr
533
+ next unless present?(a)
534
+
535
+ offset += self[a].sz
536
+ end
537
+ end
538
+
539
+ # Get bit attributes definition for given attribute.
540
+ # @param [Symbol] attr attribute defining bit attributes
541
+ # @return [Hash,nil] keys: bit attributes, values: their size in bits
542
+ def bits_on(attr)
543
+ self.class.bit_attrs[attr]
544
+ end
545
+
546
+ private
547
+
548
+ # Deeply duplicate +@attributes+
549
+ # @return [void]
550
+ def initialize_copy(_other)
551
+ attributes = {}
552
+ @attributes.each { |k, v| attributes[k] = v.dup }
553
+ @attributes = attributes
554
+ end
555
+
556
+ # Force str to binary encoding
557
+ # @param [String] str
558
+ # @return [String]
559
+ def force_binary(str)
560
+ BinStruct.force_binary(str)
561
+ end
562
+
563
+ # @param [Symbol] attr attribute name
564
+ # @return [Boolean] +true= if #from_human and #to_human are both defined for given attribute
565
+ def to_and_from_human?(attr)
566
+ self[attr].respond_to?(:to_human) && self[attr].respond_to?(:from_human)
567
+ end
568
+
569
+ def attr_defs
570
+ self.class.attr_defs
571
+ end
572
+
573
+ # rubocop:disable Metrics/AbcSize
574
+ def build_attribute(attr)
575
+ type = attr_defs[attr].type
576
+
577
+ @attributes[attr] = if attr_defs[attr].builder
578
+ attr_defs[attr].builder.call(self, type)
579
+ elsif !attr_defs[attr].options.empty?
580
+ type.new(attr_defs[attr].options)
581
+ else
582
+ type.new
583
+ end
584
+ end
585
+ # rubocop:enable Metrics/AbcSize
586
+
587
+ def initialize_value(attr, val)
588
+ type = attr_defs[attr].type
589
+ default = attr_defs[attr].default
590
+ default = default.to_proc.call(self) if default.is_a?(Proc)
591
+
592
+ value = val || default
593
+ if value.class <= type
594
+ @attributes[attr] = value
595
+ elsif @attributes[attr].respond_to?(:from_human)
596
+ @attributes[attr].from_human(value)
597
+ else
598
+ @attributes[attr].read(value)
599
+ end
600
+ end
601
+
602
+ def initialize_optional(attr)
603
+ optional = attr_defs[attr].optional
604
+ @optional_attributes[attr] = optional if optional
605
+ end
606
+
607
+ def inspect_titleize
608
+ title = self.class.to_s
609
+ +"-- #{title} #{'-' * (66 - title.length)}\n"
610
+ end
611
+
612
+ 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
+ str = inspect_shift_level(level)
619
+ str << (FMT_ATTR % [type, attr, value])
620
+ end
621
+
622
+ def inspect_shift_level(level = 1)
623
+ ' ' * (level + 1)
624
+ end
625
+ end
626
+ end
627
+
628
+ # rubocop:enable Metrics/ClassLength
@@ -7,17 +7,17 @@
7
7
  # This program is published under MIT license.
8
8
 
9
9
  module BinStruct
10
- # Mixin to define minimal API for a class to be embbeded as a field in
11
- # {Fields} type.
10
+ # Mixin to define minimal API for a class to be embbeded as an attribute in
11
+ # {Struct} object.
12
12
  #
13
13
  # == Optional methods
14
- # These methods may, optionally, be defined by fieldable types:
15
- # * +from_human+ to load data from a human-readable string.
16
- # @author Sylvain Daubert
17
- # @since 3.1.6
18
- module Fieldable
14
+ # This method may, optionally, be defined by structable types:
15
+ # * +from_human+ to load data from a human-readable ruby object (String, Integer,...).
16
+ # @author Sylvain Daubert (2016-2024)
17
+ # @author LemonTree55
18
+ module Structable
19
19
  # Get type name
20
- # @return [String]
20
+ # @return [::String]
21
21
  def type_name
22
22
  self.class.to_s.split('::').last
23
23
  end
@@ -26,15 +26,15 @@ module BinStruct
26
26
  # These methods are defined for documentation.
27
27
 
28
28
  # Populate object from a binary string
29
- # @param [String] str
30
- # @return [Fields] self
29
+ # @param [::String] str
30
+ # @return [self]
31
31
  # @abstract subclass should overload it.
32
32
  def read(str)
33
33
  super
34
34
  end
35
35
 
36
36
  # Return object as a binary string
37
- # @return [String]
37
+ # @return [::String]
38
38
  # @abstract subclass should overload it.
39
39
  def to_s
40
40
  super
@@ -46,8 +46,8 @@ module BinStruct
46
46
  to_s.size
47
47
  end
48
48
 
49
- # Return a human-readbale string
50
- # @return [String]
49
+ # Return a human-readbale object
50
+ # @return [::String,Integer]
51
51
  # @abstract subclass should overload it.
52
52
  def to_human
53
53
  super
@@ -55,8 +55,8 @@ module BinStruct
55
55
 
56
56
  # rubocop:enable Lint/UselessMethodDefinition
57
57
 
58
- # Format object when inspecting a {Fields} object
59
- # @return [String]
58
+ # Format object when inspecting a {Struct} object
59
+ # @return [::String]
60
60
  def format_inspect
61
61
  to_human
62
62
  end
@@ -7,5 +7,6 @@
7
7
  # This program is published under MIT license.
8
8
 
9
9
  module BinStruct
10
- VERSION = '0.1.0'
10
+ # BinStruct version
11
+ VERSION = '0.2.1'
11
12
  end