1use std::env;
6use std::error::Error;
7use std::fs;
8
9#[cfg(test)]
10mod tests {
11 use super::*;
12
13 #[test]
14 fn case_sensitive() {
15 let query = "duct";
16 let contents = "\
17Rust:
18safe, fast, productive.
19Pick three.
20Duct tape.";
21
22 assert_eq!(vec!["safe, fast, productive."], search(query, contents));
23 }
24
25 #[test]
26 fn case_insensitive() {
27 let query = "rUsT";
28 let contents = "\
29Rust:
30safe, fast, productive.
31Pick three.
32Trust me.";
33
34 assert_eq!(
35 vec!["Rust:", "Trust me."],
36 search_case_insensitive(query, contents)
37 );
38 }
39}
40
41pub struct Config {
42 pub query: String,
43 pub file_path: String,
44 pub ignore_case: bool,
45}
46
47impl Config {
48 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
49 args.next();
52 let query = match args.next() {
53 Some(arg) => arg,
54 None => return Err("Query string is not specified."),
55 };
56 let file_path = match args.next() {
57 Some(arg) => arg,
58 None => return Err("File path is not specified."),
59 };
60 let ignore_case = env::var("IGNORE_CASE").is_ok();
61
62 Ok(Config {
63 query,
64 file_path,
65 ignore_case,
66 })
67 }
68}
69
70pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
71 contents
72 .lines()
73 .filter(|line| line.contains(query))
74 .collect()
75}
76pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
77 let query = query.to_lowercase();
78 contents
79 .lines()
80 .filter(|line| line.to_lowercase().contains(&query))
81 .collect()
82}
83
84pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
85 let contents = fs::read_to_string(config.file_path)?;
86
87 let results = if config.ignore_case {
88 search_case_insensitive(&config.query, &contents)
89 } else {
90 search(&config.query, &contents)
91 };
92
93 for line in results {
94 println!("{line}");
95 }
96
97 Ok(())
98}