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