ffi-libfuse 0.0.1.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|