1use std::error::Error;
2use std::{env, fs};
3
4pub struct Config {
5 pub query: String,
6 pub file_path: String,
7 pub ignore_case: bool,
8}
9
10impl Config {
11 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
12 args.next();
14
15 let query = match args.next() {
16 Some(arg) => arg,
17 None => return Err("Didn't get a query string"),
18 };
19
20 let file_path = match args.next() {
21 Some(arg) => arg,
22 None => return Err("Didn't get a file path"),
23 };
24 let ignore_case = env::var("IGNORE_CASE").is_ok();
25 Ok(Config {
26 file_path,
27 query,
28 ignore_case,
29 })
30 }
31}
32
33pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
34 let content = fs::read_to_string(config.file_path)?;
35
36 let results = if config.ignore_case {
37 search_case_insensitive(&config.query, &content)
38 } else {
39 search(&config.query, &content)
40 };
41
42 for line in results {
43 println!("{}", line);
44 }
45
46 Ok(())
47}
48pub fn search<'a>(query: &'a str, content: &'a str) -> Vec<&'a str> {
63 content
64 .lines()
65 .filter(|line| line.contains(query))
66 .collect()
67}
68
69pub fn search_case_insensitive<'a>(query: &'a str, content: &'a str) -> Vec<&'a str> {
83 let query = query.to_lowercase();
84 let mut results = Vec::new();
85 for line in content.lines() {
86 if line.to_lowercase().contains(&query) {
87 results.push(line)
88 }
89 }
90 results
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn case_sensitive() {
99 let query = "duct";
100 let content = "\
101Rust:
102safe, fast, productive.
103Pick three.";
104 assert_eq!(vec!["safe, fast, productive."], search(query, content))
105 }
106
107 #[test]
108 fn case_insensitive() {
109 let query = "rUsT";
110 let content = "\
111Rust:
112safe, fast, productive.
113Pick three.
114Trust me.";
115 assert_eq!(
116 vec!["Rust:", "Trust me."],
117 search_case_insensitive(query, content)
118 )
119 }
120}