use std::{error::Error, fs, env}; pub struct Config { pub query: String, pub file_path: String, pub ignore_case: bool, pub case_option: bool, } impl Config { pub fn build(args: &[String]) -> Result { if args.len() < 4 { return Err("Not enough arguments"); } let query = args[1].clone(); let file_path = args[2].clone(); // String let ignore_case = env::var("IGNORE_CASE").is_ok(); let case_option = match args[3].as_str() { "true" => true, &_ => false }; Ok(Config {query, file_path, ignore_case, case_option}) } } // Box means the function will return a type that implements the Error trait // But we don’t have to specify what particular type the return value will be // This gives us flexibility to return error values that may be of different types in different error cases pub fn run(config: Config) -> Result<(), Box>{ // dyn dynamic let contents = fs::read_to_string(config.file_path)?; let result = if config.ignore_case { search_case_insensitive(&config.query, &contents) } else if config.case_option { search_case_insensitive(&config.query, &contents) } else { search(&config.query, &contents) }; for line in result { println!("{line}"); } Ok(()) } #[cfg(test)] mod test { use super::*; #[test] fn case_sensitive() { let query = "duct"; let content = "\ Rust: safe, fast, productive. Pick three. Duck tape"; assert_eq!(vec!["safe, fast, productive."], search(query,content)); } #[test] fn case_insensitive() { let query = "rUsT"; let content= "\ Rust: safe, fast, productive. Pick three. Trust me."; assert_eq!(vec!["Rust:", "Trust me."], search_case_insensitive(query, content)); } } pub fn search<'a>(query: &str, content: &'a str) -> Vec<&'a str> { let mut result = Vec::new(); for line in content.lines() { if line.contains(query) { result.push(line); } } result } pub fn search_case_insensitive<'a>(query: &str, content: &'a str) -> Vec<&'a str> { let mut result = Vec::new(); let query = query.to_lowercase(); for line in content.lines() { if line.to_lowercase().contains(&query) { result.push(line); } } result }