ffi-libfuse 0.4.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e105d84b1d0f73af20845ed1d39e51ae61f9ca4998fa9caf505b4e87ae08eaa9
4
- data.tar.gz: 7f9013b530bc62e9fb4071899e13f191fdc720f662221da1cac0cd554a76bc5f
3
+ metadata.gz: 791ea233a078177b2dd6a9b1cf9a75a64ed05d243de71ebf52a8028b11795653
4
+ data.tar.gz: 45d9d235948d5c4cdd09cd8ae168914e82caad31df8a2ce1727d8ed6bb2ff9ad
5
5
  SHA512:
6
- metadata.gz: 72b0a464ab67be704cb14f3ce4b07f1f9a29d260ee50864dcf8c75cc24ba2eaf43aab6ae9f79fda9edb707cded39fde10e8504be7b8ca011b1a7d625969d62f6
7
- data.tar.gz: b85626a26f149dc1852ae5928cef67eb5dd7fc4353acb6bafa4fb0061aec38dce6d6e6c8137b216bc540263ba28f5b5f627f3af8feecb9bab417bbfc64df45ab
6
+ metadata.gz: '00301169c26144390a94545130326eb20c4661af57c2fd612f2ba477ba957b1f76e316fafb7f6da41431a97bab3a6e05523dd9d40a6f1f276d956e2f04aecf5a'
7
+ data.tar.gz: a3ea2914410e7f2c4e284df9a0ed500a0c70adc59504bfd93dd398b1396be1467f07071b20d2b7bfd68be465e6c1151889975d747b33969d9a53bff9e9561f47
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.1](https://github.com/lwoggardner/ffi-libfuse/compare/v0.4.0...v0.4.1) (2024-10-26)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * support alpine linux with musl libc and fuse 3.16 ([65d362d](https://github.com/lwoggardner/ffi-libfuse/commit/65d362d7f3e87bca426742cccaabc9f421e6fc38)), closes [#26](https://github.com/lwoggardner/ffi-libfuse/issues/26) [#27](https://github.com/lwoggardner/ffi-libfuse/issues/27)
9
+
3
10
  ## [0.4.0](https://github.com/lwoggardner/ffi-libfuse/compare/v0.3.4...v0.4.0) (2024-01-21)
4
11
 
5
12
 
data/lib/ffi/accessors.rb CHANGED
@@ -3,157 +3,469 @@
3
3
  require 'ffi'
4
4
 
5
5
  module FFI
6
- # Syntax sugar for FFI::Struct
6
+ # Syntax sugar for **FFI::Struct**
7
+ #
8
+ # Modules that include {Accessors} are automatically extended by {ClassMethods} which provides for defining reader and
9
+ # writer methods over struct field members.
10
+ #
11
+ # Although designed around needs of **FFI::Struct**, eg the ability to map natural ruby names to struct field names,
12
+ # this module can be used over anything that stores attributes in a Hash like structure.
13
+ # It provides equivalent method definitions to *Module#attr_(reader|writer|accessor)* except using the index methods
14
+ # *:[<member>]*, and *:[<member>]=* instead of managing instance variables.
15
+ #
16
+ # Additionally it supports boolean attributes with '?' aliases for reader methods, and keeps track of attribute
17
+ # definitions to support {#fill},{#to_h} etc.
18
+ #
19
+ # Standard instance variable based attributes defined through *#attr_(reader|writer|accessor)*
20
+ # also get these features.
21
+ # @example
22
+ # class MyStruct < FFI::Struct
23
+ # include FFI::Accessors
24
+ #
25
+ # layout(
26
+ # a: :int,
27
+ # b: :int,
28
+ # s_one: :string,
29
+ # enabled: :bool,
30
+ # t: TimeSpec,
31
+ # p: :pointer
32
+ # )
33
+ #
34
+ # ## Attribute reader, writer, accessor over struct fields
35
+ #
36
+ # # @!attribute [r] a
37
+ # # @return [Integer]
38
+ # ffi_attr_reader :a
39
+ #
40
+ # # @!attribute [w] b
41
+ # # @return [Integer]
42
+ # ffi_attr_writer :b
43
+ #
44
+ # # @!attribute [rw] one
45
+ # # @return [String]
46
+ # ffi_attr_accessor({ one: :s_one }) # => [:one, :one=] reads/writes field :s_one
47
+ #
48
+ # ## Boolean attributes!
49
+ #
50
+ # # @!attribute [rw] enabled?
51
+ # # @return [Boolean]
52
+ # ffi_attr_accessor(:enabled?) # => [:enabled, :enabled?, :enabled=]
53
+ #
54
+ # ## Simple block converters
55
+ #
56
+ # # @!attribute [rw] time
57
+ # # @return [Time]
58
+ # ffi_attr_reader(time: :t) do |timespec|
59
+ # Time.at(timespec.tv_sec, timespec.tv_nsec) # convert TimeSpec struct to ruby Time
60
+ # end
61
+ #
62
+ # ## Complex attribute methods
63
+ #
64
+ # # writer for :time needs additional attributes
65
+ # ffi_attr_writer_method(time: :t) do |sec, nsec=0|
66
+ # sec, nsec = [sec.sec, sec.nsec] if sec.is_a?(Time)
67
+ # self[:t][tv_sec] = sec
68
+ # self[:t][tv_nsec] = nsec
69
+ # time
70
+ # end
71
+ #
72
+ # # safe readers handling a NULL struct
73
+ # safe_attrs = %i[a b].to_h { |m| [:"#{m}_safe", m] } # =>{ a_safe: :a, b_safe: b }
74
+ # ffi_attr_reader_method(**safe_attrs) do |default: nil|
75
+ # next default if null?
76
+ #
77
+ # _attr, member = ffi_reader(__method__)
78
+ # self[member]
79
+ # end
80
+ #
81
+ # ## Standard accessors over for instance variables, still supports boolean, to_h, fill
82
+ #
83
+ # # @!attribute [rw] debug?
84
+ # # @return [Boolean]
85
+ # attr_accessor :debug?
86
+ #
87
+ # ## Private accessors
88
+ #
89
+ # private
90
+ #
91
+ # ffi_attr_accessor(pointer: :p)
92
+ # end
93
+ #
94
+ # # Fill from another MyStruct (or anything that quacks like a MyStruct with readers matching our writers)
95
+ # s = MyStruct.new.fill(other)
96
+ #
97
+ # # Fill from hash...
98
+ # s = MyStruct.new.fill(b:2, one: 'str', time: Time.now, enabled: true, debug: false) # => s
99
+ # s.values #=> (FFI::Struct method) [ 0, 2, 'str', true, <TimeSpec>, FFI::Pointer::NULL ]
100
+ #
101
+ # # Struct instance to hash
102
+ # s.to_h # => { a: 0, one: 'str', time: <Time>, enabled: true, debug: false }
103
+ #
104
+ # # Attribute methods
105
+ # s.a # => 0
106
+ # s.b = 3 # => 3
107
+ # s.enabled # => true
108
+ # s.enabled? # => true
109
+ # s.time= 0,50 # => Time<50 nanoseconds after epoch>
110
+ # s.time= Time.now # => Time<now>
111
+ # s.debug? # => false
112
+ # s.pointer # => NoMethodError, private method 'pointer' called for MyStruct
113
+ # s.send(:pointer=, some_pointer) # => some_pointer
114
+ # s.send(:pointer) # => some_pointer
115
+ #
116
+ # null_s = MyStruct.new(FFI::Pointer::NULL)
117
+ # null_s.b_safe(default: 10) # => 10
118
+ #
119
+ # @see ClassMethods
7
120
  module Accessors
8
- # DSL methods for defining struct member accessors
121
+ # Class methods for defining struct member accessors
9
122
  module ClassMethods
10
- # Define both a reader and a writer for members
11
- # @param [Array<Symbol>] attrs the attribute names
12
- # @param [String] format
13
- # A format string containing a single %s to convert attr symbol to struct member
14
- # @return [void]
15
- def ffi_attr_accessor(*attrs, format: '%s')
16
- ffi_attr_reader(*attrs, format: format)
17
- ffi_attr_writer(*attrs, format: format)
18
- end
19
-
20
- #
21
- # Define a struct attribute reader for members
22
- # @param [Array<Symbol>]
23
- # attrs the attribute names used as the reader method name
24
- #
25
- # a trailing '?' will be stripped from attribute names for primary reader method name, and cause an
26
- # boolean alias method to be created.
27
- # @param [Proc|String] format
28
- # A Proc, or format string containing a single %s, to convert each attribute name to the corresponding
29
- # struct member name
30
- # @param [Boolean] simple
31
- # Controls how writer methods are defined using block
32
- # @param [Proc] block
33
- # An optional block to convert the struct field value into something more useful
34
- #
35
- # If simple is true then block takes the struct field value, otherwise method is defined directly from the block
36
- # and should use __method__ to get the attribute name. and self.class.ffi_attr_readers[__method__] to get the
37
- # member name if these are not available from enclosed variables.
38
- # @return [void]
39
- def ffi_attr_reader(*attrs, format: '%s', simple: true, &block)
40
- attrs.each do |attr|
41
- bool, attr = attr[-1] == '?' ? [true, attr[..-2]] : [false, attr]
42
-
43
- member = (format.respond_to?(:call) ? format.call(attr) : format % attr).to_sym
44
- ffi_attr_readers[attr.to_sym] = member
45
- if !block
46
- define_method(attr) { self[member] }
47
- elsif simple
48
- define_method(attr) { block.call(self[member]) }
49
- else
50
- define_method(attr, &block)
51
- end
123
+ # Keep track of default visibility since define_method doesn't do this itself
124
+ # @visibility private
125
+ %i[public private protected].each do |visibility|
126
+ define_method(visibility) do |*args|
127
+ @default_visibility = visibility if args.empty?
128
+ super(*args)
129
+ end
130
+ end
131
+
132
+ # @visibility private
133
+ def default_visibility
134
+ @default_visibility ||= :public
135
+ end
136
+
137
+ # Standard instance variable based reader with support for boolean and integration with *to_h*, *inspect* etc..
138
+ #
139
+ # The *member* registered for each attribute will be its instance variable symbol (ie with a leading '@')
140
+ # @return [Array<Symbol]
141
+ def attr_reader(*args)
142
+ super(*args.map { |a| a[-1] == '?' ? a[0..-2] : a })
143
+ ffi_attr_reader_method(**args.to_h { |a| [a, :"@#{a[-1] == '?' ? a[0..-2] : a}"] })
144
+ end
52
145
 
53
- alias_method "#{attr}?", attr if bool
146
+ # Standard instance variable based writer with support for booleans and integration with *fill* etc..
147
+ #
148
+ # The *member* registered for each attribute will be its instance variable symbol (ie with a leading '@')
149
+ def attr_writer(*args)
150
+ super(*args.map { |a| a[-1] == '?' ? a[0..-2] : a })
151
+ ffi_attr_writer_method(**args.to_h { |a| [a, :"@#{a[-1] == '?' ? a[0..-2] : a}"] })
152
+ end
153
+
154
+ # Override instance variable based accessor to build our enhanced readers and writers
155
+ def attr_accessor(*args)
156
+ attr_reader(*args) + attr_writer(*args)
157
+ end
158
+
159
+ # @!group Accessor Definition
160
+
161
+ # Define both reader and writer
162
+ # @return [Array<Symbol] list of methods defined
163
+ def ffi_attr_accessor(*attrs, **attrs_map)
164
+ ffi_attr_reader(*attrs, **attrs_map) + ffi_attr_writer(*attrs, **attrs_map)
165
+ end
166
+
167
+ # Define reader methods for the given attributes
168
+ #
169
+ # @param [Array<Symbol>] attrs
170
+ # List of struct field members to treat as attributes
171
+ #
172
+ # a trailing '?' in an attribute name indicates a boolean reader.
173
+ # eg. :debug? will define the reader method :debug and an alias method :debug? => :debug,
174
+ #
175
+ # String values are converted to Symbol
176
+ #
177
+ # @param [Hash<Symbol,Symbol>] attrs_map
178
+ # Map of attribute name to struct field name - where field names don't fit natural ruby methods etc...
179
+ #
180
+ # A Hash value in *attrs* is also treated as an *attrs_map*. String keys/values are transformed to Symbol.
181
+ #
182
+ # @param [Proc] block
183
+ # An optional block taking a single argument (the struct field value) to convert into something more useful.
184
+ #
185
+ # This block is evaluated within the method using :instance_exec
186
+ # @return [Array<Symbol>] list of methods defined
187
+ def ffi_attr_reader(*attrs, **attrs_map, &block)
188
+ ffi_attr_reader_method(*attrs, **attrs_map) do
189
+ _attr, member = ffi_attr_reader_member(__method__)
190
+ val = self[member]
191
+ block ? instance_exec(val, &block) : val
54
192
  end
55
193
  end
56
194
 
57
- # Define a struct attribute writer
58
- # @param [Array<Symbol>] attrs the attribute names
59
- # trailing '?' will be stripped from attribute names
60
- # @param [String|Proc] format
61
- # A format string containing a single %s to convert attr symbol to struct member
62
- # @param [Boolean] simple
63
- # Controls how writer methods are defined using block
195
+ # Define reader methods directly from a block
196
+ #
197
+ # @param [Array<Symbol>] attrs see {ffi_attr_reader}
198
+ # @param [Hash<Symbol,Symbol>] attrs_map
64
199
  # @param [Proc] block
65
- # An optional block to set the input value into the struct field.
66
- #
67
- # If simple is true then the struct field is set to the result of calling block with the input value,
68
- # otherwise the method is defined directly from the block. Use __method__[0..-1] to get the attribute name
69
- # and self.class.ffi_attr_writers[__method__[0..-1]] to get the struct member name
70
- # @return [void]
71
- def ffi_attr_writer(*attrs, format: '%s', simple: true, &block)
72
- attrs.each do |attr|
73
- attr = attr[..-2] if attr[-1] == '?'
74
-
75
- member = (format % attr).to_sym
76
- ffi_attr_writers[attr.to_sym] = member
77
- if !block
78
- define_method("#{attr}=") { |val| self[member] = val }
79
- elsif simple
80
- define_method("#{attr}=") { |val| self[member] = block.call(val) }
81
- else
82
- define_method("#{attr}=", &block)
83
- end
200
+ # must allow zero arity, but can have additional optional arguments or keyword arguments.
201
+ #
202
+ # the block is evaluated using :instance_exec
203
+ #
204
+ # within block the attribute name is always the method name (`__method__`) and the associated struct field
205
+ # member name is from any attribute maps supplied; ie *attrs_map* or Hash value in *attrs*.
206
+ # They can be retrieved using {ffi_attr_reader_member}
207
+ #
208
+ # `attr, member = ffi_attr_reader_member(__method__)`
209
+ #
210
+ # if not supplied a reader will still be registered for each attribute and a boolean alias created if required
211
+ # @return [Array<Symbol>] list of methods defined
212
+ # @example Related struct members
213
+ # # uid/gid are only meaningful if corresponding set_ field is true
214
+ # layout(set_uid: :bool, uid: :uint, set_gid: :bool, gid: :uint)
215
+ #
216
+ # # @!attribute [r] uid
217
+ # # @return [Integer] the user id
218
+ # # @return [nil] if uid has not been explicitly set
219
+ #
220
+ # # @!attribute [r] gid
221
+ # # @return [Integer] the group id
222
+ # # @return [nil] if gid has not been explicitly set
223
+ #
224
+ # ffi_attr_reader_method(:uid, :gid) do
225
+ # attr, member = ffi_attr_reader_member(__method__)
226
+ # setter = :"set_#{attr}"
227
+ # self[setter] ? self[:attr] : nil
228
+ # end # => [:uid :gid]
229
+ def ffi_attr_reader_method(*attrs, **attrs_map, &block)
230
+ attr_methods = map_attributes(attrs, attrs_map).flat_map do |attr, member, bool|
231
+ ffi_attr_readers_map[attr] = member
232
+ define_method(attr, &block) if block
233
+ next attr unless bool
234
+
235
+ bool_alias = :"#{attr}?"
236
+ alias_method(bool_alias, attr)
237
+ [attr, bool_alias]
84
238
  end
239
+ send(default_visibility, *attr_methods)
240
+ attr_methods
85
241
  end
86
242
 
87
- # All defined readers
88
- # @return [Hash<Symbol,Symbol>] map of attr names to member names for which readers exist
89
- def ffi_attr_readers
90
- @ffi_attr_readers ||= {}
243
+ # Define struct attribute writers for the given attributes
244
+ # @param [Array<Symbol>] attrs see {ffi_attr_reader}
245
+ # @param [Hash<Symbol,Symbol>] attrs_map
246
+ # @param [Proc<Object>] block
247
+ # An optional block taking a single argument to convert input value into a value to be placed in the underlying
248
+ # struct field
249
+ #
250
+ # This block is evaluated within the method using :instance_exec
251
+ # @return [Array<Symbol>] list of methods defined
252
+ def ffi_attr_writer(*attrs, **attrs_map, &block)
253
+ ffi_attr_writer_method(*attrs, **attrs_map) do |val|
254
+ _attr, member = ffi_attr_writer_member(__method__)
255
+ self[member] = block ? instance_exec(val, &block) : val
256
+ end
91
257
  end
92
258
 
93
- # All defined writers
94
- # @return [Hash<Symbol,Symbol>] map of attr names to member names for which writers exist
95
- def ffi_attr_writers
96
- @ffi_attr_writers ||= {}
259
+ # Define writer methods directly from a block
260
+ # @param [Array<Symbol>] attrs see {ffi_attr_reader}
261
+ # @param [Hash<Symbol,Symbol>] attrs_map
262
+ # @param [Proc] block
263
+ # must allow arity = 1, but can have additional optional arguments or keyword arguments.
264
+ #
265
+ # the block is evaluated using :instance_exec
266
+ #
267
+ # within block the attribute name is always the method name stripped of its trailing '='
268
+ # (`:"#{__method__[0..-2]}"`) and the associated struct field member name is from any attribute maps
269
+ # supplied. ie *attrs_map* or Hash value in *attrs*. They can be retrieved using {ffi_attr_writer_member}
270
+ #
271
+ # `attr, member = ffi_attr_writer_member(__method__)`
272
+ #
273
+ # if not supplied a writer method is still registered for each attribute name
274
+ # @return [Array<Symbol>] list of methods defined
275
+ def ffi_attr_writer_method(*attrs, **attrs_map, &block)
276
+ writer_methods = map_attributes(attrs, attrs_map) do |attr, member, _bool|
277
+ ffi_attr_writers_map[attr] = member
278
+ block ? define_method("#{attr}=", &block) : attr
279
+ end
280
+ send(default_visibility, *writer_methods)
281
+ writer_methods
97
282
  end
98
283
 
99
284
  # Define individual flag accessors over a bitmask field
100
- def ffi_bitflag_accessor(attr, *flags)
101
- ffi_bitflag_reader(attr, *flags)
102
- ffi_bitflag_writer(attr, *flags)
285
+ # @return [Array<Symbol>] list of methods defined
286
+ def ffi_bitflag_accessor(member, *flags)
287
+ ffi_bitflag_reader(member, *flags)
288
+ ffi_bitflag_writer(member, *flags)
103
289
  end
104
290
 
105
291
  # Define individual flag readers over a bitmask field
106
- # @param [Symbol] attr the bitmask member
107
- # @param [Array<Symbol>] flags list of flags
108
- # @return [void]
109
- def ffi_bitflag_reader(attr, *flags)
110
- flags.each do |f|
111
- ffi_attr_reader(:"#{f}?", simple: false) { self[attr].include?(f) }
292
+ # @param [Symbol] member the bitmask member
293
+ # @param [Array<Symbol>] flags list of flags to define methods for. Each flag also gets a :flag? boolean alias
294
+ # @return [Array<Symbol>] list of methods defined
295
+ def ffi_bitflag_reader(member, *flags)
296
+ bool_attrs = flags.to_h { |f| [:"#{f}?", member] }
297
+ ffi_attr_reader_method(**bool_attrs) do
298
+ flag_attr, member = ffi_attr_reader_member(__method__)
299
+ self[member].include?(flag_attr)
112
300
  end
113
301
  end
114
302
 
115
303
  # Define individual flag writers over a bitmask field
116
- # @param [Symbol] attr the bitmask member
117
- # @param [Array<Symbol>] flags list of flags
118
- # @return [void]
119
- def ffi_bitflag_writer(attr, *flags)
120
- flags.each do |f|
121
- ffi_attr_writer(f, simple: false) do |v|
122
- v ? self[attr] += [f] : self[attr] -= [f]
123
- v
304
+ # @param [Symbol] member the bitmask member
305
+ # @param [Array<Symbol>] flags list of flag attributes
306
+ # @return [Array<Symbol>] list of methods defined
307
+ def ffi_bitflag_writer(member, *flags)
308
+ writers = flags.to_h { |f| [f, member] }
309
+ ffi_attr_writer_method(**writers) do |v|
310
+ flag_attr, member = ffi_attr_writer_member(__method__)
311
+ v ? self[member] += [flag_attr] : self[member] -= flag
312
+ v
313
+ end
314
+ end
315
+
316
+ # @!endgroup
317
+ # @!group Accessor Information
318
+
319
+ # @return [Array<Symbol>]
320
+ # list of public attr accessor reader methods
321
+ def ffi_public_attr_readers
322
+ ffi_attr_readers & public_instance_methods
323
+ end
324
+
325
+ # @return [Array<Symbol>]
326
+ # list of accessor reader methods defined. (excludes boolean aliases)
327
+ def ffi_attr_readers
328
+ ffi_attr_readers_map.keys
329
+ end
330
+
331
+ # @return [Array<Symbol>]
332
+ # list of accessor writer methods (ie ending in '=')
333
+ def ffi_attr_writers
334
+ ffi_attr_writers_map.keys.map { |a| :"#{a}=" }
335
+ end
336
+
337
+ # @return [Array<Symbol>]
338
+ # list of public accessor writer methods (ie ending in '=')
339
+ def ffi_public_attr_writers
340
+ ffi_attr_writers & public_instance_methods
341
+ end
342
+
343
+ # @!endgroup
344
+
345
+ # @!visibility private
346
+ def ffi_attr_readers_map
347
+ @ffi_attr_readers_map ||= {}
348
+ end
349
+
350
+ # @!visibility private
351
+ def ffi_attr_writers_map
352
+ @ffi_attr_writers_map ||= {}
353
+ end
354
+
355
+ private
356
+
357
+ def map_attributes(attrs, attrs_map)
358
+ return enum_for(__method__, attrs, attrs_map) unless block_given?
359
+
360
+ attrs << attrs_map unless attrs_map.empty?
361
+
362
+ attrs.flat_map do |attr_entry|
363
+ case attr_entry
364
+ when Symbol, String
365
+ bool, attr = bool_attr(attr_entry)
366
+
367
+ yield attr, attr, bool
368
+ when Hash
369
+ attr_entry.flat_map do |attr, member|
370
+ bool, attr = bool_attr(attr)
371
+ yield attr, member.to_sym, bool
372
+ end
373
+ else
374
+ raise ArgumentError
124
375
  end
125
376
  end
126
377
  end
378
+
379
+ def bool_attr(attr)
380
+ attr[-1] == '?' ? [true, attr[..-2].to_sym] : [false, attr.to_sym]
381
+ end
127
382
  end
128
383
 
384
+ # @!parse extend ClassMethods
385
+ # @!visibility private
129
386
  def self.included(mod)
130
387
  mod.extend(ClassMethods)
131
388
  end
132
389
 
133
- # Fill the native struct from another object or list of properties
390
+ # Fill struct from another object or list of properties
134
391
  # @param [Object] from
135
- # for each attribute we call self.attr=(from.attr)
392
+ # if from is a Hash then its is merged with args, otherwise look for corresponding readers on from, for our
393
+ # public writer attributes
136
394
  # @param [Hash<Symbol,Object>] args
137
395
  # for each entry <attr,val> we call self.attr=(val)
396
+ # @raise [ArgumentError] if args contains properties that do not have public writers
138
397
  # @return [self]
139
398
  def fill(from = nil, **args)
399
+ ffi_attr_fill(from, writers: self.class.ffi_public_attr_writers, **args)
400
+ end
401
+
402
+ # Inspect attributes
403
+ # @param [Array<Symbol>] readers list of attribute names to include in inspect, defaults to all readers
404
+ # @return [String]
405
+ def inspect(readers: self.class.ffi_public_attr_readers)
406
+ "#{self.class.name} {#{readers.map { |r| "#{r}: #{send(r)} " }.join(',')}"
407
+ end
408
+
409
+ # Convert struct to hash
410
+ # @param [Array<Symbol>] readers list of attribute names to include in hash, defaults to all public readers.
411
+ # @return [Hash<Symbol,Object>] map of attribute name to value
412
+ def to_h(readers: self.class.ffi_public_attr_readers)
413
+ readers.to_h { |r| [r, send(r)] }
414
+ end
415
+
416
+ private
417
+
418
+ # @!visibility public
419
+ # *(private)* Fill struct from another object or list of properties
420
+ # @param [Object] from
421
+ # @param [Hash<Symbol>] args
422
+ # @param [Array<Symbol>] writers list of allowed writer methods
423
+ # @raise [ArgumentError] if args contains properties not included in writers list
424
+ # @note This *private* method allows an including classes' instance method to
425
+ # fill attributes through any writer method (vs #{fill} which only sets attributes with public writers)
426
+ def ffi_attr_fill(from, writers: self.class.ffi_attr_writers, **args)
140
427
  if from.is_a?(Hash)
141
428
  args.merge!(from)
142
429
  else
143
- self.class.ffi_attr_writers.each_key { |v| send("#{v}=", from.send(v)) if from.respond_to?(v) }
430
+ writers.each do |w|
431
+ r = w[0..-2] # strip trailing =
432
+ send(w, from.public_send(r)) if from.respond_to?(r)
433
+ end
144
434
  end
145
- args.each_pair { |k, v| send("#{k}=", v) }
435
+ args.transform_keys! { |k| :"#{k}=" }
436
+
437
+ args.each_pair { |k, v| send(k, v) }
146
438
  self
147
439
  end
148
440
 
149
- def inspect
150
- "#{self.class.name} {#{self.class.ffi_attr_readers.keys.map { |r| "#{r}: #{send(r)} " }.join(',')}"
441
+ def ffi_attr(method)
442
+ %w[? =].include?(method[-1]) ? :"#{method[0..-2]}" : method
151
443
  end
152
444
 
153
- # Convert struct to hash
154
- # @return [Hash<Symbol,Object>] map of reader attribute name to value
155
- def to_h
156
- self.class.ffi_attr_readers.keys.each_with_object({}) { |r, h| h[r] = send(r) }
445
+ # @!group Private Accessor helpers
446
+
447
+ # @!visibility public
448
+ # *(private)* Takes `__method__` and returns the corresponding attr and struct member names
449
+ # @param [Symbol] attr_method typically `__method__` (or `__callee__`)
450
+ # @param [Symbol] default default if method is not a reader method
451
+ # @return [Array<Symbol,Symbol>] attr,member
452
+ # @raise [KeyError] if method has not been defined as a reader and no default is supplied
453
+ def ffi_attr_reader_member(attr_method, *default)
454
+ attr = ffi_attr(attr_method)
455
+ [attr, self.class.ffi_attr_readers_map.fetch(attr, *default)]
157
456
  end
457
+
458
+ # @!visibility public
459
+ # *(private)* Takes `__method__` and returns the corresponding attr and struct member names
460
+ # @param [Symbol] attr_method typically `__method__` (or `__callee__`)
461
+ # @param [Symbol|nil] default default if method is not a writer method
462
+ # @return [Array<Symbol,Symbol>] attr,member
463
+ # @raise [KeyError] if method has not been defined as a writer and no default is supplied
464
+ def ffi_attr_writer_member(attr_method, *default)
465
+ attr = ffi_attr(attr_method)
466
+ [attr, self.class.ffi_attr_writers_map.fetch(attr, *default)]
467
+ end
468
+
469
+ # @!endgroup
158
470
  end
159
471
  end
data/lib/ffi/devt.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ffi'
4
-
5
4
  module FFI
6
5
  # Calculate major/minor device numbers for use with mknod etc..
7
6
  # @see makedev(3)
@@ -27,10 +26,13 @@ module FFI
27
26
  # @return [Integer] the minor component of dev
28
27
  attach_function :minor, :"#{prefix}minor", [:int], :int
29
28
  rescue FFI::NotFoundError
30
- case Platform::NAME
31
- when 'x86_64-darwin'
32
- # From https://github.com/golang/go/issues/8106 these functions are not defined on Darwin.
33
- class << self
29
+
30
+ class << self
31
+ # rubocop:disable Naming/MethodParameterName
32
+ case RUBY_PLATFORM
33
+ when 'x86_64-darwin'
34
+ # From https://github.com/golang/go/issues/8106 these functions are not defined on Darwin.
35
+
34
36
  # define major(x) ((int32_t)(((u_int32_t)(x) >> 24) & 0xff))
35
37
  def major(dev)
36
38
  (dev >> 24) & 0xff
@@ -45,9 +47,33 @@ module FFI
45
47
  def makedev(major, minor)
46
48
  (major << 24) | minor
47
49
  end
50
+
51
+ when 'x86_64-linux-musl' # eg alpine linux
52
+ # #define major(x) \
53
+ # ((unsigned)( (((x)>>31>>1) & 0xfffff000) | (((x)>>8) & 0x00000fff) ))
54
+ def major(x)
55
+ ((x >> 31 >> 1) & 0xfffff000) | ((x >> 8) & 0x00000fff)
56
+ end
57
+
58
+ # #define minor(x) \
59
+ # ((unsigned)( (((x)>>12) & 0xffffff00) | ((x) & 0x000000ff) ))
60
+ #
61
+ def minor(x)
62
+ ((x >> 12) & 0xffffff00) | (x & 0x000000ff)
63
+ end
64
+
65
+ # #define makedev(x,y) ( \
66
+ # (((x)&0xfffff000ULL) << 32) | \
67
+ # (((x)&0x00000fffULL) << 8) | \
68
+ # (((y)&0xffffff00ULL) << 12) | \
69
+ # (((y)&0x000000ffULL)) )
70
+ def makedev(x, y)
71
+ ((x & 0xfffff000) << 32) | ((x & 0x00000fff) << 8) | ((y & 0xffffff00) << 12) | (y & 0x000000ff)
72
+ end
73
+ else
74
+ raise
48
75
  end
49
- else
50
- raise
76
+ # rubocop:enable Naming/MethodParameterName
51
77
  end
52
78
  end
53
79
  end