# frozen_string_literal: true require 'ffi' module FFI # Syntax sugar for **FFI::Struct** # # Modules that include {Accessors} are automatically extended by {ClassMethods} which provides for defining reader and # writer methods over struct field members. # # Although designed around needs of **FFI::Struct**, eg the ability to map natural ruby names to struct field names, # this module can be used over anything that stores attributes in a Hash like structure. # It provides equivalent method definitions to *Module#attr_(reader|writer|accessor)* except using the index methods # *:[]*, and *:[]=* instead of managing instance variables. # # Additionally it supports boolean attributes with '?' aliases for reader methods, and keeps track of attribute # definitions to support {#fill},{#to_h} etc. # # Standard instance variable based attributes defined through *#attr_(reader|writer|accessor)* # also get these features. # @example # class MyStruct < FFI::Struct # include FFI::Accessors # # layout( # a: :int, # b: :int, # s_one: :string, # enabled: :bool, # t: TimeSpec, # p: :pointer # ) # # ## Attribute reader, writer, accessor over struct fields # # # @!attribute [r] a # # @return [Integer] # ffi_attr_reader :a # # # @!attribute [w] b # # @return [Integer] # ffi_attr_writer :b # # # @!attribute [rw] one # # @return [String] # ffi_attr_accessor({ one: :s_one }) # => [:one, :one=] reads/writes field :s_one # # ## Boolean attributes! # # # @!attribute [rw] enabled? # # @return [Boolean] # ffi_attr_accessor(:enabled?) # => [:enabled, :enabled?, :enabled=] # # ## Simple block converters # # # @!attribute [rw] time # # @return [Time] # ffi_attr_reader(time: :t) do |timespec| # Time.at(timespec.tv_sec, timespec.tv_nsec) # convert TimeSpec struct to ruby Time # end # # ## Complex attribute methods # # # writer for :time needs additional attributes # ffi_attr_writer_method(time: :t) do |sec, nsec=0| # sec, nsec = [sec.sec, sec.nsec] if sec.is_a?(Time) # self[:t][tv_sec] = sec # self[:t][tv_nsec] = nsec # time # end # # # safe readers handling a NULL struct # safe_attrs = %i[a b].to_h { |m| [:"#{m}_safe", m] } # =>{ a_safe: :a, b_safe: b } # ffi_attr_reader_method(**safe_attrs) do |default: nil| # next default if null? # # _attr, member = ffi_reader(__method__) # self[member] # end # # ## Standard accessors over for instance variables, still supports boolean, to_h, fill # # # @!attribute [rw] debug? # # @return [Boolean] # attr_accessor :debug? # # ## Private accessors # # private # # ffi_attr_accessor(pointer: :p) # end # # # Fill from another MyStruct (or anything that quacks like a MyStruct with readers matching our writers) # s = MyStruct.new.fill(other) # # # Fill from hash... # s = MyStruct.new.fill(b:2, one: 'str', time: Time.now, enabled: true, debug: false) # => s # s.values #=> (FFI::Struct method) [ 0, 2, 'str', true, , FFI::Pointer::NULL ] # # # Struct instance to hash # s.to_h # => { a: 0, one: 'str', time: