1use camino::{Utf8Path, Utf8PathBuf};
2use directories::ProjectDirs;
3
4use crate::error::{Error, Result};
5
6pub const PJ_STATE_DIR: &str = ".kata";
8pub const APPLIED_FILE: &str = "applied.toml";
10pub const GLOBAL_CONFIG_FILE: &str = "config.toml";
12
13pub fn global_config_dir() -> Result<Utf8PathBuf> {
21 if let Some(home) = std::env::var_os("KATA_HOME") {
22 let pb = Utf8PathBuf::from_path_buf(home.into())
23 .map_err(|p| Error::Config(format!("KATA_HOME is not valid UTF-8: {}", p.display())))?;
24 return Ok(pb);
25 }
26 let pd = ProjectDirs::from("", "yukimemi", "kata").ok_or_else(|| {
27 Error::Config("could not determine platform config directory".to_string())
28 })?;
29 Utf8PathBuf::from_path_buf(pd.config_dir().to_path_buf())
30 .map_err(|p| Error::Config(format!("config dir is not valid UTF-8: {}", p.display())))
31}
32
33pub fn global_config_path() -> Result<Utf8PathBuf> {
35 Ok(global_config_dir()?.join(GLOBAL_CONFIG_FILE))
36}
37
38pub fn template_cache_dir() -> Result<Utf8PathBuf> {
43 if let Some(home) = std::env::var_os("KATA_HOME") {
44 let pb = Utf8PathBuf::from_path_buf(home.into())
45 .map_err(|p| Error::Config(format!("KATA_HOME is not valid UTF-8: {}", p.display())))?;
46 return Ok(pb.join("cache").join("templates"));
47 }
48 let pd = ProjectDirs::from("", "yukimemi", "kata")
49 .ok_or_else(|| Error::Config("could not determine platform cache directory".to_string()))?;
50 let cache = Utf8PathBuf::from_path_buf(pd.cache_dir().to_path_buf())
51 .map_err(|p| Error::Config(format!("cache dir is not valid UTF-8: {}", p.display())))?;
52 Ok(cache.join("templates"))
53}
54
55pub fn applied_path(pj_root: &Utf8Path) -> Utf8PathBuf {
57 pj_root.join(PJ_STATE_DIR).join(APPLIED_FILE)
58}
59
60pub fn find_pj_root(start: &Utf8Path) -> Option<Utf8PathBuf> {
64 let mut cur: Option<&Utf8Path> = Some(start);
65 while let Some(dir) = cur {
66 if dir.join(PJ_STATE_DIR).join(APPLIED_FILE).is_file() {
67 return Some(dir.to_path_buf());
68 }
69 cur = dir.parent();
70 }
71 None
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77 use tempfile::TempDir;
78
79 #[test]
80 fn applied_path_joins_correctly() {
81 let p = Utf8Path::new("/tmp/myproj");
82 let expected = Utf8PathBuf::from("/tmp/myproj")
83 .join(PJ_STATE_DIR)
84 .join(APPLIED_FILE);
85 assert_eq!(applied_path(p), expected);
86 }
87
88 #[test]
89 fn find_pj_root_walks_up() {
90 let td = TempDir::new().unwrap();
91 let root = Utf8PathBuf::from_path_buf(td.path().to_path_buf()).unwrap();
92 let nested = root.join("a").join("b").join("c");
93 std::fs::create_dir_all(&nested).unwrap();
94 std::fs::create_dir_all(root.join(PJ_STATE_DIR)).unwrap();
95 std::fs::write(root.join(PJ_STATE_DIR).join(APPLIED_FILE), "").unwrap();
96
97 let found = find_pj_root(&nested).expect("should find ancestor");
98 assert_eq!(
99 std::fs::canonicalize(found.as_std_path()).unwrap(),
100 std::fs::canonicalize(root.as_std_path()).unwrap()
101 );
102 }
103
104 #[test]
105 fn find_pj_root_returns_none_when_missing() {
106 let td = TempDir::new().unwrap();
107 let root = Utf8PathBuf::from_path_buf(td.path().to_path_buf()).unwrap();
108 assert!(find_pj_root(&root).is_none());
109 }
110}