🔎 Buscador CLI: Indexação e Busca Eficiente de Arquivos de Texto em Rust.
- #Rust
- ✅ Rust 🦀 ✅ CLI ✅ Indexação Recursiva ✅ Testes Automatizados
Introdução
Buscador CLI é uma aplicação de linha de comando desenvolvida em Rust 🦀 para indexação rápida e busca eficiente de arquivos de texto .txt E.md em diretórios locais. Permite buscas por palavras ou frases, com opção de busca sensível a maiúsculas/minúsculas, exibindo o resultado de forma clara e detalhada.
✅ Indexação automática de arquivos de texto (busca recursiva);
✅ Busca rápida por palavras ou frases completas;
✅ Exibe nome do arquivo, número da linha e conteúdo;
✅ Suporte opcional a <b>Case Sensitive;
✅ Interface amigável no terminal;
✅ Inclui testes automatizados unitários, de integração e performance;
✅ Nível técnico:</b> Intermediário em Rust.
Justificativa Didática
Desenvolver uma aplicação de busca de arquivos em Rust é um excelente exercício para quem está aprendendo a linguagem, pois envolve conceitos fundamentais como manipulação de arquivos, recursão, tratamento de erros, uso de crates externos e testes automatizados.
Por que é necessário?
Permite praticar leitura e escrita de arquivos, navegação em diretórios, uso de structs e enums, além de integração com bibliotecas da comunidade como clap.
Vantagens gerais:
✅ Organização modular do código;
✅ Facilidade de manutenção e extensão;
✅ Testabilidade e robustez;
✅ Ganho técnico: Implementação recursiva e robusta para indexação de arquivos, com tratamento de erros idiomático usando “Result” e “ ? “.
Arquitetura da Solução
A aplicação é dividida em três módulos principais:
✅ Ponto de entrada, orquestra a execução e interação com o usuário;
✅ config.rs : Responsável pelo parsing dos argumentos de linha de comando usando clap;
✅ indexer.rs</b>: Faz a leitura, indexação e filtragem dos arquivos de texto.
O projeto suporta diferentes formas de utilização: busca em arquivos, diretórios e subdiretórios, com filtros flexíveis.
Aplicação dos princípios SOLID:
- Single Responsibility: Cada módulo tem uma responsabilidade clara (configuração, indexação, execução principal).
- Open/Closed: Fácil de estender para novos tipos de arquivos ou filtros sem alterar o núcleo.
- Dependency Inversion: Uso de crates externos desacoplados (clap, e tempfile para testes).
Implementação Detalhada: indexer.rs
Estrutura principal:
- Define a struct FileLine para armazenar metadados de cada linha lida;
- Importações: std::fs, std::io, std::path para manipulação de arquivos e diretórios;
- Dependências: tempfile para testes automatizados;
- Função principal: read_text_files faz a leitura recursiva dos arquivos, filtrando apenas .txt e .md, e retorna um vetor de FileLine.
Melhorias e Estratégias:
✅ Filtro Correto de Extensões:</b> Apenas arquivos <code>.txt</code> e <code>.md</code> são processados.
✅ Contagem Precisa de Linhas:</b> Todas as linhas, inclusive vazias, são lidas corretamente.</li>
✅ Tratamento de Erros:</b> Uso do operador <code>?</code> para propagação imediata de erros e verificação da existência do diretório.</li>
✅ Recursão Confiável:</b> Subdiretórios são processados de forma recursiva e segura.</li>
Exemplo de código src/indexer.rs:
use std::fs;
use std::io::{self, BufRead};
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct FileLine {
pub file: PathBuf,
pub line_number: usize,
pub content: String,
}
pub fn read_text_files(dir: &Path) -> io::Result<Vec<FileLine>> {
if !dir.exists() {
return Err(io::Error::new(io::ErrorKind::NotFound, "Diretório não encontrado"));
}
let mut results = Vec::new();
if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
let sub_results = read_text_files(&path)?;
results.extend(sub_results);
} else if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
let ext = ext.to_lowercase();
if ext == "txt" || ext == "md" {
let file = fs::File::open(&path)?;
let reader = io::BufReader::new(file);
for (i, line) in reader.lines().enumerate() {
let line = line?;
results.push(FileLine {
file: path.clone(),
line_number: i + 1,
content: line,
});
}
} else {
println!("➡️ Ignorando arquivo não suportado: {}", path.display());
}
}
}
}
Ok(results)
}
Implementação Detalhada: config.rs
Uso da biblioteca clap. Facilita o parsing de argumentos de linha de comando, tornando a interface mais amigável e robusta.
Resumo:
- Define a configuração do programa;
- Permite ao usuário especificar diretório, termo de busca e sensibilidade a maiúsculas/minúsculas.
- Valores padrão tornam o programa fácil de usar mesmo sem argumentos.</li>
✅ Ponto de atenção: Sensibilidade a maiúsculas/minúsculas pode ser crucial dependendo do contexto.
Exemplo de código src/config.rs:
use clap::Parser;
/// Configuração da linha de comando
#[derive(Parser, Debug)]
#[command(name = "buscador")]
#[command(about = "Motor de busca simples para arquivos de texto", long_about = None)]
pub struct Config {
/// Diretório a ser indexado
#[arg(short, long, default_value = ".")]
pub dir: String,
/// Palavra ou termo a buscar
#[arg(short, long, default_value = "")]
pub query: String,
/// Sensível a maiúsculas/minúsculas?
#[arg(long, default_value_t = false)]
pub case_sensitive: bool,
}
Implementação Detalhada: main.rs
Importações:clap, módulos internos config, indexer, std::io e std::path.
Resumo de funcionamento:
- Exibe mensagem de boas-vindas;
- Analisa argumentos da linha de comando;
- Solicita termo de busca se não informado;
- Exibe informações do diretório e busca;
- Filtra linhas com base no termo de busca
- Exibe resultados ou mensagem de erro.
Exemplo de código rc/main.rs:
mod config;
mod indexer;
use clap::Parser;
use config::Config;
use indexer::read_text_files;
use std::path::Path;
use std::io::{self, Write};
fn main() {
println!("👋 Bem-vindo ao Buscador CLI! 🚀");
let mut args = Config::parse();
if args.query.is_empty() {
print!("Por favor, digite o termo de busca: ");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
args.query = input.trim().to_string();
if args.query.is_empty() {
eprintln!("⚠️ Nenhum termo de busca informado. Encerrando o programa.");
std::process::exit(1);
}
}
println!("📁 Diretório: {}", args.dir);
println!("🔍 Termo: {}", args.query);
println!("🔠 Case sensitive: {}", args.case_sensitive);
let path = Path::new(&args.dir);
match read_text_files(path) {
Ok(lines) => {
println!("📄 {} linhas lidas para indexação", lines.len());
let termo = &args.query;
let case_sensitive = args.case_sensitive;
let resultados: Vec<_> = lines
.into_iter()
.filter(|line| {
if case_sensitive {
line.content.contains(termo)
} else {
line.content.to_lowercase().contains(&termo.to_lowercase())
}
})
.collect();
if resultados.is_empty() {
println!("🔎 Nenhum resultado encontrado para \"{}\".", termo);
} else {
println!("✅ {} resultados encontrados:", resultados.len());
for line in resultados.iter().take(15) {
println!(
"{}:{} -> {}",
line.file.display(),
line.line_number,
line.content
);
}
}
}
Err(e) => {
eprintln!("❌ Erro ao ler arquivos: {}", e);
}
}
}
Testes
✅ Testes Automatizados
O projeto inclui testes unitários, de integração e de performance, garantindo robustez e confiabilidade. Para detalhes e demonstrações, acesse:
Canal Youtube: https://www.youtube.com/@veteranoedev
Vídeo específico: https://youtu.be/1giv8YchRR4?si=Cgdcy8MSPLfyWoDz
Benchmark: Rust vs Java.
- Performance: Rust compila para código nativo, oferecendo desempenho superior e uso eficiente de memória em comparação com Java, que depende da JVM.</li>
- Segurança:</b> O sistema de tipos e o borrow checker de Rust previnem erros comuns de concorrência e acesso à memória, reduzindo bugs em aplicações de busca intensiva.</li>
✅ Considerações Finais
- Lições aprendidas: Modularização, uso de crates externos, testes automatizados e boas práticas de tratamento de erros são essenciais para aplicações robustas em Rust.
- Limitações: A busca recursiva pode ser custosa em diretórios muito grandes. Não há suporte a buscas paralelas ou indexação incremental.
- ✅ Melhorias possíveis:
- Adicionar busca paralela para maior performance;
- Implementar cache de indexação para grandes volumes de dados.</li>
Referências:
✅ Autor: Sandro Reis (com apoio de prompts de IA).
✅ Repositório: https://github.com/consultorsandro/buscador-cli.git
✅ Documentação Rust: https://doc.rust-lang.org/book/
✅ Canal Youtube: https://www.youtube.com/@veteranoedev