use std::{
env::{self},
error::Error as ErrorTrait,
fs,
io::{Error, ErrorKind},
};
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case: bool,
pub with_line_number: bool,
}
impl Config {
pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, Error> {
args.next();
let query = match args.next() {
Some(value) => value.clone(),
None => return Err(Error::new(ErrorKind::NotFound, "Query is empty!")),
};
let file_path = match args.next() {
Some(value) => value.clone(),
None => return Err(Error::new(ErrorKind::NotFound, "File path is empty!")),
};
let ignore_case = env::var("IGNORE_CASE").is_ok();
let with_line_number = String::from("-l") == args.next().unwrap_or(String::new());
Ok(Config {
query,
file_path,
ignore_case,
with_line_number,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn ErrorTrait>> {
let Config {
query,
file_path,
ignore_case,
with_line_number,
} = config;
let contents = fs::read_to_string(&file_path)?;
let results = if ignore_case {
search_case_insensitive(&query, &contents)
} else {
search(&query, &contents)
};
for (line_number, line_content) in results {
if with_line_number {
println!("{line_number}| {line_content}")
} else {
println!("{line_content}")
}
}
Ok(())
}
fn search<'a>(query: &str, contents: &'a str) -> Vec<(usize, &'a str)> {
let mut results = Vec::new();
for line_info in contents.lines().into_iter().enumerate() {
if line_info.1.contains(query) {
results.push((line_info.0 + 1, line_info.1));
}
}
results
}
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<(usize, &'a str)> {
let mut results = Vec::new();
for line_info in contents.lines().into_iter().enumerate() {
if line_info.1.to_lowercase().contains(&query.to_lowercase()) {
results.push((line_info.0 + 1, line_info.1));
}
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape";
assert_eq!(
vec![(2, "safe, fast, productive.")],
search(query, contents)
)
}
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec![(1, "Rust:"), (4, "Trust me.")],
search_case_insensitive(query, contents)
)
}
}