ktamido_minigrep/
lib.rs

1// ファイルの読み込み
2use std::fs::File;
3use std::io::prelude::*;
4// 環境変数やコマンドライン引数を扱う
5use std::env;
6// エラーを扱う
7use std::error::Error;
8
9pub struct Config {
10    pub query: String,
11    pub filename: String,
12    pub case_sensitive: bool,
13}
14impl Config {
15    pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
16        args.next();
17        // argsはnextで消費されるのでmutで受け取る必要がある。
18        // イテレータの要素を一つ取り出し、NoneであればErrを返す
19        let query = match args.next() {
20            Some(arg) => arg,
21            None      => return Err("Didn't get a query string"),
22        };
23        let filename = match args.next() {
24            Some(arg) => arg,
25            None      => return Err("Didn't get a file name"),
26        };
27        // env::var("CASE_INSENSITIVE"): 環境変数がセットされていたらOk, そうでなければErr
28        // is_err(): Okならtrue, Errならfalse
29        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
30        Ok(Config { query, filename, case_sensitive })
31    }
32}
33
34pub fn run(config: Config) -> Result<(), Box<Error>> {
35    // ?: OkならOkの中身を返す。Errならreturn Err(E)する
36    let mut f: File = File::open(config.filename)?;
37
38    let mut contents = String::new();
39    // f.read_tostring(): ファイルの内容をすべて読み込む
40    f.read_to_string(&mut contents)?;
41
42    let results = if config.case_sensitive {
43        search(&config.query, &contents)
44    } else {
45        search_case_insensitive(&config.query, &contents)
46    };
47
48    for line in results {
49        println!("{}", line);
50    }
51
52    Ok(())
53
54}
55
56pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
57    // contents.lines(): Stringを1行ずつ取り出すイテレータ
58    contents.lines()
59        // line.contains(query): line に query が含まれていれば true 。そうでなければfalse
60        .filter(|line| line.contains(query))
61        .collect()
62}
63
64pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
65    // query.to_lowercase(): query(&str) を 小文字化したStringを返す
66    let query: String = query.to_lowercase();
67    let mut results = Vec::new();
68    for line in contents.lines() {
69        if line.to_lowercase().contains(&query) {
70            results.push(line);
71        }
72    }
73    results
74}
75
76#[cfg(test)]
77mod test {
78    use super::*;
79
80    #[test]
81    fn one_result() {
82        let query = "duct";
83        let contents = "\
84Rust:
85safe, fast, productive.
86Pick three.";
87        assert_eq!(
88            vec!["safe, fast, productive."],
89            search(query, contents)
90        );
91    }
92
93    #[test]
94    fn case_insensitive() {
95        let query = "rUsT";
96        let contents = "\
97Rust:
98safe, fast, productive.
99Pick three.
100Trust me.";
101        assert_eq!(
102            vec!["Rust:", "Trust me."],
103            search_case_insensitive(query, contents)
104        );
105    }
106}