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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
// 注释文档生成:注释包含项的结构: //! => 这为包含注释的项,而不是位于注释之后的项增加文档。 // 这通常用于 crate 根文件(通常是 src/lib.rs)或模块的根文件为 crate 或模块整体提供文档。 // cargo doc --open => 生成文档:minigrep/target/doc/minigrep/index.html // 以下注释显示在 minigrep 文档的首页,位于 crate 中公有项列表之上 //! # mini grep //! //! `minigrep` 获取一个文件名和一个字符串作为参数,接着读取文件并找到其中包含字符串参数的行,然后打印出这些行。 use std::error::Error; use std::fs; use std::env; // ======== 以下代码段与项目无关,仅用于示范文档注释生成 ======== // 注释文档生成:使用 pub use 导出合适的公有 API // cargo doc --open => 生成文档:minigrep/target/doc/minigrep/index.html => Re-exports // pub use才会生成Re-exports文档段,仅use不行... pub use self::kinds::PrimaryColor; pub use self::utils::mix; // cargo doc --open => 生成文档:minigrep/target/doc/minigrep/index.html => Modules => kinds pub mod kinds { /// The primary colors according to the RYB color model. pub enum PrimaryColor { Red, Yellow, Blue, } /// The secondary colors according to the RYB color model. pub enum SecondaryColor { Orange, Green, Purple, } } // cargo doc --open => 生成文档:minigrep/target/doc/minigrep/index.html => Modules => utils pub mod utils { use crate::kinds::*; // 下面是markdown格式文档注释,所以在web显示时会续成一行... /// Combines two primary colors in equal amounts to create /// a secondary color. pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor { SecondaryColor::Orange } } // ======== 以上代码段与项目无关,仅用于示范文档注释生成 ======== pub struct Config { // 要搜索的字符串 pub query: String, // 要搜索的文件名 pub filename: String, /* 增加一个额外的功能来改进 minigrep: 用户可以通过设置`环境变量`来设置搜索是否是大小写敏感的 。 当然,我们也可以将其设计为一个命令行参数并要求用户每次需要时都加上它,不过在这里我们将使用环境变量。 这允许用户设置环境变量一次之后在整个终端会话中所有的搜索都将是大小写不敏感的。 设置环境变量方法: PowerShell => $env:CASE_INSENSITIVE=1 查看 $env:CASE_INSENSITIVE cmd => set CASE_INSENSITIVE=1 查看 set CASE_INSENSITIVE linux => CASE_INSENSITIVE=1 查看 echo $CASE_INSENSITIVE */ pub case_sensitive: bool, // 大小写敏感 } impl Config { /* 注释文档生成:文档注释使用三斜杠 /// 而不是两斜杆以支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。 常用的文档注释: Examples Panics Errors Safety 在文档注释中增加示例代码块是一个清楚的表明如何使用库的方法,这么做还有一个额外的好处:cargo test 也会像测试那样运行文档中的示例代码! 需要注意:写完文档后改动了代码,会导致例子不能正常工作。 cargo doc --open =>生成文档:minigrep/target/doc/minigrep/index.html => struct.Config.html cargo test => 单元测试unittests和文档测试Doc-tests */ /// 根据运行参数,创建并返回一个`Config`结构体 /// /// # Examples /// /// ``` /// let cfg = chry_minigrep::Config::new(std::env::args()); /// /* /// let cfg = chry_minigrep::Config::new(std::env::args()).unwrap_or_else(|err| { /// eprintln!("Problem parsing arguments: {}", err); /// process::exit(1); /// }); /// */ /// ``` // 因为我们拥有 args 的所有权,并且将通过对其进行迭代来改变 args ,所以我们可以将 mut 关键字添加到 args 参数的规范中以使其可变。 // 返回 Result 而不应该 返回Config或调用 panic! pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> { // &'static str => 显式静态声明 => 等价于隐式静态声明 &str args.next(); // env::args 返回值的第一个值是程序的名称, 忽略并获取下一个值 // 要搜索的字符串 let query = match args.next() { Some(arg) => arg, None => return Err("Didn't get a query string"), }; // 要搜索的文件名 let filename = match args.next() { Some(arg) => arg, None => return Err("Didn't get a filename string"), }; // println!("case_sensitive: {:?}", env::var("CASE_INSENSITIVE")); let case_sensitive = env::var("CASE_INSENSITIVE").is_err(); // 注意这里仅判断环境变量存在与否,不关心环境变量的内容! // println!("case_sensitive: {}", case_sensitive); /* env::var 返回一个 Result,它在环境变量被设置时返回包含其值的 Ok 成员,并在环境变量未被设置时返回 Err 成员。 使用 Result 的 is_err 方法来检查其是否是一个 error(也就是环境变量未被设置的情况),这也就意味着我们 需要 进行一个大小写敏感搜索。 如果CASE_INSENSITIVE 环境变量被设置为任何值,is_err 会返回 false 并将进行大小写不敏感搜索。 我们并不关心环境变量所设置的 值,只关心它是否被设置了,所以检查 is_err 而不是 unwrap、expect 或任何我们已经见过的 Result 的方法。 */ Ok(Config { query, filename, case_sensitive }) } } /* 测试驱动开发(Test Driven Development, TDD)的模式来逐步增加 minigrep 的搜索逻辑。这是一个软件开发技术,它遵循如下步骤: 1.编写一个失败的测试,并运行它以确保它失败的原因是你所期望的。 2.编写或修改足够的代码来使新的测试通过。 3.重构刚刚增加或修改的代码,并确保测试仍然能通过。 4.从步骤 1 开始重复! 这只是众多编写软件的方法之一,不过 TDD 有助于驱动代码的设计。在编写能使测试通过的代码之前编写测试有助于在开发过程中保持高测试覆盖率。 编写测试函数模块,就可去掉 src/lib.rs 和 src/main.rs 中用于检查程序行为的 println! 语句,因为不再真正需要他们了。 */ // ======================================================================== // 编写失败的测试 // ======================================================================== #[cfg(test)] mod tests { use super::*; #[test] fn case_sensitive() { let query = "hello"; let contents = "\ 01 abcdefg 02 hello world 03 Hello world 04 123465789"; assert_eq!(vec![" 02 hello world"], search(query, contents)); } // 编写一个大小写不敏感 search 函数的失败测试 #[test] fn case_insensitive() { let query = "hello"; let contents = "\ 01 abcdefg 02 hello world 03 Hello world 04 123465789"; assert_eq!(vec![" 02 hello world", "03 Hello world"], search_case_insensitive(query, contents)); } } // ======================================================================== // 编写使测试通过的代码 // ======================================================================== /* search函数设计: 遍历内容的每一行文本。 查看这一行是否包含要搜索的字符串。 如果有,将这一行加入列表返回值中。 如果没有,什么也不做。 返回匹配到的结果列表 使用显式生命周期'a:表明contents的生命周期和返回的vector生命周期相关联 因为实现里面vector包含了contents slice的字符串 slice */ fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { /* let mut results = Vec::new(); // 使用 lines 方法遍历每一行 for line in contents.lines() { // 用查询字符串搜索每一行 if line.contains(query) { // 存储匹配的行 results.push(line); } } results */ // 使用迭代器适配器来使代码更简明 也避免了一个可变的中间 results vector 的使用。 // 函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。 // 去掉可变状态可能会使得将来进行并行搜索的增强变得更容易,因为我们不必管理 results vector 的并发访问。 contents.lines() .filter(|line| line.contains(query)) // 使用 filter 适配器只保留 line.contains(query) 返回 true 的那些行 .collect() // 将匹配行收集到另一个 vector 中 } fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> { let query = query.to_lowercase(); let mut results = Vec::new(); // 使用 lines 方法遍历每一行 for line in contents.lines() { // 用查询字符串搜索每一行 if line.to_lowercase().contains(&query) { // 存储匹配的行 results.push(line); } } results } pub fn run(cfg: Config) -> Result<(), Box<dyn Error>> { // Box<dyn Error> 意味着函数会返回实现了 Error trait 的类型,不过无需指定具体将会返回的值的类型。 // 这提供了在不同的错误场景可能有不同类型的错误返回值的灵活性。这也就是 dyn,它是 “动态的”(“dynamic”)的缩写。 // 使用 ? => 允许返回的 “任何类型的错误(实现了Error trait的类型)” => Box<dyn Error> // 可以后头看一下17result.rs中的传播(propagating)概念 let contents = fs::read_to_string(cfg.filename)?; // println!("With text:{}", contents); // println!("Hello, world!"); let results = if cfg.case_sensitive { search(&cfg.query, &contents) } else { search_case_insensitive(&cfg.query, &contents) }; for line in results { println!("{}", line); } Ok(()) }