1use std::error::Error;
2use std::fs::File;
3use std::io::Read;
4use std::{env, io};
5
6pub struct Config {
7 query: String,
8 filename: String,
9 case_sensitive: bool,
10}
11
12impl Config {
13 pub fn new(mut args: env::Args) -> Result<Config, &'static str> {
14 args.next();
15 let query = match args.next() {
16 Some(arg) => arg,
17 None => return Err("query is nil"),
18 };
19 let filename = match args.next() {
20 Some(arg) => arg,
21 None => return Err("filename is nil"),
22 };
23 let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
24 Ok(Config {
25 query,
26 filename,
27 case_sensitive,
28 })
29 }
30}
31
32pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
33 let contents = read_file(&config.filename)?;
34 let result = if config.case_sensitive {
35 search(&config.query, &contents)
36 } else {
37 search_case_sensitive(&config.query, &contents)
38 };
39 for line in result.iter() {
40 println!("{}", line);
41 }
42 Ok(())
43}
44
45fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
46 contents
47 .lines()
48 .filter(|line| line.contains(query))
49 .map(|line| line.trim())
50 .collect()
51}
52
53fn search_case_sensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
54 contents
55 .lines()
56 .filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
57 .map(|line| line.trim())
58 .collect()
59}
60
61fn read_file(path: &str) -> Result<String, io::Error> {
62 let mut s = String::new();
63 File::open(path)?.read_to_string(&mut s)?;
64 Ok(s)
65}
66
67#[cfg(test)]
68mod test {
69 use super::*;
70
71 #[test]
72 fn one_result() {
73 let query = "Apache";
74 let filename = "./LICENSE";
75 let contents = read_file(filename).unwrap();
76 let results = search(query, contents.as_str());
77 for line in results.iter() {
78 println!("matching line : {}", line);
79 }
80 assert!(results.len() > 0)
81 }
82}