1use std::{env, error::Error, fs};
6
7
8
9pub struct Config {
10 pub query: String,
11 pub filename: String,
12 pub case_sensitive: bool,
13}
14
15impl Config {
16 pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
17 args.next();
18
19 let query = match args.next() {
20 Some(v) => v,
21 None => return Err("Didn't get a query string"),
22 };
23
24 let filename = match args.next() {
25 Some(v) => v,
26 None => return Err("Didn't get a filename string"),
27 };
28
29 let case_sensitive = match args.next() {
30 Some(v) => "--insensitive" != v,
31 None => env::var("CASE_INSENSITIVE").is_err(),
32 };
33
34 Ok(Config {
35 query,
36 filename,
37 case_sensitive,
38 })
39 }
40}
41
42pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
43 let contents = fs::read_to_string(config.filename)?;
44
45 let results = if config.case_sensitive {
46 search(&config.query, &contents)
47 } else {
48 search_case_insensitive(&config.query, &contents)
49 };
50
51 for line in results {
52 println!("{}", line);
53 }
54 Ok(())
55}
56
57pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
72 contents
73 .lines()
74 .filter(|line| line.contains(query))
75 .collect()
76}
77
78pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
96 contents
97 .lines()
98 .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
99 .collect()
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn case_sensitive() {
108 let query = "duct";
109 let contents = "\
110Rust:
111safe, fast, productive.
112Pick three.
113Duct tape.";
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 assert_eq!(
126 vec!["Rust:", "Trust me."],
127 search_case_insensitive(query, contents)
128 );
129 }
130}