use std::error::Error;
use std::fs;
use std::env;
pub struct Config{
pub query: String,
pub filename:String,
pub case_sensitive: bool,
}
impl Config {
pub fn new(mut args: impl Iterator<Item=String>) -> Result<Config,&'static str> {
args.next();
let query = match args.next() {
Some(arg) => arg,
None => return Err("Did not get a query string")
};
let filename = match args.next() {
Some(arg) => arg,
None => return Err("Did not get a filename")
};
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
println!("case_sensitive: {}", case_sensitive) ;
Ok(Config{
query,
filename,
case_sensitive,
})
}
}
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.filename)?;
let results = if config.case_sensitive{
search(&config.query, &contents)
} else {
search_insensitive(&config.query, &contents)
};
for line in results {
println!("{}", line);
}
Ok(())
}
pub fn search<'a>(query:&str, contents:&'a str)
-> Vec<&'a str> {
contents.lines()
.filter(|line| line.contains(&query)).collect()
}
pub fn search_insensitive<'a>(query:&str, contents:&'a str)
-> Vec<&'a str> {
contents.lines()
.filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_case_sensitive() {
let query = "duct";
let contents = "\"
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
#[test]
fn test_case_insensitive() {
let query = "ruST";
let contents = "\"
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(vec!["Rust:", "Trust me."], search_insensitive(query, contents));
}
#[test]
fn test_config_new_constructor() {
let args : Vec<String> = vec!("executable".to_string(), "query".to_string(), "file".to_string());
let config = Config::new(args.into_iter()).unwrap();
assert_eq!(config.query, "query");
assert_eq!(config.filename, "file");
}
#[test]
#[should_panic(expected="Did not get a filename")]
fn test_config_new_constructor_bad_args() {
let args : Vec<String> = vec!("executable".to_string(), "query".to_string());
let _config = Config::new(args.into_iter()).unwrap();
}
#[test]
fn test_run_fails_if_file_not_exist() {
let bad_filename="not_exist";
let args : Vec<String> = vec!("executable".to_string(),
"query".to_string(),
bad_filename.to_string());
let config = Config::new(args.into_iter()).unwrap();
let result = run(config);
match result{
Ok(()) => {
println!("Result = Ok(())");
panic!("Test should fail to open file 'not_exist'. Delete the file doesn't exist");
},
Err(error) => {
println!("Result is an Error {:?}", error);
}
}
}
#[test]
fn test_run_succeeds_if_file_exist() -> Result<(), Box<dyn Error> > {
let filename="poem.txt";
let args : Vec<String> = vec!("executable".to_string(),
"query".to_string(),
filename.to_string());
let config = Config::new(args.into_iter()).unwrap();
run(config)
}
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Picl three/";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
}