ffi-libfuse 0.3.4 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/README.md +1 -1
  4. data/lib/ffi/accessors.rb +419 -93
  5. data/lib/ffi/boolean_int.rb +1 -1
  6. data/lib/ffi/devt.rb +36 -10
  7. data/lib/ffi/flock.rb +31 -27
  8. data/lib/ffi/libfuse/adapter/context.rb +1 -1
  9. data/lib/ffi/libfuse/adapter/debug.rb +54 -16
  10. data/lib/ffi/libfuse/adapter/fuse2_compat.rb +43 -26
  11. data/lib/ffi/libfuse/adapter/fuse3_support.rb +7 -8
  12. data/lib/ffi/libfuse/adapter/interrupt.rb +1 -1
  13. data/lib/ffi/libfuse/adapter/pathname.rb +1 -1
  14. data/lib/ffi/libfuse/adapter/ruby.rb +211 -160
  15. data/lib/ffi/libfuse/adapter/safe.rb +70 -22
  16. data/lib/ffi/libfuse/callbacks.rb +2 -1
  17. data/lib/ffi/libfuse/filesystem/accounting.rb +1 -1
  18. data/lib/ffi/libfuse/filesystem/mapped_files.rb +33 -7
  19. data/lib/ffi/libfuse/filesystem/pass_through_dir.rb +0 -1
  20. data/lib/ffi/libfuse/filesystem/virtual_dir.rb +294 -127
  21. data/lib/ffi/libfuse/filesystem/virtual_file.rb +85 -79
  22. data/lib/ffi/libfuse/filesystem/virtual_fs.rb +34 -15
  23. data/lib/ffi/libfuse/filesystem/virtual_link.rb +60 -0
  24. data/lib/ffi/libfuse/filesystem/virtual_node.rb +104 -87
  25. data/lib/ffi/libfuse/filesystem.rb +1 -1
  26. data/lib/ffi/libfuse/fuse2.rb +3 -2
  27. data/lib/ffi/libfuse/fuse3.rb +6 -6
  28. data/lib/ffi/libfuse/fuse_args.rb +14 -21
  29. data/lib/ffi/libfuse/fuse_buf.rb +112 -0
  30. data/lib/ffi/libfuse/fuse_buf_vec.rb +228 -0
  31. data/lib/ffi/libfuse/fuse_cmdline_opts.rb +19 -16
  32. data/lib/ffi/libfuse/fuse_common.rb +10 -4
  33. data/lib/ffi/libfuse/fuse_config.rb +35 -23
  34. data/lib/ffi/libfuse/fuse_conn_info.rb +1 -1
  35. data/lib/ffi/libfuse/fuse_context.rb +2 -1
  36. data/lib/ffi/libfuse/fuse_loop_config.rb +68 -20
  37. data/lib/ffi/libfuse/fuse_operations.rb +86 -41
  38. data/lib/ffi/libfuse/gem_helper.rb +2 -9
  39. data/lib/ffi/libfuse/io.rb +56 -0
  40. data/lib/ffi/libfuse/main.rb +33 -26
  41. data/lib/ffi/libfuse/test_helper.rb +67 -61
  42. data/lib/ffi/libfuse/version.rb +1 -1
  43. data/lib/ffi/libfuse.rb +1 -1
  44. data/lib/ffi/stat/native.rb +4 -4
  45. data/lib/ffi/stat.rb +35 -12
  46. data/lib/ffi/stat_vfs.rb +1 -2
  47. data/lib/ffi/struct_array.rb +2 -1
  48. data/lib/ffi/struct_wrapper.rb +6 -4
  49. data/sample/hello_fs.rb +1 -1
  50. metadata +6 -3
  51. data/lib/ffi/libfuse/fuse_buffer.rb +0 -257
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d59c87631dfe38e9dc49b36f829acdd704165162deff3bad6479f3394f348657
4
- data.tar.gz: 01ab562dd537f1f11f7e3c454e49c5df3ef1ce5cfd6cb8b1aa6b5e6a7f89a4c0
3
+ metadata.gz: 791ea233a078177b2dd6a9b1cf9a75a64ed05d243de71ebf52a8028b11795653
4
+ data.tar.gz: 45d9d235948d5c4cdd09cd8ae168914e82caad31df8a2ce1727d8ed6bb2ff9ad
5
5
  SHA512:
