zcking_minigrep/
lib.rs

1//! # Minigrep
2//!
3//! `minigrep` is a simple alternative implementation of the
4//! common Unix command-line tool `grep`
5
6use std::env;
7use std::error::Error;
8use std::fs::File;
9use std::io::prelude::*;
10
11/// Holds the command-line arguments given.
12///
13/// # Examples
14///
15/// ```
16/// extern crate zcking_minigrep;
17///
18/// let config = zcking_minigrep::Config {
19///     query: String::from("Ishmael"),
20///     filename: String::from("moby_dick.txt"),
21///     case_sensitive: true,
22/// };
23/// ```
24pub struct Config {
25    /// The term to search for
26    pub query: String,
27    /// Path to the file to search in
28    pub filename: String,
29    /// Whether case should be ignored or not when searching
30    pub case_sensitive: bool,
31}
32
33impl Config {
34    /// Creates a new `Config` by parsing arguments.
35    ///
36    /// # Errors
37    /// If the query or filename arguments were omitted,
38    /// an error will be returned with a message indicating as such.
39    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
40        args.next();
41
42        let query = match args.next() {
43            Some(arg) => arg,
44            None => return Err("Didn't get a query string"),
45        };
46
47        let filename = match args.next() {
48            Some(arg) => arg,
49            None => return Err("Didn't get a file name"),
50        };
51
52        // `is_err` will return false if the var is set to anything
53        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
54
55        Ok(Config {
56            query,
57            filename,
58            case_sensitive,
59        })
60    }
61}
62
63pub fn run(config: Config) -> Result<(), Box<Error>> {
64    let mut f = File::open(config.filename)?;
65
66    let mut contents = String::new();
67    f.read_to_string(&mut contents)?;
68
69    let results = if config.case_sensitive {
70        search(&config.query, &contents)
71    } else {
72        search_case_insensitive(&config.query, &contents)
73    };
74
75    for line in results {
76        println!("{}", line);
77    }
78
79    Ok(())
80}
81
82pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
83    contents
84        .lines()
85        .filter(|line| line.contains(query))
86        .collect()
87}
88
89fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
90    let query = query.to_lowercase();
91    let mut results = Vec::new();
92
93    for line in contents.lines() {
94        if line.to_lowercase().contains(&query) {
95            results.push(line);
96        }
97    }
98
99    results
100}
101
102#[cfg(test)]
103mod test {
104    use super::*;
105
106    #[test]
107    fn case_sensitive() {
108        let query = "duct";
109        let contents = "\
110Rust:
111safe, fast, productive.
112Pick three.";
113
114        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
115    }
116
117    #[test]
118    fn case_insensitive() {
119        let query = "rUsT";
120        let contents = "\
121Rust:
122safe, fast, productive.
123Pick three.
124Trust me.";
125
126        assert_eq!(
127            vec!["Rust:", "Trust me."],
128            search_case_insensitive(query, contents)
129        );
130    }
131}