xer_minigrep/lib.rs
1//! # Minigrep
2//! minigrep is a minimal version of grep as shown in the [rust book](https://doc.rust-lang.org/book/).
3//!
4//! This is the documentation for the library crate part of minigrep.
5
6use std::env;
7use std::error::Error;
8use std::fs;
9
10#[derive(Debug)]
11pub struct Config {
12 pub query: String,
13 pub file_path: String,
14 pub ignore_case: bool,
15}
16
17impl Config {
18 /// Creates a `Config` from the arguments which could come from
19 /// `std::env::args()` or any other `String` iterator.
20 ///
21 /// # Examples
22 ///
23 /// ```
24 /// use xer_minigrep::Config;
25 ///
26 /// fn main() {
27 /// let args = vec!(String::from("./minigrep"), String::from("body"), String::from("poem.txt"));
28 /// let iter = args.into_iter();
29 /// let config = Config::build(iter).unwrap();
30 /// println!("{:?}", config);
31 /// }
32 /// ```
33 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Self, String> {
34 args.next()
35 .ok_or(String::from("Program name argument missing"))?;
36
37 // TODO: `unwrap_or_else` with closure is shorter
38 let query = match args.next() {
39 Some(query) => query,
40 None => return Err(String::from("Didn't get query string argument")),
41 };
42 let file_path = match args.next() {
43 Some(file_path) => file_path,
44 None => return Err(String::from("Didn't get file path string argument")),
45 };
46
47 let ignore_case = env::var("IGNORE_CASE").is_ok();
48 Ok(Self {
49 query,
50 file_path,
51 ignore_case,
52 })
53 }
54}
55
56/// Performs a search according to what values are in the passed in `Config`,
57/// which could mean case sensitive search, case insensitive search, etc.
58///
59/// # Examples
60///
61/// ```
62/// use xer_minigrep::Config;
63///
64/// fn main() {
65/// let args = vec!(String::from("./minigrep"), String::from("body"), String::from("poem.txt"));
66/// let iter = args.into_iter();
67/// let config = Config::build(iter).unwrap();
68/// xer_minigrep::run(config).unwrap();
69/// }
70/// ```
71///
72/// # Errors
73///
74/// This function will return a `Result` if it failed to read the file
75pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
76 let contents = fs::read_to_string(&config.file_path)?;
77
78 let results = if config.ignore_case {
79 search_case_insensitive(&config.query, &contents)
80 } else {
81 search(&config.query, &contents)
82 };
83
84 for line in results {
85 println!("{line}");
86 }
87 Ok(())
88}
89
90/// Returns a vector of references to `str` where each `str` is a line that
91/// matched case-sensitively a search for `query` in `contents`.
92///
93/// # Examples
94///
95/// ```
96/// fn main() {
97/// let contents = "\
98/// Rust:
99/// safe, fast, productive.
100/// Pick three.
101/// Trust Me.";
102/// let results = xer_minigrep::search("rust", contents);
103/// assert_eq!(vec!("Trust Me."), results);
104/// }
105/// ```
106pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
107 contents
108 .lines()
109 .filter(|line| line.contains(query))
110 .collect()
111}
112
113/// Returns a vector of references to `str` where each `str` is a line that
114/// matched case-insensitively a search for `query` in `contents`.
115///
116/// # Examples
117///
118/// ```
119/// fn main() {
120/// let contents = "\
121/// Rust:
122/// safe, fast, productive.
123/// Pick three.
124/// Trust Me.";
125/// let results = xer_minigrep::search_case_insensitive("rust", contents);
126/// assert_eq!(vec!("Rust:", "Trust Me."), results);
127/// }
128/// ```
129pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
130 contents
131 .lines()
132 .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
133 .collect()
134}
135
136#[cfg(test)]
137mod lib_test;