1use std::{env, error::Error, fs, vec};
15
16pub struct Config {
17 pub query: String,
18 pub file_path: String,
19 pub ignore_case: bool,
20}
21
22impl Config {
23 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
24 args.next();
25
26 let query = match args.next() {
27 Some(query) => query,
28 None => return Err("Didn't get a query string"),
29 };
30
31 let file_path = match args.next() {
32 Some(path) => path,
33 None => return Err("Didn't get a file path"),
34 };
35
36 let ignore_case = env::var("IGNORE_CASE").is_ok();
37
38 Ok(Config {
39 query,
40 file_path,
41 ignore_case,
42 })
43 }
44}
45
46fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
47 let mut result = vec![];
48 for line in contents.lines() {
49 if line.contains(query) {
50 result.push(line)
51 }
52 }
53 result
54}
55
56fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
57 let mut result = Vec::new();
58 let query = query.to_lowercase();
59 for line in contents.lines() {
60 if line.to_lowercase().contains(&query) {
61 result.push(line)
62 }
63 }
64 result
65}
66
67pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
83 let contents = fs::read_to_string(config.file_path)?;
84
85 let results = if config.ignore_case {
86 search_case_insensitive(&config.query, &contents)
87 } else {
88 search(&config.query, &contents)
89 };
90
91 for line in results {
92 println!("{line}");
93 }
94 Ok(())
95}
96
97#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn case_sensitive() {
106 let query = "duct";
107 let contents = "\
108Rust:
109safe, fast, productive.
110Pick three.
111Duct tape.";
112 assert_eq!(vec!["safe, fast, productive."], search(query, contents));
113 }
114
115 #[test]
116 fn case_insensitive() {
117 let query = "rUsT";
118 let contents = "\
119Rust:
120safe, fast, productive.
121Pick three.
122Trust me.";
123 assert_eq!(
124 vec!["Rust:", "Trust me."],
125 search_case_insensitive(query, contents)
126 );
127 }
128}