Capítulo 17 14 min de leitura

Sistema de Arquivos & I/O

O módulo stdlib::fs envolve as primitivas de arquivo do runtime C em auxiliares Glide mais seguros (listagens ordenadas, retornos Vector<string>, remoção de newlines finais, leituras seguras para binários). O módulo stdlib::io cobre leituras de stdin, gravações brutas em stdout, flush e logging em stderr — as partes que as macros print! / println! não alcançam.

Importação

import stdlib::fs::*;   // arquivos, diretórios, caminhos
import stdlib::io::*;   // stdin / stdout / stderr

Os dois módulos são independentes; importe apenas o que for usar. Note que eprintln vive em stdlib::io, então qualquer programa de arquivo que registra erros precisa das duas importações.

Superfície pública de relance

Módulo Itens
stdlib::fs fs_read · fs_read_or_default · fs_read_lines · fs_lines_count · fs_write · fs_exists · fs_is_dir · fs_size · fs_list · fs_list_rec · fs_basename · fs_extension · fs_mkdir_p · fs_remove_file · fs_remove_dir_rec · fs_copy_file · fs_copy_dir_rec · fs_symlink · fs_read_bytes · fs_write_bytes · fs_write_slice
stdlib::io read_line · read_int · read_bytes · prompt · read_yes_no · io_write · io_write_bytes · flush · eprintln

Ambos os módulos expõem apenas funções livres — sem structs, enums ou traits públicos. Os externs __glide_* / read_file / write_file sobre os quais são construídos são privados ao módulo; o código do usuário sempre passa pelos invólucros fs_* / io_*.


Lendo arquivos

fs_read / fs_read_or_default

pub fn fs_read(path: string) -> string
pub fn fs_read_or_default(path: string, fallback: string) -> string

fs_read retorna o arquivo inteiro como uma string no heap, ou "" em qualquer erro (arquivo ausente, permissão negada). Como tanto "ausente" quanto "vazio" colapsam para "", use fs_read_or_default quando precisar de um fallback real — ele verifica a existência antes e só retorna fallback quando o arquivo está ausente (um arquivo que existe mas está vazio ainda produz "").

Este é o padrão canônico de "ler um arquivo de configuração": grava um padrão quando o arquivo está ausente, depois lê e analisa linha por linha.

import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    let path: string = "app.conf";

    // Distingue ausente de vazio.
    if !fs_exists(path) {
        eprintln("no config; writing a default");
        fs_write(path, "verbose=false\nport=8080\n");
    }

    let body: string = fs_read(path);
    if body.eq("") {
        eprintln("config missing or empty");
        return 1;
    }

    // Analisa linhas simples no formato chave=valor.
    let lines: *Vector<string> = body.split("\n");
    defer lines.free();
    for let i: i32 = 0; i < lines.len(); i++ {
        let line: string = lines.get(i).trim();
        if line.eq("") { continue; }
        if line.starts_with("#") { continue; }
        let kv: *Vector<string> = line.split("=");
        defer kv.free();
        if kv.len() == 2 {
            println!("key:", kv.get(0).trim(), "value:", kv.get(1).trim());
        }
    }

    // Variante com fallback: nunca dá erro para arquivo ausente.
    let raw: string = fs_read_or_default("missing.conf", "default=1\n");
    println!("fallback length:", raw.len());
    return 0;
}

fs_read_lines / fs_lines_count

pub fn fs_read_lines(path: string) -> *Vector<string>
pub fn fs_lines_count(path: string) -> i32

fs_read_linespath e divide em \n, descartando a entrada vazia final causada por um newline final — assim len() corresponde à contagem intuitiva de linhas. fs_lines_count retorna essa mesma contagem sem alocar um vetor (varre em busca de \n, mais um se o arquivo for não vazio e não terminado em newline); retorna 0 para arquivos vazios ou ausentes. Um arquivo ausente também produz um vetor vazio de fs_read_lines — sem erro, sem crash.

