CLI: argparse & spinner
Dois módulos pequenos para construir ferramentas de linha de comando. stdlib::argparse é um parser de argumentos alocado no heap que suporta flags longas/curtas de três tipos de valor (string, int, bool), captura de posicionais, o marcador -- de fim de flags e texto --help gerado automaticamente. stdlib::spinner é um spinner de progresso em braille minimalista que anima no stderr e degrada com elegância em não-TTYs.
Import
import stdlib::argparse::*; // ArgParser, StrFlag, IntFlag, BoolFlag
import stdlib::spinner::*; // spinner_start / spinner_set / spinner_finish
Superfície pública em resumo
| Módulo | Item público | Tipo |
|---|---|---|
argparse |
StrFlag, IntFlag, BoolFlag |
structs (valores de flag em caixa) |
argparse |
ArgParser |
struct |
argparse |
new, free |
ciclo de vida do ArgParser |
argparse |
add_string, add_int, add_bool |
declarar flags |
argparse |
parse_argv, parse_tokens |
parsear (-> !i32) |
argparse |
get_string, get_int, get_bool, positional |
ler resultados |
argparse |
help_requested, format_help, print_help |
ajuda |
spinner |
spinner_start, spinner_set, spinner_finish |
funções livres |
argparse — visão geral
Crie um parser com ArgParser::new, declare cada flag com add_string / add_int / add_bool, depois chame parse_argv() (ou parse_tokens() para uma lista fornecida). Após um parse bem-sucedido, leia os valores com get_string / get_int / get_bool e os argumentos posicionais com positional().
import stdlib::argparse::*;
import stdlib::io::*;
fn main() -> !i32 {
let p: *ArgParser = ArgParser::new("greet", "Say hello to someone");
defer p.free();
p.add_string("name", "n", "world", "who to greet");
p.add_int("count", "c", 1, "number of greetings");
p.add_bool("verbose", "v", false, "extra output");
let r: !i32 = p.parse_argv();
if !r.ok { eprintln("error: ".concat(r.err)); return ok(2); }
if p.help_requested() { p.print_help(); return ok(0); }
let who: string = p.get_string("name");
let n: i32 = p.get_int("count");
for let i: i32 = 0; i < n; i++ { println!("hello,", who); }
return ok(0);
}
Formas de token reconhecidas
| Token | Significado |
|---|---|
--name=value |
flag longa com valor inline |
--name value |
flag longa; o valor é o próximo token |
-n value |
flag curta; o valor é o próximo token |
--name / -n |
flag bool sem valor (inverte o padrão) |
--help / -h |
ativa help_requested(); não é impresso automaticamente |
-- |
marcador de fim de flags; tudo depois é posicional |
| qualquer outra coisa | argumento posicional (incluindo negativos no estilo -3) |
Structs de valor de flag
Os três contêineres de valor em caixa são públicos. Raramente você os constrói diretamente — add_* os aloca e armazena — mas estão visíveis para inspeção.
| Struct | Campos | Usado por |
|---|---|---|
StrFlag |
pub default_value: string, pub value: string |
add_string / get_string |
IntFlag |
pub default_value: i32, pub value: i32 |
add_int / get_int |
BoolFlag |
pub default_value: bool, pub value: bool |
add_bool / get_bool |
pub struct StrFlag { pub default_value: string, pub value: string }
pub struct IntFlag { pub default_value: i32, pub value: i32 }
pub struct BoolFlag { pub default_value: bool, pub value: bool }
// Os structs de valor de flag são públicos e podem ser construídos diretamente.
let f: IntFlag = IntFlag{ default_value: 5, value: 5 };
let sf: StrFlag = StrFlag{ default_value: "x", value: "x" };
let bf: BoolFlag = BoolFlag{ default_value: false, value: true };
ArgParser
pub struct ArgParser { /* prog, desc, mapas de flags, ordem, posicionais ... (privado) */ }
Todos os campos são privados; interaja através dos métodos abaixo. Internamente ele mantém três HashMaps de structs de flag em caixa (um por tipo), vários mapas string→string para alias curto/longo, tags de tipo e texto de ajuda, um Vector<string> de nomes de flags na ordem de declaração, e o Vector<string> de posicionais.
Construção
ArgParser::new
pub fn new(prog: string, desc: string) -> *ArgParser
Aloca um parser vazio no heap. prog e desc aparecem na saída do --help. O ponteiro retornado possui vários hash maps e vetores internos; libere-o com free() (combine com defer).
let p: *ArgParser = ArgParser::new("greet", "Say hello");
defer p.free();
free
pub fn free(self: *ArgParser)
Libera cada struct de flag, hash map e vetor de posse, depois o parser em si. O ponteiro fica inválido depois disso — chame apenas uma vez, de preferência via defer.
Declarando flags
| Método | Assinatura |
|---|---|
add_string |
pub fn add_string(self: *ArgParser, long: string, short: string, default_value: string, help: string) |
add_int |
pub fn add_int(self: *ArgParser, long: string, short: string, default_value: i32, help: string) |
add_bool |
pub fn add_bool(self: *ArgParser, long: string, short: string, default_value: bool, help: string) |
Cada um declara uma flag. long é o nome --long; short é o alias de um caractere -s, ou "" para nenhum. default_value inicializa o valor antes do parse, e help é a descrição por flag em --help.
p.add_string("name", "n", "world", "who to greet"); // --name=alice --name alice -n alice
p.add_int("count", "c", 1, "number of greetings"); // --count=5 --count 5 -c 5
p.add_bool("verbose", "v", false, "extra output"); // --verbose / -v sets it true
Parseando
parse_argv
pub fn parse_argv(self: *ArgParser) -> !i32
Parseia o argv do processo a partir do índice 1 (o índice 0, o caminho do programa, é ignorado). Retorna ok(0) em caso de sucesso ou err(message) no primeiro token malformado (flag desconhecida, valor ausente, int/bool inválido). Leia a mensagem via .err.
let r: !i32 = p.parse_argv();
if !r.ok { eprintln("error: ".concat(r.err)); return 2; }
parse_tokens
pub fn parse_tokens(self: *ArgParser, tokens: *Vector<string>) -> !i32
A mesma lógica de parse sobre uma lista de tokens fornecida pelo chamador — útil para testes e para construir subcomandos manualmente. parse_argv é implementado em termos deste.
let toks: *Vector<string> = Vector::new();
toks.push_all!("--name=alice", "--count", "3", "-v", "file.txt");
p.parse_tokens(toks)?;
Mensagens de erro que você pode checar (todas acessadas via .err):
| Condição | Mensagem |
|---|---|
flag --long desconhecida |
unknown flag: --<name> |
--int-flag sem valor seguinte |
flag --<name> requires a value |
curta -i sem valor seguinte |
flag -<short> requires a value |
| não-inteiro para uma flag int | flag --<name> expects an integer, got: <val> |
não-bool para uma flag bool (via =) |
flag --<name> expects a bool, got: <val> |
Lendo valores
| Método | Assinatura | Retorna |
|---|---|---|
get_string |
pub fn get_string(self: *ArgParser, long: string) -> string |
flag string parseada |
get_int |
pub fn get_int(self: *ArgParser, long: string) -> i32 |
flag int parseada |
get_bool |
pub fn get_bool(self: *ArgParser, long: string) -> bool |
flag bool parseada |
positional |
pub fn positional(self: *ArgParser) -> *Vector<string> |
args posicionais, em ordem |
let who: string = p.get_string("name");
let n: i32 = p.get_int("count");
if p.get_bool("verbose") { eprintln("verbose mode on"); }
let files: *Vector<string> = p.positional();
for let i: i32 = 0; i < files.len(); i++ { println!("file:", files.get(i)); }
Posicionais, -- e números negativos
positional() coleta, em ordem: qualquer token que não seja flag, qualquer token curto desconhecido -x (assim -3 sobrevive), e tudo após um separador -- (mesmo que pareça uma flag).
import stdlib::argparse::*;
import stdlib::io::*;
fn main() -> i32 {
let p: *ArgParser = ArgParser::new("calc", "");
defer p.free();
p.add_bool("verbose", "v", false, "noisy");
p.add_int("scale", "s", 1, "scale factor");
let toks: *Vector<string> = Vector::new();
// -3 não é uma flag curta conhecida, então sobrevive como posicional.
// Tudo após `--` é posicional mesmo que pareça uma flag.
toks.push_all!("-v", "--scale=2", "-3", "--", "--scale", "literal");
let r: !i32 = p.parse_tokens(toks);
if !r.ok { eprintln(r.err); return 2; }
println!("verbose:", format!("{}", p.get_bool("verbose"))); // true
println!("scale:", format!("{}", p.get_int("scale"))); // 2
let pos: *Vector<string> = p.positional();
for let i: i32 = 0; i < pos.len(); i++ {
println!("positional:", pos.get(i)); // -3, --scale, literal
}
return 0;
}
Saída de ajuda
| Método | Assinatura |
|---|---|
help_requested |
pub fn help_requested(self: *ArgParser) -> bool |
format_help |
pub fn format_help(self: *ArgParser) -> string |
print_help |
pub fn print_help(self: *ArgParser) |
help_requested() informa se --help / -h foi visto durante o parse — o parser nunca imprime a ajuda sozinho, então o chamador decide o que fazer. format_help() monta a string de uso (nome do programa, descrição, uma linha por flag na ordem de declaração, com tipo + padrão + ajuda); print_help() imprime no stdout via print!.
if p.help_requested() { p.print_help(); return ok(0); }
let txt: string = p.format_help(); // captura em vez de imprimir
format_help produz uma saída com este formato:
usage: greet [flags] [positional...]
Say hello to someone
flags:
--name (-n) [string, default="world"]
who to greet
--count (-c) [int, default=1]
number of greetings
--verbose (-v) [bool, default=false]
extra output
--help (-h) show this message
Um desc vazio (passado como "") omite o bloco de descrição; uma flag declarada com help vazio omite sua segunda linha (indentada).
Exemplo completo: um CLI real com spinner
Uma ferramenta completa no estilo build: várias flags, tratamento de --help, exibição de erros e trabalho real envolto em um spinner.
import stdlib::argparse::*;
import stdlib::spinner::*;
import stdlib::io::*;
fn main() -> !i32 {
let p: *ArgParser = ArgParser::new("build", "Compile and link a target");
defer p.free();
p.add_string("target", "t", "app", "target name to build");
p.add_int("jobs", "j", 1, "number of parallel jobs");
p.add_bool("release", "r", false, "optimized release build");
p.add_bool("color", "", true, "colorized output (--color turns it off)");
let r: !i32 = p.parse_argv();
if !r.ok { eprintln("error: ".concat(r.err)); return ok(2); }
if p.help_requested() { p.print_help(); return ok(0); }
let target: string = p.get_string("target");
let jobs: i32 = p.get_int("jobs");
let release: bool = p.get_bool("release");
let mode: string = "debug";
if release { mode = "release"; }
eprintln(format!("building {} ({}) with {} jobs", target, mode, jobs));
// Spinner em torno do trabalho. Em um TTY anima; em pipe, degrada
// para linhas de status simples.
spinner_start("compiling ".concat(target));
// ... o trabalho demorado aconteceria aqui ...
spinner_set("linking ".concat(target));
// ... mais trabalho ...
spinner_finish(true, "built ".concat(target));
// Posicionais restantes (após as flags, ou após um `--`).
let extra: *Vector<string> = p.positional();
for let i: i32 = 0; i < extra.len(); i++ {
println!("extra:", extra.get(i));
}
return ok(0);
}
Invoque assim:
build -t mylib -j 4 --release -- a.o b.o
Padrão: subcomandos sobre parse_tokens
Não existe um tipo de subcomando embutido. Construa um removendo o primeiro token posicional e despachando para um parser por comando alimentado via parse_tokens.
import stdlib::argparse::*;
import stdlib::io::*;
fn run_add(rest: *Vector<string>) -> !i32 {
let p: *ArgParser = ArgParser::new("git add", "stage files");
defer p.free();
p.add_bool("all", "a", false, "stage everything");
p.parse_tokens(rest)?;
println!("add all =", format!("{}", p.get_bool("all")));
let files: *Vector<string> = p.positional();
for let i: i32 = 0; i < files.len(); i++ { println!("stage:", files.get(i)); }
return ok(0);
}
fn main() -> !i32 {
let argv: *Vector<string> = Vector::new();
argv.push_all!("add", "-a", "main.glide", "lib.glide"); // seria o argv real
if argv.len() == 0 { eprintln("missing subcommand"); return ok(2); }
let cmd: string = argv.get(0);
let rest: *Vector<string> = Vector::new();
for let i: i32 = 1; i < argv.len(); i++ { rest.push(argv.get(i)); }
if cmd.eq("add") { run_add(rest)?; return ok(0); }
eprintln("unknown subcommand: ".concat(cmd));
return ok(2);
}
spinner
Um único spinner global. Uma pthread em segundo plano redesenha um frame de braille a cada 80 ms no stderr. Iniciar um novo spinner enquanto um está ativo o substitui silenciosamente (une o antigo primeiro). Quando o stderr não é um TTY (saída em pipe, CI sem PTY) a animação é suprimida: spinner_start / spinner_set imprimem sua mensagem como uma linha simples, e spinner_finish ainda emite sua linha final, mantendo os logs limpos.
| Função | Assinatura | Descrição |
|---|---|---|
spinner_start |
pub fn spinner_start(msg: string) |
Inicia (ou substitui) o spinner com msg como status inicial. |
spinner_set |
pub fn spinner_set(msg: string) |
Atualiza o texto de status do spinner ativo. Não faz nada se nenhum estiver ativo (emite uma nova linha em não-TTY). |
spinner_finish |
pub fn spinner_finish(ok: bool, msg: string) |
Para e une o animador, limpa a linha e imprime ✓ msg (quando ok) ou ✗ msg. Um msg vazio suprime a linha final. |
import stdlib::spinner::*;
fn main() -> i32 {
spinner_start("compiling...");
// ... trabalho demorado ...
spinner_set("linking...");
// ... mais trabalho ...
spinner_finish(true, "built target/my_app");
spinner_start("deploying...");
spinner_finish(false, "deploy failed"); // imprime ✗ deploy failed
spinner_start("warming up");
spinner_finish(true, ""); // sem linha final
return 0;
}
Os frames ciclam pelos dez glifos de braille ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏. Ao finalizar a linha é apagada (\r\x1b[K) antes de o resumo ✓/✗ ser impresso.