Builder @cli
En esta página
El builder @cli transforma una struct anotada en una CLI completa sin escribir
un parser a mano. Cada campo se convierte en una opción o argumento posicional;
los defaults, tipos y textos de ayuda salen del propio código — sin divergir de
la fuente.
Flags y defaults
Anota la struct con @cli(name: "...") y cada campo con @arg(short, long, default, help). Args.parse_args(argv) recibe un array de strings y devuelve
una instancia tipada:
Tres llamadas a parse_args demuestran: sin flags (defaults), forma larga y
forma corta con =value. No se necesitan argumentos de proceso reales — el
array se inyecta directamente, haciendo el ejemplo ejecutable en el sandbox.
// Feature: `@cli` builder — declarative argument parsing
// Syntax: annotate a struct with `@cli(name: "...")`. Each field
// becomes a CLI option via `@arg(short, long, default, help)`.
// `Args.parse_args(argv)` parses an array and returns a typed instance.
// When to use: real CLI tools — instead of hand-rolling
// `process.argv()` parsing, get types, defaults, --help and --version
// for free.
@cli(name: "demo")
struct Args {
@arg(short, long, help: "Verbose")
verbose: bool,
@arg(long, short: "n", default: 10, help: "Count")
count: int,
}
// No flags — defaults kick in.
let a = Args.parse_args([])
print(a.verbose) // expected: false
print(a.count) // expected: 10
// Long form: --flag value
let b = Args.parse_args(["--verbose", "--count", "20"])
print(b.verbose) // expected: true
print(b.count) // expected: 20
// Short form, with =value syntax.
let c = Args.parse_args(["-v", "-n=42"])
print(c.verbose) // expected: true
print(c.count) // expected: 42
Argumentos posicionales
@arg(positional) marca un campo como posicional. required lo hace
obligatorio; multiple reúne todos los posicionales restantes en una lista:
Simula cat main.txt x y z: el primer posicional va a input, los demás llenan
extras. También ejecutable en el sandbox — sin dependencia del proceso.
// Feature: `@arg(positional, ...)` — positional arguments
// Syntax: `positional` marks the field as a positional, `required`
// makes it mandatory, `multiple` collects all remaining args.
// When to use: file-input arguments (cat, mv, …), commands that
// take a target plus a variadic list of items.
@cli(name: "cat")
struct Args {
@arg(positional, required, help: "Input file")
input: str,
@arg(positional, multiple, help: "Extra files")
extras: [str],
}
// Single positional — extras stays empty.
let a = Args.parse_args(["main.txt"])
print(a.input) // expected: main.txt
print(a.extras.len()) // expected: 0
// Multiple — first goes to `input`, the rest fill `extras`.
let b = Args.parse_args(["main.txt", "x", "y", "z"])
print(b.input) // expected: main.txt
print(b.extras.len()) // expected: 3
print(b.extras[0]) // expected: x
print(b.extras[1]) // expected: y
print(b.extras[2]) // expected: z
--help y --version automáticos
Con @cli(name, version) y help: "..." en cada campo, el runtime genera
--help y --version sin código adicional. El texto nunca queda desactualizado
porque proviene directamente de la declaración:
El parsing normal sigue funcionando; para ver la salida de --help, ejecuta
zolo run 11-cli-help.zolo -- --help localmente. El sandbox no admite --help
vía process.argv().
// Feature: `--help` and `--version` are auto-generated from the struct
// Syntax: `@cli(name, version)` plus per-field `help: "..."`. Passing
// `--help` prints usage + every flag with its help text and exits;
// `--version` prints the version line.
// When to use: every real CLI. The help text reflects the struct
// declaration, so it never drifts out of sync.
@cli(name: "demo", version: "1.0")
struct Args {
@arg(short, long, help: "Verbose output")
verbose: bool,
@arg(long, default: 10, help: "Number of items")
count: int,
}
// Normal parse — defaults flow through.
let a = Args.parse_args([])
print(a.verbose) // expected: false
print(a.count) // expected: 10
// To see the help text, run the file with `--help`:
// zolo run 11-cli-help.zolo -- --help
// → Usage: demo [OPTIONS]
// Options:
// -v, --verbose Verbose output
// --count <COUNT> Number of items (default: 10)
// -h, --help Print help
// -V, --version Print version
//
// Or `--version`:
// zolo run 11-cli-help.zolo -- --version
// → demo 1.0
Requiere la CLI o el host de Zolo; ábrelo en el playground o ejecútalo localmente.
Subcomandos
Anota un enum con @subcommand en un campo de la struct principal. Cada
variante se convierte en un subcomando independiente (con sus propios flags si
es necesario), y match gestiona el despacho — el mismo patrón de
git commit / cargo build:
tool -v build --arch x86_64, tool start y tool test — tres invocaciones
demostradas pasando arrays directamente, sin dependencia del proceso real.
// Feature: subcommands — `@subcommand` over an enum
// Syntax: declare an enum where each variant is a subcommand. A
// field with `@subcommand cmd: Command` becomes the dispatch slot.
// Each subcommand variant can declare its own flags via `@arg(...)`.
// When to use: tools shaped like `git commit`, `cargo build`, `kubectl get`.
// Variants without fields become no-flag subcommands; tuple/struct
// variants get their own flags.
enum Command {
Build { @arg(long) arch: str },
Start,
Test,
}
@cli(name: "tool")
struct Args {
@arg(short, long)
verbose: bool,
@subcommand
cmd: Command,
}
// Dispatch via `match` — the idiomatic shape for an enum.
fn run(args: Args) {
print(args.verbose)
match args.cmd {
.Build { arch } => print("build arch={arch}"),
.Start => print("start"),
.Test => print("test"),
}
}
// `tool -v build --arch x86_64`
run(Args.parse_args(["-v", "build", "--arch", "x86_64"]))
// expected:
// true
// build arch=x86_64
// `tool start`
run(Args.parse_args(["start"]))
// expected:
// false
// start
// `tool test`
run(Args.parse_args(["test"]))
// expected:
// false
// test
Desafío
Agrega un cuarto subcomando Deploy { @arg(long) env: str } al enum e
implementa el brazo correspondiente en match. Llama a
Args.parse_args(["deploy", "--env", "prod"]) y verifica la salida.
Consulta también