1use std::fs::File;
2use std::io::prelude::*;
3use std::error::Error;
4use std::env;
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: env::Args) -> Result<Config, &'static str> {
14 args.next();
15
16 let query = match args.next() {
17 Some(arg) => arg,
18 None => return Err("Didn't get a query string"),
19 };
20 let filename = match args.next() {
21 Some(arg) => arg,
22 None => return Err("Didn't get a file name"),
23 };
24 let case_sensitive = match env::var("CASE_INSENSITIVE") {
25 Ok(val) => val == "0",
26 Err(_) => true,
27 };
28
29 Ok(Config { query, filename, case_sensitive })
30 }
31}
32
33pub fn run(config: Config) -> Result<(), Box<dyn Error>>{
34 let mut f = File::open(config.filename)?;
35
36 let mut contents = String::new();
37 f.read_to_string(&mut contents)?;
38
39 let results = if config.case_sensitive {
40 search(&config.query, &contents)
41 } else {
42 search_case_insensitive(&config.query, &contents)
43 };
44
45 for line in results {
46 println!("{}", line);
47 }
48
49 Ok(())
50}
51
52pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
53 contents.lines()
54 .filter(|line| line.contains(query))
55 .collect()
56}
57
58pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
59 let query = query.to_lowercase();
60
61 contents.lines()
62 .filter(|line| line.to_lowercase().contains(&query))
63 .collect()
64}
65
66
67#[cfg(test)]
68mod test {
69 use super::*;
70 const CONTENTS: &'static str = "Rust:\nsafe, fast, productive.\nPick three.\nDuct tape.\nTrust me.";
71
72 #[test]
73 fn case_sensitive() {
74 let query = "duct";
75 assert_eq!(
76 vec!["safe, fast, productive."],
77 search(query, CONTENTS)
78 );
79 }
80
81 #[test]
82 fn case_insensitive() {
83 let query = "rUsT";
84 assert_eq!(
85 vec!["Rust:", "Trust me."],
86 search_case_insensitive(query, CONTENTS)
87 );
88 }
89}