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