1use std::env;
7use std::error::Error;
8use std::fs;
9
10pub struct Config {
15 pub query: String,
16 pub file_path: String,
17 pub ignore_case: bool,
18}
19
20impl Config {
21 pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
32 args.next();
33
34 let query = match args.next() {
35 Some(arg) => arg,
36 None => return Err("Didn't get a query string"),
37 };
38
39 let file_path = match args.next() {
40 Some(arg) => arg,
41 None => return Err("Didn't get a file path"),
42 };
43
44 let ignore_case = env::var("IGNORE_CASE").is_ok();
45
46 Ok(Config {
47 query,
48 file_path,
49 ignore_case,
50 })
51 }
52}
53
54pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
57 println!("Searching for {}", config.query);
58 println!("In file {}", config.file_path);
59
60 let contents = fs::read_to_string(config.file_path)?;
61
62 let results = if config.ignore_case {
63 search_case_insensitive(&config.query, &contents)
64 } else {
65 search(&config.query, &contents)
66 };
67
68 for line in results {
69 println!("{line}");
70 }
71
72 Ok(())
73}
74
75pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
93 contents
94 .lines()
95 .filter(|line| line.contains(query))
96 .collect()
97}
98
99pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
107 contents
108 .lines()
109 .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
110 .collect()
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn one_result() {
119 let query = "duct";
120 let contents = "\
121Rust:
122safe, fast, productive.
123Pick three.
124Duct tape.";
125
126 assert_eq!(vec!["safe, fast, productive."], search(query, contents));
127 }
128
129 #[test]
130 fn case_insensitive() {
131 let query = "rUsT";
132 let contents = "\
133Rust:
134safe, fast, productive.
135Pick three.
136Trust me.";
137
138 assert_eq!(
139 vec!["Rust:", "Trust me."],
140 search_case_insensitive(query, contents)
141 );
142 }
143}