Skip to main content

tokmd_analysis_types/
util.rs

1use std::path::{Path, PathBuf};
2#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
3use std::time::{SystemTime, UNIX_EPOCH};
4
5use crate::FileStatRow;
6
7#[derive(Debug, Clone, Default)]
8pub struct AnalysisLimits {
9    pub max_files: Option<usize>,
10    pub max_bytes: Option<u64>,
11    pub max_file_bytes: Option<u64>,
12    pub max_commits: Option<usize>,
13    pub max_commit_files: Option<usize>,
14}
15
16#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
17pub fn now_ms() -> u128 {
18    // Keep wasm receipts from reusing zero as a fake wall-clock sentinel.
19    js_sys::Date::now().max(1.0) as u128
20}
21
22#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
23pub fn now_ms() -> u128 {
24    SystemTime::now()
25        .duration_since(UNIX_EPOCH)
26        .unwrap_or_default()
27        .as_millis()
28}
29
30pub fn normalize_path(path: &str, root: &Path) -> String {
31    let mut out = path.replace('\\', "/");
32    if let Ok(stripped) = Path::new(&out).strip_prefix(root) {
33        out = stripped.to_string_lossy().replace('\\', "/");
34    }
35    while let Some(stripped) = out.strip_prefix("./") {
36        out = stripped.to_string();
37    }
38    out
39}
40
41pub fn path_depth(path: &str) -> usize {
42    path.split('/').filter(|seg| !seg.is_empty()).count().max(1)
43}
44
45pub fn is_test_path(path: &str) -> bool {
46    let lower = path.to_lowercase();
47    if lower.contains("/test/") || lower.contains("/tests/") || lower.contains("__tests__") {
48        return true;
49    }
50    if lower.contains("/spec/") || lower.contains("/specs/") {
51        return true;
52    }
53    let name = lower.rsplit('/').next().unwrap_or(&lower);
54    name.contains("_test")
55        || name.contains(".test.")
56        || name.contains(".spec.")
57        || name.starts_with("test_")
58        || name.ends_with("_test.rs")
59}
60
61pub fn is_infra_lang(lang: &str) -> bool {
62    let l = lang.to_lowercase();
63    matches!(
64        l.as_str(),
65        "json"
66            | "yaml"
67            | "toml"
68            | "markdown"
69            | "xml"
70            | "html"
71            | "css"
72            | "scss"
73            | "less"
74            | "makefile"
75            | "dockerfile"
76            | "hcl"
77            | "terraform"
78            | "nix"
79            | "cmake"
80            | "ini"
81            | "properties"
82            | "gitignore"
83            | "gitconfig"
84            | "editorconfig"
85            | "csv"
86            | "tsv"
87            | "svg"
88    )
89}
90
91pub fn empty_file_row() -> FileStatRow {
92    FileStatRow {
93        path: String::new(),
94        module: String::new(),
95        lang: String::new(),
96        code: 0,
97        comments: 0,
98        blanks: 0,
99        lines: 0,
100        bytes: 0,
101        tokens: 0,
102        doc_pct: None,
103        bytes_per_line: None,
104        depth: 0,
105    }
106}
107
108pub fn normalize_root(root: &Path) -> PathBuf {
109    root.canonicalize().unwrap_or_else(|_| root.to_path_buf())
110}
111
112#[cfg(test)]
113mod tests;