tinygrep/
lib.rs

1use std::env;
2use std::error::Error;
3use std::fs;
4
5pub struct Config {
6    pub query: String,
7    pub file_path: String,
8    pub ignore_case: bool,
9}
10
11impl Config {
12    pub fn build(mut args: impl ExactSizeIterator<Item = String>) -> Result<Config, &'static str> {
13        // skip first arg as it is binary filepath
14        args.next();
15        if args.len() < 2 {
16            return Err("Not enough arguments");
17        }
18
19        let ignore_case: bool;
20        if args.len() > 2 {
21            // argument overrides envar
22            ignore_case = match args.next() {
23                Some(arg) => arg.contains("IGNORE_CASE"),
24                None => false,
25            };
26        } else {
27            ignore_case = env::var("IGNORE_CASE").is_ok();
28        }
29
30        let query = match args.next() {
31            Some(arg) => arg,
32            None => return Err("Didn't get a query string"),
33        };
34        let file_path = match args.next() {
35            Some(arg) => arg,
36            None => return Err("Didn't get a file path"),
37        };
38
39        Ok(Config {
40            query,
41            file_path,
42            ignore_case,
43        })
44    }
45}
46
47pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
48    contents
49        .lines()
50        .filter(|line| line.contains(query))
51        .collect()
52}
53
54pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
55    // only handles basic unicode
56    contents
57        .lines()
58        .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
59        .collect()
60}
61
62pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
63    let contents = fs::read_to_string(config.file_path)?;
64
65    let results = if config.ignore_case {
66        search_case_insensitive(&config.query, &contents)
67    } else {
68        search(&config.query, &contents)
69    };
70
71    for line in results {
72        println!("{line}");
73    }
74
75    Ok(())
76}
77
78// TODO: add integration tests for cli testing
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn case_sensitive() {
85        let query = "duct";
86        let contents = "\
87Rust:
88safe, fast, productive.
89Pick three.
90Duct tape.";
91        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
92    }
93
94    #[test]
95    fn case_insensitive() {
96        let query = "rUst";
97        let contents = "\
98Rust:
99safe, fast, productive.
100Pick three.
101Trust me.";
102
103        assert_eq!(
104            vec!["Rust:", "Trust me."],
105            search_case_insensitive(query, contents)
106        )
107    }
108}