minigrep_spc 0.1.0

minigrep implementation as copied from the Rust Book
Documentation
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

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(); // discard first arg

        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,
        })
    }
}


/// Run function
///
/// # Examples
///
/// ```
/// use minigrep_spc::Config;
/// use std::env;
/// use std::process;
///
/// 
/// let arguments: Vec<String> = vec![String::from("one"), String::from("one"), String::from("poem.txt") ];
/// let config = Config::new(arguments.into_iter()).unwrap_or_else( |err| {
///     eprintln!("Problem parsing arguments: {}", err);
///     process::exit(1);
/// });
/// if let Err(e) = minigrep_spc::run(config) {
///    eprintln!("Application error: {}", e);
///    process::exit(1);
/// };
///
/// ```
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() {
        //setup test
        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> > {
        //setup test
        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));   
    }

}