eaverdeja_minigrep/
lib.rs

1use std::env;
2use std::error::Error;
3use std::fs;
4
5/// Searches a file for a given query string.
6/// Prints out all lines that contain the query string.
7///
8/// # Examples
9///
10/// ```
11/// let args = [String::from("frog"), String::from("poem.txt")].into_iter();
12/// let config = Config::build(args).unwrap();
13/// run(config)
14/// ```
15pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
16    let contents = fs::read_to_string(config.file_path)?;
17
18    let results = if config.ignore_case {
19        search_case_insensitive(&config.query, &contents)
20    } else {
21        search(&config.query, &contents)
22    };
23
24    for line in results {
25        println!("{line}");
26    }
27
28    Ok(())
29}
30
31/// Searches for `query` in `contents`, case sensitive.
32/// Returns a list of references to lines in `contents` that contain `query`.
33///
34/// # Examples
35///
36/// ```
37/// let query = "duct";
38/// let contents = "\
39/// Rust:
40/// safe, fast, productive.
41/// Pick three.
42/// Duct tape.";
43///
44/// let expected = vec!["safe, fast, productive."];
45/// let actual = search(query, contents);
46/// assert_eq!(expected, actual);
47/// ```
48pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
49    contents
50        .lines()
51        .filter(|line| line.contains(query))
52        .collect()
53}
54
55pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
56    let query = query.to_lowercase();
57
58    contents
59        .lines()
60        .filter(|line| line.to_lowercase().contains(&query))
61        .collect()
62}
63
64pub struct Config {
65    pub query: String,
66    pub file_path: String,
67    pub ignore_case: bool,
68}
69
70impl Config {
71    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
72        args.next(); // program name
73
74        let query = match args.next() {
75            Some(arg) => arg,
76            None => return Err("Didn't get a query string"),
77        };
78
79        let file_path = match args.next() {
80            Some(arg) => arg,
81            None => return Err("Didn't get a file path"),
82        };
83
84        let ignore_case = env::var("IGNORE_CASE").is_ok();
85
86        Ok(Config {
87            query,
88            file_path,
89            ignore_case,
90        })
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn case_sensitive() {
100        let query = "duct";
101        let contents = "\
102Rust:
103safe, fast, productive.
104Pick three.
105Duct tape.";
106
107        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
108    }
109
110    #[test]
111    fn case_insensitive() {
112        let query = "rUsT";
113        let contents = "\
114Rust:
115safe, fast, productive.
116Pick three.
117Trust me.";
118
119        assert_eq!(
120            vec!["Rust:", "Trust me."],
121            search_case_insensitive(query, contents)
122        )
123    }
124}