[ANN] OptParse.jl – a composable, type-stable CLI parser

[ANN] OptParse.jl – a composable, type-stable CLI parser

Hi all,

I’ve been working on OptParse.jl, a command-line argument parser for Julia built around three main ideas:

  • type stability: the main goal was to make a CLI library that works well with trimming.
  • composability: an ‘everything is a parser’ approach, where larger CLIs are built from small reusable pieces.
  • parse, don’t validate: validation is embedded in the parser itself, so a successful parse already gives you a valid result.

repo: OptParse.jl
docs: OptParse docs

The design is inspired by libraries like optparse-applicative (Haskell) and Optique (TypeScript), but adapted to Julia’s type system and compilation model.

Quick example

using OptParse

parser = object((
    name = option("-n", "--name", str("NAME")),
    port = option("-p", "--port", integer("PORT"; min = 1000)),
    verbose = flag("-v", "--verbose"),
))

result = argparse(parser, ["--name", "myserver", "-p", "8080", "-v"])

@assert result.name == "myserver"
@assert result.port == 8080
@assert result.verbose == true

The API is organized around a few kinds of “parser building blocks”:

Primitive parsers
match basic CLI structure such as options, flags, positional arguments, and commands

  • input = arg(str("INPUT"))
  • output = option("-o", "--output", str("OUTPUT"))
  • verbose = flag("-v", "--verbose")

Value parsers
Convert raw strings into typed validated values.

These are responsible for turning a matched string into a typed valid value.
They are the validation layer of the system.

  • integer("PORT"; min = 1024, max = 65535)
  • choice("MODE", ["debug", "release"])

Constructors
Combine smaller parsers into larger ones.

  • object(...) for named collections of parsers
  • or(...) for alternatives

This is what makes subcommands and larger application parsers ergonomic to express.

Modifiers
Adjust parser behavior, for example by making something optional or repeatable

  • default(p, value)
  • optional(p)
  • multiple(p)

Bigger Demo

module HelloWorld

using OptParse

const hello = command("hello", object((;
    cmd = @constant(:hello),
    name = option("-n", "--name", str("NAME")),
)))

const goodbye = command("goodbye", object((;
    cmd = @constant(:goodbye),
    name = option("-n", "--name", str("NAME")),
)))

const parser = or(hello, goodbye)

const Hello = resulttype(hello)
const Goodbye = resulttype(goodbye)

runaction(x::Hello) = println(Core.stdout, "Hello, $(x.name)!")
runaction(x::Goodbye) = println(Core.stdout, "Goodbye, $(x.name)!")

function @main(args::Vector{String})::Cint
 obj = argparse(parser, args)
 isnothing(obj) && return 1

 runaction(obj)
 return 0
end

end # module HelloWorld

and then after compiling with juliac

$ helloworld hello --name OptParse
Hello, OptParse!

$ helloworld goodbye --name OptParse
Goodbye, OptParse!

Extensibility

The package is extensible in design, but today new parser families and value parsers still need package-level integration to preserve type stability and trimming behavior.

Current status

This is still experimental and under active development. This means a lot of churn.

Next steps:

  • automatic usage/help generation (ongoing)
  • some API polish and changes
  • broader real-world validation
  • extra parser types that are still missing
  • extra value parsers

Feedback welcome

The user-facing layer is still intentionally a bit minimal; I’d rather add convenience APIs based on actual usage than guess wrong too early.

I’d especially like feedback on:

  • API ergonomics
  • dispatching mechanism of the parse result
  • readability of parser definitions for medium/large CLIs
  • expected help/usage behavior and style
  • missing parser/value-parser combinators

Moreover, the combinator surface is large enough that real-world stress testing would be especially valuable.

Acknowledgements

This library has very few dependencies but those few have been essential:

  • ErrorTypes.jl
  • WrappedUnions.jl
  • Accessors.jl

Thanks for the amazing work on these!

That’s it, hope you’ll like it!
Cheers

7 Likes