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