import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    fs_write("words.txt", "alpha\nbeta\ngamma\n");

    let lines: *Vector<string> = fs_read_lines("words.txt");
    defer lines.free();
    println!("got", lines.len(), "lines");   // 3, não 4 — "" final descartado
    for let i: i32 = 0; i < lines.len(); i++ {
        let w: string = lines.get(i).trim();
        if w.len() >= 5 { println!(w); }     // "alpha", "gamma"
    }

    let n: i32 = fs_lines_count("words.txt");
    println!("lines_count:", n);             // 3

    // Arquivo ausente -> 0 / vetor vazio, sem crash.
    let missing: *Vector<string> = fs_read_lines("nope.txt");
    defer missing.free();
    println!("missing len:", missing.len(), "count:", fs_lines_count("nope.txt"));
    return 0;
}

O vetor retornado é um *Vector<string> bruto — libere-o (ou use defer .free()) quando terminar.


Escrevendo arquivos

fs_write

pub fn fs_write(path: string, content: string) -> bool

Grava content em path no modo binário ("wb"), substituindo qualquer arquivo existente. Retorna true somente quando todos os bytes foram gravados. O número de bytes gravados é content.len() medido como string C, então um 0x00 embutido trunca a saída — use `fs_write_bytes` para payloads binários.

import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    if !fs_write("out.txt", "hello\n") {
        eprintln("write failed");
        return 1;
    }
    return 0;
}

Existência, tamanho e tipo

Função Assinatura Descrição
fs_exists fn fs_exists(path: string) -> bool true se path pode ser aberto para leitura. false para ausente ou ilegível.
fs_is_dir fn fs_is_dir(path: string) -> bool true somente se path é um diretório existente (distingue diretório de arquivo).
fs_size fn fs_size(path: string) -> i64 Tamanho do arquivo em bytes; -1 se o caminho está ausente ou stat falha. Uma syscall stat, sem abertura.
import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    fs_write("favicon.ico", "icon-bytes");

    if fs_exists("favicon.ico") { println!("exists"); }
    if fs_is_dir(".") { println!("cwd is a dir"); }
    if !fs_is_dir("favicon.ico") { println!("favicon is a file, not a dir"); }

    let n: i64 = fs_size("favicon.ico");
    if n > 0 { println!("serve it,", n, "bytes"); }

    let missing: i64 = fs_size("does-not-exist");
    if missing == -1 { println!("size of missing path is -1"); }
    return 0;
}

Listando diretórios

fs_list

pub fn fs_list(dir: string, suffix: string) -> *Vector<string>

Lista nomes de arquivo diretamente sob dir (sem recursão). Retorna apenas nomes, não caminhos completos, ordenados de forma ascendente para saída estável. Filtra por suffix (ex.: ".glide"); passe "" para manter todos. Diretórios são excluídos; no POSIX, entradas com prefixo de ponto também são ignoradas. Um diretório ausente produz um vetor vazio (sem erro).

import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    let files: *Vector<string> = fs_list("src/stdlib", ".glide");
    defer files.free();
    for let i: i32 = 0; i < files.len(); i++ {
        println!("- ", files.get(i));
    }
    return 0;
}

fs_list_rec

pub fn fs_list_rec(dir: string, suffix: string) -> *Vector<string>

Lista recursivamente todos os arquivos sob dir cujo nome termina em suffix (passe "" para todos). Retorna caminhos relativos completos (cada um prefixado com dir/...), não basenames. Ignora diretórios com prefixo de ponto mais glide_modules/, build/, target/ e node_modules/, para que passagens de documentação e lint não varram dependências ou saída de build. Ordenado dentro de cada nível de diretório, percorrido em profundidade.

Este é o padrão de "percorrer uma árvore e inspecionar cada arquivo", combinado com os auxiliares de caminho abaixo:

