- Build options from parsing help text, instead of the other way around
- Pattern matching for result processing (with
case/in …) - Short, single-file implementation: easy to read and modify
help_text = <<~HELP
Options:
-f, --flag Flag
-v, --verbose LEVEL Required arg
-o, --output [FILE] Optional arg
HELP
opts = Strop.parse_help(help_text) # extract from help
result = Strop.parse(opts, ARGV) # parse argv -> Result
result = Strop.parse!(opts, ARGV) # same but on error prints message and exits
result = Strop.parse!(help_text) # Automatically parse help text, ARGV defaultStrop.parse!(help).each do |item|
case item
in Strop::Opt[label: "help"] then show_help
in Strop::Opt[label: "verbose", value:] then set_verbose(value)
in Strop::Opt[label: "output", value: nil] then output = :stdout
in Strop::Opt[label: "color"] then item.no? ? disable_color : enable_color
in Strop::Arg[value:] then files << value
in Strop::Sep then break
end
endOr, more succinctly:
Strop.parse!(help).each do |item|
case item
in label: "help" then show_help # only Opt has .label
in arg: then files << arg # `value:` might match an Opt, so Arg offers alias .arg
in Strop::Sep then # same. leave blank to keep looping, but exhaust the case
end
endYou can generate the case expression above with:
puts Strop.parse_help(help_text).to_s(:case)res.opts # all Opt objects
res.args # all Arg objects
res.rest # args after -- separator
res["flag"] # find opt by name
res[["flag"]] # find all opts matching name
opt = res.opts.first
arg = res.args.first
opt.decl # Optdecl matched for this Opt instance
opt.name # name used in invocation (could differ from label)
opt.value # argument passed to this option
opt.label # primary name (first long name or first name), used for pattern matching
opt.no? # true if --no-foo variant used
opt.yes? # opposite of `no?`
arg.value # positional argument
arg.arg # same as .value, useful for pattern matching
arg.to_s # implicit string conversion (same as .value)
Sep # -- end of options marker; Const, not instantiatedNotice that parsing --[no-]flag from help results in a single Optdecl[:flag, :"no-flag"], and you can use yes?/no? to check which was passed.
Auto-extracts indented option lines from help:
Options:
-f, --flag Flag
-v, --verbose LEVEL Required arg
-o, --output [FILE] Optional arg
--color=MODE Optional with =
--debug[=LEVEL] Required/optional with =
--[no-]quiet --quiet/--no-quiet pair
--[no]force --force/--noforce pair
By default parse_help expects options to be indented by 2 or 4 spaces. Override with:
Strop.parse_help(text, pad: / {6}/) # 6 spaces exactly
Strop.parse_help(text, pad: /\t/) # tabs ????Use at least two spaces before description, and only a single space before args.
--file PATH # !! PATH seen as description and ignored, --file considered a flag (no arg)
--quiet Suppresses output # !! interpreted as --quiet=Suppresses
The latter case is detected and a warning is printed, but best to avoid this situation altogether.
cmd -abc # short option clumping (-a -b -c)
cmd -fVAL, --foo=VAL # attached values
cmd -f VAL, --foo VAL # separate values
cmd --foo val -- --bar # --bar becomes positional after --
cmd --intermixed args and --options # flexible ordering
cmd --ver # partial matching (--ver matches --verbose if unique)include Strop::Exports # For brevity in exanples. Not required.
Optdecl[:f] # flag only: -f
Optdecl[:f?] # optional arg: -f [X]
Optdecl[:f!] # required arg: -f x
Optdecl[:f, :foo] # multiple names: -f or --foo
Optdecl[:f?, :foo] # multiple + arg modifier: use ?/! only on first
Optdecl[:f?, :foo?] # so this will result in -f, --foo?
Optdecl[:f, :foo, arg: :may] # explicit arg form: --foo [ARG]
Optdecl[:f?, arg: :shant] # explicit form allows using ?/! in option name: -f?
Optdecl[:foo_bar] # --foo-bar: Underscores in symbol names get replaced with `-`
Optdecl["foo_bar"] # --foo_bar: but not in strings.optlist = Optlist[optdecl1, optdecl2] # combine decls into optlist
optlist["f"] # lookup by name
optlist["fl"] # partial match (finds "flag" if unique):shant- no argument allowed:may- optional argument (takes next token if not option-like):must- required argument (error if missing)
Adding hidden options
If you want to use parse_help mainly, but need secret options:
optlist = Strop.parse_help HELP
optlist << Optdecl[:D, :debug]