1use std::error::Error;
2use std::env;
3use std::fs::File;
4use std::io::prelude::*;
5
6pub struct Config {
7 pub query: String,
8 pub filename: String,
9 pub case_sensitive: bool,
10}
11
12impl Config {
13 pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
16 args.next();
17
18 let query = match args.next() {
22 Some(arg) => arg,
24 None => return Err("Didn't get a query string"),
25 };
26 let filename = match args.next() {
27 Some(arg) => arg,
28 None => return Err("Didn't get a file name"),
29 };
30 let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
31
32 Ok(Config { query, filename, case_sensitive })
33 }
34}
35
36pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
39 contents.lines()
47 .filter(|line| line.contains(query))
48 .collect()
49}
50
51pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
52 let query = query.to_lowercase();
53
54 contents.lines()
62 .filter(|line| line.to_lowercase().contains(&query))
63 .collect()
64}
65
66pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
68 let mut file = File::open(config.filename)?;
69 let mut contents = String::new();
70 file.read_to_string(&mut contents)?;
71 let results = if config.case_sensitive {
72 search(&config.query, &contents)
73 } else {
74 search_case_insensitive(&config.query, &contents)
75 };
76 for line in results {
77 println!("{}", line);
78 }
79 Ok(())
80}
81
82#[cfg(test)]
83mod test {
84 use super::*;
85
86 #[test]
87 fn case_sensitive() {
88 let query = "duct";
89 let contents = "\
90 Rust:
91safe, fast, productive.
92Pick three.
93Duct tape.";
94
95 assert_eq!(
96 vec!["safe, fast, productive."],
97 search(query, contents)
98 )
99 }
100
101 #[test]
102 fn case_insensitive() {
103 let query = "rUsT";
104 let contents = "\
105 Rust:
106safe, fast, productive.
107Pick three.
108Trust me.";
109
110 assert_eq!(
111 vec!["Rust:", "Trust me."],
112 search_case_insensitive(query, contents)
113 );
114 }
115}
116