import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    // Filhos imediatos, apenas nomes, filtrados por sufixo.
    let top: *Vector<string> = fs_list("src", ".glide");
    defer top.free();
    for let i: i32 = 0; i < top.len(); i++ {
        println!("- ", top.get(i));
    }

    // Recursivo, caminhos relativos completos.
    let all: *Vector<string> = fs_list_rec("src", ".glide");
    defer all.free();
    println!("found", all.len(), "glide files");
    for let i: i32 = 0; i < all.len(); i++ {
        let p: string = all.get(i);
        println!(fs_basename(p), "ext:", fs_extension(p));
    }
    return 0;
}

Auxiliares de caminho

Estas são funções de string puras — não tocam o sistema de arquivos.

Função Assinatura Descrição
fs_basename fn fs_basename(path: string) -> string Último componente do caminho (após o último / ou \). Retorna a entrada inalterada quando não há separador.
fs_extension fn fs_extension(path: string) -> string Extensão sem o ponto inicial, em minúsculas. "" quando não há extensão.
import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    println!(fs_basename("src/stdlib/fs.glide"));   // fs.glide
    println!(fs_basename("/usr/local/bin/glide"));  // glide
    println!(fs_basename("noslash.txt"));           // noslash.txt

    println!(fs_extension("config.toml"));          // toml
    println!(fs_extension("archive.tar.gz"));       // gz
    println!(fs_extension("Makefile"));             // ""
    println!(fs_extension(".bashrc"));              // ""
    return 0;
}

fs_basename trata ambos os separadores, então fs_basename("C:\\Program Files\\Glide\\bin") é "bin".


Criando e modificando diretórios

Função Assinatura Descrição
fs_mkdir_p fn fs_mkdir_p(path: string) -> bool Cria path e todos os pais ausentes. Idempotente; false apenas em um erro real.
fs_remove_file fn fs_remove_file(path: string) -> bool Remove um arquivo. true em sucesso ou se já estava ausente.
fs_remove_dir_rec fn fs_remove_dir_rec(path: string) -> bool Remove path recursivamente. Idempotente (ausente → sucesso). Delega para rm -rf / rmdir /S /Q.
fs_copy_file fn fs_copy_file(src: string, dst: string) -> bool Cópia de arquivo segura para binário, sobrescrevendo dst. false em qualquer erro de I/O.
fs_copy_dir_rec fn fs_copy_dir_rec(src: string, dst: string) -> bool Copia src recursivamente para dst, criando dst. Delega para cp -r / xcopy /S.
fs_symlink fn fs_symlink(target: string, link: string) -> bool Faz link ser um link no formato de diretório apontando para target. Idempotente. symlink(2) no POSIX; junction de diretório no Windows (mklink /J, sem admin).
import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    if !fs_mkdir_p("cache/sub/leaf") {
        eprintln("can't make cache");
        return 1;
    }

    fs_write("cache/a.txt", "hello");
    if !fs_copy_file("cache/a.txt", "cache/b.txt") {
        eprintln("copy failed");
    }

    fs_copy_dir_rec("cache", "backup/cache");
    fs_symlink("cache/sub", "cache/link");

    // Limpeza é idempotente — não são necessárias pré-verificações.
    fs_remove_file("cache/a.txt");
    fs_remove_dir_rec("cache");
    fs_remove_dir_rec("backup");
    println!("done");
    return 0;
}

I/O Binário

Estas sobrevivem a bytes NUL embutidos, ao contrário de fs_read / fs_write.

fs_read_bytes / fs_write_bytes / fs_write_slice

