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