1use anyhow::Result;
2use sha2::{Digest, Sha256};
3use std::path::{Path, PathBuf};
4
5pub fn project_hash_from_root(project_root: &str) -> String {
12 let normalized = normalize_path(Path::new(project_root));
14 let path_str = normalized.to_string_lossy();
15
16 let mut hasher = Sha256::new();
17 hasher.update(path_str.as_bytes());
18 format!("{:x}", hasher.finalize())
19}
20
21pub fn is_64_char_hex(s: &str) -> bool {
23 s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
24}
25
26pub fn normalize_path(path: &Path) -> PathBuf {
28 path.canonicalize().unwrap_or_else(|_| {
29 if path.is_absolute() {
30 path.to_path_buf()
31 } else {
32 std::env::current_dir()
33 .map(|cwd| cwd.join(path))
34 .unwrap_or_else(|_| path.to_path_buf())
35 }
36 })
37}
38
39pub fn paths_equal(path1: &Path, path2: &Path) -> bool {
41 normalize_path(path1) == normalize_path(path2)
42}
43
44pub fn discover_project_root(explicit_project_root: Option<&str>) -> Result<PathBuf> {
49 if let Some(root) = explicit_project_root {
50 return Ok(PathBuf::from(root));
51 }
52
53 if let Ok(env_root) = std::env::var("AGTRACE_PROJECT_ROOT") {
54 return Ok(PathBuf::from(env_root));
55 }
56
57 let cwd = std::env::current_dir()?;
58 Ok(cwd)
59}
60
61pub fn resolve_effective_project_hash(
63 explicit_hash: Option<&str>,
64 all_projects: bool,
65) -> Result<(Option<String>, bool)> {
66 if let Some(hash) = explicit_hash {
67 Ok((Some(hash.to_string()), false))
68 } else if all_projects {
69 Ok((None, true))
70 } else {
71 let project_root_path = discover_project_root(None)?;
72 let current_project_hash = project_hash_from_root(&project_root_path.to_string_lossy());
73 Ok((Some(current_project_hash), false))
74 }
75}
76
77pub fn truncate(s: &str, max: usize) -> String {
79 if s.chars().count() <= max {
80 s.to_string()
81 } else {
82 s.chars().take(max).collect::<String>() + "...(truncated)"
83 }
84}