Skip to content

Commit

Permalink
Regex support is also added.
Browse files Browse the repository at this point in the history
  • Loading branch information
Raghav-Bell committed Sep 8, 2023
1 parent 7235883 commit f1fc57f
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 12 deletions.
47 changes: 46 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "graby"
version = "0.1.1"
version = "1.0.0"
edition = "2021"
authors = ["Raghav <[email protected]>"]
license = "MIT"
Expand All @@ -17,3 +17,4 @@ categories = ["command-line-utilities"]
[dependencies]
clap = { version = "4.3.19", features = ["derive"] }
anyhow = "1.0"
regex = "1.9.5"
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

# Introduction
This is a small implementation of `grep` command line tool in rust (see References).
Unlike `grep` this implementation does not support `regex`.
From version 1.0.0 onwards regular expressions or `regex` is also supported.
For complete implementation of `grep` in rust, check <a href="https://github.com/BurntSushi/ripgrep"> `ripgrep`</a>.

## Installation
Expand All @@ -17,14 +17,15 @@ cargo add graby
```
or manually add following in `Cargo.toml` file.
```
graby = "0.1.1" # graby = "version"
graby = "1.0.0" # graby = "version"
```
To build `graby` from source you need to install rust on your device and run the following commands:
```
git clone https://github.com/Raghav-Bell/graby.git
cd graby
cargo run -- --help
```
or you can also build it from released binaries.
## Usage
For searching `QUERY` pattern in `FILE_PATH` use following command:
```
Expand All @@ -33,6 +34,18 @@ graby --q QUERY --f FILE_PATH
For more options run
```
graby --help
Usage: graby.exe [OPTIONS] --query <QUERY> --file-path <FILE_PATH>
Options:
-q, --query <QUERY> Pattern to search in the file
-r, --regex-match Take pattern as regular expression
-f, --file-path <FILE_PATH> Path to the file
-i, --ignore-case Ignore case distinctions while searching QUERY in FILE_PATH
-v, --invert-match Print the lines without QUERY pattern in the FILE_PATH
-h, --help Print help
-V, --version Print version
```
or check [documentation](https://docs.rs/graby/0.1.0/graby/).
<br>It is licensed under MIT.
Expand Down
29 changes: 21 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![allow(unused)]
use anyhow::{Context, Result};
pub mod regex_feature;
use clap::Parser;
use std::io::{self, read_to_string, BufRead, BufReader, Write};
use std::{error::Error, fs::File, path::PathBuf};
Expand All @@ -11,6 +12,9 @@ pub struct Config {
/// Pattern to search in the file.
#[arg(short, long)]
pub query: String,
/// Take pattern as regular expression.
#[arg(short = 'r', long)]
pub regex_match: bool,
/// Path to the file.
#[arg(short, long)]
pub file_path: PathBuf,
Expand All @@ -21,16 +25,25 @@ pub struct Config {
#[arg(short = 'v', long)]
pub invert_match: bool,
}
/// Search for the query pattern in the given file and display the lines that contain it.
/// Checks if pattern is regular expression or not.
pub fn run(config: Config) -> Result<()> {
if config.regex_match {
regex_feature::run_regex(config)
} else {
run_string(config)
}
}

/// Search for the string query in the given file and display the lines that contain it.
pub fn run_string(config: Config) -> Result<()> {
// Open the file
let file = File::open(&config.file_path)
.with_context(|| format!("could not open the file `{:?}`", &config.file_path))?;
// Read the file in buffer (8 KB).
let reader = BufReader::new(file);
let contents = read_to_string(reader)
.with_context(|| format!("could not read the file `{:?}`", &config.file_path))?;
// Searching for the query with ignore_case option in the contents.
// Searching for the query string with ignore_case option in the contents.
let results = if config.ignore_case {
search_case_insensitive(&config.query, &contents, config.invert_match)
} else {
Expand All @@ -43,7 +56,7 @@ pub fn run(config: Config) -> Result<()> {
}
Ok(())
}
/// This function search for the query with exact case.
/// This function search for the query string with exact case.
pub fn search<'a>(query: &str, contents: &'a str, invert: bool) -> Vec<&'a str> {
let mut results = Vec::new();
//Branches if invert_match option is active or not.
Expand All @@ -62,9 +75,9 @@ pub fn search<'a>(query: &str, contents: &'a str, invert: bool) -> Vec<&'a str>
}
results
}
/// This function search for the query without case distinction.
/// This function search for the query string without case distinction.
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str, invert: bool) -> Vec<&'a str> {
// Lower the case of query pattern.
// Lower the case of query string.
let query = query.to_lowercase();
let mut results = Vec::new();
if invert {
Expand All @@ -87,7 +100,7 @@ pub fn search_case_insensitive<'a>(query: &str, contents: &'a str, invert: bool)
mod tests {
use super::*;
#[test]
// Test for case sensitive search function.
// Test for case sensitive `search` function.
fn case_sensitive() {
let query = "duct";
let contents = "\
Expand All @@ -101,7 +114,7 @@ Duckt three.";
}

#[test]
// Test for case search_case_insensitive function.
// Test for `case search_case_insensitive function`.
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Expand All @@ -115,7 +128,7 @@ Trust me.";
);
}
#[test]
// Test for the invert_match option.
// Test for the `invert_match` option.
fn invert_search() {
let query = "Duckt";
let contents = "\
Expand Down
112 changes: 112 additions & 0 deletions src/regex_feature.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#![allow(unused)]
use crate::Config;
use anyhow::{Context, Result};
use regex::Regex;
use std::io::{self, read_to_string, BufRead, BufReader, Write};
use std::{error::Error, fs::File, path::PathBuf};

/// Search for the query regex in the given file and display the lines that contain it.
pub fn run_regex(config: Config) -> Result<()> {
// Open the file
let file = File::open(&config.file_path)
.with_context(|| format!("could not open the file `{:?}`", &config.file_path))?;
// Read the file in buffer (8 KB).
let reader = BufReader::new(file);
let contents = read_to_string(reader)
.with_context(|| format!("could not read the file `{:?}`", &config.file_path))?;

// Searching for the query regex with ignore_case option in the contents.
let results = if config.ignore_case {
// adding regex ignore case flag in query string.
let query_formatted = format!("(?i){}", &config.query);
// Making regex from given `query_formatted`.
let query_re = Regex::new(&query_formatted[..])
.with_context(|| format!("given regular expression `{:?}` is wrong", &config.query))?;
search_regex(&query_re, &contents, config.invert_match)
} else {
// Making regex from given query.
let query_re = Regex::new(&config.query)
.with_context(|| format!("given regular expression `{:?}` is wrong", &config.query))?;
search_regex(&query_re, &contents, config.invert_match)
};

// Writing buffer of lines on the terminal which satisfies the command.
for line in results {
writeln!(io::BufWriter::new(io::stdout().lock()), "{line}");
}
Ok(())
}
/// This function search for the query regex with exact case.
pub fn search_regex<'a>(query_re: &Regex, contents: &'a str, invert: bool) -> Vec<&'a str> {
let mut results = Vec::new();

