ffi-libfuse 0.0.1.pre
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 +7 -0
- data/.yardopts +1 -0
- data/README.md +100 -0
- data/lib/ffi/accessors.rb +145 -0
- data/lib/ffi/devt.rb +30 -0
- data/lib/ffi/flock.rb +47 -0
- data/lib/ffi/gnu_extensions.rb +115 -0
- data/lib/ffi/libfuse/ackbar.rb +112 -0
- data/lib/ffi/libfuse/adapter/context.rb +37 -0
- data/lib/ffi/libfuse/adapter/debug.rb +89 -0
- data/lib/ffi/libfuse/adapter/fuse2_compat.rb +91 -0
- data/lib/ffi/libfuse/adapter/fuse3_support.rb +87 -0
- data/lib/ffi/libfuse/adapter/interrupt.rb +37 -0
- data/lib/ffi/libfuse/adapter/pathname.rb +23 -0
- data/lib/ffi/libfuse/adapter/ruby.rb +334 -0
- data/lib/ffi/libfuse/adapter/safe.rb +58 -0
- data/lib/ffi/libfuse/adapter/thread_local_context.rb +36 -0
- data/lib/ffi/libfuse/adapter.rb +79 -0
- data/lib/ffi/libfuse/callbacks.rb +61 -0
- data/lib/ffi/libfuse/fuse2.rb +159 -0
- data/lib/ffi/libfuse/fuse3.rb +162 -0
- data/lib/ffi/libfuse/fuse_args.rb +166 -0
- data/lib/ffi/libfuse/fuse_buffer.rb +155 -0
- data/lib/ffi/libfuse/fuse_callbacks.rb +48 -0
- data/lib/ffi/libfuse/fuse_cmdline_opts.rb +44 -0
- data/lib/ffi/libfuse/fuse_common.rb +249 -0
- data/lib/ffi/libfuse/fuse_config.rb +205 -0
- data/lib/ffi/libfuse/fuse_conn_info.rb +211 -0
- data/lib/ffi/libfuse/fuse_context.rb +79 -0
- data/lib/ffi/libfuse/fuse_file_info.rb +100 -0
- data/lib/ffi/libfuse/fuse_loop_config.rb +43 -0
- data/lib/ffi/libfuse/fuse_operations.rb +870 -0
- data/lib/ffi/libfuse/fuse_opt.rb +54 -0
- data/lib/ffi/libfuse/fuse_poll_handle.rb +59 -0
- data/lib/ffi/libfuse/fuse_version.rb +43 -0
- data/lib/ffi/libfuse/job_pool.rb +53 -0
- data/lib/ffi/libfuse/main.rb +200 -0
- data/lib/ffi/libfuse/test/operations.rb +56 -0
- data/lib/ffi/libfuse/test.rb +3 -0
- data/lib/ffi/libfuse/thread_pool.rb +147 -0
- data/lib/ffi/libfuse/version.rb +8 -0
- data/lib/ffi/libfuse.rb +24 -0
- data/lib/ffi/ruby_object.rb +95 -0
- data/lib/ffi/stat/constants.rb +29 -0
- data/lib/ffi/stat/native.rb +50 -0
- data/lib/ffi/stat/time_spec.rb +137 -0
- data/lib/ffi/stat.rb +96 -0
- data/lib/ffi/stat_vfs.rb +81 -0
- data/lib/ffi/struct_array.rb +39 -0
- data/lib/ffi/struct_wrapper.rb +100 -0
- data/sample/memory_fs.rb +189 -0
- data/sample/no_fs.rb +69 -0
- metadata +165 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_version'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
# @!visibility private
|
8
|
+
#
|
9
|
+
# Option description
|
10
|
+
#
|
11
|
+
# @see Args#parse!
|
12
|
+
class FuseOpt < FFI::Struct
|
13
|
+
layout template: :pointer, # Matching template and optional parameter formatting
|
14
|
+
offset: :ulong, # Unused in FFI::Libfuse (harder to prepare structs and offsets than just just call block)
|
15
|
+
value: :int # Value to set the variable to, or to be passed as 'key' to the processing function.
|
16
|
+
|
17
|
+
# @!method initialize(address=nil)
|
18
|
+
|
19
|
+
def fill(template, value)
|
20
|
+
str_ptr = FFI::MemoryPointer.from_string(template)
|
21
|
+
self[:template] = str_ptr
|
22
|
+
self[:offset] = (2**(8 * FFI::Type::INT.size)) - 1 # -(1U) in a LONG!!
|
23
|
+
self[:value] = value.to_i
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def null
|
28
|
+
# NULL opt to terminate the list
|
29
|
+
self[:template] = FFI::Pointer::NULL
|
30
|
+
self[:offset] = 0
|
31
|
+
self[:value] = 0
|
32
|
+
end
|
33
|
+
|
34
|
+
# @!visibility private
|
35
|
+
# DataConverter for Hash<String,Integer> to Opt[] required by fuse_parse_opt
|
36
|
+
module OptList
|
37
|
+
extend FFI::DataConverter
|
38
|
+
native_type FFI::Type::POINTER
|
39
|
+
|
40
|
+
class << self
|
41
|
+
def to_native(opts, _ctx)
|
42
|
+
raise ArgumentError, "Opts #{opts} must be a Hash" unless opts.respond_to?(:each_pair)
|
43
|
+
|
44
|
+
native = FFI::MemoryPointer.new(:char, FuseOpt.size * (opts.size + 1), false)
|
45
|
+
opts.map.with_index { |(template, key), i| FuseOpt.new(native + (i * FuseOpt.size)).fill(template, key) }
|
46
|
+
FuseOpt.new(native + (opts.size * FuseOpt.size)).null
|
47
|
+
|
48
|
+
native
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FFI
|
4
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
5
|
+
module Libfuse
|
6
|
+
attach_function :fuse_notify_poll, [:pointer], :int
|
7
|
+
attach_function :fuse_pollhandle_destroy, [:pointer], :void
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# @!visibility private
|
11
|
+
|
12
|
+
# @!method fuse_notify_poll(ph)
|
13
|
+
# @!method fuse_pollhandle_destroy(ph)
|
14
|
+
end
|
15
|
+
|
16
|
+
# struct fuse_poll_handle
|
17
|
+
# @todo build a filsystem that uses poll and implement an appropriate ruby interface
|
18
|
+
# @see https://libfuse.github.io/doxygen/poll_8c.html
|
19
|
+
class FusePollHandle
|
20
|
+
extend FFI::DataConverter
|
21
|
+
native_type :pointer
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# @!visibility private
|
25
|
+
def from_native(ptr, _ctx)
|
26
|
+
# TODO: we may need a weakref cache on ptr.address so that we don't create different ruby ph for the same
|
27
|
+
# address, and call destroy on the first one that goes out of scope.
|
28
|
+
new(ptr)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @!visibility private
|
32
|
+
def to_native(value, _ctx)
|
33
|
+
value.ph
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!visibility private
|
37
|
+
def finalizer(pollhandle)
|
38
|
+
proc { Libfuse.fuse_pollhandle_destroy(pollhandle) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!visibility private
|
43
|
+
attr_reader :ph
|
44
|
+
|
45
|
+
# @!visibility private
|
46
|
+
def initialize(pollhandle)
|
47
|
+
@ph = pollhandle
|
48
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(pollhandle))
|
49
|
+
end
|
50
|
+
|
51
|
+
# @see FuseOperations#poll
|
52
|
+
def notify_poll
|
53
|
+
Libfuse.fuse_notify_poll(ph)
|
54
|
+
end
|
55
|
+
alias notify notify_poll
|
56
|
+
alias fuse_notify_poll notify_poll
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require_relative '../../ffi/accessors'
|
5
|
+
require_relative 'version'
|
6
|
+
|
7
|
+
module FFI
|
8
|
+
# Ruby FFI Binding for [libfuse](https://github.com/libfuse/libfuse)
|
9
|
+
module Libfuse
|
10
|
+
extend FFI::Library
|
11
|
+
|
12
|
+
# The fuse library to load from 'LIBFUSE' environment variable if set, otherwise prefer Fuse3 over Fuse2
|
13
|
+
LIBFUSE = ENV['LIBFUSE'] || %w[libfuse3.so.3 libfuse.so.2]
|
14
|
+
ffi_lib(LIBFUSE)
|
15
|
+
|
16
|
+
# @!scope class
|
17
|
+
# @!method fuse_version()
|
18
|
+
# @return [Integer] the fuse version
|
19
|
+
# See {FUSE_VERSION} which captures this result in a constant
|
20
|
+
|
21
|
+
attach_function :fuse_version, [], :int
|
22
|
+
|
23
|
+
# prior to 3.10 this is Major * 10 + Minor, after 3.10 and later is Major * 100 + Minor
|
24
|
+
# @return [Integer] the version of libfuse
|
25
|
+
FUSE_VERSION = fuse_version
|
26
|
+
|
27
|
+
fv_split = FUSE_VERSION >= 300 ? 100 : 10 # since 3.10
|
28
|
+
|
29
|
+
# @return [Integer] the FUSE major version
|
30
|
+
FUSE_MAJOR_VERSION = FUSE_VERSION / fv_split
|
31
|
+
|
32
|
+
# @return [Integer] the FUSE minor version
|
33
|
+
FUSE_MINOR_VERSION = FUSE_VERSION % fv_split
|
34
|
+
|
35
|
+
if FUSE_MAJOR_VERSION == 2 && FFI::Platform::IS_GNU
|
36
|
+
require_relative '../gnu_extensions'
|
37
|
+
|
38
|
+
extend(GNUExtensions)
|
39
|
+
# libfuse2 has busted symbols
|
40
|
+
ffi_lib_versions(%w[FUSE_2.9.1 FUSE_2.9 FUSE_2.8 FUSE_2.7 FUSE_2.6 FUSE_2.5 FUSE_2.4 FUSE_2.3 FUSE_2.2])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'thread_pool'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
# A JobPool is a ThreadPool whose worker threads are consuming from a Queue
|
8
|
+
class JobPool
|
9
|
+
# Create a Job Pool
|
10
|
+
# @param [Hash<Symbol,Object>] options
|
11
|
+
# @see ThreadPool.new
|
12
|
+
# @param [Proc] worker the unit of work that will be yielded the scheduled jobs
|
13
|
+
def initialize(**options, &worker)
|
14
|
+
@jq = Queue.new
|
15
|
+
@tp = ThreadPool.new(**options) { (args = @jq.pop) && worker.call(*args) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Schedule a job
|
19
|
+
# @param [Array<Object>] args
|
20
|
+
# @return [self]
|
21
|
+
def schedule(*args)
|
22
|
+
@jq.push(args)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
alias << schedule
|
26
|
+
alias push schedule
|
27
|
+
|
28
|
+
# Close the JobPool
|
29
|
+
# @return [self]
|
30
|
+
def close
|
31
|
+
@jq.close
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
# Join the JobPool
|
36
|
+
# @return [self]
|
37
|
+
def join(&block)
|
38
|
+
@tp.join(&block)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# @see ThreadPool#list
|
43
|
+
def list
|
44
|
+
@tp.list
|
45
|
+
end
|
46
|
+
|
47
|
+
# @see ThreadPool#group
|
48
|
+
def group
|
49
|
+
@tp.group
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'fuse_common'
|
4
|
+
require_relative 'fuse_args'
|
5
|
+
require_relative 'fuse_operations'
|
6
|
+
|
7
|
+
module FFI
|
8
|
+
module Libfuse
|
9
|
+
# Controls the main run loop for a FUSE filesystem
|
10
|
+
module Main
|
11
|
+
class << self
|
12
|
+
# Main function of FUSE
|
13
|
+
#
|
14
|
+
# This function:
|
15
|
+
#
|
16
|
+
# - parses command line options - see {fuse_parse_cmdline}
|
17
|
+
# and exits immediately if help or version options were processed
|
18
|
+
# - installs signal handlers for INT, HUP, TERM to unmount and exit filesystem
|
19
|
+
# - installs custom signal handlers if operations implements {fuse_traps}
|
20
|
+
# - creates a fuse handle mounted with registered operations - see {fuse_create}
|
21
|
+
# - calls either the single-threaded (option -s) or the multi-threaded event loop - see {FuseCommon#run}
|
22
|
+
#
|
23
|
+
# @param [Array<String>] argv mount.fuse arguments
|
24
|
+
# expects progname, mountpoint, options....
|
25
|
+
# @param [FuseArgs] args
|
26
|
+
# alternatively constructed args
|
27
|
+
# @param [Object|FuseOperations] operations
|
28
|
+
# something that responds to the fuse callbacks and optionally our abstract configuration methods
|
29
|
+
# @param [Object] private_data
|
30
|
+
# any data to be made available to the {FuseOperations#init} callback
|
31
|
+
#
|
32
|
+
# @return [Integer] suitable for process exit code
|
33
|
+
def fuse_main(*argv, operations:, args: argv, private_data: nil)
|
34
|
+
run_args = fuse_parse_cmdline(args: args, handler: operations)
|
35
|
+
return 2 unless run_args
|
36
|
+
|
37
|
+
fuse_args = run_args.delete(:args)
|
38
|
+
mountpoint = run_args.delete(:mountpoint)
|
39
|
+
|
40
|
+
fuse = fuse_create(mountpoint, args: fuse_args, operations: operations, private_data: private_data)
|
41
|
+
|
42
|
+
return 0 if run_args[:show_help] || run_args[:show_version]
|
43
|
+
return 2 if !fuse || !mountpoint
|
44
|
+
|
45
|
+
return unless fuse
|
46
|
+
|
47
|
+
warn run_args.to_s if run_args[:debug]
|
48
|
+
|
49
|
+
fuse.run(**run_args)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Parse command line arguments
|
53
|
+
#
|
54
|
+
# - parses standard command line options (-d -s -h -V)
|
55
|
+
# will call {fuse_debug}, {fuse_version}, {fuse_help} if implemented by handler
|
56
|
+
# - parses custom options if handler implements {fuse_options} and {fuse_opt_proc}
|
57
|
+
# - records signal handlers if operations implements {fuse_traps}
|
58
|
+
# - parses standard fuse mount options
|
59
|
+
#
|
60
|
+
# @param [Array<String>] argv mount.fuse arguments
|
61
|
+
# expects progname, [fsname,] mountpoint, options.... from mount.fuse3
|
62
|
+
# @param [FuseArgs] args
|
63
|
+
# alternatively constructed args
|
64
|
+
# @param [Object] handler
|
65
|
+
# something that responds to our abstract configuration methods
|
66
|
+
# @param [Object] private_data passed to handler.fuse_opt_proc
|
67
|
+
#
|
68
|
+
# @return [Hash<Symbol,Object>]
|
69
|
+
# * fsname [String]: the fsspec from /etc/fstab
|
70
|
+
# * mountpoint [String]: the mountpoint argument
|
71
|
+
# * args [FuseArgs]: remaining fuse_args to pass to {fuse_create}
|
72
|
+
# * show_help [Boolean]: -h or --help
|
73
|
+
# * show_version [Boolean]: -v or --version
|
74
|
+
# * debug [Boolean]: -d
|
75
|
+
# * others are options to pass to {FuseCommon#run}
|
76
|
+
def fuse_parse_cmdline(*argv, args: argv, handler: nil, private_data: nil)
|
77
|
+
args = fuse_init_args(args)
|
78
|
+
|
79
|
+
# Parse args and print cmdline help
|
80
|
+
run_args = Fuse.parse_cmdline(args, handler: handler)
|
81
|
+
|
82
|
+
# process custom options
|
83
|
+
if %i[fuse_options fuse_opt_proc].all? { |m| handler.respond_to?(m) }
|
84
|
+
parse_ok = args.parse!(handler.fuse_options, private_data) do |*p_args|
|
85
|
+
handler.fuse_opt_proc(*p_args)
|
86
|
+
end
|
87
|
+
return unless parse_ok
|
88
|
+
end
|
89
|
+
|
90
|
+
run_args[:traps] = handler.fuse_traps if handler.respond_to?(:fuse_traps)
|
91
|
+
|
92
|
+
args.parse!(RUN_OPTIONS, run_args) { |*opt_args| hash_opt_proc(*opt_args, discard: %i[native max_threads]) }
|
93
|
+
|
94
|
+
run_args[:args] = args
|
95
|
+
run_args
|
96
|
+
end
|
97
|
+
|
98
|
+
# @return [FuseCommon|nil] the mounted filesystem or nil if not mounted
|
99
|
+
def fuse_create(mountpoint, *argv, operations:, args: nil, private_data: nil)
|
100
|
+
args = fuse_init_args(args || argv.unshift(mountpoint))
|
101
|
+
|
102
|
+
operations = FuseOperations.new(delegate: operations) unless operations.is_a?(FuseOperations)
|
103
|
+
|
104
|
+
fuse = Fuse.new(mountpoint, args, operations, private_data)
|
105
|
+
fuse if fuse.mounted?
|
106
|
+
end
|
107
|
+
|
108
|
+
# Helper fuse_opt_proc function to capture options into a hash
|
109
|
+
#
|
110
|
+
# See {FuseArgs.parse!}
|
111
|
+
def hash_opt_proc(hash, arg, key, _out, discard: [])
|
112
|
+
return :keep if %i[unmatched non_option].include?(key)
|
113
|
+
|
114
|
+
hash[key] = arg =~ /=/ ? arg.split('=', 2).last : true
|
115
|
+
discard.include?(key) ? :discard : :keep
|
116
|
+
end
|
117
|
+
|
118
|
+
# @!visibility private
|
119
|
+
|
120
|
+
# Version text
|
121
|
+
def version
|
122
|
+
"#{name}: #{VERSION}"
|
123
|
+
end
|
124
|
+
|
125
|
+
def fuse_init_args(args)
|
126
|
+
if args.is_a?(Array)
|
127
|
+
args = args.map(&:to_s) # handle mountpoint as Pathname etc..
|
128
|
+
|
129
|
+
# https://github.com/libfuse/libfuse/issues/621 handle "source" field sent from /etc/fstab via mount.fuse3
|
130
|
+
# if arg[1] and arg[2] are both non option fields then replace arg1 with -ofsname=<arg1>
|
131
|
+
unless args.size <= 2 || args[1]&.start_with?('-') || args[2]&.start_with?('-')
|
132
|
+
args[1] = "-ofsname=#{args[1]}"
|
133
|
+
end
|
134
|
+
args = FuseArgs.create(*args)
|
135
|
+
end
|
136
|
+
|
137
|
+
return args if args.is_a?(FuseArgs)
|
138
|
+
|
139
|
+
raise ArgumentError "fuse main args: must be Array<String> or #{FuseArgs.class.name}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# @!group Abstract Configuration
|
144
|
+
|
145
|
+
# @!method fuse_options
|
146
|
+
# @abstract
|
147
|
+
# @return [Hash] custom option schema
|
148
|
+
# @see FuseArgs#parse!
|
149
|
+
|
150
|
+
# @!method fuse_opt_proc(data,arg,key,out)
|
151
|
+
# @abstract
|
152
|
+
# Process custom options
|
153
|
+
# @see FuseArgs#parse!
|
154
|
+
|
155
|
+
# @!method fuse_traps
|
156
|
+
# @abstract
|
157
|
+
# @return [Hash] map of signal name or number to signal handler as per Signal.trap
|
158
|
+
|
159
|
+
# @!method fuse_version
|
160
|
+
# @abstract
|
161
|
+
# @return [String] a custom version string to output with -V option
|
162
|
+
|
163
|
+
# @!method fuse_help
|
164
|
+
# @abstract
|
165
|
+
# @return [String] help text to explain custom options to show with -h option
|
166
|
+
|
167
|
+
# @!method fuse_debug(enabled)
|
168
|
+
# @abstract
|
169
|
+
# Indicate to the filesystem whether debugging option is in use.
|
170
|
+
# @param [Boolean] enabled if -d option is in use
|
171
|
+
# @return [void]
|
172
|
+
|
173
|
+
# @!endgroup
|
174
|
+
|
175
|
+
# @!visibility private
|
176
|
+
|
177
|
+
# Standard help options
|
178
|
+
STANDARD_OPTIONS = {
|
179
|
+
'-h' => :show_help, '--help' => :show_help,
|
180
|
+
'-d' => :debug, 'debug' => :debug,
|
181
|
+
'-V' => :show_version, '--version' => :show_version
|
182
|
+
}.freeze
|
183
|
+
|
184
|
+
# Custom options that control how the fuse loop runs
|
185
|
+
RUN_OPTIONS = STANDARD_OPTIONS.merge(
|
186
|
+
{
|
187
|
+
'native' => :native, # Use native libfuse functions for the process loop, primarily for testing
|
188
|
+
'max_threads=' => :max_active,
|
189
|
+
'remember=' => :remember
|
190
|
+
}
|
191
|
+
).freeze
|
192
|
+
|
193
|
+
# Help text
|
194
|
+
HELP = <<~END_HELP
|
195
|
+
#{name} options:
|
196
|
+
-o max_threads maximum number of worker threads
|
197
|
+
END_HELP
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../fuse_operations'
|
4
|
+
|
5
|
+
module FFI
|
6
|
+
module Libfuse
|
7
|
+
module Test
|
8
|
+
# A FuseOperations that holds callback procs in a Hash rather than FFI objects and allows for direct invocation of
|
9
|
+
# callback methods
|
10
|
+
# @!parse FuseOperations
|
11
|
+
class Operations
|
12
|
+
include FuseCallbacks
|
13
|
+
|
14
|
+
def initialize(delegate:, fuse_wrappers: [])
|
15
|
+
@callbacks = {}
|
16
|
+
initialize_callbacks(delegate: delegate, wrappers: fuse_wrappers)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @!visibility private
|
20
|
+
def [](member)
|
21
|
+
@callbacks[member]
|
22
|
+
end
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
def []=(member, value)
|
26
|
+
@callbacks[member] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!visibility private
|
30
|
+
def members
|
31
|
+
FuseOperations.members
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Allow the fuse operations to be called directly - useful for testing
|
37
|
+
# @todo some fancy wrapper to convert tests using Fuse2 signatures when Fuse3 is the loaded library
|
38
|
+
# and vice-versa
|
39
|
+
def method_missing(method, *args)
|
40
|
+
callback = callback?(method) && self[method]
|
41
|
+
return super unless callback
|
42
|
+
|
43
|
+
callback.call(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def respond_to_missing?(method, _private = false)
|
47
|
+
self[method] && callback?(method)
|
48
|
+
end
|
49
|
+
|
50
|
+
def callback?(method)
|
51
|
+
callback_members.include?(method)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|