cli_program_doc/
lib.rs

1//! # Cli Program Doc
2//!
3//! `cli_program_doc` is an utility that allows you to search a string into a text
4
5use std::env;
6
7pub struct Config {
8    pub query: String,
9    pub file_path: String,
10    pub ignore_case: bool,
11}
12
13impl Config {
14    // env::args passed from main is defined in std::env::Args as implementing the Iterator trait and returning String values
15    // the args parameter has a generic type with trait bounds Iterator<Item = String> instead of &[String]
16    // we are taking ownership of argos and we’ll be mutating it by iterating over it so we need mut
17    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
18        // the first element will be the name of the program, ignore it
19        args.next();
20
21        // thanks to the iterator we don't need to clone the values
22        let query = match args.next() {
23            Some(arg) => arg,
24            None => return Err("Didn't get a query string"),
25        };
26
27        // iterate and use match to extract the value
28        let file_path = match args.next() {
29            Some(arg) => arg,
30            None => return Err("Didn't get a file path"),
31        };
32
33        // env::var returns a Result that returns Err variant if the env variable is not set
34        // is_ok returns false if the variable is not set, so we perform a case sensitive search
35        // we use is_ok instead of unwrap/expect because we don’t need the value just know if it’s set or not
36
37        let ignore_case = env::var("IGNORE_CASE").is_ok();
38
39        // clone cannot take ownership but only borrow from main
40        // we need to clone those field to have ownership
41        Ok(Config {
42            query,
43            file_path,
44            ignore_case,
45        })
46    }
47}
48
49use std::error::Error;
50use std::fs;
51
52pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
53    //// we use the ? operator in place of expect, instead of panicking ? will return the error value from the current function to the caller to handle
54    let file_contents = fs::read_to_string(config.file_path)?;
55
56    let results = if config.ignore_case {
57        search_non_sensitive(&config.query, &file_contents)
58    } else {
59        search(&config.query, &file_contents)
60    }; // this is an assignment
61
62    for line in results {
63        println!("{line}");
64    }
65
66    Ok(())
67}
68
69/// Does a case sensitive search of the query you pass in the contents and returns matching lines
70/// ```
71/// let query = "Guest";
72/// let contents = "\
73///       This is a Guest line.
74///       Rust is safe, fast, productive.";
75///
76/// assert_eq!(vec!["This is a Guest line."], cli_program_doc::search(query, contents));
77/// ```
78
79pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
80    // by using an iterator and a closure we avoid using a temporary vector that handles state
81    contents
82        .lines()
83        .filter(|line| line.contains(query))
84        .collect()
85}
86
87pub fn search_non_sensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
88    let query = query.to_lowercase();
89
90    contents
91        .lines()
92        .filter(|line| line.to_lowercase().contains(&query))
93        .collect()
94}
95
96#[cfg(test)]
97mod tests {
98    // bring into scope the code in the rest of the library
99    // we use the glob operator to do so
100    use super::*;
101
102    #[test]
103    fn search_kw_in_text() {
104        let query = "safe";
105        // the backlash tells Rust not to put a newline char at the beginning of the contents of this string literal
106        let contents = "\
107        Rust:
108        safe, fast, productive.
109        Pick three.
110        Safe, fast, productive.";
111        // check that we return the line that matches our query in that content
112        assert_eq!(
113            vec!["        safe, fast, productive."],
114            search(query, contents)
115        );
116    }
117
118    #[test]
119    fn search_kw_in_text_non_sensitive() {
120        let query = "RUSt";
121        let contents = "\
122        Rust:
123        safe, fast, productive.
124        Pick three.
125        Safe, fast, productive.";
126
127        // Rust: should be the line returned if there is a match
128        assert_eq!(vec!["Rust:"], search_non_sensitive(query, contents));
129    }
130}