1use std::{env, error::Error, fs};
2
3pub enum ConfigErr {
4 InvalidArgs,
5}
6
7pub struct Config {
8 pub query: String,
9 pub file_path: String,
10 pub ignore_case: bool,
11}
12
13impl Config {
14 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, ConfigErr> {
15 args.next();
16
17 let query = match args.next() {
18 Some(arg) => arg,
19 None => return Err(ConfigErr::InvalidArgs),
20 };
21
22 let file_path = match args.next() {
23 Some(arg) => arg,
24 None => return Err(ConfigErr::InvalidArgs),
25 };
26
27 let ignore_case = match env::var("MINIGREP_IGNORE_CASE") {
28 Ok(val) => val == "1" || val.eq_ignore_ascii_case("true"),
29 Err(_) => false,
30 };
31
32 Ok(Config {
33 query,
34 file_path,
35 ignore_case,
36 })
37 }
38}
39
40pub fn run(config: &Config) -> Result<(), Box<dyn Error>> {
41 let contents = fs::read_to_string(&config.file_path)?;
42
43 let results = if config.ignore_case {
44 search_case_insensitive(&config.query, &contents)
45 } else {
46 search(&config.query, &contents)
47 };
48
49 for line in results {
50 println!("{line}");
51 }
52
53 Ok(())
54}
55
56pub fn search<'a>(query: &str, contents: &'a str) -> Box<[&'a str]> {
57 contents
58 .lines()
59 .filter(|line| line.contains(query))
60 .collect()
61}
62
63pub fn contains_ignore_case(haystack: &str, needle: &str) -> bool {
64 let mut haystack_it = haystack.chars().peekable();
65
66 while haystack_it.peek().is_some() {
67 if haystack_it
68 .clone()
69 .zip(needle.chars())
70 .all(|(a, b)| a.to_lowercase().eq(b.to_lowercase()))
71 {
72 return true;
73 }
74
75 haystack_it.next();
76 }
77
78 false
79}
80
81pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Box<[&'a str]> {
82 contents
83 .lines()
84 .filter(|line| contains_ignore_case(line, query))
85 .collect()
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn one_result() {
94 let query = "duct";
95 let contents = "\
96Rust:
97safe, fast, productive.
98Pick three.";
99 assert_eq!(
100 ["safe, fast, productive."],
101 search(query, contents).as_ref()
102 );
103 }
104
105 #[test]
106 fn case_sensitive() {
107 let query = "duct";
108 let contents = "\
109Rust:
110safe, fast, productive.
111Pick three.
112Duct tape.";
113 assert_eq!(
114 ["safe, fast, productive."],
115 search(query, contents).as_ref()
116 );
117 }
118
119 #[test]
120 fn ignore_case() {
121 let query = "rUsT";
122 let contents = "\
123Rust:
124safe, fast, productive.
125Pick three.
126Trust me.";
127 assert_eq!(
128 ["Rust:", "Trust me."],
129 search_case_insensitive(query, contents).as_ref()
130 );
131 }
132}