bin_struct 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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