//Branches if `invert_match` option is active or not.
if invert {
for line in contents.lines() {
if !query_re.is_match(line) {
results.push(line);
}
}
} else {
for line in contents.lines() {
if query_re.is_match(line) {
results.push(line);
}
}
}
results
}

/// Small unitest to check the functionality of the program.
#[cfg(test)]
mod tests {
use super::*;
#[test]
// Test for exact case regex search function.
fn regex_case_sensitive() {
let query = Regex::new("HR[0-9]{3}-[0-9]{3}-[0-9]{4}").unwrap();
let contents = "\
Rust:
safe, fast, productive.
Duckt three.
phone: HR111-222-3333";
assert_eq!(
vec!["phone: HR111-222-3333"],
search_regex(&query, contents, false)
);
}

#[test]
// Test for ignore case regex search.
fn regex_case_insensitive() {
let query = Regex::new("(?i)rUsT[0-9]{3}").unwrap();
let contents = "\
Rust123:
RUst324
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust123:", "RUst324"],
search_regex(&query, contents, false)
);
}
#[test]
// Test for the `invert_match` option.
fn regex_invert_search() {
let query = Regex::new("Duckt[0-9]{3}").unwrap();
let query_insensitive = Regex::new("(?i)Duckt[0-9]{3}").unwrap();
let contents = "\
safe, fast, productive.
Duckt567 three.
duckt980";
assert_ne!(
vec!["Duckt567 three."],
search_regex(&query, contents, true)
);
assert_eq!(
vec!["safe, fast, productive."],
search_regex(&query_insensitive, contents, true)
);
}
}

0 comments on commit f1fc57f

Please sign in to comment.