Cliff, the Command Line InterFace Factory, is a lightweight, type-stable argument parsing library for Julia built to help you assemble polished CLIs without fuss. It combines familiar ergonomics with a few focused conveniences:
- Standard command-line affordances such as positional arguments, long options (
--arg=foo), short options (-n 5), flags (--version), and repeated arguments (-vvv). - Arbitrarily nested subcommands so complex workflows like
git remote add ...feel natural. - Type-stable retrieval helpers that convert values on access, keeping downstream code predictable.
- Early-stopping hooks and auto-help support so
--helpand similar flags can exit gracefully.
using Cliff
hello = Parser([
Argument("name"),
Argument("--uppercase"; flag = true),
Argument("--excitement"; default = "1"),
])
function greet(argv)
args = hello(argv)
name = args["name"]
exclamations = repeat("!", args["--excitement", Int])
if args["--uppercase", Bool]
name = uppercase(name)
end
println("Hello ", name, exclamations)
end
greet(["Julia", "--uppercase", "--excitement", "3"])
# prints: Hello JULIA!!!See examples/example.jl for a more detailed walk-through of nested commands, typed retrieval, validation, and error handling.
Cliff provides three building blocks:
Argument– defines positional arguments, short options, long options, and flags.Command– describes a sub-command with its own arguments and nested sub-commands.Parser– top-level parser that aggregates arguments and commands.
Parsing command line arguments returns a Parsed object—typically stored in a variable named args—that exposes convenient indexing for retrieving values as strings or strongly typed data.
Values are stored as strings but can be converted when accessed from a Parsed object:
args["name"]orargs["name", String]returns the raw string value.args["name", Bool]recognisestrue,false,1,0,yes,no,on, andoff(case insensitive).args["name", T]usesBase.parse(T, value)for any typeTwith a parsing method, such asInt,Float64, orUInt.args["name", Vector{T}](or the shorthandargs["name", T, +]) converts each provided value for repeatable arguments or flags.args["name", Union{String, Nothing}]returnsnothingwhen the argument was optional and not supplied. Useargs["name", T, -](orargs["name", -]for strings) as a shorthand for optional typed lookups.
This allows you to keep your parser definitions declarative while still retrieving strongly typed values at the call site. The returned Parsed object also exposes success, complete, stopped, stop_argument, and an optional error::ParseError for full diagnostics.
Cliff can validate incoming values without custom code:
choices = [...]forces inputs to match a curated allowlist. Defaults must appear in the same list.regex = r"..."requires each value to match the supplied pattern.- Use
required = falseto force an argument to remain optional, even for positionals. Optional arguments default to an empty va lue list unless you provide your own.
Both options apply to positional arguments and options, and they work alongside repetition controls. When validation fails Cliff raises a ParseError with kind :invalid_value so you can render a friendly message or surface it to the user as-is.
Argument("mode"; choices = ["fast", "slow"], default = "fast")
Argument("--name"; regex = r"^[a-z]+$", default = "guest")
Argument("--tag"; choices = ["red", "blue"], repeat = true)Bring Cliff into scope with using Cliff and assemble your parser from the building blocks above:
using Cliff
parser = Parser([
Argument("input"),
Argument(["--count", "-c"]; default = "1"),
Argument("--tag"; repeat = true),
Argument("--mode"; choices = ["fast", "slow"], default = "fast"),
Argument("--verbose"; flag = true)
], [
Command("run", [Argument("task")], [
Command("fast", [Argument("--threads"; default = "4")])
])
])
args = parser(["input.txt", "--tag", "demo", "--tag", "release", "run", "task-name", "fast", "--threads", "8"])
println(args.command) # ["run", "fast"]
println(args["input"]) # "input.txt"
println(args["task"]) # "task-name"
println(args["--mode"]) # "fast"
println(args["--threads", Int]) # 8
println(args["--verbose", Bool]) # false
# Collect repeated values
println(args["--tag", Vector{String}]) # ["demo", "release"]Flags default to an empty value list and record an empty string "" for every occurrence. args["--verbose", Bool] therefore returns false unless the flag appears on the command line, args["--verbose"] yields "" when present (and throws if omitted), and args["--verbose", Int] reports the number of times the flag was provided. When a flag is absent args["--verbose", Vector{T}] returns an empty vector, otherwise it contains an empty string for each occurrence, making it easy to detect whether users opted in.
Mark any argument with stop = true to halt parsing once that argument (and any associated value) has been consumed. Cliff treats these arguments as optional so they never block required-argument checks. This is particularly handy for implementing manual --help handling:
help_parser = Parser([
Argument("input"),
Argument("--help"; flag = true, stop = true)
], [
Command("run", [Argument("task")])
])
help_args = help_parser(["--help"])
help_args.stopped # true
help_args.complete # false – required arguments are unchecked
help_args.stop_argument # "--help"Prefer Argument("--help"; auto_help = true) when you want Cliff to wire up
help flags automatically. Auto help arguments behave like the manual example
above but also propagate to sub-commands and, when invoked with error_mode = :exit, print a basic usage summary before exiting.
When validation fails you can control how Cliff responds using the error_mode keyword:
parse(help_parser, String[]; error_mode = :return) # -> Parsed with success = false
parse(help_parser, String[]; error_mode = :throw) # -> throws ParseError
parse(help_parser, String[]; error_mode = :exit) # -> prints message and exits (default)examples/example.jl demonstrates all core features—including nested commands, positional and option defaults, value validation, repeating arguments, early stopping, and error handling. The script prints @show summaries for top-level arguments and active sub-commands, exits on error, and honours --help stops. Run it with different argument lists to see how Parsed changes:
julia --project examples/example.jl --help
julia --project examples/example.jl target --tag demo --tag test --verbose --profile release --label nightly-build run quick --repeat once --repeat twice --threads 6 fast --limit 5 --extra a --extra b