ctypes 0.2.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.
@@ -0,0 +1,278 @@
1
+ # encoding: ASCII-8BIT
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-FileCopyrightText: 2025 Cisco
5
+ # SPDX-License-Identifier: MIT
6
+
7
+ module CTypes
8
+ # define a bit-field type for use in structures
9
+ # @example 8-bit integer split into three multibit values
10
+ # class MyBits < CTypes::Bitfield
11
+ # # declare bit layout in right-to-left order
12
+ # layout do
13
+ # unsigned :bit # single bit for a
14
+ # skip 1 # skip one bit
15
+ # unsigned :two, 2 # two bits for this field
16
+ # signed :nibble, 4 # four bit nibble as a signed int
17
+ # end
18
+ # end
19
+ #
20
+ # # size is the number of bytes required by the layout, but can be larger
21
+ # # when requested in #layout
22
+ # MyBits.size # => 1
23
+ #
24
+ # # The bits are packed right-to-left
25
+ # MyBits.pack({bit: 1}) # => "\x01" (0b00000001)
26
+ # MyBits.pack({two: 3}) # => "\x0c" (0b00001100)
27
+ # MyBits.pack({nibble: -1}) # => "\xf0" (0b11110000)
28
+ #
29
+ # # unpack a value and access individual fields
30
+ # value = MyBits.unpack("\xf1") # => #<Bitfield bit: 1, two: 0, nibble: -1>
31
+ # value.bit # => 1
32
+ # value.two # => 0
33
+ # value.nibble # => -1
34
+ #
35
+ # # update the value and repack
36
+ # value.two = 1
37
+ # value.nibble = 0
38
+ # value.to_binstr # => "\x05" (0b00000101)
39
+ class Bitfield
40
+ extend Type
41
+ using PrettyPrintHelpers
42
+
43
+ # describe the layout of the bitfield
44
+ #
45
+ # @example right-to-left bit layout
46
+ # layout do
47
+ # unsigned :bit # single bit for a
48
+ # skip 1 # skip one bit
49
+ # unsigned :two, 2 # two bits for this field
50
+ # signed :nibble, 4 # four bit nibble as a signed int
51
+ # end
52
+ #
53
+ # @example explicit bit layout
54
+ # layout do
55
+ # field :bit, offset: 0, bits: 1
56
+ # field :two, offset: 2, bits: 2
57
+ # field :nibble, offset: 4, bits: 4
58
+ # end
59
+ #
60
+ # @example create a two-byte bitfield, but only one bit declared
61
+ # layout do
62
+ # size 2 # bitfield will take two bytes
63
+ # field :bit, offset: 9, bits: 1
64
+ # end
65
+ #
66
+ def self.layout(&block)
67
+ raise Error, "no block given" unless block
68
+ builder = Builder.new(&block)
69
+ builder.instance_eval(&block)
70
+ apply_layout(builder)
71
+ end
72
+
73
+ # @api private
74
+ def self.builder
75
+ Builder.new
76
+ end
77
+
78
+ # @api private
79
+ def self.apply_layout(builder)
80
+ @type, @bits, @dry_type, @endian, @layout = builder.result
81
+
82
+ # clear all existing field accessors
83
+ @fields ||= {}
84
+ @fields.each { |_, methods| remove_method(*methods) }
85
+ @fields.clear
86
+
87
+ @bits.each do |name, _|
88
+ @fields[name] = attr_accessor(name)
89
+ end
90
+
91
+ self
92
+ end
93
+ private_class_method :apply_layout
94
+
95
+ # get the size of the bitfield in bytes
96
+ def self.size
97
+ @type.size
98
+ end
99
+
100
+ # check if bitfield is a fixed size
101
+ def self.fixed_size?
102
+ true
103
+ end
104
+
105
+ # check if bitfield is greedy
106
+ def self.greedy?
107
+ false
108
+ end
109
+
110
+ # pack a ruby hash containing bitfield values into a binary string
111
+ # @param value [Hash] value to be encoded
112
+ # @param endian [Symbol] optional endian override
113
+ # @param validate [Boolean] set to false to disable value validation
114
+ # @return [::String] binary encoding for value
115
+ def self.pack(value, endian: default_endian, validate: true)
116
+ value = value.to_hash.freeze
117
+ value = @dry_type[value] unless validate == false
118
+ out = 0
119
+ @bits.each do |(name, offset, mask, _)|
120
+ out |= (value[name] & mask) << offset
121
+ end
122
+ @type.pack(out, endian:, validate:)
123
+ end
124
+
125
+ # convert a String containing the binary represention of a c type into the
126
+ # equivalent ruby type
127
+ #
128
+ # @param buf [::String] bytes that make up the type
129
+ # @param endian [Symbol] endian of data within buf
130
+ # @return [Bitfield] unpacked bitfield
131
+ def self.unpack_one(buf, endian: default_endian)
132
+ value, rest = @type.unpack_one(buf, endian:)
133
+ out = _new
134
+ @bits.each do |(name, offset, mask, signed, var)|
135
+ v = (value >> offset) & mask
136
+ v |= (-1 << signed) | v if signed && v[signed - 1] == 1
137
+ out.instance_variable_set(var, v)
138
+ end
139
+
140
+ [out, rest]
141
+ end
142
+
143
+ # get the list of fields defined in this bitfield
144
+ def self.fields
145
+ @fields.keys
146
+ end
147
+
148
+ # check if the bitfield declared a specific field
149
+ def self.has_field?(name)
150
+ @fields.has_key?(name)
151
+ end
152
+
153
+ def self.pretty_print(q)
154
+ q.ctype("bitfield", @endian) do
155
+ q.seplist(@layout, -> { q.breakable(";") }) do |cmd|
156
+ q.text(cmd)
157
+ end
158
+ end
159
+ end
160
+ class << self
161
+ alias_method :inspect, :pretty_inspect # :nodoc:
162
+ end
163
+
164
+ # generate ruby code needed to create this type
165
+ def self.export_type(q)
166
+ q << "bitfield {"
167
+ q.break
168
+ q.nest(2) do
169
+ @layout.each do |cmd|
170
+ q << cmd
171
+ q.break
172
+ end
173
+ end
174
+ q << "}"
175
+ q << ".with_endian(%p)" % [@endian] if @endian
176
+ end
177
+
178
+ class << self
179
+ # @method _new
180
+ # allocate an uninitialized instance of the bitfield
181
+ # @return [Bitfield] uninitialized bitfield instance
182
+ # @api private
183
+ alias_method :_new, :new
184
+ private :_new
185
+ end
186
+
187
+ # allocate an instance of the Bitfield and initialize default values
188
+ # @param fields [Hash] values to set
189
+ # @return [Bitfield]
190
+ def self.new(fields = nil)
191
+ buf = fields.nil? ? ("\0" * size) : pack(fields)
192
+ unpack(buf)
193
+ end
194
+
195
+ def self.==(other)
196
+ return true if super
197
+ return false unless other.is_a?(Class) && other < Bitfield
198
+ other.field_layout == @bits &&
199
+ other.default_endian == default_endian &&
200
+ other.size == size
201
+ end
202
+
203
+ # @api private
204
+ def self.field_layout # :nodoc:
205
+ @bits
206
+ end
207
+
208
+ # set a bitfield value
209
+ # @param k [Symbol] field name
210
+ # @param v value
211
+ def []=(k, v)
212
+ has_field!(k)
213
+ instance_variable_set(:"@#{k}", v)
214
+ end
215
+
216
+ # get an bitfield value
217
+ # @param k [Symbol] field name
218
+ # @return value
219
+ def [](k)
220
+ has_field!(k)
221
+ instance_variable_get(:"@#{k}")
222
+ end
223
+
224
+ def has_key?(name)
225
+ self.class.has_field?(name)
226
+ end
227
+
228
+ def has_field!(name) # :nodoc:
229
+ raise UnknownFieldError, "unknown field: %p" % name unless
230
+ self.class.has_field?(name)
231
+ end
232
+ private :has_field!
233
+
234
+ def to_h(shallow: false)
235
+ out = {}
236
+ self.class.field_layout.each do |name, _, _, _, var|
237
+ out[name] = instance_variable_get(var)
238
+ end
239
+ out
240
+ end
241
+ alias_method :to_hash, :to_h
242
+
243
+ def pretty_print(q) # :nodoc:
244
+ q.group(4, "bitfield {", "}") do
245
+ q.seplist(self.class.field_layout, -> { q.breakable("") }) do |name, _|
246
+ q.text(".#{name} = ")
247
+ q.pp(instance_variable_get(:"@#{name}"))
248
+ q.text(", ")
249
+ end
250
+ end
251
+ end
252
+ alias_method :inspect, :pretty_inspect # :nodoc:
253
+
254
+ # return the binary representation of this Bitfield instance
255
+ # @return [::String] binary representation of struct
256
+ def to_binstr(endian: self.class.default_endian)
257
+ self.class.pack(to_h, endian:)
258
+ end
259
+
260
+ # determine if this instance of the bitfield is equal to another instance
261
+ #
262
+ # @note this implementation also supports Hash equality through {to_h}
263
+ def ==(other)
264
+ case other
265
+ when self.class
266
+ self.class.field_layout.all? do |name, _, _, _, var|
267
+ instance_variable_get(var) == other[name]
268
+ end
269
+ when Hash
270
+ other == to_h
271
+ else
272
+ super
273
+ end
274
+ end
275
+ end
276
+ end
277
+
278
+ require_relative "bitfield/builder"
@@ -0,0 +1,154 @@
1
+ # encoding: ASCII-8BIT
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-FileCopyrightText: 2025 Cisco
5
+ # SPDX-License-Identifier: MIT
6
+
7
+ module CTypes
8
+ # map bits in an integer to specific flags
9
+ #
10
+ # This class enables the mapping of individual bits in an integer to specific
11
+ # flags that each bit represents. This class only supports single bit
12
+ # fields. See `Bitfield` if you need to unpack multibit values.
13
+ #
14
+ # @example 8-bit bitmap, the long way
15
+ # b = Bitmap.new(
16
+ # bits: Enum.new({a: 0, b: 1, c: 7}),
17
+ # type: CTypes::UInt8)
18
+ #
19
+ # # pack some values
20
+ # b.pack([]) # => "\x00"
21
+ # b.pack([:a]) # => "\x01"
22
+ # b.pack([:b]) # => "\x02"
23
+ # b.pack([:c]) # => "\x80"
24
+ # b.pack([:c, :a]) # => "\x81"
25
+ # b.pack([:a, :c]) # => "\x81"
26
+ #
27
+ # # unpack some values
28
+ # b.unpack("\x00") # => []
29
+ # b.unpack("\x01") # => [:a]
30
+ # b.unpack("\x02") # => [:b]
31
+ # b.unpack("\x03") # => [:a, :b]
32
+ # b.unpack("\x80") # => [:c]
33
+ #
34
+ # @example 8-bit bitmap, using helpers
35
+ # include CTypes::Helpers # for bitmap and uint8
36
+ # b = bitmap(uint8, {a: 0, b: 1, c: 7})
37
+ # b.pack([:a, :c]) # => "\x81"
38
+ # b.unpack("\x03") # => [:a, :b]
39
+ class Bitmap
40
+ extend Forwardable
41
+ include Type
42
+
43
+ # create a new Bitmap
44
+ # @param bits [Enum] mapping of bit position to name
45
+ # @param type [Type] ctype to encode value as; defaults to uint32
46
+ def initialize(bits:, type: Helpers.uint32)
47
+ raise Error, "bits must be an Enum instance: %p" % bits unless
48
+ bits.is_a?(Enum)
49
+
50
+ @type = type
51
+ @bits = bits
52
+ @bits_max = type.size * 8
53
+ @bits_constraint = Dry::Types["integer"]
54
+ .constrained(gteq: 0, lt: @bits_max)
55
+ @dry_type = Dry::Types["array"].of(@bits.dry_type).default([].freeze)
56
+ end
57
+ def_delegators :@type, :greedy?
58
+
59
+ def size
60
+ @type.size
61
+ end
62
+
63
+ def fixed_size?
64
+ @type.fixed_size?
65
+ end
66
+
67
+ # pack a ruby Array containing symbol names for each bit into a binary
68
+ # string
69
+ # @param value [::Array] Array of bits to be set, by name or right-to-left
70
+ # index
71
+ # @param endian [Symbol] optional endian override
72
+ # @param validate [Boolean] set to false to disable value validation
73
+ # @return [::String] binary encoding for value
74
+ #
75
+ # @example packing with named values
76
+ # b = CTypes::Helpers.bitmap(uint8, {a: 0, b: 1, c: 7})
77
+ # b.pack([:a, :c]) # => "\x81"
78
+ #
79
+ # @example packing explicit bits
80
+ # b = CTypes::Helpers.bitmap(uint8, {a: 0, b: 1, c: 7})
81
+ # b.pack([0, 7]) # => "\x81" (0b10000000)
82
+ # b.pack([0, 6]) # => Dry::Types::ConstraintError
83
+ # b.pack([0, 6], validate: false) # => "\x41" (0b01000001)
84
+ #
85
+ def pack(value, endian: default_endian, validate: true)
86
+ value = @dry_type[value] unless validate == false
87
+ mapping = @bits.mapping
88
+ bits = value.inject(0) do |out, v|
89
+ bit = case v
90
+ when Integer
91
+ v
92
+ when /\Abit_(\d+)\z/
93
+ $1.to_i
94
+ when Symbol
95
+ mapping[v]
96
+ else
97
+ raise Error, "unknown bitmap value: %p" % v
98
+ end
99
+ @bits_constraint[bit]
100
+ out |= 1 << bit
101
+ end
102
+ @type.pack(bits, endian: @type.endian || endian, validate: validate)
103
+ end
104
+
105
+ # convert a String containing the binary represention of a c type into the
106
+ # equivalent ruby type
107
+ #
108
+ # @param buf [::String] bytes that make up the type
109
+ # @param endian [Symbol] endian of data within buf
110
+ # @return [::Array] unpacked bitfield
111
+ #
112
+ # @example
113
+ # b = bitmap(uint8, {a: 0, b: 1, c: 7})
114
+ # b.unpack("\x00") # => []
115
+ # b.unpack("\x01") # => [:a]
116
+ # b.unpack("\x81") # => [:a, :c]
117
+ #
118
+ # @example allow unlabelled bits in the value
119
+ # b = bitmap(uint8, {a: 0, b: 1, c: 7})
120
+ # b.unpack("\x04") # => Dry::Types::ConstraintError
121
+ #
122
+ # # create a new permissive bitmap from the initial bitmap
123
+ # bp = b.permissive
124
+ # bp.unpack("\x04") # => [:bit_2]
125
+ #
126
+ # # known bits are still unpacked with the proper name
127
+ # bp.unpack("\x05") # => [:a, :bit_2]
128
+ #
129
+ def unpack_one(buf, endian: default_endian)
130
+ value, rest = @type.unpack_one(buf, endian: @type.endian || endian)
131
+ bits = []
132
+ @bits_max.times do |bit|
133
+ next if value & (1 << bit) == 0
134
+ v = @bits.dry_type[bit]
135
+ v = :"bit_#{v}" if v.is_a?(Integer)
136
+ bits << v
137
+ end
138
+ [bits, rest]
139
+ end
140
+
141
+ # get a permissive version of this bitmap
142
+ # @return [Bitmap] permissive version of the bitmap.
143
+ #
144
+ # @example
145
+ # b = CTypes::Helpers.bitmap(uint8, {a: 0})
146
+ # b.unpack("\x04") # => Dry::Types::ConstraintError
147
+ #
148
+ # b = CTypes::Helpers.bitmap(uint8, {a: 0}).permissive
149
+ # b.unpack("\x04") # => [:bit_2]
150
+ def permissive
151
+ Bitmap.new(type: @type, bits: @bits.permissive)
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SPDX-FileCopyrightText: 2025 Cisco
4
+ # SPDX-License-Identifier: MIT
5
+
6
+ module CTypes
7
+ # {Enum} layout builder
8
+ #
9
+ # This class is used to define the name/value pairs for {Enum} types. The
10
+ # builder supports dynamic numbering similar to that used in C. The
11
+ # {Builder} is used internally by {Enum}, and should not be used directly.
12
+ #
13
+ # @example Supported syntax and dynamic numbering
14
+ # CTypes::Enum.new do |builder|
15
+ # # automatic numbering
16
+ # builder << :a # :a will have value 0
17
+ # builder << :b # :b will have value 1
18
+ # builder << {c: 10} # :c will have value 10
19
+ # builder << :d # :d will have value 11
20
+ #
21
+ # # bulk assignment
22
+ # builder << %i[e f g] # :e == 12, :f == 11, :g == 12
23
+ #
24
+ # # bulk assignment with values
25
+ # builder << {z: 25, y: 24, x: 23}
26
+ # builder << :max # :max == 26
27
+ # end
28
+ class Enum::Builder
29
+ def initialize(&block)
30
+ @map = {}
31
+ @next = 0
32
+ @default = nil
33
+ block.call(self)
34
+ end
35
+ attr_reader :map
36
+ attr_accessor :default
37
+
38
+ # append new key/value pairs to the {Enum}
39
+ # @param value name or name/value pairs
40
+ #
41
+ # @example
42
+ # CTypes::Enum.new do |builder|
43
+ # # automatic numbering
44
+ # builder << :a # :a will have value 0
45
+ # builder << :b # :b will have value 1
46
+ # builder << {c: 10} # :c will have value 10
47
+ # builder << :d # :d will have value 11
48
+ #
49
+ # # bulk assignment
50
+ # builder << %i[e f g] # :e == 12, :f == 11, :g == 12
51
+ #
52
+ # # bulk assignment with values
53
+ # builder << {z: 25, y: 24, x: 23}
54
+ # builder << :max # :max == 26
55
+ # end
56
+ def <<(value)
57
+ case value
58
+ when Hash
59
+ value.each_pair { |k, v| set(k, v) }
60
+ when ::Array
61
+ value.each { |v| set(v, @next) }
62
+ else
63
+ set(value, @next)
64
+ end
65
+ self
66
+ end
67
+
68
+ def push(*values)
69
+ values.each { |v| self << v }
70
+ end
71
+
72
+ private
73
+
74
+ def set(key, value)
75
+ key = key.to_sym
76
+ raise Error, "duplicate key %p" % key if
77
+ @map.has_key?(key)
78
+ raise Error, "value must be Integer: %p" unless
79
+ value.is_a?(Integer)
80
+ @map[key] = value
81
+ @next = value + 1 if value >= @next
82
+ @default ||= key
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,201 @@
1
+ # encoding: ASCII-8BIT
2
+ # frozen_string_literal: true
3
+
4
+ # SPDX-FileCopyrightText: 2025 Cisco
5
+ # SPDX-License-Identifier: MIT
6
+ require "forwardable"
7
+
8
+ module CTypes
9
+ # Pack & unpack C enum values
10
+ #
11
+ # @example 32-bit contiguous enum
12
+ # state = Enum.new(%i[start stop block])
13
+ # state.pack(:block) # => "\2\0\0\0"
14
+ # state.unpack("\0\0\0\0") # => :start
15
+ #
16
+ # @example 8-bit contiguous enum
17
+ # state = Enum.new(UInt8, %i[start stop block])
18
+ # state.pack(:block) # => "\2"
19
+ # state.unpack("\1") # => :stop
20
+ #
21
+ # @example sparse enum
22
+ # state = Enum.new do |e|
23
+ # e << %i{a b c}
24
+ # e << {d: 32}
25
+ # end
26
+ # state.pack(:d) # => "\x20\x00\x00\x00"
27
+ # state.unpack("\x02\x00\x00\x00") # => :c
28
+ #
29
+ class Enum
30
+ include Type
31
+ using PrettyPrintHelpers
32
+ extend Forwardable
33
+
34
+ # @example contiguous 32-bit enum
35
+ # Enum.new([:a, :b, :c])
36
+ # Enum.new(%i[a b c])
37
+ #
38
+ # @example contiguous 8-bit enum
39
+ # Enum.new(:uint8, %i[a b c])
40
+ #
41
+ # @example sparse enum
42
+ # Enum.new({a: 46, b: 789})
43
+ #
44
+ # @example 16-bit sparse enum
45
+ # Enum.new(UInt16, {a: 46, b: 789})
46
+ #
47
+ # @example 8-bit sparse enum
48
+ # Enum.new(Uint8) do |e|
49
+ # e << %i{zero one two} # define 0, 1, 2
50
+ # e << {eighty: 80} # skip 3-79 (incl), define 80
51
+ # e << :eighty_one
52
+ # e << {a: 100, b: 200} # define multiple sparse values
53
+ # end
54
+ #
55
+ # @example dynamically generated enum
56
+ # Enum.new do |e|
57
+ # # declare state_0 through state_31
58
+ # 32.times do |i|
59
+ # e << "state_#{i}"
60
+ # end
61
+ # end
62
+ #
63
+ # @see Builder
64
+ def initialize(type = Helpers.uint32, values = nil, permissive: false,
65
+ &block)
66
+ builder = if block
67
+ Builder.new(&block)
68
+ else
69
+ if values.nil?
70
+ values = type
71
+ type = Helpers.uint32
72
+ end
73
+
74
+ Builder.new { |b| b << values }
75
+ end
76
+
77
+ @dry_type = Dry::Types["symbol"]
78
+ .default(builder.default)
79
+ .enum(builder.map)
80
+ @type = type
81
+ @size = @type.size
82
+ @dry_type = @dry_type.lax if permissive
83
+ end
84
+ attr_reader :size, :type
85
+ def_delegators :@type, :signed?, :greedy?
86
+
87
+ # encode a ruby type into a String containing the binary representation of
88
+ # the enum
89
+ #
90
+ # @param value [Symbol, Integer] value to be encoded
91
+ # @param endian [Symbol] endian to pack with
92
+ # @param validate [Boolean] set to false to disable value validation
93
+ # @return [::String] binary encoding for value
94
+ #
95
+ # @example
96
+ # e = Enum.new(%i[stopped running blocked])
97
+ # e.pack(:running) # => "\1\0\0\0"
98
+ # e.pack(2) # => "\2\0\0\0"
99
+ def pack(value, endian: default_endian, validate: true)
100
+ value = @dry_type[value] if validate
101
+ out = @dry_type.mapping[value]
102
+ out ||= case value
103
+ when /\Aunknown_(\h+)\z/
104
+ out = $1.to_i(16)
105
+ when Integer
106
+ value
107
+ else
108
+ raise Error, "unknown enum value: %p" % value
109
+ end
110
+
111
+ @type.pack(out, endian: @type.endian || endian, validate:)
112
+ end
113
+
114
+ # convert a String containing the binary represention of a c enum into the
115
+ # ruby value
116
+ #
117
+ # @param buf [String] bytes that make up the type
118
+ # @param endian [Symbol] endian of data within buf
119
+ # @return [Array(Symbol, ::String)] decoded type, and remaining bytes
120
+ # @see Type#unpack
121
+ #
122
+ # @example
123
+ # e = Enum.new(%i[stopped running blocked])
124
+ # e.unpack("\1\0\0\0") # => :running
125
+ def unpack_one(buf, endian: default_endian)
126
+ value, rest = @type.unpack_one(buf, endian: @type.endian || endian)
127
+ out = @dry_type[value]
128
+ out = ("unknown_%0#{@size * 2}x" % value).to_sym unless out.is_a?(Symbol)
129
+ [out, rest]
130
+ end
131
+
132
+ # @api private
133
+ def mapping
134
+ @dry_type.mapping
135
+ end
136
+
137
+ def pretty_print(q) # :nodoc:
138
+ q.group(2, "enum(", ")") do
139
+ if @type != Helpers.uint32
140
+ q.pp(@type)
141
+ q.comma_breakable
142
+ end
143
+ q.group(0, "{", "}") do
144
+ q.seplist(@dry_type.mapping) do |name, value|
145
+ q.text("#{name}: #{value}")
146
+ end
147
+ end
148
+ end
149
+ end
150
+ alias_method :inspect, :pretty_inspect # :nodoc:
151
+
152
+ def export_type(q)
153
+ q << "enum("
154
+ if @type != UInt32
155
+ q << @type
156
+ q << ", "
157
+ end
158
+ q << "{"
159
+ q.break
160
+
161
+ q.nest(2) do
162
+ @dry_type.mapping.each do |name, value|
163
+ q << "#{name}: #{value},"
164
+ q.break
165
+ end
166
+ end
167
+ q << "})"
168
+ q << ".permissive" if @dry_type.is_a?(Dry::Types::Lax)
169
+ end
170
+
171
+ def ==(other)
172
+ return false unless other.is_a?(Enum)
173
+ other.type == @type && other.mapping == @dry_type.mapping
174
+ end
175
+
176
+ def default_value
177
+ # with `.lax` added to dry_type for permissive enum, the standard
178
+ # `dry_type[]` doesn't work for a default. Instead, we're going to try
179
+ # whatever key is set for 0, and failing that, just the first value
180
+ # defined for the enum
181
+ self[0] || @dry_type.mapping.first.first
182
+ end
183
+
184
+ def permissive
185
+ Enum.new(@type, @dry_type.mapping, permissive: true)
186
+ end
187
+
188
+ # lookup the key for a given Integer value in this Enum
189
+ #
190
+ # @example
191
+ # e = Enum.new(%i[a b c])
192
+ # e[1] # => :b
193
+ # e[5] # => nil
194
+ def [](value)
195
+ @inverted_mapping ||= @dry_type.mapping.invert
196
+ @inverted_mapping[value]
197
+ end
198
+ end
199
+ end
200
+
201
+ require_relative "enum/builder"