[易学易懂系列|rustlang语言|零基础|快速入门|(25)|实战2:命令行工具minigrep(2)]

释放双眼,带上耳机,听听看~!

[易学易懂系列|rustlang语言|零基础|快速入门|(25)|实战2:命令行工具minigrep(2)]

项目实战

实战2:命令行工具minigrep

我们继续开发我们的minigrep。

我们现在以TDD测试驱动开发的模式,来开发新的功能search函数。

开始吧,我们先在src/lib.rs文件中,增加测试代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1#[cfg(test)]
2mod tests {
3    use super::*;
4
5    #[test]
6    fn one_result() {
7        let query = "duct";
8        let contents = "\
9Rust:
10safe, fast, productive.
11Pick three.";
12
13        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
14    }
15}
16
17

然后同样再写一个空函数:


1
2
3
4
1pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
2    vec![]
3}
4

然后,我们要跑一下命令:


1
2
1cargo test
2

结果显示:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
1$ cargo test
2   Compiling minigrep v0.1.0 (file:///projects/minigrep)
3--warnings--
4    Finished dev [unoptimized + debuginfo] target(s) in 0.43 secs
5     Running target/debug/deps/minigrep-abcabcabc
6
7running 1 test
8test tests::one_result ... FAILED
9
10failures:
11
12---- tests::one_result stdout ----
13        thread 'tests::one_result' panicked at 'assertion failed: `(left ==
14right)`
15left: `["safe, fast, productive."]`,
16right: `[]`)', src/lib.rs:48:8
17note: Run with `RUST_BACKTRACE=1` for a backtrace.
18
19
20failures:
21    tests::one_result
22
23test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
24
25error: test failed, to rerun pass '--lib'
26

测试不通过。

我们的实现函数search,只简单返回一个空vector。当然,不通过。

怎么办?

重构一下search函数:


1
2
3
4
5
6
7
8
9
10
11
12
1pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
2    let mut results = Vec::new();
3
4    for line in contents.lines() {
5        if line.contains(query) {
6            results.push(line);
7        }
8    }
9
10    results
11}
12

然后,我们跑一下命令:


1
2
1cargo test
2

结果显示:


1
2
3
4
5
1running 1 test
2test tests::one_result ... ok
3
4test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
5

测试通过!漂亮!

这就是TDD的基本流程。

我们看看示图:

[易学易懂系列|rustlang语言|零基础|快速入门|(25)|实战2:命令行工具minigrep(2)]

好吧,现在我们可以直接调用search函数了,把它放到src/lib.rs中的run函数里:


1
2
3
4
5
6
7
8
9
10
11
12
1//重构从文件中读取内容的业务逻辑
2pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
3    let contents = fs::read_to_string(config.filename)?;
4
5    println!("With text:\n{}", contents);
6
7    for line in search(&config.query, &contents) {
8       println!("-----search result ------{}", line);
9    }
10    Ok(())
11}
12

然后,我们用运行命令:


1
2
1cargo run frog poem.txt
2

结果为:


1
2
3
1.......
2-----search result ------How public, like a frog
3

我们找到包含frog单词的那一行!完美!

我们再找下body单词:


1
2
1cargo run body poem.txt
2

结果为:


1
2
3
4
5
6
1......
2-----search result ------I'm nobody! Who are you?
3-----search result ------Are you nobody, too?
4-----search result ------How dreary to be somebody!
5
6

结果正确!

当然,我们也可以试试不存在的单词:


1
2
1cargo run monomorphization poem.txt
2

结果为:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
1E:\code\rustProject\minigrep> cargo run monomorphization poem.txt
2    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
3     Running `target\debug\minigrep.exe monomorphization poem.txt`
4With text:
5I'm nobody! Who are you?
6Are you nobody, too?
7Then there's a pair of us - don't tell!
8They'd banish us, you know.
9
10How dreary to be somebody!
11How public, like a frog
12To tell your name the livelong day
13To an admiring bog!
14

没有出现带“—–search result ——”的结果信息,说明没有找到单词:monomorphization。

结果符合期望。

现在我们再以TDD的开发方式来开发新的功能函数search_case_insensitive,新增测试代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1#[test]
2fn case_insensitive() {
3    let query = "rUsT";
4    let contents = "\
5Rust:
6safe, fast, productive.
7Pick three.
8Trust me.";
9
10    assert_eq!(
11        vec!["Rust:", "Trust me."],
12        search_case_insensitive(query, contents)
13    );
14}
15
16

当然,我们可以简单实现一下search_case_insensitive函数,让它fail,因为这个逻辑比较简单,那就直接实现吧:


1
2
3
4
5
6
7
8
9
10
11
12
13
1pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
2    let query = query.to_lowercase();
3    let mut results = Vec::new();
4
5    for line in contents.lines() {
6        if line.to_lowercase().contains(&query) {
7            results.push(line);
8        }
9    }
10
11    results
12}
13

好的,我们跑一下命令:


1
2
1cargo test
2

结果:


1
2
3
4
5
6
7
8
9
1$cargo test
2   Compiling minigrep v0.1.0 (E:\code\rustProject\minigrep)
3    Finished dev [unoptimized + debuginfo] target(s) in 1.38s
4     Running target\debug\deps\minigrep-07da7ef1bffd9ef9.exe
5
6running 2 tests
7test case_insensitive ... ok
8test tests::one_result ... ok
9

测试通过,漂亮!

我们现在把这个方法整合到run函数里:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1//重构从文件中读取内容的业务逻辑
2pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
3    let contents = fs::read_to_string(config.filename)?;
4
5    println!("With text:\n{}", contents);
6
7    let results = if config.case_sensitive {
8        search(&config.query, &contents)
9    } else {
10        search_case_insensitive(&config.query, &contents)
11    };
12    for line in results {
13        println!("-----search result ------{}", line);
14    }
15    Ok(())
16}
17

当然,我们也要把属性case_sensitive放到结构体Config里面:


1
2
3
4
5
6
7
8
1//结构体Config用来封装参数属性
2pub struct Config {
3    pub query: String,
4    pub filename: String,
5    pub case_sensitive: bool,
6}
7
8

src/lib.rs的完整代码如下 :


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
1use std::env;
2use std::error::Error;
3use std::fs;
4//结构体Config用来封装参数属性
5pub struct Config {
6    pub query: String,
7    pub filename: String,
8    pub case_sensitive: bool,
9}
10
11//为结构体实现一个构造器,其主要功能也是读取和解析参数
12impl Config {
13    pub fn new(args: &[String]) -> Result<Config, &'static str> {
14        if args.len() < 3 {
15            return Err("参数个数不够!not enough arguments");
16        }
17
18        let query = args[1].clone();
19        let filename = args[2].clone();
20        let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
21        Ok(Config {
22            query,
23            filename,
24            case_sensitive,
25        })
26    }
27}
28//重构从文件中读取内容的业务逻辑
29pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
30    let contents = fs::read_to_string(config.filename)?;
31
32    println!("With text:\n{}", contents);
33
34    let results = if config.case_sensitive {
35        search(&config.query, &contents)
36    } else {
37        search_case_insensitive(&config.query, &contents)
38    };
39    for line in results {
40        println!("-----search result ------{}", line);
41    }
42    Ok(())
43}
44pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
45    let mut results = Vec::new();
46
47    for line in contents.lines() {
48        if line.contains(query) {
49            results.push(line);
50        }
51    }
52
53    results
54}
55pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
56    let query = query.to_lowercase();
57    let mut results = Vec::new();
58
59    for line in contents.lines() {
60        if line.to_lowercase().contains(&query) {
61            results.push(line);
62        }
63    }
64
65    results
66}
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn one_result() {
73        let query = "duct";
74        let contents = "\
75Rust:
76safe, fast, productive.
77Pick three.";
78
79        assert_eq!(vec!["safe, fast, productive."], search(query, contents));
80    }
81}
82#[test]
83fn case_insensitive() {
84    let query = "rUsT";
85    let contents = "\
86Rust:
87safe, fast, productive.
88Pick three.
89Trust me.";
90
91    assert_eq!(
92        vec!["Rust:", "Trust me."],
93        search_case_insensitive(query, contents)
94    );
95}
96
97

我们现在运行命令:


1
2
1cargo run to poem.txt
2

结果:


1
2
3
4
5
1......
2-----search result ------Are you nobody, too?
3-----search result ------How dreary to be somebody!
4
5

现在我们把环境变量设置一下:

如果你的终端是powershell,用以下命令


1
2
1 $env:CASE_INSENSITIVE=1
2

我们现在运行命令:


1
2
1cargo run to poem.txt
2

结果:


1
2
3
4
5
6
1......
2-----search result ------Are you nobody, too?
3-----search result ------How dreary to be somebody!
4-----search result ------To tell your name the livelong day
5-----search result ------To an admiring bog!
6

我们现在看看把错误信息输出到一个output.txt文件,运行以下命令:


1
2
1cargo run > output.txt
2

我们观察到,控制台没有输出。

但当前工程目录有一个output.txt文件,打开文件。里面有错误信息。

我们现在来用eprintln!宏替换掉println!宏。src/main.rs代码更新如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1use minigrep::run;
2use minigrep::Config;
3
4use std::env;
5use std::process;
6//主函数,程序入口
7fn main() {
8    let args: Vec<String> = env::args().collect();
9
10    
11    let config = Config::new(&args).unwrap_or_else(|err| {
12        eprintln!("Problem parsing arguments: {}", err);
13        process::exit(1);
14    });
15
16    if let Err(e) = run(config) {
17        //根据处理结果返回值 来处理,如果有错误,则打印信息,并直接退出当前程序
18        eprintln!("Application error: {}", e);
19
20        process::exit(1);
21    }
22}
23
24

运行以下命令:


1
2
1cargo run > output.txt
2

我们观察到,控制台现在会输出错误信息。

我们运行如下命令:


1
2
1 cargo run to poem.txt > output.txt
2

这里控制台没有输出。

但打开output.txt,里面的信息为:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1With text:
2I'm nobody! Who are you?
3Are you nobody, too?
4Then there's a pair of us - don't tell!
5They'd banish us, you know.
6
7How dreary to be somebody!
8How public, like a frog
9To tell your name the livelong day
10To an admiring bog!
11-----search result ------Are you nobody, too?
12-----search result ------How dreary to be somebody!
13-----search result ------To tell your name the livelong day
14-----search result ------To an admiring bog!
15
16

以上,希望对你有用。


1
2
1如果遇到什么问题,欢迎加入:rust新手群,在这里我可以提供一些简单的帮助,加微信:360369487,注明:博客园+rust
2

参考文章:

https://doc.rust-lang.org/stable/book/ch12-05-working-with-environment-variables.html

给TA打赏
共{{data.count}}人
人已打赏
安全技术

c++编码规范

2022-1-11 12:36:11

气候事件

湛江:台风“威马逊”最大可能在雷州湾登陆

2014-7-18 14:55:33

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索