1use std::{env, error::Error, fs};
2
3pub struct Config {
4 query: String,
5 file_path: String,
6 ignore_case: bool,
7}
8
9impl Config {
10 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Self, &'static str> {
11 args.next();
12 let query = match args.next() {
13 Some(s) => s,
14 None => return Err("Didn't get a query argument."),
15 };
16 let file_path = match args.next() {
17 Some(s) => s,
18 None => return Err("Didn't get a file path argument."),
19 };
20 let ignore_case = env::var("IGNORE_CASE").is_ok();
21 Ok(Config {
22 query,
23 file_path,
24 ignore_case,
25 })
26 }
27}
28
29pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
30 let contents = fs::read_to_string(config.file_path)?;
31 for line in search(&config.query, &contents, &config.ignore_case) {
32 println!("{line}");
33 }
34 Ok(())
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 #[test]
42 fn search_test() {
43 let query = "duct";
44 let contents = "\
45Rust:
46safe, fast, productive.
47Pick three.";
48 assert_eq!(
49 vec!["safe, fast, productive."],
50 search(&query, &contents, &false)
51 );
52 }
53 #[test]
54 fn case_insensitive_search_test() {
55 let query = "rUsT";
56 let contents = "\
57Rust:
58safe, fast, productive.
59Pick three.
60Trust me.";
61 let ignore_case = true;
62 assert_eq!(
63 vec!["Rust:", "Trust me."],
64 search(&query, &contents, &ignore_case)
65 );
66 }
67}
68
69pub fn search<'a>(query: &str, contents: &'a str, ignore_case: &'a bool) -> Vec<&'a str> {
70 contents
71 .lines()
72 .filter(|line| {
73 if *ignore_case {
74 line.to_lowercase().contains(&query.to_lowercase())
75 } else {
76 line.contains(query)
77 }
78 })
79 .collect()
80}