Capítulo 19 12 min de leitura

Ambiente & argumentos de linha de comando

O módulo stdlib::env é a janela do Glide para o ambiente por processo: os argumentos de linha de comando com que o programa foi iniciado, as variáveis de ambiente que ele herdou, e a capacidade de lê-las, mutá-las, enumerá-las e expandi-las — além de um exit imediato do processo. Informações estáticas do host (nome do SO, arquitetura, caminho do executável, separadores de caminho) residem em stdlib::os.

Importação

import stdlib::env::*;

Superfície pública resumida

Item Tipo Assinatura
env_args_count fn fn env_args_count() -> i32
env_args_at fn fn env_args_at(i: i32) -> string
env_args fn fn env_args() -> *Vector<string>
env_get fn fn env_get(name: string) -> string
env_has fn fn env_has(name: string) -> bool
env_set fn fn env_set(name: string, value: string) -> !
env_unset fn fn env_unset(name: string) -> !
env_all fn fn env_all() -> *Vector<EnvKV>
env_expand fn fn env_expand(s: string) -> string
env_exit fn fn env_exit(code: i32)
EnvKV struct pub struct EnvKV { pub key: string, pub value: string }

Toda função pública e o único tipo público estão documentados a seguir.

Argumentos de linha de comando

A indexação de argumentos espelha a convenção de argc/argv do C: o índice 0 é o caminho do programa e os argumentos fornecidos pelo usuário começam em 1.

Função Assinatura Descrição
env_args_count fn env_args_count() -> i32 Número total de argumentos de linha de comando, incluindo o caminho do programa no índice 0.
env_args_at fn env_args_at(i: i32) -> string Argumento no índice i. Retorna "" para índices fora do intervalo.
env_args fn env_args() -> *Vector<string> Todos os argumentos do usuário como um vector, pulando o caminho do programa no índice 0.

env_args_count / env_args_at

env_args_at é totalmente segura: qualquer índice fora do intervalo (negativo, ou >= env_args_count()) produz "" em vez de causar uma falha.

import stdlib::env::*;

fn main() -> i32 {
    let n: i32 = env_args_count();
    println!("argc =", n);

    let prog: string = env_args_at(0);   // caminho do programa
    let first: string = env_args_at(1);  // primeiro argumento do usuário, "" se ausente
    println!("prog =", prog);
    println!("first =", first);
    return 0;
}

Invocado como ./app build --release:

output
argc = 3
prog = ./app
first = build

env_args

env_args retorna um *Vector<string> recém-alocado contendo apenas os argumentos do usuário (a partir do índice 1), que é geralmente o que um parser de argumentos precisa.

import stdlib::env::*;

fn main() -> i32 {
    let args: *Vector<string> = env_args();
    for let i: i32 = 0; i < args.len(); i++ {
        println!("arg", i, args.get(i));
    }
    return 0;
}

Fundamentos de parsing de argumentos

env deliberadamente não inclui um parser de flags — ele entrega o vector de argumentos bruto e você decide a convenção. O padrão abaixo lida com flags simples (-v / --verbose) e pares --chave=valor dividindo em =:

import stdlib::env::*;

fn main() -> i32 {
    let args: *Vector<string> = env_args();
    let mut verbose: bool = false;
    let mut out: string = "a.out";

    for let i: i32 = 0; i < args.len(); i++ {
        let a: string = args.get(i);
        if a.eq("-v") || a.eq("--verbose") {
            verbose = true;
        } else if a.contains("=") {
            let parts: *Vector<string> = a.split("=");
            if parts.len() == 2 && parts.get(0).eq("--out") {
                out = parts.get(1);
            }
        }
    }

    println!("verbose =", verbose);
    println!("out =", out);
    return 0;
}

Lendo variáveis de ambiente

Função Assinatura Descrição
env_get fn env_get(name: string) -> string Valor de name, ou "" quando não definida.
env_has fn env_has(name: string) -> bool true somente se name está definida com um valor não vazio.

env_get

