A Type Stable Composable CLI Parser for Julia, inspired by optparse-applicative and Optique (typescript version).
Warning
Work In Progress: OptParse is in active development. The API is experimental and subject to frequent change. Type stability is tested and promising, but needs more real-world validation.
The aim is to provide an argument parsing package for CLI apps that supports trimming.
In OptParse, everything is a parser. Complex parsers are built from simpler ones through composition. Following the principle of "parse, don't validate," OptParse returns exactly what you ask for—or fails with a clear explanation.
Each parser is a tree of subparsers. Leaf nodes do the actual parsing, intermediate nodes compose and orchestrate parsers to create new behaviours. Parsing is done in two passes:
- in the first, the input is checked against each branch of the tree until a match is found. Each node updates its state
to reflect if it succeded or not. This is the
parsestep. - if the input match any of the branches we consider the step successful, otherwise we return the error of why it failed to match.
- the second pass is the
completestep. The tree is collapsed, eventual validation error handled and a final object returned.
- automatic usage and help printing
- more value parsers (like, dates, paths, uri...)
-
mapmodifier, unfortunately until julia has something likeTypedCallabes it's impossible to ensure type stability with arbitrary functions. -
longest-matchcombinator (maybe, still debating utility) -
groupcombinator: light simple parser useful only for enclosing multiple parsers together in the same category. mainly useful for help messages. - automatic suggestions / shell completions
- polish and stabilize the public error-reporting interface
using OptParse
# Define a parser
parser = object((
name = option("-n", "--name", str()),
port = option("-p", "--port", integer(min=1000)),
verbose = switch("-v", "--verbose")
))
# Parse arguments
result = argparse(parser, ["--name", "myserver", "-p", "8080", "-v"])
@assert result.name == "myserver"
@assert result.port == 8080
@assert result.verbose == trueThe style implemented in this library is the following:
- short form names only accept single letters:
-nis fine,-runwill be treated as bundled-r -u -n. - short form options must separate the flag from the value:
-n name. No gcc style-L/usr/include. - long form is represented with two dashes
--long --means: from that point on, stop recognizing flags, options, and commands. Everything after it is treated as positional input.
For the public entrypoints:
argparse(parser, argv)returns the parsed value or throwsOptParse.ParseExceptiontryargparse(parser, argv)is the lower-level entrypoint and returns a result object instead of throwing
OptParse provides four types of building blocks:
The fundamental parsers that match command-line tokens:
option- Matches key-value pairs:--port 8080or-p 8080flag- Boolean flags like:--verboseor-v. A plainflagMUST be present. Seeswitchfor a flag that is false if not passed.argument- Positional arguments:cp source destinationcommand- Subcommands:git add file.txt
# Options with different styles
port = option("-p", "--port", integer())
result = argparse(port, ["--port=8080"]) # Long form with =
result = argparse(port, ["-p", "8080"]) # Short form
# Flags can be bundled
parser = object((
all = flag("-a"),
long = flag("-l"),
human = flag("-h")
))
result = argparse(parser, ["-alh"]) # Equivalent to ["-a", "-l", "-h"]Type-safe parsers that convert strings to values:
str()- String values with optional pattern validationinteger()/i8(),u32(), etc. - Integer types with min/max boundsflt()/flt32(),flt64()- Floating point numberschoice()- Enumerated values from a string list or@enumtypeuuid()- UUID validation
# Type-safe parsing with constraints
port = option("-p", integer(min=1000, max=65535))
level = option("-l", choice(["debug", "info", "warn", "error"]))
config = option("-c", str(pattern=r".*\.toml$"))
@enum Mode begin
Debug
Release
end
mode = option("--mode", choice(Mode))Enhance parsers with additional behavior:
optional- Convenience wrapper forwithDefault(p, nothing)withDefault- Provides a fallback valuemultiple- Allows repeated matches, returns a vector
# Optional values
email = optional(option("-e", "--email", str()))
# With defaults
port = withDefault(option("-p", integer()), 8080)
# Multiple values
packages = multiple(argument(str())) # pkg add Package1 Package2 Package3
# Verbosity levels
verbosity = multiple(flag("-v")) # -v -v -v or -vvvCompose parsers into complex structures:
object- Named tuple of parsers (most common)or- Mutually exclusive alternatives (for subcommands)tup- Ordered tuple (preserves parser order)objmerge/concat- Merge multiple parser groups
or(...) is order-dependent: branches are tried in the order they are listed, and the first semantic match wins. Put broader positional parsers like argument(...) or multiple(argument(...)) last.
# Object composition
parser = object((
input = argument(str(metavar="INPUT")),
output = option("-o", "--output", str()),
force = switch("-f", "--force")
))
# Alternative commands with or
addCmd = command("add", object((
action = @constant(:add),
packages = multiple(argument(str()))
)))
removeCmd = command("remove", object((
action = @constant(:remove),
packages = multiple(argument(str()))
)))
pkgParser = or(addCmd, removeCmd)Here's a more realistic example showing subcommands:
using OptParse
# Shared options
commonOpts = object((
verbose = switch("-v", "--verbose"),
quiet = switch("-q", "--quiet")
))
# Add command
addCmd = command("add", objmerge(
commonOpts,
object((packages = multiple(argument(str(metavar="PACKAGE"))),))
))
# Remove command
removeCmd = command("remove", "rm", objmerge(
commonOpts,
object((
all = switch("--all"),
packages = multiple(argument(str(metavar="PACKAGE")))
))
))
# Instantiate command
instantiateCmd = command("instantiate", objmerge(
commonOpts,
object((
manifest = switch("-m", "--manifest"),
project = switch("-p", "--project")
))
))
# Complete parser
parser = or(addCmd, removeCmd, instantiateCmd)
# Usage examples:
# julia pkg.jl add DataFrames Plots -v
# julia pkg.jl remove --all -q
# julia pkg.jl instantiate --manifestOptParse is designed for type stability. The return type of your parser is fully determined at compile time:
parser = object((
name = option("-n", str()),
port = option("-p", integer())
))
# Return type: @NamedTuple{name::String, port::Int64)}
parser = or(
object((mode = @constant(:a), value = argument(integer()))),
object((mode = @constant(:b), value = argument(str())))
)
# Return type: Union{@NamedTuple{mode::Val{:a}, ...}, @NamedTuple{mode::Val{:b}, ...}}OptParse exposes two entrypoints:
parser = option("-p", integer(min=1000))
# Throwing API
value = argparse(parser, ["-p", "3000"])
# Lower-level API
result = tryargparse(parser, ["-p", "3000"])argparse returns the parsed value on success and throws OptParse.ParseException on failure.
tryargparse returns a result object instead of throwing, which is useful if you want to inspect failures programmatically.
Rendered error messages are produced centrally from structured internal diagnostics. The exact wording may evolve, but failures are surfaced with parser-specific context, for example invalid values, missing required inputs, or unexpected arguments.
parser = option("-p", integer(min=1000))
try
argparse(parser, ["-p", "abc"])
catch err
@assert err isa OptParse.ParseException
endusing Pkg
Pkg.add(url="https://github.com/ghyatzo/OptParse")Comprehensive documentation is available through Julia's help system:
julia> using OptParse
julia> ?option
julia> ?object
julia> ?or
For more detailed documentation, see the Documentation.
Additionally, the Optique's excellent documentation website or docs from the great optparse-applicative, might be of interest for more in depth analysis or phylosophy. There are some differences but the core concepts are the same since both have been an inspiration on the design of this library.
Contributions are welcome! Please feel free to submit issues or pull requests.
MIT License. See LICENSE for details.
- optparse-applicative - Haskell command-line parser
- Optique - Typescript CLI parsing library