1
2use std::{fs, env, error::Error};
6
7pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
8 let contents = fs::read_to_string(config.filename)?;
9
10 let results = if config.ignore_case {
11 search_case_insensitive(&config.query, &contents)
12 }else {
13 search(&config.query, &contents)
14 };
15
16 for line in results {
17 println!("{}", line);
18 }
19
20 Ok(())
21}
22
23pub struct Config {
26 pub query: String,
28 pub filename: String,
30 pub ignore_case: bool
32}
33
34impl Config {
35 pub fn new(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
36
37 args.next();
39
40 let query = match args.next() {
42 Some(arg) => arg,
43 None => return Err("Didn't get a query string")
44 };
45
46 let filename = match args.next() {
48 Some(arg) => arg,
49 None => return Err("Didn't get a filename")
50 };
51
52 let ignore_case = env::var("IGNORE_CASE").is_ok();
53
54 Ok(Config {
55 query,
56 filename,
57 ignore_case
58 })
59 }
60}
61
62pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
68 contents
70 .lines()
71 .filter(|line| line.contains(query))
72 .collect()
73
74}
75
76pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
78 let query = query.to_lowercase();
79
80 contents
81 .lines()
82 .filter(
83 |line|
84 line
85 .to_lowercase()
86 .contains(&query)
87 )
88 .collect()
89
90}
91
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn case_sensitive() {
99 let query = "duct";
100 let contents = "\
101Rust:
102safe, fast, productive.
103Duct tape
104Pick three.";
105
106 assert_eq!(vec!["safe, fast, productive."], search(query, contents))
107 }
108
109 #[test]
110 fn case_insensitive() {
111 let query = "rUsT";
112 let contents = "\
113Rust:
114safe, fast, productive.
115Pick three.
116Trust me.";
117
118 assert_eq!(
119 vec!["Rust:", "Trust me."],
120 search_case_insensitive(query, contents)
121 )
122 }
123}