重构: scan.rs拆4模块(stack/discover/readme/sample)共享常量提mod.rs
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
//! 项目技术栈探测 — 浅读根目录标志文件识别技术栈
|
||||
//!
|
||||
//! 纯函数、零状态、零外部依赖(仅 std + serde_json + anyhow)。
|
||||
//! 只读根目录标志文件,不递归遍历(控制性能与安全)。
|
||||
//!
|
||||
//! 供 commands 层薄封装暴露为 IPC 命令,新建/导入项目时自动填充 `ProjectRecord.stack`;
|
||||
//! 未来 df-ai/df-workflow 亦可复用以感知「项目是什么技术栈」。
|
||||
//!
|
||||
//! ## 模块拆分(2026-06-22,REFACTOR-260619-10)
|
||||
//!
|
||||
//! 原 `scan.rs`(741 行)按职责拆为 4 子模块,纯文件重组、零行为变化:
|
||||
//! - [`stack`]:技术栈探测(`detect_stack` + package.json 解析)
|
||||
//! - [`discover`]:历史项目发现/monorepo 展开(`discover_projects`/`is_monorepo`)
|
||||
//! - [`readme`]:README 读取与首段抽取(`read_readme_raw`/`extract_description`)
|
||||
//! - [`sample`]:项目采样(`collect_sample` + 目录树/清单/噪音目录)
|
||||
//!
|
||||
//! 跨模块共享的常量/helper 留在本 `mod.rs`(`SAMPLE_IGNORED_DIRS`/`truncate_chars`),
|
||||
//! 子模块经 `use super::*` 引用。`pub use` 重导出保持外部 `df_project::scan::*` 路径不变。
|
||||
//!
|
||||
//! README 噪音处理与图片收集已抽离至 `scan_helpers`(2026-06-19 自底向上拆分),
|
||||
//! 此处通过 `pub use` 重导出 `ImageRef` 等保持外部路径 `df_project::scan::*` 不变。
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
#[path = "../scan_helpers.rs"]
|
||||
mod scan_helpers;
|
||||
pub use scan_helpers::ImageRef;
|
||||
|
||||
pub mod discover;
|
||||
pub mod readme;
|
||||
pub mod sample;
|
||||
pub mod stack;
|
||||
|
||||
// ── 对外稳定 API 重导出(外部 `df_project::scan::*` 路径不变)──
|
||||
pub use discover::{discover_projects, is_monorepo, DiscoveredProject};
|
||||
pub use readme::extract_description;
|
||||
pub use sample::{collect_sample, ProjectSample};
|
||||
pub use stack::detect_stack;
|
||||
|
||||
// ============================================================
|
||||
// 跨模块共享常量/helper
|
||||
// ============================================================
|
||||
|
||||
/// 目录树/项目发现过滤的噪音目录(依赖产物/构建/缓存/IDE)。
|
||||
///
|
||||
/// 同时被 [`discover`] 与 [`sample`] 引用,故置于本 `mod.rs`,`pub(super)` 暴露给子模块。
|
||||
pub(super) const SAMPLE_IGNORED_DIRS: &[&str] = &[
|
||||
"node_modules", "target", ".git", "dist", "build", ".next", "venv", ".venv",
|
||||
"__pycache__", ".idea", ".vscode", ".cache", "out", "coverage", ".svelte-kit",
|
||||
".turbo", ".angular", ".gradle", "vendor",
|
||||
];
|
||||
|
||||
/// 按字符数截断(避免截断 UTF-8 多字节边界)。
|
||||
///
|
||||
/// 被 [`readme`](crate::scan::readme) 与 [`sample`](crate::scan::sample) 引用,故置于本 `mod.rs`。
|
||||
pub(super) fn truncate_chars(s: &str, max: usize) -> String {
|
||||
if s.chars().count() <= max {
|
||||
return s.to_string();
|
||||
}
|
||||
let truncated: String = s.chars().take(max).collect();
|
||||
format!("{}…(已截断)", truncated)
|
||||
}
|
||||
|
||||
/// 规范化路径用于比较:canonicalize 解析绝对规范路径(失败降级),
|
||||
/// 统一正斜杠 + 小写。防 `C:\a\b` vs `C:/a/b/` 绕过重复检查。
|
||||
/// 注:仅用于比较,存库保留用户输入的原始可读路径。
|
||||
pub fn normalize_path(p: &str) -> String {
|
||||
match Path::new(p).canonicalize() {
|
||||
Ok(abs) => abs.to_string_lossy().replace('\\', "/").to_lowercase(),
|
||||
Err(_) => p
|
||||
.trim_end_matches(['\\', '/'])
|
||||
.replace('\\', "/")
|
||||
.to_lowercase(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// ── normalize_path 跨平台归一化 ──
|
||||
// 锁定:① 反斜杠→正斜杠 ② 小写 ③ 尾部分隔符裁剪 ④ 相同逻辑路径相等(防重复录入绕过)。
|
||||
// 注:canonicalize 成功路径在不同 OS 返回不同形态(Windows 带前缀),故只断言跨平台不变性,
|
||||
// 不断言精确串,避免与平台耦合。
|
||||
#[test]
|
||||
fn normalize_path_uses_forward_slash_and_lowercase() {
|
||||
// 不存在的路径走降级分支(trim + replace + lowercase)
|
||||
let n = normalize_path(r"C:\Foo\Bar\");
|
||||
assert!(
|
||||
!n.contains('\\'),
|
||||
"反斜杠未归一: {n}"
|
||||
);
|
||||
assert_eq!(n, n.to_lowercase(), "未小写: {n}");
|
||||
assert!(
|
||||
!n.ends_with('/') && !n.ends_with('\\'),
|
||||
"尾部分隔符未裁剪: {n}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_path_equivalent_inputs_equal() {
|
||||
// 两种写法应归一为同一串(去重场景)
|
||||
let a = normalize_path(r"C:\Foo\Bar");
|
||||
let b = normalize_path("C:/Foo/Bar/");
|
||||
assert_eq!(a, b, "等价路径归一不等: {a} vs {b}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_path_relative_is_lowercase_and_forward_slash() {
|
||||
let n = normalize_path(r"..\Some\Path");
|
||||
assert!(!n.contains('\\'), "反斜杠未归一: {n}");
|
||||
assert_eq!(n, n.to_lowercase(), "未小写: {n}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user