1use std::env;
2use std::error::Error;
3use std::fs;
4
5pub struct Config {
6 pub query: String,
7 pub filename: String,
8 pub case_sensitive: bool,
9}
10
11impl Config {
12 pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
13 args.next();
14
15 let query = match args.next() {
16 Some(arg) => arg,
17 None => return Err("Didn't get a query string"),
18 };
19
20 let filename = match args.next() {
21 Some(arg) => arg,
22 None => return Err("Didn't get a file name"),
23 };
24
25 let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
26
27 Ok(Config {
28 query,
29 filename,
30 case_sensitive,
31 })
32 }
33}
34
35pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
36 let contents = fs::read_to_string(config.filename)?;
37
38 let results = if config.case_sensitive {
39 search(&config.query, &contents)
40 } else {
41 search_case_insensitive(&config.query, &contents)
42 };
43
44 for line in results {
45 println!("{}", line);
46 }
47
48 Ok(())
49}
50pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
61 contents
62 .lines()
63 .filter(|line| line.contains(query))
64 .collect()
65}
66
67pub fn search_case_insensitive<'a>(
68 query: &str,
69 contents: &'a str,
70) -> Vec<&'a str> {
71 let query = query.to_lowercase();
72 let mut results = Vec::new();
73
74 for line in contents.lines() {
75 if line.to_lowercase().contains(&query) {
76 results.push(line);
77 }
78 }
79
80 results
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn case_sensitive() {
89 let query = "duct";
90 let contents = "\
91Rust:
92safe, fast, productive.
93Pick three.
94Duct tape.";
95
96 assert_eq!(vec!["safe, fast, productive."], search(query, contents));
97 }
98
99 #[test]
100 fn case_insensitive() {
101 let query = "rUsT";
102 let contents = "\
103Rust:
104safe, fast, productive.
105Pick three.
106Trust me.";
107
108 assert_eq!(
109 vec!["Rust:", "Trust me."],
110 search_case_insensitive(query, contents)
111 );
112 }
113}