Project

strop

0.0
No release in over 3 years
Build options from parsing help text, with pattern matching for result processing
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies
 Project Readme

Strop: Command-line Option Parser

Distinctive features

  • 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

Core workflow

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 default

Processing parsed results

Strop.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
end

Or, 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
end

You can generate the case expression above with:

puts Strop.parse_help(help_text).to_s(:case)

Result members (Array of Opt, Arg, Sep)

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 instantiated

Notice 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.

Help text format for parsing

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.

Command-line parsing features

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)

Manual option declaration building

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.

Option lists:

optlist = Optlist[optdecl1, optdecl2] # combine decls into optlist
optlist["f"]                          # lookup by name
optlist["fl"]                         # partial match (finds "flag" if unique)

Argument requirements

  • :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]