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