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