milligrep/
lib.rs

1/*
2Copyright (C) 2021  Jayesh Mann <jayeshmann06@gmail.com>
3
4This program is free software: you can redistribute it and/or modify
5it under the terms of the GNU Affero General Public License as published
6by the Free Software Foundation, either version 3 of the License, or
7(at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12GNU Affero General Public License for more details.
13
14You should have received a copy of the GNU Affero General Public License
15along with this program.  If not, see <https://www.gnu.org/licenses/>. */
16
17//! Custom simplified implementation of grep
18
19use std::env;
20use std::error::Error;
21use std::fs;
22
23/// The necessary configurations for initializing milligrep.
24///
25pub struct Config {
26    /// The pattern to search for in the contents of the file.
27    pub query: String,
28    /// The file along with the path in which the pattern search will be conducted.
29    pub filename: String,
30    /// CASE_SENSITIVE flag, either true or false
31    pub case_sensitive: bool,
32}
33
34impl Config {
35    /// Initializes a new Config.
36    ///
37    /// Returns error if incorrect option is passed.
38    pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
39        args.next();
40        let query = match args.next() {
41            Some(arg) => arg,
42            None => return Err("Didn't get a query string"),
43        };
44        let filename = match args.next() {
45            Some(arg) => arg,
46            None => return Err("Didn't get the file name"),
47        };
48
49        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
50
51        Ok(Config {
52            query,
53            filename,
54            case_sensitive,
55        })
56    }
57}
58/// Runs the library logic.
59/// Expects a reference to [`Config`](struct@Config).
60///
61/// # Errors
62///
63/// Returns error on the following situations:
64///
65/// - `file` doesn't exist.
66/// - `file` could not be read (unauthorized read permission).
67pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
68    let contents = fs::read_to_string(config.filename)?;
69
70    let results = if config.case_sensitive {
71        search(&config.query, &contents)
72    } else {
73        search_case_insensitive(&config.query, &contents)
74    };
75
76    for line in results {
77        println!("{}", line);
78    }
79
80    Ok(())
81}
82/// Returns the line(s) where the pattern is matched in the contents of the file.
83/// It is case sensitive.
84pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
85    contents
86        .lines()
87        .filter(|line| line.contains(query))
88        .collect()
89}
90/// Returns the line(s) where the pattern is matched in the contents of the file.
91/// It is case in-sensitive.
92pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
93    contents
94        .lines()
95        .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
96        .collect()
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    // #[test]
103    // fn new_config() {
104    //     let arg_array: Vec<String>;
105    //     let res = Config::new(&arg_array).expect("Failed to init config.");
106    // }
107
108    #[test]
109    fn case_sensitive() {
110        let query = "duct";
111        let contents = "\
112Rust:
113safe, fast, productive.
114Pick three.
115Duct tape.";
116
117        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
118    }
119
120    #[test]
121    fn case_insensitive() {
122        let query = "rUsT";
123        let contents = "\
124Rust:
125safe, fast, productive.
126Pick three.
127Trust me.";
128
129        assert_eq!(
130            vec!["Rust:", "Trust me."],
131            search_case_insensitive(query, contents)
132        );
133    }
134}