1use std::env;
6use std::error::Error;
7use std::fs;
8
9pub struct Config {
10 pub query: String,
11 pub filename: String,
12 pub case_sensitive: bool,
13}
14
15impl Config {
16 pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
17 args.next();
18
19 let query = match args.next() {
20 Some(arg) => arg,
21 None => return Err("Expected query"),
22 };
23
24 let filename = match args.next() {
25 Some(arg) => arg,
26 None => return Err("Expected filename"),
27 };
28
29 let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
30
31 Ok(Config {
32 query,
33 filename,
34 case_sensitive,
35 })
36 }
37}
38
39pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
40 let contents = fs::read_to_string(config.filename)?;
41
42 let results = if config.case_sensitive {
45 search(&config.query, &contents)
46 } else {
47 search_case_insensitive(&config.query, &contents)
48 };
49
50 for line in results {
51 println!("{}", line);
52 }
53
54 Ok(())
55}
56
57pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
81 contents
82 .lines()
83 .filter(|line| line.contains(query))
84 .collect()
85}
86
87pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
88 let query = query.to_lowercase();
89 let mut results = Vec::new();
90
91 for line in contents.lines() {
92 if line.to_lowercase().contains(&query) {
93 results.push(line);
94 }
95 }
96 results
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn one_result() {
105 let query = "duct";
106 let contents = "\
107Rust:
108safe, fast, productive.
109Pick three.";
110
111 assert_eq!(vec!["safe, fast, productive."], search(query, contents));
112 }
113
114 #[test]
115 fn case_insensitive() {
116 let query = "rUsT";
117 let contents = "\
118Rust:
119safe, fast, productive.
120Pick three.
121Trust me.";
122
123 assert_eq!(
124 vec!["Rust:", "Trust me."],
125 search_case_insensitive(query, contents)
126 );
127 }
128}