use std::path::{Path, PathBuf};
pub fn find_claude_dir(override_path: Option<&Path>) -> Option<PathBuf> {
if let Some(p) = override_path {
if p.exists() {
return Some(p.to_path_buf());
}
}
if let Ok(val) = std::env::var("CLAUDE_DATA_DIR") {
let p = PathBuf::from(val);
if p.exists() {
return Some(p);
}
}
let home = dirs::home_dir()?;
let default = home.join(".claude");
if default.exists() {
Some(default)
} else {
None
}
}
pub fn is_lossy_encoded(encoded: &str) -> bool {
if encoded.len() < 4 {
return false;
}
encoded[3..].contains("---")
}
pub fn decode_project_name(encoded: &str) -> Option<(String, String)> {
if encoded.len() < 3 {
return Some((encoded.to_string(), encoded.to_string()));
}
if is_lossy_encoded(encoded) {
return None;
}
let chars: Vec<char> = encoded.chars().collect();
let drive = chars[0];
let rest: String = chars[3..].iter().collect();
if drive.is_ascii_alphabetic() && encoded.chars().nth(1) == Some('-') && encoded.chars().nth(2) == Some('-') {
let full_path = format!("{}:\\{}", drive, rest.replace('-', "\\"));
let display = Path::new(&full_path)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| encoded.to_string());
Some((display, full_path))
} else if encoded.starts_with("d--") {
let full_path = format!("/{}", rest.replace('-', "/"));
let display = Path::new(&full_path)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| encoded.to_string());
Some((display, full_path))
} else {
let full_path = rest.replace('-', "/");
let display = Path::new(&full_path)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| encoded.to_string());
Some((display, full_path))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_windows_path() {
let result = decode_project_name("D--dev-rust-my-crate");
assert!(result.is_some());
let (display, full) = result.unwrap();
assert_eq!(display, "crate");
assert_eq!(full, "D:\\dev\\rust\\my\\crate");
}
#[test]
fn test_decode_windows_path_dash_in_name() {
let result = decode_project_name("D--dev-foo-bar-baz");
assert!(result.is_some());
let (display, _full) = result.unwrap();
assert_eq!(display, "baz");
}
#[test]
fn test_decode_unix_path() {
let result = decode_project_name("d--opt-shared-lib");
assert!(result.is_some());
let (_display, full) = result.unwrap();
assert!(full.contains("opt"));
assert!(full.contains("shared"));
}
#[test]
fn test_decode_short_name() {
let result = decode_project_name("ab");
assert!(result.is_some());
let (display, full) = result.unwrap();
assert_eq!(display, "ab");
assert_eq!(full, "ab");
}
#[test]
fn test_decode_chinese_path_rejected() {
assert_eq!(decode_project_name("D--dev-foo---bar"), None);
}
#[test]
fn test_decode_pure_chinese_rejected() {
assert_eq!(decode_project_name("D--dev------"), None);
}
#[test]
fn test_is_lossy_encoded_chinese_path() {
assert!(is_lossy_encoded("D--dev-foo---bar"));
}
#[test]
fn test_is_lossy_encoded_pure_chinese_name() {
assert!(is_lossy_encoded("D--dev------"));
}
#[test]
fn test_is_lossy_encoded_normal_ascii() {
assert!(!is_lossy_encoded("D--dev-my-app"));
}
#[test]
fn test_is_lossy_encoded_short_names() {
assert!(!is_lossy_encoded("ab"));
assert!(!is_lossy_encoded("D--"));
assert!(!is_lossy_encoded("D--a"));
}
#[test]
fn test_is_lossy_encoded_dash_in_project_name() {
assert!(!is_lossy_encoded("D--dev-my-app"));
}
#[test]
fn test_is_lossy_encoded_triple_dash_edge() {
assert!(is_lossy_encoded("D--dev-my---app"));
}
#[test]
fn test_is_lossy_encoded_minimum_length() {
assert!(!is_lossy_encoded(""));
assert!(!is_lossy_encoded("D"));
assert!(!is_lossy_encoded("D-"));
assert!(!is_lossy_encoded("D--"));
}
#[test]
fn test_is_lossy_encoded_exactly_three_dashes() {
assert!(is_lossy_encoded("D------")); assert!(is_lossy_encoded("D--a---b")); }
#[test]
fn test_decode_project_name_single_segment() {
let result = decode_project_name("x");
assert!(result.is_some());
let (display, full) = result.unwrap();
assert_eq!(display, "x");
assert_eq!(full, "x");
}
#[test]
fn test_decode_project_name_unix_style() {
let result = decode_project_name("d--home-user-project");
assert!(result.is_some());
let (display, full) = result.unwrap();
assert_eq!(display, "project");
assert_eq!(full, "d:\\home\\user\\project");
}
#[test]
fn test_decode_project_name_non_drive_prefix() {
let result = decode_project_name("abc-def-ghi");
assert!(result.is_some());
let (display, full) = result.unwrap();
assert_eq!(display, "ghi");
assert_eq!(full, "/def/ghi");
}
}