use colored::*;
use std::error::Error;
use std::{fs, vec};
use std::collections::HashMap;
pub struct Arguments {
pub query: String, pub file_name: String, pub help_option: bool, pub view_version: bool, pub case_ignore: bool, pub line_number: bool, pub query_count: bool, pub non_match: bool, pub line_count: bool, pub view_stats: bool, }
pub struct LineInfo<'a> {
pub line_number: usize,
pub line_content: &'a str,
}
pub struct SearchResult<'a> {
pub line_info: Vec<LineInfo<'a>>,
pub count: usize,
pub line_count: usize,
}
impl Default for Arguments {
fn default() -> Self {
Arguments {
query: Default::default(),
file_name: Default::default(),
help_option: false,
view_version: false,
case_ignore: false,
line_number: false,
query_count: false,
non_match: false,
line_count: false,
view_stats: false,
}
}
}
impl Arguments {
pub fn new(args: &[String]) -> Result<Arguments, &'static str> {
let error_message = Err("Unknown command, run 'cargo new minigrep_help' to learn more\n");
if args.len() == 2 {
if args[1].eq("minigrep_help") {
return Ok(Arguments {
help_option: true,
..Default::default()
});
}
} else if args.len() == 3 {
if args[1].eq("minigrep") {
if args[2].eq("-v") || args[2].eq("--version") {
return Ok(Arguments {
view_version: true,
..Default::default()
});
}
}
else if args[2].eq("-S") || args[2].eq("--stats") {
return Ok(Arguments {
file_name: args[1].clone(),
view_stats: true,
..Default::default()
});
}
else {
return Ok(Arguments {
query: args[1].clone(),
file_name: args[2].clone(),
..Default::default()
});
}
} else if args.len() >= 4 {
let option_list = &args[3..];
let mut argument = Arguments {
query: args[1].clone(),
file_name: args[2].clone(),
..Default::default()
};
if option_list.contains(&"-i".to_string())
|| option_list.contains(&"--ignore-case".to_string())
{
argument.case_ignore = true;
}
if option_list.contains(&"-n".to_string())
|| option_list.contains(&"--line-number".to_string())
{
argument.line_number = true;
}
if option_list.contains(&"-c".to_string())
|| option_list.contains(&"--query-count".to_string())
{
argument.query_count = true;
}
if option_list.contains(&"-lc".to_string())
|| option_list.contains(&"--line-count".to_string())
{
argument.line_count = true;
}
if option_list.contains(&"-I".to_string())
|| option_list.contains(&"--invert-match".to_string())
{
argument.non_match = true;
}
return Ok(argument);
}
return error_message;
}
}
pub fn run(arguments: Arguments) -> Result<(), Box<dyn Error>> {
if arguments.help_option {
view_help_menu();
return Ok(());
}
if arguments.view_version {
view_version();
return Ok(());
}
if arguments.view_stats {
view_file_stats(arguments);
return Ok(());
}
let contents = fs::read_to_string(&arguments.file_name)?;
println!("\n{}", format!("{}:", &arguments.file_name).yellow());
let search_result = if arguments.case_ignore {
search_case_insensitive(&arguments.query, &contents)
}
else if arguments.non_match {
search_invert_match(&arguments.query, &contents)
}
else {
search(&arguments.query, &contents)
};
let lines = search_result.line_info;
if lines.len() == 0 {
println!("No results");
}
if arguments.line_number {
for line in lines {
println!("{}: {}", format!("{}", line.line_number).cyan(), line.line_content);
}
} else {
for line in lines {
println!("{}", line.line_content);
}
}
if arguments.query_count {
println!("{}", format!("\nCount: {}", search_result.count).green().bold());
}
else {
println!("");
}
if arguments.line_count {
println!("{}", format!("Line count: {}\n", search_result.line_count).green().bold());
}
Ok(())
}
pub fn search<'a>(query: &str, contents: &'a str) -> SearchResult<'a> {
let mut line_info: Vec<LineInfo> = vec![];
let mut count = 0;
let mut line_count = 0;
for (line_no, line) in contents.lines().into_iter().enumerate() {
if line.contains(query) {
line_info.push(LineInfo {
line_number: line_no,
line_content: line,
});
count += line.matches(query).count();
line_count += 1;
}
}
SearchResult { line_info, count, line_count}
}
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> SearchResult<'a> {
let query = query.to_lowercase();
let mut line_info: Vec<LineInfo> = vec![];
let mut count = 0;
let mut line_count = 0;
for (line_no, line) in contents.lines().into_iter().enumerate() {
if line.to_lowercase().contains(&query) {
line_info.push(LineInfo {
line_number: line_no,
line_content: line,
});
count += line.to_ascii_lowercase().matches(&query).count();
line_count += 1;
}
}
SearchResult { line_info, count, line_count}
}
pub fn search_invert_match<'a>(query: &str, contents: &'a str) -> SearchResult<'a> {
let mut line_info: Vec<LineInfo> = vec![];
let mut line_count = 0;
for (line_no, line) in contents.lines().into_iter().enumerate() {
if !line.contains(query) {
line_info.push(LineInfo {
line_number: line_no,
line_content: line,
});
line_count += 1;
}
}
SearchResult { line_info, count: 0, line_count}
}
pub fn view_file_stats(arguments: Arguments) {
let contents = fs::read_to_string(&arguments.file_name).expect("Unknown File");
let contents = contents.to_lowercase();
let words_list: Vec<&str> = contents.split_whitespace().collect();
let mut word_count = HashMap::new();
let mut char_count = 0;
let mut line_count = 0;
for _line in contents.lines().into_iter() {
line_count += 1;
}
println!("\n{}\n", format!("{}", arguments.file_name).yellow().bold());
for word in words_list {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("{}", format!("{0: <10} | {1: <10}", "Sequence", "Frequency").bold());
println!("{}", format!("----------------------").bold());
for (word, frequency) in word_count {
println!("{0: <10} {1} {2: <10}", word, format!("|").bold(), frequency);
char_count += word.len();
}
println!("\n{}", format!("{}: {}", "Character count", char_count).bold());
println!("{}\n", format!("{}: {}", "Line count", line_count).bold());
}
pub fn view_help_menu() {
println!("\nRust {} help menu\n", format!("minigrep").yellow().bold());
println!(
"{}: cargo run [QUERY] [FILE_NAME] [OPTION]",
format!("Usage").cyan().bold()
);
println!(
"{}: Search for QUERY i.e a word in the FILE_NAME provided",
format!("Description").cyan().bold()
);
println!(
"{}: 'cargo run hello hello_world.txt -i'\n",
format!("Example").cyan().bold()
);
println!("{}", format!("Pattern Selection:").yellow().bold());
println!(
"{}\tignore case distinctions\t\t'cargo run hello hello_world.txt -i'",
format!(" -i, --ignore-case").blue().bold()
);
println!("{}", format!("\nOutput Control:").yellow().bold());
println!(
"{}\tprint line numbers with output lines \t'cargo run hello hello_world.txt -n'",
format!(" -n, --line-number").blue().bold()
);
println!(
"{}\toutput total occurences of query \t'cargo run butter recipe.txt -c'",
format!(" -c, --query-count").blue().bold()
);
println!(
"{}\toutput total lines containing query \t'cargo run phone devices.txt -lc'",
format!(" -lc, --line-count").blue().bold()
);
println!(
"{}\toutput non-matching lines \t\t'cargo run puppy pets.txt -I'",
format!(" -I, --invert-match").blue().bold()
);
println!("{}", format!("\nMiscellaneous:").yellow().bold());
println!(
"{}\t\tdisplays file statistics\t\t'cargo run war_and_peace.txt -S'",
format!(" -S, --stats").blue().bold()
);
println!(
"{}\tdisplays version information\t\t'cargo run minigrep -v'",
format!(" -v, --version").blue().bold()
);
println!(
"{}\tdisplays help menu\t\t\t'cargo run minigrep_help'",
format!(" minigrep_help").blue().bold()
);
}
pub fn view_version() {
println!("\n{}", format!("Minigrep v1.0.0").yellow().bold());
println!("Made by Nithin for learning purposes only");
println!("https://github.com/nithinmanoj10/minigrep")
}