1use std::{env, error::Error, fs, io};
2use regex::{Regex, RegexSetBuilder};
3
4pub struct Config {
5 pub query: String,
6 pub file_path: String,
7 pub ignore_case: bool,
8}
9
10impl Config {
11 pub fn new() -> Config {
13 let query = String::new();
14 let file_path = String::new();
15 let ignore_case = false;
16 Config{query, file_path, ignore_case}
17 }
18
19 pub fn build(mut args: impl Iterator<Item=String>) -> Result<Config, &'static str> {
20 args.next();
22 let query = match args.next(){
23 Some(arg)=>arg,
24 None=>return Err("Didn't get a query string"),
25 };
26 let file_path = match args.next(){
27 Some(arg)=>arg,
28 None=>String::new(),
29 };
30 let ignore_case = env::var("IGNORE_CASE").is_ok();
31 Ok(Config{query, file_path, ignore_case})
32 }
33}
34
35pub fn run(config:Config)-> Result<(), Box<dyn Error>>{
36 let contents ;
38 if config.file_path == String::new() {
39 contents = io::read_to_string(io::stdin()).unwrap();
40 } else {
41 contents = fs::read_to_string(&config.file_path)?;
42 }
43
44 let query_result = search(&config, &contents);
45 for line in query_result{
46 println!("{}", colored(line, &config.query));
47 }
48 Ok(())
49}
50
51pub fn colored<'a>(line: &'a str, query: &'a str) -> String {
52 let re = Regex::new(query).unwrap();
53 let colored_s = re.replace_all(&line, "\x1B[31m$0\x1B[0m");
54 colored_s.to_string()
55}
56
57pub fn search<'a>(config: &Config, contents: &'a str) -> Vec<&'a str>{
58 let mut res = Vec::new();
59 let query = if config.ignore_case {
60 config.query.to_lowercase()
61 } else {
62 config.query.to_string()
63 };
64 let set = RegexSetBuilder::new(&[query.as_str()]).case_insensitive(config.ignore_case).build().unwrap();
65
66 for line in contents.lines(){
67 if set.is_match(line){
68 res.push(line);
69 }
70 }
71 res
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn case_sensitive() {
80 let mut config = Config::new();
81 config.query = "duct".to_string();
82 let contents = "\
83Rust:
84safe, fast, productive.
85Pick three.
86Duct tape.";
87
88 assert_eq!(
89 vec!["safe, fast, productive."],
90 search(&config, contents)
91 );
92 }
93
94 #[test]
95 fn case_insensitive() {
96 let mut config = Config::new();
97 config.query = "rUsT".to_string();
98 config.ignore_case = true;
99 let contents = "\
100Rust:
101safe, fast, productive.
102Pick three.
103Trust me.";
104
105 assert_eq!(
106 vec!["Rust:", "Trust me."],
107 search(&config, contents)
108 );
109 }
110}