Skip to main content

zrbecker_minigrep/
lib.rs

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}