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