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