daveparr_minigrep/
lib.rs

1//! # daveparr_minigrep
2//!
3//! `daveparr_minigrep` is a simple command line program that searches for a string in a file.
4//! It is based on the example in ["The Rust Book"](https://doc.rust-lang.org/book/ch12-00-an-io-project.html).
5
6use std::error::Error;
7use std::fs;
8
9/// The run function is the entry point for the program.
10/// It takes a Config instance and returns a Result.
11pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
12    let contents = fs::read_to_string(config.file_path)?;
13
14    let results = if config.ignore_case {
15        search_case_insensitive(&config.query, &contents)
16    } else {
17        search(&config.query, &contents)
18    };
19
20    for line in results {
21        println!("{line}");
22    }
23
24    Ok(())
25}
26
27/// The Config struct holds the values of the command line arguments.
28pub struct Config {
29    pub query: String,
30    pub file_path: String,
31    pub ignore_case: bool,
32}
33
34/// The Config struct has an implementation of the build method.
35impl Config {
36    /// The build method takes an iterator of strings and returns a Result of Config or a string slice.
37    /// # Errors
38    /// Returns an error if the query string or file path are not provided.
39    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
40        args.next(); // skip the program name
41
42        let query = match args.next() {
43            Some(arg) => arg,
44            None => return Err("Didn't get a query string"),
45        };
46        let file_path = match args.next() {
47            Some(arg) => arg,
48            None => return Err("Didn't get a file path"),
49        };
50
51        let ignore_case = std::env::var("IGNORE_CASE").is_ok();
52
53        Ok(Config {
54            query,
55            file_path,
56            ignore_case,
57        })
58    }
59}
60
61/// The search function takes a query string and a string slice and returns a vector of string slices that match the query.
62/// # Example
63/// ```
64/// let query = "duct";
65/// let contents = "\
66/// Rust:
67/// safe, fast, productive.
68/// Pick three.
69/// Duct tape.";
70/// assert_eq!(vec!["safe, fast, productive."], daveparr_minigrep::search(query, contents));
71/// ```
72pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
73    contents
74        .lines()
75        .filter(|line| line.contains(query))
76        .collect()
77}
78
79/// The search_case_insensitive function takes a query string and a string slice and returns a vector of string slices that match the query, ignoring case.
80/// # Example
81/// ```
82/// let query = "rUsT";
83/// let contents = "\
84/// Rust:
85/// safe, fast, productive.
86/// Pick three.
87/// Trust me.";
88/// assert_eq!(vec!["Rust:", "Trust me."], daveparr_minigrep::search_case_insensitive(query, contents));
89/// ```
90pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
91    let query = query.to_lowercase();
92    let mut results = Vec::new();
93
94    for line in contents.lines() {
95        if line.to_lowercase().contains(&query) {
96            results.push(line)
97        }
98    }
99
100    results
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn case_sensitive() {
109        let query = "duct";
110        let contents = "\
111Rust:
112safe, fast, productive.
113Pick three.
114Duct tape.";
115
116        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
117    }
118
119    #[test]
120    fn case_insensitive() {
121        let query = "rUsT";
122        let contents = "\
123Rust:
124safe, fast, productive.
125Pick three.
126Trust me.";
127
128        assert_eq!(
129            vec!["Rust:", "Trust me."],
130            search_case_insensitive(query, contents)
131        );
132    }
133}