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;