pandastd_mini_grep/
lib.rs1use std::io::{Error, ErrorKind};
9use std::{env, fs};
10
11pub fn run(conf: Config) -> Result<(), Error> {
12 println!(
13 "ignore case: {}",
14 if conf.ignore_case { "true" } else { "false" }
15 );
16 println!("searching for \"{}\" in \"{}\"\n", conf.query, conf.path);
17 let contents = fs::read_to_string(conf.path)?;
18 let results = search(conf.ignore_case, &conf.query, &contents)?;
19 for line in results {
20 println!("{}", line);
21 }
22 Ok(())
23}
24
25pub struct Config {
33 query: String,
34 path: String,
35 ignore_case: bool,
36}
37
38impl Config {
39 pub fn new(args: &[String]) -> Result<Config, Error> {
40 if args.len() != 3 {
41 Err(Error::new(
42 ErrorKind::InvalidInput,
43 "need 2 arguments, [query] [path]",
44 ))?
45 }
46 let query = &args[1];
48 let path = &args[2];
49 let ignore_case = match env::var("IGNORE_CASE") {
51 Ok(val) => val == "1" || val == "true" || val == "TRUE" || val == "True",
52 Err(_) => false,
53 };
54 Ok(Config {
56 query: query.clone(),
57 path: path.clone(),
58 ignore_case,
59 })
60 }
61 pub fn iter_new(mut args: impl Iterator<Item = String>) -> Result<Config, Error> {
63 let mut index = 0;
64 let ignore_case = match env::var("IGNORE_CASE") {
65 Ok(val) => val == "1" || val == "true" || val == "TRUE" || val == "True",
66 Err(_) => false,
67 };
68 let mut cfg = Config {
69 query: String::new(),
70 path: String::new(),
71 ignore_case,
72 };
73 while let Some(arg) = args.next() {
74 match index {
75 1 => cfg.query = arg,
76 2 => cfg.path = arg,
77 _ => Err(Error::new(
78 ErrorKind::InvalidInput,
79 "need 2 arguments, [query] [path]",
80 ))?,
81 }
82 index += 1;
83 }
84 Ok(cfg)
85 }
86}
87
88pub fn search<'a>(
89 ignore_case: bool,
90 query: &str,
91 contents: &'a str,
92) -> Result<Vec<&'a str>, Error> {
93 let mut query = query.to_string();
94 if ignore_case {
95 query = query.to_lowercase();
96 }
97 let mut results = Vec::new();
98 for line in contents.lines() {
99 let mut search_line = line.to_string();
100 if ignore_case {
101 search_line = search_line.to_lowercase();
102 }
103 if search_line.contains(&query) {
104 results.push(line);
105 }
106 }
107 Ok(results)
108}
109
110pub fn filter_search<'a>(
112 ignore_case: bool,
113 query: &str,
114 contents: &'a str,
115) -> Result<Vec<&'a str>, Error> {
116 let results = contents
117 .lines()
118 .filter(|line| {
119 let mut search_line = line.to_string();
120 let mut search_query = query.to_string();
121 if ignore_case {
122 search_line = search_line.to_lowercase();
123 search_query = search_query.to_lowercase();
124 }
125 search_line.contains(&search_query)
126 })
127 .collect();
128 Ok(results)
129}
130
131#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn test_new() {
148 let args = vec![
149 String::from("mini_grep"),
150 String::from("searchstring"),
151 String::from("example-filename.txt"),
152 ];
153 let conf = Config::new(&args).unwrap();
154 assert_eq!(conf.query, "searchstring");
155 assert_eq!(conf.path, "example-filename.txt");
156 }
157
158 #[test]
159 #[should_panic(expected = "need 2 arguments, [query] [path]")]
160 fn test_new_with_invalid_args() {
161 let args = vec![String::from("mini_grep"), String::from("searchstring")];
162 let _conf = Config::new(&args).unwrap();
163 }
164
165 #[test]
166 fn test_search() {
167 let query = "duct";
168 let contents = "\
170Rust:
171safe, fast, productive.
172Pick three.";
173 let results = search(true, query, contents).unwrap();
174 assert_eq!(results, vec!["safe, fast, productive."]);
175 }
176
177 #[test]
178 #[should_panic(expected = "no results found for searchstring")]
179 fn test_search_with_invalid_query() {
180 let query = "searchstring";
181 let contents = "\
182Rust:
183safe, fast, productive.
184Pick three.";
185 let _results = search(true, query, contents).unwrap();
188 }
189}