1use 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(); 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
42pub 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 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 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}