#![deny(missing_docs)]
use {
colored::Colorize,
std::{env, error::Error, fs::File, io::Read},
};
const ARGS_LIMIT: usize = 4;
pub struct Config {
pub file: String,
pub pattern: String,
}
impl Config {
pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
if args.len() >= ARGS_LIMIT {
return Err("Expected 2 arguments but more than 2 arguments were provided!");
}
args.next();
let (file, pattern): (String, String) = (
match args.next() {
Some(arg) => arg,
None => return Err("No file provided!"),
},
match args.next() {
Some(arg) => arg.to_lowercase(),
None => return Err("No pattern provided!"),
},
);
Ok(Config { file, pattern })
}
}
pub fn search<'a>(pattern: &str, content: &'a str) -> Vec<&'a str> {
content
.lines()
.filter(|line: &&str| line.to_lowercase().contains(&pattern.to_ascii_lowercase()))
.collect()
}
pub fn summarize(occurrence: usize, line_count: usize) -> String {
let result_summary: String;
if occurrence == 0 {
result_summary = format!(
"{:1} {:1} {:1}",
"Found".red(),
occurrence.to_string().red(),
"occurrence!".red()
)
} else {
let (mut occurrence_word, mut line_word): (String, String) =
(String::from("occurrence"), String::from("line"));
if occurrence > 1 {
occurrence_word.push('s');
}
if line_count > 1 {
line_word.push('s');
}
result_summary = format!(
"{:1}\n{:1} {:1} {:1} {:1} {:1} {:1} {:1}{:1}",
"--Summary--".yellow(),
"*".green().bold(),
"Found".green(),
occurrence.to_string().trim().blue().bold(),
occurrence_word.green(),
"in".green(),
line_count.to_string().blue().bold(),
line_word.green(),
"!".green()
);
}
result_summary
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let (mut file, mut data): (File, String) = (File::open(&config.file)?, String::new());
file.read_to_string(&mut data)?;
let result: Vec<&str> = search(&config.pattern, &data);
let (mut line_count, mut total_occurrence): (usize, usize) = (0usize, 0usize);
let mut output: String = String::new();
for line in result {
let occurrence: usize = line.to_ascii_lowercase().matches(&config.pattern).count();
total_occurrence += occurrence;
let formatted_output: String = format!(
"{:1} | {:1} |\n",
line.magenta(),
occurrence.to_string().yellow().bold()
);
output.push_str(&*formatted_output);
line_count += 1;
}
let input_summary: String = format!(
"{:1} {:1} {:1} {:1}\n",
"Search result for".green(),
&config.pattern.bright_cyan().bold(),
"in".green(),
&config.file.bright_cyan().bold(),
);
println!(
"\n{:1}\n{:1}\n{:1}",
input_summary,
output,
summarize(total_occurrence, line_count)
);
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn case_sensitive() {
let pattern: &str = "is";
let data: &str = "Hello There!\nThis is just a random text.\nThere is no errors.\n";
assert_eq!(
vec!["This is just a random text.", "There is no errors."],
search(pattern, data)
);
}
#[test]
fn case_insensitive() {
let pattern: &str = "tH";
let data: &str =
"Hello There!\nThis is another random text.\nThere should be no errors as well.";
assert_eq!(
vec![
"Hello There!",
"This is another random text.",
"There should be no errors as well."
],
search(pattern, data)
);
}
}