grep_clone/
lib.rs

1//! # Grep Clone
2//! A mini version of grep that supports searching a file with a substring.
3//!
4//! It's only implemented here for learning purposes.
5//!
6//! Project originally specified at: <https://doc.rust-lang.org/book/ch12-00-an-io-project.html>
7
8use std::env;
9use std::error::Error;
10use std::fs;
11
12/// # Struct that defines required/supported arguments
13/// The struct can be used to parse arguments from an iterator.
14pub struct Args {
15    /// Pattern used as a substring search.
16    pub query: String,
17    /// Path to the file that should be opened and used for searching.
18    pub file_path: String,
19    /// Whether the search should be case-sensitive or not.
20    pub ignore_case: bool,
21}
22
23impl Args {
24    /// # Builds arguments from an iterator
25    /// Expects argument in the order:
26    /// 0. executable_name
27    /// 1. query
28    /// 2. file_path
29    /// 3. ignore_case (optional)
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// let iter = vec![
35    ///     String::from("grep_clone"),
36    ///     String::from("Who"),
37    ///     String::from("poem.txt"),
38    /// ].into_iter();
39    /// let args = grep_clone::Args::build(iter);
40    /// ```
41    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Args, &'static str> {
42        args.next();
43
44        let query = match args.next() {
45            Some(val) => val,
46            None => return Err("Missing query"),
47        };
48
49        let file_path = match args.next() {
50            Some(val) => val,
51            None => return Err("Missing file path"),
52        };
53
54        let mut ignore_case: bool = false;
55        if env::var("IGNORE_CASE").is_ok() {
56            ignore_case = true;
57        }
58
59        let next = args.next().unwrap_or(String::from(""));
60        if next.contains("-i") || next.contains("--ignore-case") {
61            ignore_case = true;
62        }
63
64        Ok(Args {
65            query,
66            file_path,
67            ignore_case,
68        })
69    }
70}
71
72/// # Case insensitive search
73///
74/// Search ignoring case.
75/// 
76/// # Example
77///
78/// ```
79/// let contents = "
80/// who is
81/// this?
82/// ";
83/// let query = "who";
84/// assert_eq!(grep_clone::case_insensitive_search(query, contents), vec!["who is"]);
85/// ```
86pub fn case_insensitive_search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
87    let query = query.to_lowercase();
88
89    contents
90        .lines()
91        .filter(|line| line.to_lowercase().contains(&query))
92        .collect()
93}
94
95/// # Search
96///
97/// Search taking into account the character case.
98/// 
99/// # Example
100///
101/// ```
102/// let contents = "
103/// who is
104/// this?
105/// Who are you?
106/// ";
107/// let query = "who";
108/// assert_eq!(grep_clone::search(query, contents), vec!["who is"]);
109/// ```
110pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
111    contents
112        .lines()
113        .filter(|line| line.contains(query))
114        .collect()
115}
116
117
118/// # Run
119///
120/// Entrypoint used by binary executable.
121/// 
122/// # Example
123///
124/// ```
125/// let iter = vec![
126///     String::from("grep_clone"),
127///     String::from("Who"),
128///     String::from("poem.txt"),
129/// ].into_iter();
130/// let args = grep_clone::Args::build(iter).unwrap();
131/// grep_clone::run(args);
132/// ```
133pub fn run(args: Args) -> Result<(), Box<dyn Error>> {
134    let contents = fs::read_to_string(args.file_path)?;
135
136    let result: Vec<&str>;
137
138    if args.ignore_case {
139        result = case_insensitive_search(&args.query, &contents);
140    } else {
141        result = search(&args.query, &contents);
142    }
143    for line in result {
144        println!("{line}");
145    }
146    Ok(())
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn one_result() {
155        let query = "duct";
156        let contents = "\
157Rust:
158safe, fast, productive.
159Pick three.
160Duct tape.";
161
162        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
163    }
164
165    #[test]
166    fn case_insensitive() {
167        let query: &str = "RuSt";
168        let contents = "\
169Rust:
170safe, fast, productive.
171Pick three.
172Trust me.";
173
174        assert_eq!(
175            vec!["Rust:", "Trust me."],
176            case_insensitive_search(query, contents)
177        );
178    }
179}