minigrep_spc/
lib.rs

1//! # My Crate
2//!
3//! `my_crate` is a collection of utilities to make performing certain
4//! calculations more convenient.
5
6use std::error::Error;
7use std::fs;
8use std::env;
9
10pub struct Config{
11    pub query: String,
12    pub filename:String,
13    pub case_sensitive: bool,
14}
15impl Config {
16    pub fn new(mut args: impl Iterator<Item=String>) -> Result<Config,&'static str> {
17        
18        args.next(); // discard first arg
19
20        let query = match args.next() {
21            Some(arg) => arg,
22            None => return Err("Did not get a query string")
23        };
24
25        let filename = match args.next() {
26            Some(arg) => arg,
27            None => return Err("Did not get a filename")
28        };
29
30        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
31        println!("case_sensitive: {}", case_sensitive) ;
32        
33        Ok(Config{
34            query, 
35            filename,
36            case_sensitive,
37        })
38    }
39}
40
41
42/// Run function
43///
44/// # Examples
45///
46/// ```
47/// use minigrep_spc::Config;
48/// use std::env;
49/// use std::process;
50///
51/// 
52/// let arguments: Vec<String> = vec![String::from("one"), String::from("one"), String::from("poem.txt") ];
53/// let config = Config::new(arguments.into_iter()).unwrap_or_else( |err| {
54///     eprintln!("Problem parsing arguments: {}", err);
55///     process::exit(1);
56/// });
57/// if let Err(e) = minigrep_spc::run(config) {
58///    eprintln!("Application error: {}", e);
59///    process::exit(1);
60/// };
61///
62/// ```
63pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
64    let contents = fs::read_to_string(config.filename)?;   
65    let results = if config.case_sensitive{
66        search(&config.query, &contents)
67    } else {
68        search_insensitive(&config.query, &contents)
69    };
70
71    for line in results {
72        println!("{}", line);
73    }
74    Ok(())
75}
76
77pub fn search<'a>(query:&str, contents:&'a str) 
78        -> Vec<&'a str> {
79    contents.lines()
80    .filter(|line| line.contains(&query)).collect() 
81}
82pub fn search_insensitive<'a>(query:&str, contents:&'a str) 
83        -> Vec<&'a str> {
84    contents.lines()
85    .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
86    .collect() 
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    #[test]
93    fn test_case_sensitive() {
94        let query = "duct";
95        let contents = "\"
96Rust:
97safe, fast, productive.
98Pick three.
99Duct tape.";
100        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
101}
102
103#[test]
104fn test_case_insensitive() {
105    let query = "ruST";
106    let contents = "\"
107Rust:
108safe, fast, productive.
109Pick three.
110Trust me.";
111    assert_eq!(vec!["Rust:", "Trust me."], search_insensitive(query, contents));
112}
113
114#[test]
115    fn test_config_new_constructor() {
116        let args : Vec<String> = vec!("executable".to_string(), "query".to_string(), "file".to_string());
117        let config = Config::new(args.into_iter()).unwrap();
118        assert_eq!(config.query, "query");      
119        assert_eq!(config.filename, "file");      
120    }
121
122    #[test]
123    #[should_panic(expected="Did not get a filename")]
124    fn test_config_new_constructor_bad_args() {
125        let args : Vec<String> = vec!("executable".to_string(), "query".to_string());
126        let _config = Config::new(args.into_iter()).unwrap();
127    }
128
129    #[test]
130    fn test_run_fails_if_file_not_exist() {
131        //setup test
132        let bad_filename="not_exist";
133        let args : Vec<String> = vec!("executable".to_string(), 
134            "query".to_string(), 
135            bad_filename.to_string());
136        let config = Config::new(args.into_iter()).unwrap();       
137        let result = run(config);
138        match result{
139            Ok(()) => {
140                println!("Result = Ok(())");
141                panic!("Test should fail to open file 'not_exist'. Delete the file doesn't exist");
142            },
143            Err(error) => {
144                println!("Result is an Error {:?}", error);
145            }
146        }
147    }
148    #[test]
149    fn test_run_succeeds_if_file_exist() -> Result<(), Box<dyn Error> > {
150        //setup test
151        let filename="poem.txt";
152        let args : Vec<String> = vec!("executable".to_string(), 
153            "query".to_string(), 
154            filename.to_string());
155        let config = Config::new(args.into_iter()).unwrap();       
156        run(config)
157    }
158
159    #[test]
160    fn one_result() {
161        let query = "duct";
162        let contents = "\
163Rust:
164safe, fast, productive.
165Picl three/";
166         assert_eq!(vec!["safe, fast, productive."], search(query, contents));   
167    }
168
169}