在Rust学习社区看到了 用Rust语言实现的minigrep,对于初学者的我来说,这个项目很感兴趣,于是跟着实现了一遍,并完善了一点逻辑,以下是对项目的介绍
效果展示
本次演示介绍针对原作者代码程序的查询逻辑做了一点点小的优化,原程序逻辑的查询是放在了程序运行的时候,逻辑修改后启动的时候可以添加参数,也可以启动后添加,具体如下
修改前
查询时 需要输入查询的字符串和文件
cargo run -- th poem.txt
修改后
- 启动程序:
cargo run
- 输入要查询的字符串
th
- 输入要查询的文件路径
poem.txt
- 查询到的结果:
Then there’s a pair of us - don’t tell!
They’d banish us, you know.
To tell your name the livelong day- 退出程序:输入
:q
如下图所示
代码实现
代码结构
minigrep
├── Cargo.lock
├── Cargo.toml
├── output.txt
├── poem.txt
├── src
├── lib.rs
└── main.rs
代码展示
/src/main.rs
use std::{env, process};
use std::io::stdin;
use minigrep::Config;
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() == 1 {
// 如果没输入查询参数,就循环等待用户的输入
loop {
println!("请输入要查询的字符串:");
// 等待用户输入
let mut input = String::new();
stdin().read_line(&mut input).expect("无法读取");
let mut args = input.split_whitespace();
let mut a = String::new();
let query = args.next().unwrap_or_else(|| {
println!("请输入有效的查询字符串");
stdin().read_line(&mut a);
&a.as_str().trim()
});
if(":q".eq(query.clone())){
println!("程序退出");
process::exit(1);
}
let mut a = String::new();
let file_path = args.next().unwrap_or_else(|| {
println!("请输入文件路径:");
stdin().read_line(&mut a);
&a.as_str().trim()
});
let config1 = Config { query: query.to_string(), file_path: file_path.to_string(), ignore_case: true };
if let Err(e) = minigrep::run(config1) {
eprintln!("程序出错:{e}");
}
}
} else {
// 启动时的入参
let config = Config::build(env::args()).unwrap_or_else(|err| {
eprintln!("程序解析 参数异常 {err}");
process::exit(1);
});
println!("search for {}", config.query);
println!("in file {}", config.file_path);
if let Err(e) = minigrep::run(config) {
eprintln!("程序出错:{e}");
process::exit(1);
}
}
}
/src/lib.rs
use std::error::Error;
use std::{env, fs};
// 查询业务逻辑
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
if let Ok(contents) = fs::read_to_string(config.file_path){
let lines = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
println!("查询到的结果:");
for line in lines {
println!("{line}")
}
Ok(())
}else {
Err(Box::from("未查询到文件"))
}
}
// 配置实体
pub struct Config {
pub query: String,
pub file_path: String,
pub ignore_case:bool,
}
impl Config {
// 构建配置实体
pub fn build(mut args: impl Iterator<Item =String>) -> Result<Config, &'static str> {
let _programeName = args.next();
let query = match args.next() {
Some(query) => query,
None => return Err("未获取到 要查询的 字符串参数"),
};
let file_path = match args.next() {
Some(file_path) => file_path,
None => return Err("未获取到文件路径"),
};
// 从环境变量中找到是否包含 IGNORE_CASE
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config { query, file_path ,ignore_case})
}
}
// 对参数解析
fn parse_config(args: &[String]) -> Config {
let query = args[1].clone();
let file_path = args[2].clone();
let ignore_case = env::var("IGNORE_CASE").is_ok();
Config { query, file_path ,ignore_case}
}
// 大小写敏感查询
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.contains(query))
.collect()
}
// 忽略大小写查询
pub fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents
.lines()
.filter(|line| line.to_lowercase().contains(&query.to_lowercase()))
.collect()
}
// 测试模块
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn one_result() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents))
}
#[test]
fn test_case_insensitive() {
let query = "RusT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.";
assert_eq!(vec!["Rust:"], search_case_insensitive(query, contents))
}
}