lib/ffi/libfuse/fuse_args.rb in ffi-libfuse-0.0.1.rctest12 vs lib/ffi/libfuse/fuse_args.rb in ffi-libfuse-0.1.0.rc20220550
- old
+ new
@@ -9,14 +9,17 @@
module Libfuse
# struct fuse_args
class FuseArgs < FFI::Struct
layout :argc, :int, :argv, :pointer, :allocated, :int
- # Create an fuse_args struct from command line options
+ # Create a fuse_args struct from command line options
# @param [Array<String>] argv command line args
- # args[0] is expected to be program name
+ #
+ # first arg is expected to be program name
# @return [FuseArgs]
+ # @example
+ # FFI::Libfuse::FuseArgs.create($0,*ARGV)
def self.create(*argv)
new.fill(*argv)
end
# @!visibility private
@@ -32,17 +35,17 @@
self[:allocated] = 0
self
end
# @!attribute [r] argc
- # @return [Integer] count of args
+ # @return [Integer] count of args
def argc
self[:argc]
end
# @!attribute [r] argv
- # @return [Array<String>]
+ # @return [Array<String>] list of args
def argv
# noinspection RubyResolve
self[:argv].get_array_of_pointer(0, argc).map(&:read_string)
end
@@ -51,11 +54,11 @@
self[:allocated]
end
# @!visibility private
def inspect
- "#{self.class.name} - #{%i[argc argv allocated].map { |m| [m, send(m)] }.to_h}"
+ "#{self.class.name} - #{%i[argc argv allocated].to_h { |m| [m, send(m)] }}"
end
# Add an arg to this arg list
# @param [String] arg
def add(arg)
@@ -70,83 +73,115 @@
end
#
# Option parsing function
#
+ # Wraps fuse_opt_parse() in ruby sugar and safety
+ #
# @param [Hash<String,Symbol>] opts option schema
#
# hash keys are a String template to match arguments against
#
# 1. "-x", "-foo", "--foo", "--foo-bar", etc. These match only themselves. Invalid values are "--" and anything
# beginning with "-o"
# 2. "foo", "foo-bar", etc. These match "-ofoo", "-ofoo-bar" or the relevant option in a comma separated option
# list
- # 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter
+ # 3. "bar=", "--foo=", etc. These are variations of 1) and 2) which have a parameter value
# 4. '%' Formats Not Supported (or needed for Ruby!)
# 5. "-x ", etc. Matches either "-xparam" or "-x param" as two separate arguments
# 6. '%' Formats Not Supported
#
# hash values are the Symbol sent to the block for a matching argument
#
# - :keep Argument is not passed to block, but behave as if the block returns :keep
# - :discard Argument is not passed to block, but behave as if the block returns :discard
# - any other value is yielded as 'key' property on matching argument
#
- # @param [Object] data an optional object that will be passed thru to the block
+ # note that multiple templates can refer to the same key to support multiple option styles
#
- # @yieldparam [Object] data
- # @yieldparam [String] arg is the whole argument or option including the parameter if exists.
- #
- # A two-argument option ("-x foo") is always converted to single argument option of the form "-xfoo" before this
- # function is called.
- #
- # Options of the form '-ofoo' are yielded without the '-o' prefix.
- #
+ # @param [Object] data an optional object that will be passed to the block
+ # @param [Array<Symbol>|nil] ignore
+ # the keys in this list will be kept without being passed to the block. pass nil to observe all keys
+ # @yield [key:, value:, match:, data:, out:]
+ # block is called for each remaining arg
# @yieldparam [Symbol] key determines why the processing function was called
#
# - :unmatched for arguments that *do not match* any supplied option
# - :non_option for non-option arguments (after -- or not beginning with -)
# - with appropriate value from opts hash for a matching argument
- #
- # @yieldparam [FuseArgs] outargs can {add} or {insert} additional args as required
- #
+ # @yieldparam [String|Boolean] value the option value or true for option without a value
+ # @yieldparam [String] match the matching template specification (ie a key in opts)
+ # @yieldparam [Object] data
+ # @yieldparam [FuseArgs] out can {add} or {insert} additional args as required
# eg. if one arg implies another
#
# @yieldreturn [Symbol] the argument action
#
- # - :error an error
- # - :keep the current argument (to pass on further)
- # - :handled,:discard success and discard the current argument (ie because it has been handled)
- #
- # @return [nil|FuseArgs] nil on error, self on success
- def parse!(opts, data = nil, &block)
- # turn option value symbols into integers including special negative values from fuse_opt.h
+ # - :error an error, alternatively raise {Error}
+ # - :keep retain the current argument for further processing
+ # - :handled,:discard remove the current argument from further processing
+ # @return [nil|self] nil on error otherwise self
+ def parse!(opts, data = nil, ignore: %i[non_option unmatched], &block)
+ ignore ||= []
+
+ # first create an array of unique symbols such that positive indexes are custom options and negative indexes are
+ # special values (see fuse_opt.h), ie so we can turn the integer received in fuse_opt_proc back into a symbol
symbols = opts.values.uniq + %i[discard keep non_option unmatched]
+ # transform our symbol keys into integers suitable for FuseOpts
int_opts = opts.transform_values do |v|
%i[discard keep].include?(v) ? symbols.rindex(v) - symbols.size : symbols.index(v)
end
- fop = proc { |d, arg, key, outargs| fuse_opt_proc(d, arg, symbols[key], outargs, &block) }
- result = Libfuse.fuse_opt_parse(self, data, int_opts, fop)
+ # keep track of opt templates by key so we extract parameter values from arg
+ param_opts, bool_opts = opts.keys.partition { |t| t =~ /(\s+|=)$/ }.map do |opt_list|
+ opt_list.group_by { |t| opts[t] }
+ end
- result.zero? ? self : nil
+ fop = fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
+ Libfuse.fuse_opt_parse(self, data, int_opts, fop).zero? ? self : nil
end
private
# Valid return values from parse! block
FUSE_OPT_PROC_RETURN = { error: -1, keep: 1, handled: 0, discard: 0 }.freeze
- def fuse_opt_proc(data, arg, key, out, &block)
- res = block.call(data, arg, key, out)
+ def fuse_opt_proc(symbols, bool_opts, param_opts, ignore, &block)
+ proc do |data, arg, key, out|
+ key = symbols[key]
+ next FUSE_OPT_PROC_RETURN.fetch(:keep) if ignore.include?(key)
+
+ match, value =
+ if %i[unmatched non_option].include?(key)
+ [nil, arg]
+ elsif bool_opts[key]&.include?(arg)
+ [arg, true]
+ elsif (opt = param_opts[key]&.detect { |t| arg.start_with?(t.rstrip) })
+ # Contrary to fuse_opt.h the separating space is not always stripped from these options
+ # https://github.com/libfuse/libfuse/issues/667
+ [opt, arg[opt.rstrip.length..].lstrip]
+ else
+ warn "FuseOptProc error - Cannot match option for #{arg}"
+ next -1
+ end
+
+ safe_opt_proc(key: key, value: value, match: match, data: data, out: out, &block)
+ end
+ end
+
+ def safe_opt_proc(**args, &block)
+ res = block.call(**args)
res.is_a?(Integer) ? res : FUSE_OPT_PROC_RETURN.fetch(res)
rescue KeyError => e
warn "FuseOptProc error - Unknown result #{e.key}"
- -1
- rescue StandardError => e
- warn "FuseOptProc error - #{e.class.name}:#{e.message}"
- -1
+ FUSE_OPT_PROC_RETURN.fetch(:error)
+ rescue Error => e
+ warn "#{e.message}: #{args.select { |k, _v| %i[key value].include?(k) }}\n#{argv}"
+ FUSE_OPT_PROC_RETURN.fetch(:error)
+ rescue StandardError, ScriptError => e
+ warn "FuseOptProc error - #{e.class.name}:#{e.message}\n\t#{e.backtrace.join("\n\t")}"
+ FUSE_OPT_PROC_RETURN.fetch(:error)
end
end
# typedef int (*fuse_opt_proc_t)(void *data, const char *arg, int key, struct fuse_args *outargs);
callback :fuse_opt_proc_t, [RubyObject, :string, :int, FuseArgs.by_ref], :int