Retorna "" para uma variável não definida. Como um valor vazio e uma variável não definida são ambos lidos como "", use env_has quando precisar distingui-los.

import stdlib::env::*;

fn main() -> i32 {
    let path: string = env_get("PATH");
    println!("PATH len =", path.len());
    return 0;
}

env_has

import stdlib::env::*;

fn main() -> i32 {
    if env_has("DEBUG") {
        println!("debug on");
    } else {
        println!("debug off");
    }
    return 0;
}

Lendo configurações com valores padrão

Um idioma comum é "ler uma variável, analisá-la e recorrer a um padrão." Como string.try_parse_int() retorna !i32, é possível definir um padrão para toda a expressão com ??. Encapsule a consulta de modo que não definida e não analisável ambas se apresentem como um err, então reduza-as ao valor padrão em um único lugar:

import stdlib::env::*;

fn port_from_env() -> !i32 {
    let raw: string = env_get("PORT");
    if raw.eq("") { return err("PORT unset"); }
    return raw.try_parse_int();   // err em texto não numérico
}

fn main() -> i32 {
    // Padrão quando não definida OU não analisável, em uma única expressão.
    let port: i32 = port_from_env() ?? 8080;

    let host: string = env_get("HOST");
    if host.eq("") { host = "127.0.0.1"; }   // padrão para string

    println!("listening on", host, port);
    return 0;
}

Mutando variáveis de ambiente

Ambos os mutadores retornam o tipo resultado sem payload !. Erros são raros e têm origem no SO — tipicamente um nome inválido (p. ex., um que contenha =) ou um limite do sistema. Propague com ? em uma função !T, ou inspecione .ok / .err.

Função Assinatura Descrição
env_set fn env_set(name: string, value: string) -> ! Define name como value, sobrescrevendo qualquer valor existente. Err se o SO rejeitar a chamada.
env_unset fn env_unset(name: string) -> ! Remove name. Idempotente — remover uma variável inexistente não é um erro.

env_set é multiplataforma: no Windows usa _putenv_s, que atualiza tanto o bloco de ambiente Win32 quanto a tabela CRT, para que um env_get posterior enxergue o novo valor; no POSIX usa setenv(name, value, 1). env_unset remove a variável de ambos os repositórios.

env_set / env_unset

import stdlib::env::*;

fn main() -> !i32 {
    env_set("LANG", "en_US.UTF-8")?;
    let v: string = env_get("LANG");
    println!("LANG =", v);

    env_unset("LANG")?;
    if env_has("LANG") {
        println!("still set");
    } else {
        println!("unset ok");
    }
    return ok(0);
}

Para tratar o erro sem propagá-lo, vincule o resultado e leia seus campos. eprintln (de stdlib::io) escreve na stderr:

import stdlib::env::*;
import stdlib::io::*;   // eprintln

fn main() -> i32 {
    let r: ! = env_set("GLIDE_DOC_DEMO", "1");
    if !r.ok {
        eprintln(r.err);
        return 1;
    }
    println!("set:", env_get("GLIDE_DOC_DEMO"));   // "1"
    println!("has:", env_has("GLIDE_DOC_DEMO"));   // true

    let u: ! = env_unset("GLIDE_DOC_DEMO");
    if !u.ok { eprintln(u.err); return 1; }
    println!("has after unset:", env_has("GLIDE_DOC_DEMO"));  // false
    return 0;
}

Enumerando o ambiente

struct EnvKV

Uma entrada de ambiente decomposta, conforme retornada por env_all().

pub struct EnvKV {
    pub key: string,
    pub value: string,
}
Campo Tipo Descrição
key string Nome da variável (texto à esquerda do primeiro =).
value string Valor da variável (texto à direita do primeiro =).

env_all

Função Assinatura Descrição
env_all fn env_all() -> *Vector<EnvKV> Instantâneo de todo o ambiente do processo, dividido em pares EnvKV.

O vector retornado é uma cópia tirada no momento da chamada. Entradas brutas malformadas (aquelas sem =, ou com chave vazia) são ignoradas. Chamar env_set / env_unset depois não atualiza um vector já retornado — chame env_all() novamente para um instantâneo atualizado.