pub fn fs_read_bytes(path: string, out_len: *i32) -> string
pub fn fs_write_bytes(path: string, data: *void, n: i32) -> bool
pub fn fs_write_slice(path: string, data: string, offset: i32, n: i32) -> bool
  • fs_read_bytespath como bytes brutos em uma string Glide prefixada por comprimento; out_len recebe a contagem exata de bytes. Retorna "" e 0 em falha. (Internamente libera o buffer C após copiar para uma string rastreada pela arena, sem vazamentos.)
  • fs_write_bytes grava n bytes de um buffer *void no modo binário.
  • fs_write_slice grava n bytes a partir de offset dentro de uma string Glide — útil para extrair um arquivo de um arquivo compactado em memória sem uma cópia extra de substring. Sem verificação de limites: o chamador garante que offset + n está dentro do intervalo.
import stdlib::fs::*;
import stdlib::io::*;

fn main() -> i32 {
    // Lê bytes brutos (sobrevive a NUL embutido).
    let mut n: i32 = 0;
    let buf: string = fs_read_bytes("favicon.ico", &n);
    if n > 0 {
        // Round-trip de uma fatia do buffer sem cópia extra.
        fs_write_slice("copy.ico", buf, 0, n);
        println!("copied", n, "bytes");
    }

    // Grava a partir de um buffer *void bruto.
    let blob: *void = malloc(16) as *void;
    if fs_write_bytes("out.bin", blob, 16) {
        println!("wrote 16 bytes");
    }
    free(blob);

    fs_remove_file("copy.ico");
    fs_remove_file("out.bin");
    return 0;
}

Entrada padrão

read_line

pub fn read_line() -> string

Lê uma linha do stdin incluindo o newline final se presente; "" em EOF. Apoiado por um buffer fixo de 8 KiB (fgets), então uma única chamada retorna no máximo ~8191 bytes de uma linha muito longa. Use isso quando precisar da linha bruta (piping linha a linha); use `prompt` quando quiser o newline removido.

read_int

pub fn read_int() -> !i32

Lê uma linha, remove espaços em branco e faz o parse — o único !T nesses módulos. Inspecione com .ok / .val / .err, ou propagule com ?.

read_bytes

pub fn read_bytes(n: i32) -> string

Lê até n bytes brutos; a string retornada pode ser mais curta que n próximo ao EOF, e é "" para n <= 0.

prompt / read_yes_no

pub fn prompt(msg: string) -> string
pub fn read_yes_no(prompt_msg: string) -> bool

prompt grava msg, faz flush, então lê uma linha e a retorna sem o newline final (trata tanto \n quanto \r\n). read_yes_no exibe o prompt e retorna true quando a resposta aparada começa com y ou Y.

Um programa interativo completo tocando todos os auxiliares de stdin:

import stdlib::io::*;

fn main() -> i32 {
    let name: string = prompt("name? ");
    println!("hello,", name);

    let raw: string = read_line();        // mantém o newline final
    println!("raw line len:", raw.len());

    let age: !i32 = read_int();
    if age.ok { println!("age:", age.val); }
    else { println!("not an i32:", age.err); }

    if read_yes_no("continue? ") {
        println!("continuing");
    }

    let chunk: string = read_bytes(4096);
    println!("read", chunk.len(), "bytes");
    return 0;
}

Saída padrão e stderr

Função Assinatura Descrição
io_write fn io_write(s: string) Grava s em stdout, sem newline. Chamado io_write (não write) para evitar colisão com write(2) da libc.
io_write_bytes fn io_write_bytes(s: string, n: i32) Grava exatamente n bytes de s — seguro para NULs embutidos.
flush fn flush() Força o flush do stdout. Importa para prompts/progresso quando stdout é um pipe (buffer de bloco).
eprintln fn eprintln(s: string) Grava s em stderr com um newline final. Stderr não tem buffer, então não é necessário flush.
import stdlib::io::*;

fn main() -> i32 {
    io_write("> ");        // sem newline; precisa de flush para aparecer antes de uma leitura
    flush();

    let header: string = "ABCDEFGHIJKLMNOP";
    io_write_bytes(header, 16);   // gravação em stdout segura para binário
    io_write("\n");
    flush();

    eprintln("warning: missing config file, using defaults");
    return 0;
}