grep_rs/
lib.rs

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    // 构造函数
12    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        // 跳过程序名
21        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    // 读取文件
37    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}