import stdlib::env::*;

fn main() -> i32 {
    let all: *Vector<EnvKV> = env_all();
    println!("env count =", all.len());
    for let i: i32 = 0; i < all.len(); i++ {
        let kv: EnvKV = all.get(i);
        println!(kv.key, "=", kv.value);
    }
    return 0;
}

Como env_has/env_get não conseguem distinguir FOO= (vazio) de um FOO não definido, env_all é a ferramenta para uma verificação exata de presença. Retornar ?string permite ao chamador ramificar em some/none:

import stdlib::env::*;

fn lookup(key: string) -> ?string {
    let all: *Vector<EnvKV> = env_all();
    for let i: i32 = 0; i < all.len(); i++ {
        let kv: EnvKV = all.get(i);
        if kv.key.eq(key) { return some(kv.value); }
    }
    return none();
}

fn main() -> i32 {
    let r: ?string = lookup("PATH");
    match r {
        some(v) => println!("PATH value length", v.len()),
        none() => println!("PATH absent"),
    }
    return 0;
}

Expansão de variáveis

env_expand

Função Assinatura Descrição
env_expand fn env_expand(s: string) -> string Expande referências $VAR, ${VAR} e %VAR% em s para seus valores atuais no ambiente.

Regras de expansão:

  • $VAR — forma POSIX simples. O nome começa no primeiro caractere de início de
  • nome (A-Z, a-z, _) e se estende pelos caracteres de continuação de nome seguintes (esses mais 0-9).

  • ${VAR} — chaves POSIX, encerradas pelo } correspondente.
  • %VAR% — forma Windows, encerrada pelo próximo %.
  • Variáveis desconhecidas são expandidas para a string vazia.
  • Um $ seguido de um caractere que não seja de nome, ou um % sem %
  • fechador, é mantido intacto (preservado literalmente).

import stdlib::env::*;

fn main() -> !i32 {
    env_set("DEMO_DIR", "/srv")?;

    // $VAR e %VAR% podem ser escritos inline.
    println!(env_expand("posix: $DEMO_DIR/logs"));
    println!(env_expand("win: %DEMO_DIR%\\logs"));

    // ${VAR} colidiria com a interpolação de strings do próprio Glide, então
    // construa o literal por concatenação em vez de escrever as chaves inline.
    let tmpl: string = "braced: ".concat("$").concat("{").concat("DEMO_DIR").concat("}/x");
    println!(env_expand(tmpl));

    // Variável desconhecida -> vazio; $ isolado antes de caractere não-nome é preservado.
    println!(env_expand("missing=[$NOPE_VAR] price=$5"));
    return ok(0);
}
output
posix: /srv/logs
win: /srv\logs
braced: /srv/x
missing=[] price=$5

Encerramento do processo

env_exit

Função Assinatura Descrição
env_exit fn env_exit(code: i32) Encerra o processo imediatamente com code.

env_exit chama diretamente o exit do C: ignora a limpeza do lado Glide, portanto blocos defer e auto-drops (let v* = ...) nos stack frames envolventes não serão executados. Use-a para encerramento forçado de alto nível após reportar um erro; prefira retornar um i32 não nulo de main quando quiser o desempilhamento normal.

import stdlib::env::*;
import stdlib::io::*;   // eprintln

fn main() -> i32 {
    let mode: string = env_get("MODE");
    if mode.eq("") {
        eprintln("MODE is required");
        env_exit(2);   // ignora limpeza por defer/auto-drop
    }
    println!("mode =", mode);
    return 0;
}

Veja também

  • os — informações estáticas do host: nome do SO, arquitetura, caminho do
  • executável e os separadores de caminho/lista que você combina com env_get("PATH").

  • ioeprintln e o restante da I/O de stdin/stdout/stderr usada nos
  • caminhos de erro acima.

  • cli — parsing de argumentos de nível mais alto construído sobre env_args*.