Skip to content

9shrey/rustgrep

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rustgrep

A grep-like CLI tool I built in Rust to learn the language properly — ownership, error handling, concurrency, all of it. It searches for text patterns across files and directories, kind of like a stripped-down ripgrep.

What it does

$ rustgrep Rust src/
src/main.rs:use std::path::Path;
src/search.rs:/// Returns true if `line` contains `query`, respecting case sensitivity.
src/cli.rs:/// A fast file search tool inspired by ripgrep

Give it a pattern and a path. It'll search recursively through directories, print matching lines with filenames, and skip over anything it can't read (binary files, permission errors, etc).

Flags

Flag What it does
-i Case-insensitive matching
-n Show line numbers
-c Highlight matches in red (ANSI colors)
-p Parallel search across files using rayon

You can combine them:

rustgrep -i -n -c -p "error" /var/log/

Building

cargo build --release

Binary ends up in target/release/rustgrep.

Project layout

src/
  main.rs     — entry point, wires everything together
  cli.rs      — argument parsing with clap
  search.rs   — the actual search logic, file walking, parallel mode
tests/
  integration_test.rs — end-to-end tests against the binary

I kept it to three source files on purpose. cli.rs owns the config, search.rs owns the logic, main.rs just calls both and handles errors. No over-abstraction.

How the search works

  1. Parse CLI args into a Config struct (clap does the heavy lifting)
  2. Check if the target path is a file or directory
  3. If it's a directory, walk it recursively collecting files
  4. For each file, read it line-by-line with BufReader
  5. Check each line against the query (with to_lowercase() for -i mode)
  6. Print matches in file:line format

With -p, step 4-6 runs in parallel across all collected files using rayon's par_iter. On a directory with 500 small files, this gave roughly a 1.2x speedup in benchmarks — not huge for tiny files, but the gap widens with larger codebases.

Error handling

No unwrap() calls in the final code. Everything returns Result, errors propagate with ?, and main() is the only place that prints error messages and sets the exit code. Files that fail to open just get a stderr warning and the search keeps going — same behavior as real grep.

Tests

cargo test

9 unit tests covering search_line and highlight_match edge cases (case sensitivity, empty strings, no-match scenarios). 9 integration tests that spawn the actual binary and check stdout/stderr/exit codes for each feature.

What I learned building this

  • Rust's ownership model clicked once I had to pass &Config through multiple function layers without cloning
  • The difference between String and &str stops being confusing once you think "who owns this data?"
  • ? operator makes error handling feel lightweight instead of painful
  • rayon is almost cheating — changing .iter() to .par_iter() and everything just works because &Config is already Send + Sync
  • BufReader matters — reading files line-by-line without it would be painfully slow

Dependencies

  • clap — CLI argument parsing
  • rayon — data parallelism

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages