use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
pub struct PackageCache {
root: PathBuf,
}
impl PackageCache {
pub fn default_location() -> Result<Self, String> {
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.map_err(|_| "Could not determine home directory".to_string())?;
Ok(PackageCache {
root: PathBuf::from(home).join(".tl").join("packages"),
})
}
pub fn new(root: PathBuf) -> Self {
PackageCache { root }
}
pub fn root(&self) -> &Path {
&self.root
}
pub fn package_dir(&self, name: &str, version: &str) -> PathBuf {
self.root.join(name).join(version)
}
pub fn is_cached(&self, name: &str, version: &str) -> bool {
self.package_dir(name, version).exists()
}
pub fn list_versions(&self, name: &str) -> Vec<String> {
let pkg_dir = self.root.join(name);
if !pkg_dir.is_dir() {
return Vec::new();
}
let mut versions = Vec::new();
if let Ok(entries) = std::fs::read_dir(&pkg_dir) {
for entry in entries.flatten() {
if entry.path().is_dir()
&& let Some(name) = entry.file_name().to_str()
{
versions.push(name.to_string());
}
}
}
versions.sort();
versions
}
pub fn remove(&self, name: &str, version: &str) -> Result<(), String> {
let dir = self.package_dir(name, version);
if dir.exists() {
std::fs::remove_dir_all(&dir)
.map_err(|e| format!("Failed to remove cached package: {e}"))?;
}
let pkg_dir = self.root.join(name);
if pkg_dir.exists()
&& let Ok(entries) = std::fs::read_dir(&pkg_dir)
&& entries.count() == 0
{
let _ = std::fs::remove_dir(&pkg_dir);
}
Ok(())
}
pub fn source_root(&self, name: &str, version: &str) -> Option<PathBuf> {
let dir = self.package_dir(name, version);
if !dir.exists() {
return None;
}
let src_dir = dir.join("src");
if src_dir.is_dir() {
return Some(src_dir);
}
Some(dir)
}
pub fn ensure_dir(&self) -> Result<(), String> {
std::fs::create_dir_all(&self.root)
.map_err(|e| format!("Failed to create cache directory: {e}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn cache_dir_layout() {
let dir = TempDir::new().unwrap();
let cache = PackageCache::new(dir.path().to_path_buf());
let pkg_dir = cache.package_dir("mylib", "1.0.0");
assert!(pkg_dir.ends_with("mylib/1.0.0"));
}
#[test]
fn cache_is_cached() {
let dir = TempDir::new().unwrap();
let cache = PackageCache::new(dir.path().to_path_buf());
assert!(!cache.is_cached("mylib", "1.0.0"));
let pkg_dir = cache.package_dir("mylib", "1.0.0");
std::fs::create_dir_all(&pkg_dir).unwrap();
assert!(cache.is_cached("mylib", "1.0.0"));
}
#[test]
fn cache_list_versions() {
let dir = TempDir::new().unwrap();
let cache = PackageCache::new(dir.path().to_path_buf());
assert!(cache.list_versions("mylib").is_empty());
std::fs::create_dir_all(cache.package_dir("mylib", "1.0.0")).unwrap();
std::fs::create_dir_all(cache.package_dir("mylib", "2.0.0")).unwrap();
let versions = cache.list_versions("mylib");
assert_eq!(versions, vec!["1.0.0", "2.0.0"]);
}
#[test]
fn cache_remove() {
let dir = TempDir::new().unwrap();
let cache = PackageCache::new(dir.path().to_path_buf());
std::fs::create_dir_all(cache.package_dir("mylib", "1.0.0")).unwrap();
assert!(cache.is_cached("mylib", "1.0.0"));
cache.remove("mylib", "1.0.0").unwrap();
assert!(!cache.is_cached("mylib", "1.0.0"));
}
#[test]
fn cache_source_root() {
let dir = TempDir::new().unwrap();
let cache = PackageCache::new(dir.path().to_path_buf());
assert!(cache.source_root("mylib", "1.0.0").is_none());
let pkg_dir = cache.package_dir("mylib", "1.0.0");
std::fs::create_dir_all(pkg_dir.join("src")).unwrap();
let root = cache.source_root("mylib", "1.0.0").unwrap();
assert!(root.ends_with("src"));
let cache2 = PackageCache::new(dir.path().to_path_buf());
let pkg_dir2 = cache2.package_dir("nolib", "1.0.0");
std::fs::create_dir_all(&pkg_dir2).unwrap();
let root2 = cache2.source_root("nolib", "1.0.0").unwrap();
assert!(root2.ends_with("1.0.0"));
}
}