first commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+110
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
mod core;
|
||||
mod filter;
|
||||
|
||||
pub use core::search;
|
||||
pub use filter::{ should_skip_dir, is_valid_extension };
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user