Article image
Sandro Reis
Sandro Reis25/06/2025 11:56
Compartilhe

🔎 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&lt;Vec&lt;FileLine&gt;&gt; {

  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

Compartilhe
Comentários (2)
DIO Community
DIO Community - 25/06/2025 12:42

Sandro, que projeto incrível! Seu artigo mostra com clareza, organização e didática como unir Rust, boas práticas de engenharia de software e foco em performance para resolver um problema real. A forma como você estruturou a aplicação reflete maturidade técnica, e a atenção aos princípios SOLID e aos testes automatizados reforça um compromisso com qualidade que inspira qualquer pessoa em formação.

Na DIO, valorizamos justamente esse tipo de iniciativa: transformar aprendizado em aplicação prática, compartilhando com a comunidade soluções que não apenas funcionam, mas também educam. O buscador CLI não é só uma ferramenta útil, é um excelente exemplo de como Rust pode ser usado para criar aplicações eficientes, modulares e seguras desde o início da jornada.

Na sua visão, qual foi o ponto mais desafiador no processo de desenvolvimento e o que você aprendeu com ele? 

Sandro Reis
Sandro Reis - 25/06/2025 12:01

Editorzinho difícil...