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