xer_minigrep/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! # Minigrep
//! minigrep is a minimal version of grep as shown in the [rust book](https://doc.rust-lang.org/book/).
//!
//! This is the documentation for the library crate part of minigrep.

use std::env;
use std::error::Error;
use std::fs;

#[derive(Debug)]
pub struct Config {
    pub query: String,
    pub file_path: String,
    pub ignore_case: bool,
}

impl Config {
    /// Creates a `Config` from the arguments which could come from
    /// `std::env::args()` or any other `String` iterator.
    ///
    /// # Examples
    ///
    /// ```
    /// use xer_minigrep::Config;
    ///
    /// fn main() {
    ///		let args = vec!(String::from("./minigrep"), String::from("body"), String::from("poem.txt"));
    /// 	let iter = args.into_iter();
    /// 	let config = Config::build(iter).unwrap();
    /// 	println!("{:?}", config);
    /// }
    /// ```
    pub fn build(mut args: impl Iterator<Item = String>) -> Result<Self, String> {
        args.next()
            .ok_or(String::from("Program name argument missing"))?;

        // TODO: `unwrap_or_else` with closure is shorter
        let query = match args.next() {
            Some(query) => query,
            None => return Err(String::from("Didn't get query string argument")),
        };
        let file_path = match args.next() {
            Some(file_path) => file_path,
            None => return Err(String::from("Didn't get file path string argument")),
        };

        let ignore_case = env::var("IGNORE_CASE").is_ok();
        Ok(Self {
            query,
            file_path,
            ignore_case,
        })
    }
}

/// Performs a search according to what values are in the passed in `Config`,
/// which could mean case sensitive search, case insensitive search, etc.
///
/// # Examples
///
/// ```
/// use xer_minigrep::Config;
///
/// fn main() {
///		let args = vec!(String::from("./minigrep"), String::from("body"), String::from("poem.txt"));
/// 	let iter = args.into_iter();
/// 	let config = Config::build(iter).unwrap();
/// 	xer_minigrep::run(config).unwrap();
/// }
/// ```
///
/// # Errors
///
/// This function will return a `Result` if it failed to read the file
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
    let contents = fs::read_to_string(&config.file_path)?;

    let results = if config.ignore_case {
        search_case_insensitive(&config.query, &contents)
    } else {
        search(&config.query, &contents)
    };

    for line in results {
        println!("{line}");
    }
    Ok(())
}

/// Returns a vector of references to `str` where each `str` is a line that
/// matched case-sensitively a search for `query` in `contents`.
///
/// # Examples
///
/// ```
/// fn main() {
/// 	let contents = "\
/// Rust:
/// safe, fast, productive.
/// Pick three.
/// Trust Me.";
/// 	let results = xer_minigrep::search("rust", contents);
/// 	assert_eq!(vec!("Trust Me."), results);
/// }
/// ```
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.contains(query))
        .collect()
}

/// Returns a vector of references to `str` where each `str` is a line that
/// matched case-insensitively a search for `query` in `contents`.
///
/// # Examples
///
/// ```
/// fn main() {
/// 	let contents = "\
/// Rust:
/// safe, fast, productive.
/// Pick three.
/// Trust Me.";
/// 	let results = xer_minigrep::search_case_insensitive("rust", contents);
/// 	assert_eq!(vec!("Rust:", "Trust Me."), results);
/// }
/// ```
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
        .collect()
}

#[cfg(test)]
mod lib_test;