mingrep/src/lib.rs

176 lines
4.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! # Grep CLI Tool
//!
//! 一个简单的命令行工具,用于从文件中搜索指定的查询字符串。
//!
//! # 功能概述
//!
//! - 提供命令行接口以执行文本搜索。
//! - 支持通过环境变量 `IGNORE_CASE` 切换大小写不敏感模式。
//! - 在遇到错误时提供友好的提示信息。
//!
//! # 使用方法
//!
//! 假设程序的可执行文件名为 `grep`,可以通过以下方式运行:
//!
//! ```bash
//! ./grep <query> <file_path>
//! ```
//!
//! 例如:
//! ```bash
//! ./grep rust example.txt
//! ```
//! 如果设置了环境变量 `IGNORE_CASE`,查询会忽略大小写。
//! - CMD
//! ``` bash
//! IGNORE_CASE=1 ./grep rust example.txt
//! ```
//! - Powershell
//! ``` powershell
//! $env:IGNORE_CASE = 1 ./grep rust example.txt
//! ```
use std::error::Error;
use std::{env, fs};
/// 配置结构体,存储查询字符串、文件路径以及是否忽略大小写的标志。
pub struct Config {
/// 查询字符串
pub query: String, // 修改为 pub便于外部访问
/// 文件路径
pub file_path: String, // 修改为 pub便于外部访问
/// 是否忽略大小写
pub ignore_case: bool, // 修改为 pub便于外部访问
}
impl Config {
/// 从命令行参数构建 `Config` 实例。
///
/// # 参数
///
/// - `args`: 命令行参数的迭代器。
///
/// # 返回值
///
/// 返回 `Config` 实例,或者在参数缺失时返回错误消息。
///
/// # 错误
///
/// 当缺少查询字符串或文件路径时,返回对应的错误消息。
///
/// # 示例
///
/// ```
/// let args = vec!["program".to_string(), "query".to_string(), "file.txt".to_string()];
/// let config = Config::build(args.into_iter());
/// assert!(config.is_ok());
/// ```
pub fn build(mut args: impl Iterator<Item = String>) -> Result<Config, &'static str> {
args.next();
let query = match args.next() {
Some(args) => args,
None => return Err("Please provide a query string"),
};
let file_path = match args.next() {
Some(args) => args,
None => return Err("Please provide a file path"),
};
let ignore_case = env::var("IGNORE_CASE").is_ok();
Ok(Config {
query,
file_path,
ignore_case,
})
}
}
/// 运行搜索逻辑。
///
/// # 参数
///
/// - `config`: 包含查询信息的 `Config` 实例。
///
/// # 返回值
///
/// 成功返回 `Ok(())`,失败返回包含错误信息的 `Box<dyn Error>`。
///
/// # 错误
///
/// 如果文件读取失败,返回错误信息。
pub fn run(config: Config) -> Result<(), Box<dyn Error>> {
let contents = fs::read_to_string(config.file_path)?;
let result = if config.ignore_case {
search_case_insensitive(&config.query, &contents)
} else {
search(&config.query, &contents)
};
for line in result {
println!("{line}")
}
Ok(())
}
/// 在内容中进行大小写敏感的查询。
///
/// # 参数
///
/// - `query`: 查询字符串。
/// - `contents`: 文件内容。
///
/// # 返回值
///
/// 包含匹配行的 `Vec<&str>`。
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines()
.filter(|line| line.contains(query))
.collect()
}
/// 在内容中进行大小写不敏感的查询。
///
/// # 参数
///
/// - `query`: 查询字符串。
/// - `contents`: 文件内容。
///
/// # 返回值
///
/// 包含匹配行的 `Vec<&str>`。
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
contents.lines()
.filter(|line| line.to_lowercase().contains(&query))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
/// 测试大小写敏感的查询功能。
#[test]
fn case_sensitive() {
let query = "duct";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Duct tape.";
assert_eq!(vec!["safe, fast, productive."], search(query, contents));
}
/// 测试大小写不敏感的查询功能。
#[test]
fn case_insensitive() {
let query = "rUsT";
let contents = "\
Rust:
safe, fast, productive.
Pick three.
Trust me.";
assert_eq!(
vec!["Rust:", "Trust me."],
search_case_insensitive(query, contents)
);
}
}