thoughts_tool/utils/
paths.rs1use anyhow::Result;
2use dirs;
3use std::path::Path;
4use std::path::PathBuf;
5
6pub fn expand_path(path: &Path) -> Result<PathBuf> {
8 let path_str = path.to_string_lossy();
9
10 if let Some(stripped) = path_str.strip_prefix("~/") {
11 let home = dirs::home_dir()
12 .ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
13 Ok(home.join(stripped))
14 } else if path_str == "~" {
15 dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))
16 } else {
17 Ok(path.to_path_buf())
18 }
19}
20
21pub fn ensure_dir(path: &Path) -> Result<()> {
23 if !path.exists() {
24 std::fs::create_dir_all(path)?;
25 }
26 Ok(())
27}
28
29pub fn sanitize_dir_name(name: &str) -> String {
31 name.chars()
32 .map(|c| match c {
33 '/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
34 _ => c,
35 })
36 .collect()
37}
38
39pub fn get_repo_config_path(repo_root: &Path) -> PathBuf {
43 repo_root.join(".thoughts").join("config.json")
44}
45
46#[cfg(target_os = "macos")]
48pub fn get_external_metadata_dir() -> Result<PathBuf> {
49 let home =
50 dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
51 Ok(home.join(".thoughts").join("data").join("external"))
52}
53
54#[allow(dead_code)]
56pub fn get_local_metadata_path(repo_root: &Path) -> PathBuf {
58 repo_root.join(".thoughts").join("data").join("local.json")
59}
60
61#[allow(dead_code)]
63pub fn get_repo_rules_path(repo_root: &Path) -> PathBuf {
65 repo_root.join(".thoughts").join("rules.json")
66}
67
68fn xdg_config_home() -> Result<PathBuf> {
72 if let Some(dir) = std::env::var_os("XDG_CONFIG_HOME") {
73 return Ok(PathBuf::from(dir));
74 }
75 let home =
76 dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
77 Ok(home.join(".config"))
78}
79
80pub fn get_repo_mapping_path() -> Result<PathBuf> {
84 Ok(xdg_config_home()?.join("agentic").join("repos.json"))
85}
86
87pub fn get_legacy_repo_mapping_path() -> Result<PathBuf> {
91 let home =
92 dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
93 Ok(home.join(".thoughts").join("repos.json"))
94}
95
96pub fn get_personal_config_path() -> Result<PathBuf> {
98 let home =
99 dirs::home_dir().ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?;
100 Ok(home.join(".thoughts").join("config.json"))
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use serial_test::serial;
107
108 #[test]
109 #[serial]
110 fn test_expand_path() {
111 let home = dirs::home_dir().unwrap();
113 assert_eq!(expand_path(Path::new("~/test")).unwrap(), home.join("test"));
114 assert_eq!(expand_path(Path::new("~")).unwrap(), home);
115
116 assert_eq!(
118 expand_path(Path::new("/tmp/test")).unwrap(),
119 PathBuf::from("/tmp/test")
120 );
121
122 assert_eq!(
124 expand_path(Path::new("test")).unwrap(),
125 PathBuf::from("test")
126 );
127 }
128
129 #[test]
130 fn test_sanitize_dir_name() {
131 assert_eq!(sanitize_dir_name("normal-name_123"), "normal-name_123");
132 assert_eq!(
133 sanitize_dir_name("bad/name:with*chars?"),
134 "bad_name_with_chars_"
135 );
136 }
137}