Projeto: ferramenta de linha de comando
LidoNesta aula, você construirá uma ferramenta de linha de comando em Rust, aprendendo a estruturar o projeto, usar a crate clap para parsing de argumentos, ler arquivos e formatar a saída. O objetivo é consolidar os conceitos em um projeto prático e reutilizável.
Nesta aula, vamos colocar em prática os conhecimentos adquiridos ao longo do curso, desenvolvendo uma ferramenta de linha de comando (CLI) completa em Rust. Você aprenderá a estruturar um projeto, utilizar a crate clap para processar argumentos, ler arquivos de forma eficiente e apresentar resultados de maneira clara e formatada.
Ao final, você terá uma ferramenta funcional que pode ser estendida e adaptada para suas próprias necessidades. Vamos construir um utilitário de busca textual simples, mas poderoso, que lê um arquivo e exibe as linhas que correspondem a um padrão, com numeração de linhas e destaque.
Estrutura
Um projeto Rust bem organizado é essencial para manutenção e escalabilidade. Para uma CLI, a estrutura mais comum é:
meu_comando/Vamos criar um novo projeto com o Cargo:
cargo new meu_comando --binIsso gera a seguinte estrutura:
meu_comando/Dentro do diretório, temos o arquivo Cargo.toml que define as dependências e metadados, e o diretório src com o arquivo main.rs. Para projetos maiores, podemos dividir o código em módulos. Por exemplo, podemos ter um módulo cli.rs para a definição da interface de linha de comando, um módulo file_reader.rs para leitura de arquivos, e um módulo output.rs para formatação da saída. A estrutura ficaria assim:
meu_comando/Em main.rs, declaramos os módulos e a função principal:
mod cli;Essa organização ajuda a manter o código limpo e facilita a testabilidade.
clap
A crate clap é a mais popular para construção de interfaces de linha de comando em Rust. Ela permite definir argumentos, opções e subcomandos de forma declarativa, com geração automática de ajuda e mensagens de erro.
Primeiro, adicionamos a dependência ao Cargo.toml:
[dependencies]Em seguida, podemos usar a API derivada (derive) para definir nossa estrutura de argumentos. Vamos criar um comando que aceita um padrão de busca e um caminho de arquivo:
use clap::Parser;Aqui, Parser é um trait que gera o parsing a partir da estrutura. O campo pattern é o primeiro argumento posicional, e path é o segundo. A docstring (///) é usada para gerar a mensagem de ajuda.
No main.rs, chamamos Cli::parse() para obter os argumentos:
let args = Cli::parse();Podemos testar a execução: cargo run -- "padrão" "arquivo.txt". O clap cuida de validações, como número de argumentos, e exibe ajuda se necessário.
Leitura de arquivos
Para ler o conteúdo de um arquivo, usamos a biblioteca padrão. Vamos usar std::fs::read_to_string, que lê o arquivo inteiro em uma String. Isso é adequado para arquivos de texto moderados.
use std::fs;Tratamos o erro com expect ou melhor, com match:
let content = fs::read_to_string(&args.path)Para arquivos grandes, seria melhor usar BufReader e ler linha por linha. Vamos implementar uma função que retorna um iterador de linhas:
use std::io::{BufRead, BufReader};Com isso, podemos percorrer as linhas sem carregar o arquivo inteiro na memória.
Saída formatada
Para apresentar os resultados, vamos formatar a saída de forma legível. Queremos exibir o número da linha e a linha correspondente, talvez destacando o padrão. Vamos usar a função println! com formatação.
for (index, line) in lines.enumerate() {Para destacar o padrão, podemos usar a crate colored (opcional) ou simplesmente usar códigos ANSI. Vamos manter simples: exibir a linha na íntegra.
Exemplo de saída:
1: linha de exemplo com padrãoPodemos adicionar uma opção --verbose para mostrar mais detalhes, como o nome do arquivo.
Também é importante tratar o caso em que nenhuma linha corresponde: exibir uma mensagem informativa.
Boas práticas e observações finais
Ao construir uma CLI, é importante:
- Usar
clappara validação e ajuda automática. - Tratar erros de forma clara, usando
Resulte mensagens amigáveis. - Separar a lógica de negócio da apresentação.
- Escrever testes para as funções principais.
- Documentar o código e o uso da ferramenta.
Com esses conceitos, você pode estender a ferramenta com opções como ignorar maiúsculas, recursão em diretórios, etc.
Referências
- Documentação oficial do Cargo
- Documentação da crate clap
- Documentação de std::fs
- Trait BufRead
- Capítulo 12 do Livro de Rust (Projeto CLI)
- Clippy - Lints para Rust
Exercícios
- Modifique a estrutura para incluir um módulo
search.rsque contenha uma funçãosearch_linesque recebe o padrão e o conteúdo e retorna um vetor de tuplas (número da linha, texto). Implemente a função e use-a nomain.rs.
// src/search.rsEm main.rs:
mod search;- Adicione uma opção
--ignore-casepara que a busca seja insensível a maiúsculas/minúsculas. Use a crateregex? Não, useto_lowercase().
use clap::Parser;Na função de busca, compare com to_lowercase().
- Implemente a leitura do arquivo linha por linha usando
BufReaderelines(), retornando um iterador. Ajuste omainpara usar essa leitura.
use std::fs::File;- Adicione uma opção
--countque, em vez de exibir as linhas, exibe apenas o número de correspondências.
if args.count {- Escreva testes unitários para a função
search_lines(ou similar) cobrindo casos com e sem correspondências, e comignore_case.
#[cfg(test)]