first commit

This commit is contained in:
2026-05-28 17:08:11 +08:00
commit 833abc1698
9 changed files with 313 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+110
View File
@@ -0,0 +1,110 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "rustgrep"
version = "0.1.0"
dependencies = [
"crossbeam",
"walkdir",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "rustgrep"
version = "0.1.0"
edition = "2024"
[profile.release]
opt-level = "z" # 告诉编译器:放弃优化文件大小("z" 表示追求最小体积)
lto = true # 开启全链接优化(Link-Time Optimization),把没用到的依赖代码彻底删干净
codegen-units = 1 # 降低并行编译限制,让编译器能更激进地优化代码
panic = "abort" # 发生崩溃时直接退出,不保留复杂的栈回溯信息(能省巨多体积)
strip = true # 自动剥离所有的调试信息和符号表(相当于执行了 Linux 的 strip 命令)
[dependencies]
walkdir = "2"
crossbeam = "0.8"
+24
View File
@@ -0,0 +1,24 @@
use std::fs;
use std::path::Path;
pub mod searcher;
pub use searcher::{ should_skip_dir, is_valid_extension };
// 处理单个文件搜索
pub fn search_in_file(query: &str, path: &Path, ignore_case: bool) -> Result<(), std::io::Error> {
let contents = fs::read_to_string(path)?;
// 🌟 2. 跨模块调用:通过包名或相对路径调用 searcher 里的函数
let results = searcher::search(query, &contents, ignore_case);
if !results.is_empty() {
println!("\x1b[35m{:?} :\x1b[0m", path);
let total_lines = contents.lines().count();
let max_width = total_lines.to_string().len();
for (line_num, line) in results {
println!("\x1b[32m{:>width$} |\x1b[0m {}", line_num, line, width = max_width);
}
println!();
}
Ok(())
}
+71
View File
@@ -0,0 +1,71 @@
use std::env;
use std::path::Path;
use walkdir::WalkDir;
use rustgrep::{ search_in_file, should_skip_dir, is_valid_extension };
fn main() {
// 1.收集命令行参数到一个 Vector
let args: Vec<String> = env::args().collect();
// 2.简单的边界检查,防止索引越界
if args.len() < 3 {
println!("用法提示:minigrep <搜索关键字> <文件路径>");
return;
}
let query = &args[1].clone();
let target_path = &args[2];
let extensions_str: Option<String> = if args.len() >= 4 { Some(args[3].clone()) } else { None };
// 检查环境变量是否设置了 IGNORE_CASE 只要有值就是true
let ignore_case = env::var("IGNORE_CASE").is_ok();
let temp_query: String;
let real_query = if ignore_case {
temp_query = query.to_lowercase();
&temp_query
} else {
query
};
// 判断目标路径是否存在
if !Path::new(target_path).exists() {
eprintln!("错误:路径 '{}' 不存在", target_path);
std::process::exit(1);
}
let mut it = WalkDir::new(target_path).into_iter();
// 使用WalkDir递归遍历路径
while let Some(entry) = it.next() {
// 通过match安全处理异常
match entry {
Ok(dir_entry) => {
let path = dir_entry.path();
if path.is_dir() && should_skip_dir(path) {
it.skip_current_dir();
continue;
}
// 只搜索文件 跳过文件夹
if path.is_file() {
if !is_valid_extension(path, &extensions_str) {
continue;
}
if let Err(e) = search_in_file(real_query, path, ignore_case) {
// 只在严重错误时打印警告
if e.kind() != std::io::ErrorKind::InvalidData {
eprintln!("警告: 无法读取文件 {:?}: {}", path, e);
}
}
}
}
Err(e) => eprintln!("遍历目录时出错: {}", e),
}
}
}
+11
View File
@@ -0,0 +1,11 @@
/// 核心搜索函数 <'a> 是显式生命周期注解
pub fn search<'a>(query: &str, contents: &'a str, ignore_case: bool) -> Vec<(usize, &'a str)> {
contents
.lines()
.enumerate()
.map(|(idx, line)| (idx + 1, line))
.filter(|(_, line)| {
if ignore_case { line.to_lowercase().contains(&query) } else { line.contains(query) }
})
.collect()
}
+48
View File
@@ -0,0 +1,48 @@
use std::path::Path;
use std::sync::OnceLock;
// 定义静态黑名单目录数组
const IGNORE_DIRS: &[&str] = &["target", ".git", "node_modules", ".idea", "build"];
static EXT_LIST: OnceLock<Vec<String>> = OnceLock::new();
/// 判断是否需要跳过该目录(True 代表需要拦截并剪枝)
pub fn should_skip_dir(path: &Path) -> bool {
path.file_name()
.and_then(|name| name.to_str())
.map_or(false, |name| IGNORE_DIRS.contains(&name))
}
/// 判断文件是否满足后缀过滤规则(True 代表匹配成功,可以读取)
pub fn is_valid_extension(path: &Path, extensions_str: &Option<String>) -> bool {
// 1. 没有参数直接放行
let ext_list_str = match extensions_str {
Some(s) if !s.trim().is_empty() => s,
_ => {
return true;
}
};
let extensions = EXT_LIST.get_or_init(move || {
ext_list_str
.split(",")
.map(|s| s.trim().to_lowercase())
.filter(|s| !s.is_empty())
.collect::<Vec<String>>()
});
let has_white_list = extensions.iter().any(|s| !s.starts_with('!'));
// 如果传了后缀限制,拿当前文件的后缀去匹配
path.extension()
.and_then(|os_str| os_str.to_str())
.map(|s| s.to_lowercase())
.map_or(false, |ext| {
let ignore = format!("!{}", ext);
if extensions.contains(&ignore) {
false
} else {
if has_white_list { extensions.contains(&ext) } else { true }
}
})
}
+5
View File
@@ -0,0 +1,5 @@
mod core;
mod filter;
pub use core::search;
pub use filter::{ should_skip_dir, is_valid_extension };
+28
View File
@@ -0,0 +1,28 @@
Rust:
safe, fast, productive.
Pick three.
Trust me.
Rust:
safe, fast, productive.
Pick three.
Trust me.
Rust:
safe, fast, productive.
Pick three.
Trust me.
Rust:
safe, fast, productive.
Pick three.
Trust me.
Rust:
safe, fast, productive.
Pick three.
Trust me.
Rust:
safe, fast, productive.
Pick three.
Trust me.
Rust:
safe, fast, productive.
Pick three.
Trust me.