6
- metadata.gz: ae437c153a84155c86a9e2d71d4dd1c6234879fae7553676efacd311e26cae3cf653dd4439b2ec4ab584cd01698636915fc6c9cefd14a2f045c2b19e4077e60b
7
- data.tar.gz: af5fab46502ff889710facc930ee59d7bb27e129da3c70f0d5089c1fa85e29bbfe3ec91b01f75db907da5f8be220755edec6b4b3976421ce0454cc43830387da
6
+ metadata.gz: '00301169c26144390a94545130326eb20c4661af57c2fd612f2ba477ba957b1f76e316fafb7f6da41431a97bab3a6e05523dd9d40a6f1f276d956e2f04aecf5a'
7
+ data.tar.gz: a3ea2914410e7f2c4e284df9a0ed500a0c70adc59504bfd93dd398b1396be1467f07071b20d2b7bfd68be465e6c1151889975d747b33969d9a53bff9e9561f47
data/CHANGELOG.md CHANGED
@@ -1,5 +1,34 @@
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
+
10
+ ## [0.4.0](https://github.com/lwoggardner/ffi-libfuse/compare/v0.3.4...v0.4.0) (2024-01-21)
11
+
12
+
13
+ ### ⚠ BREAKING CHANGES
14
+
15
+ * **filesystem:** Fuse callbacks :init and :destroy are no longer passed on to sub-filesystems.
16
+ * **adapters:** Adapter::Debug now includes Adapter::Safe.
17
+ * Option parsing errors via raise exception rather than return false/nil
18
+
19
+ ### Features
20
+
21
+ * **adapters:** Adapter::Debug now includes Adapter::Safe. ([a595304](https://github.com/lwoggardner/ffi-libfuse/commit/a59530427d7eb85961a724969eaa6ec099c5e4f6))
22
+ * **filesystem:** Support :rename operation in virtual filesystems ([a595304](https://github.com/lwoggardner/ffi-libfuse/commit/a59530427d7eb85961a724969eaa6ec099c5e4f6))
23
+ * **filesystem:** Support symlinks and hardlinks in virtual filesystems (VirtualDir/MemoryFS) ([a595304](https://github.com/lwoggardner/ffi-libfuse/commit/a59530427d7eb85961a724969eaa6ec099c5e4f6))
24
+ * Option parsing errors via raise exception rather than return false/nil ([a595304](https://github.com/lwoggardner/ffi-libfuse/commit/a59530427d7eb85961a724969eaa6ec099c5e4f6))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * **fuse2compat:** Enhanced Fuse2 compatibility in Fuse2Compat module ([a595304](https://github.com/lwoggardner/ffi-libfuse/commit/a59530427d7eb85961a724969eaa6ec099c5e4f6))
30
+ * symlinks and hard links ([a595304](https://github.com/lwoggardner/ffi-libfuse/commit/a59530427d7eb85961a724969eaa6ec099c5e4f6))
31
+
3
32
  ## [0.3.4](https://github.com/lwoggardner/ffi-libfuse/compare/v0.3.3...v0.3.4) (2023-01-08)
4
33
 
5
34
 
data/README.md CHANGED
@@ -84,7 +84,7 @@ class HelloFS
84
84
  end
85
85
 
86
86
  # Start the file system
87
- FFI::Libfuse.fuse_main(operations: HelloFS.new) if __FILE__ == $0
87
+ exit(FFI::Libfuse.fuse_main(operations: HelloFS.new)) if __FILE__ == $0
88
88
 
89
89
  ```
90
90
  <!-- SAMPLE END: sample/hello_fs.rb -->
data/lib/ffi/accessors.rb CHANGED
@@ -3,143 +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>] attrs the attribute names
23
- # @param [Proc|String] format
24
- # A Proc, or format string containing a single %s, to convert attr to struct member name
25
- # @param [Boolean] simple
26
- # Controls how writer methods are defined using block
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
145
+
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
+ #
27
182
  # @param [Proc] block
28
- # An optional block to the struct field(s) into something more useful
29
- #
30
- # If simple is true then block takes the struct field value, otherwise method is defined directly from the block
31
- # and should use __method__ to get the attr name, and self.class.ffi_attr_readers[__method__] to get the member
32
- # name
33
- # @return [void]
34
- def ffi_attr_reader(*attrs, format: '%s', simple: true, &block)
35
- attrs.each do |attr|
36
- member = (format.respond_to?(:call) ? format.call(attr) : format % attr).to_sym
37
- ffi_attr_readers[attr] = member
38
- if !block
39
- define_method(attr) { self[member] }
40
- elsif simple
41
- define_method(attr) { block.call(self[member]) }
42
- else
43
- define_method(attr, &block)
44
- end
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
45
192
  end
46
193
  end
47
194
 
48
- # Define a struct attribute writer
49
- # @param [Array<Symbol>] attrs the attribute names
50
- # @param [String|Proc] format
51
- # A format string containing a single %s to convert attr symbol to struct member
52
- # @param [Boolean] simple
53
- # 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
54
199
  # @param [Proc] block
55
- # An optional block to set the input value into the struct field.
56
- #
57
- # If simple is true then the struct field is set to the result of calling block with the input value,
58
- # otherwise the method is defined directly from the block. Use __method__[0..-1] to get the attribute name
59
- # and self.class.ffi_attr_writers[__method__[0..-1]] to get the struct member name
60
- # @return [void]
61
- def ffi_attr_writer(*attrs, format: '%s', simple: true, &block)
62
- attrs.each do |attr|
63
- member = (format % attr).to_sym
64
- ffi_attr_writers[attr.to_sym] = member
65
- if !block
66
- define_method("#{attr}=") { |val| self[member] = val }
67
- elsif simple
68
- define_method("#{attr}=") { |val| self[member] = block.call(val) }
69
- else
70
- define_method("#{attr}=", &block)
71
- 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]
72
238
  end
239
+ send(default_visibility, *attr_methods)
240
+ attr_methods
73
241
  end
74
242
 
75
- # All defined readers
76
- # @return [Hash<Symbol,Symbol>] map of attr names to member names for which readers exist
77
- def ffi_attr_readers
78
- @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
79
257
  end
80
258
 
81
- # All defined writers
82
- # @return [Hash<Symbol,Symbol>] map of attr names to member names for which writers exist
83
- def ffi_attr_writers
84
- @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
85
282
  end
86
283
 
87
284
  # Define individual flag accessors over a bitmask field
88
- def ffi_bitflag_accessor(attr, *flags)
89
- ffi_bitflag_reader(attr, *flags)
90
- 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)
91
289
  end
92
290
 
93
291
  # Define individual flag readers over a bitmask field
94
- # @param [Symbol] attr the bitmask member
95
- # @param [Array<Symbol>] flags list of flags
96
- # @return [void]
97
- def ffi_bitflag_reader(attr, *flags)
98
- flags.each { |f| 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)
300
+ end
99
301
  end
100
302
 
101
303
  # Define individual flag writers over a bitmask field
102
- # @param [Symbol] attr the bitmask member
103
- # @param [Array<Symbol>] flags list of flags
104
- # @return [void]
105
- def ffi_bitflag_writer(attr, *flags)
106
- flags.each do |f|
107
- ffi_attr_writer(f, simple: false) do |v|
108
- v ? self[attr] += [f] : self[attr] -= [f]
109
- 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
110
375
  end
111
376
  end
112
377
  end
378
+
379
+ def bool_attr(attr)
380
+ attr[-1] == '?' ? [true, attr[..-2].to_sym] : [false, attr.to_sym]
381
+ end
113
382
  end
114
383
 
384
+ # @!parse extend ClassMethods
385
+ # @!visibility private
115
386
  def self.included(mod)
116
387
  mod.extend(ClassMethods)
117
388
  end
118
389
 
119
- # Fill the native struct from another object or list of properties
390
+ # Fill struct from another object or list of properties
120
391
  # @param [Object] from
121
- # 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
122
394
  # @param [Hash<Symbol,Object>] args
123
395
  # for each entry <attr,val> we call self.attr=(val)
396
+ # @raise [ArgumentError] if args contains properties that do not have public writers
124
397
  # @return [self]
125
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)
126
427
  if from.is_a?(Hash)
127
428
  args.merge!(from)
128
429
  else
129
- 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
130
434
  end
131
- args.each_pair { |k, v| send("#{k}=", v) }
435
+ args.transform_keys! { |k| :"#{k}=" }
436
+
437
+ args.each_pair { |k, v| send(k, v) }
132
438
  self
133
439
  end
134
440
 
135
- def inspect
136
- "#{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
137
443
  end
138
444
 
139
- # Convert struct to hash
140
- # @return [Hash<Symbol,Object>] map of reader attribute name to value
141
- def to_h
142
- 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)]
143
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
144
470
  end
145
471
  end
@@ -21,7 +21,7 @@ module FFI
21
21
  end
22
22
 
23
23
  %i[char short int long int8 int16 int32 int64].each do |t|
24
- FFI.typedef(BooleanInt.new(t), "bool_#{t}".to_sym)
24
+ FFI.typedef(BooleanInt.new(t), :"bool_#{t}")
25
25
  end
26
26
  end
27
27
  end