Skip to main content

brcap/
lib.rs

1use std::{env, error::Error, fs};
2
3const CASE_INSENSITIVE: &str = "CASE_INSENSITIVE";
4
5#[derive(Debug, PartialEq)]
6pub struct Config {
7    query: String,
8    filename: String,
9    case_sensitive: bool,
10}
11
12impl Config {
13    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
14        args.next();
15
16        let query = match args.next() {
17            Some(arg) => arg,
18            None => return Err("Didn't get a Query String"),
19        }; // clone is not idiomatic in most cases
20        let filename = match args.next() {
21            Some(arg) => arg,
22            None => return Err("Didn't get a file name"),
23        };
24
25        let case_sensitive = env::var(CASE_INSENSITIVE).is_err();
26
27        Ok(Config {
28            query: query,
29            filename: filename,
30            case_sensitive,
31        })
32    }
33}
34
35pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
36    let contents = fs::read_to_string(&config.filename)?;
37
38    let results = if config.case_sensitive {
39        search(&config.query, &contents)
40    } else {
41        search_case_insensitive(&config.query, &contents)
42    };
43
44    results.iter().for_each(|line| println!("{}", line));
45
46    Ok(())
47}
48
49/// Searches into some content(text) by an specifc query provided
50///
51/// # Examples
52/// ```
53/// let query = "World";
54/// let contents = "Welcome back into!\n
55/// Do not be afraid!\n
56/// That is my World!";
57///
58/// let line_matches: Vec<&str> = brcap::search(query, contents);
59///
60/// assert_eq!(vec!["That is my World!"], line_matches);
61/// ```
62pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
63    contents
64        .lines()
65        .map(|line| line.trim())
66        .filter(|line| line.contains(query))
67        .collect()
68}
69
70pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
71    contents
72        .lines()
73        .map(|line| line.trim())
74        .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
75        .collect()
76}
77
78#[cfg(test)]
79mod public_api_tests {
80    use super::*;
81
82    #[test]
83    fn it_instantiate_config_passing_args() {
84        let args = env::args();
85
86        let expected_config = Config {
87            query: String::from("search term"),
88            filename: String::from("filename to search"),
89            case_sensitive: true,
90        };
91
92        if let Ok(config) = Config::new(args) {
93            assert_eq!(config, expected_config);
94        }
95    }
96
97    #[test]
98    fn it_searches_by_query() {
99        let query = "duct";
100        let contents = "\
101            Rust:
102            safe, fast, productive.
103            Pick three.
104            Duct tape.";
105
106        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
107    }
108
109    #[test]
110    fn it_searches_with_case_sentitive() {
111        let query = "rUsT";
112        let contents = "\
113            Rust:
114            safe, fast, productive.
115            Pick three.
116            Trust me.";
117
118        assert_eq!(
119            vec!["Rust:", "Trust me."],
120            search_case_insensitive(query, contents)
121        );
122    }
123}