Extending OptParse

This page is the procedural part of the development guide: what to do when you add a new value parser or parser family.

Adding A New Value Parser

The shortest path is:

  1. add a new file under src/parsers/valueparsers/
  2. define a concrete error struct <: AbstractParseError with an @enum code field
  3. define render_error(io::IO, err::YourError) for the error type
  4. define the concrete value parser struct <: AbstractValueParser{T, E}
  5. define its call overload (::YourValueParser)(input::String)::ParseResult{T, E}
  6. include it from src/parsers/valueparsers/valueparsers.jl
  7. add exported constructor functions and docstrings in src/parsers/parser.jl
  8. add display support in src/display/parser_show.jl if needed
  9. add unit tests
  10. add it to docs/src/reference.md if it is public

Expected shape

Use existing value parser files as templates, especially:

  • string.jl
  • integer.jl
  • float.jl

Typical pieces:

@enum MyValErrCode::UInt8 begin
    MYVAL_SomeError
end

struct MyValError <: AbstractParseError
    code::MyValErrCode
    token::String
end

function render_error(io::IO, err::MyValError)
    ...
end

struct MyVal{T} <: AbstractValueParser{T, MyValError}
    metavar::String
    ...
end

function (p::MyVal{T})(input::String)::ParseResult{T, MyValError} where {T}
    ...
end

Constructor placement

Public constructor functions belong in src/parsers/parser.jl, not in the leaf file.

That file is the public API layer for value parsers and should contain:

  • the public constructor names
  • positional metavar overloads when appropriate
  • docstrings

Positional metavar style

The current preferred API is:

str("FILE")
integer("PORT")
flt("RATIO")
choice("MODE", values)
path("FILE")

The metavar = ... keyword still exists at the lower level, but docstrings and examples should prefer the positional form.

Value parser checklist

Before considering a value parser done, verify:

  • parse returns ParseResult{T, E}
  • a concrete error struct <: AbstractParseError exists for the family
  • render_error(io, err) renders it correctly
  • the public constructor is exported if intended to be public
  • show output is readable
  • docstrings exist
  • reference docs include the constructor
  • unit tests cover success, failure, and @test_opt

Adding A New Parser Family

The shortest path is:

  1. decide whether it is a primitive, constructor, or modifier
  2. add the concrete family file under the appropriate src/parsers/... subdirectory
  3. define a family-specific state alias
  4. define an error struct <: AbstractParseError with @enum codes and a render_error method
  5. define the concrete parser struct <: AbstractParser{T, E, S, P, R}
  6. implement parse
  7. implement complete
  8. include it from the family include file
  9. make sure child parser fields stay concrete and parametric
  10. add the public constructor function and docstring in src/parsers/parser.jl
  11. add show_children / printnode support if needed
  12. add tests
  13. add docs, reference entries, and examples if public

Start from a state alias

Always define a parser-family-specific state alias first.

Examples:

const GateState = ParseResult{Bool, ArgGateError}
const OptionState{T, E} = ParseResult{T, E}
const CommandState{S} = Option{Option{S}}
const MultipleState{S} = Vector{S}

This usually clarifies the rest of the implementation immediately.

Tight signatures

Use the family alias in method signatures:

function parse(p::ArgGate{Bool, ArgGateError, GateState}, ctx::Context{GateState})::InnerParseResult{GateState, ArgGateError}

Do not leave parser family methods broadly typed unless there is a very strong reason.

Public constructor placement

Public constructor functions belong in src/parsers/parser.jl.

That file is the user-facing API layer for parser families:

  • exported names
  • docstrings
  • curried convenience overloads where needed

Parser-family checklist

Before considering a parser family done, verify:

  • the state alias is explicit
  • parse and complete signatures reflect the real invariant state type
  • family-specific errors render correctly
  • child parser fields stay concrete and parametric
  • the public constructor exists and is exported if public
  • show output is useful
  • docs/examples/reference mention the new parser if public
  • tests cover both behavior and inference

Display And Docstring Conventions

Display extension points

The display interface has three extension points:

  • Base.show(io, p) — compact inline representation (required)
  • show_children(p) — returns children for tree display, or nothing for leaves (optional)
  • printnode(io, p) — tree header label, defaults to show (optional)

Public names should match displayed names

If the public constructor is command(...), the parser pretty-printer should show command(...), not an older or internal alias.

Likewise:

  • flag(...) should display as flag(...)
  • switch(...) should display as switch(...)
  • sequence(...) should display as sequence(...)

One subtle exception exists right now:

  • flag(...) is implemented as default(switch(...), false)
  • the pretty-printer special-cases this shape so public flag(...) still displays as flag(...)

Reference docs must mention exported bindings explicitly

Documenter will complain if:

  • an exported name has a docstring
  • but the reference page does not include it in an @docs block

So when adding a public constructor, always update:

  • docs/src/reference.md