angrep/
lib.rs

1//! # Minigrep
2//! 
3//! `minigrep` is an example for new commer of Rust to study how to write idiomatic Rust code.
4//! 
5//! Have fun!
6//! 
7//! # About
8//! 
9//! My name is Junan Lu.
10//! New to Rust.
11//! Glod to meet you.
12//!
13
14use std::{env, error::Error, fs, vec};
15
16pub struct Config {
17    pub query: String,
18    pub file_path: String,
19    pub ignore_case: bool,
20}
21
22impl Config {
23    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
24        args.next();
25
26        let query = match args.next() {
27            Some(query) => query,
28            None => return Err("Didn't get a query string"),
29        };
30
31        let file_path = match args.next() {
32            Some(path) => path,
33            None => return Err("Didn't get a file path"),
34        };
35
36        let ignore_case = env::var("IGNORE_CASE").is_ok();
37
38        Ok(Config {
39            query,
40            file_path,
41            ignore_case,
42        })
43    }
44}
45
46fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
47    let mut result = vec![];
48    for line in contents.lines() {
49        if line.contains(query) {
50            result.push(line)
51        }
52    }
53    result
54}
55
56fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
57    let mut result = Vec::new();
58    let query = query.to_lowercase();
59    for line in contents.lines() {
60        if line.to_lowercase().contains(&query) {
61            result.push(line)
62        }
63    }
64    result
65}
66
67/// Run the query with config
68/// 
69/// # Example
70/// ```
71/// use angrep::{Config, run};
72/// 
73/// let args = vec![
74///     String::from("bin/minigrep.exe"),
75///     String::from("body"),
76///     String::from("poem.txt"),
77/// ].into_iter();
78/// 
79/// let config = Config::build(args).expect("should be able to build");
80/// let result = run(config);
81/// ```
82pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
83    let contents = fs::read_to_string(config.file_path)?;
84
85    let results = if config.ignore_case {
86        search_case_insensitive(&config.query, &contents)
87    } else {
88        search(&config.query, &contents)
89    };
90
91    for line in results {
92        println!("{line}");
93    }
94    Ok(())
95}
96
97// ===========================================================================
98// test
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn case_sensitive() {
106        let query = "duct";
107        let contents = "\
108Rust:
109safe, fast, productive.
110Pick three.
111Duct tape.";
112        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
113    }
114
115    #[test]
116    fn case_insensitive() {
117        let query = "rUsT";
118        let contents = "\
119Rust:
120safe, fast, productive.
121Pick three.
122Trust me.";
123        assert_eq!(
124            vec!["Rust:", "Trust me."],
125            search_case_insensitive(query, contents)
126        );
127    }
128}