use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VenvProfile {
Strict,
Relaxed,
}
impl VenvProfile {
pub fn as_str(self) -> &'static str {
match self {
VenvProfile::Strict => "strict",
VenvProfile::Relaxed => "relaxed",
}
}
pub fn from_str(s: &str) -> Option<Self> {
match s {
"strict" => Some(VenvProfile::Strict),
"relaxed" => Some(VenvProfile::Relaxed),
_ => None,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct VenvRecord {
pub root: String,
pub profile: String,
}
#[derive(Debug, Clone)]
pub struct Venv {
pub name: String,
pub root: PathBuf,
pub profile: VenvProfile,
}
fn path_for_venvs(base: &Path) -> PathBuf {
base.join(".dal").join("venvs.json")
}
pub fn load_venvs(registry_path: &Path) -> HashMap<String, VenvRecord> {
if let Ok(data) = fs::read_to_string(registry_path) {
if let Ok(map) = serde_json::from_str::<HashMap<String, VenvRecord>>(&data) {
return map;
}
}
HashMap::new()
}
pub fn save_venvs(registry_path: &Path, venvs: &HashMap<String, VenvRecord>) -> Result<(), String> {
if let Some(parent) = registry_path.parent() {
fs::create_dir_all(parent).map_err(|e| format!("create .dal: {}", e))?;
}
let data =
serde_json::to_string_pretty(venvs).map_err(|e| format!("serialize venvs: {}", e))?;
fs::write(registry_path, data)
.map_err(|e| format!("write {}: {}", registry_path.display(), e))?;
Ok(())
}
pub fn project_registry_path(base: &Path) -> PathBuf {
path_for_venvs(base)
}
pub fn global_registry_path() -> Option<PathBuf> {
if let Ok(p) = std::env::var("DAL_VENV_REGISTRY") {
if !p.is_empty() {
return Some(PathBuf::from(p));
}
}
dirs::config_dir().map(|d| d.join("dal").join("venvs.json"))
}
pub fn resolve_venv(name: &str, project_base: &Path) -> Result<(Venv, PathBuf), String> {
let proj_path = project_registry_path(project_base);
let mut venvs = load_venvs(&proj_path);
if let Some(rec) = venvs.remove(name) {
let profile = VenvProfile::from_str(&rec.profile).unwrap_or(VenvProfile::Relaxed);
let root = PathBuf::from(&rec.root);
if !root.exists() {
return Err(format!(
"venv '{}' root does not exist: {}",
name,
root.display()
));
}
return Ok((
Venv {
name: name.to_string(),
root,
profile,
},
proj_path,
));
}
if let Some(global_path) = global_registry_path() {
let mut venvs = load_venvs(&global_path);
if let Some(rec) = venvs.remove(name) {
let profile = VenvProfile::from_str(&rec.profile).unwrap_or(VenvProfile::Relaxed);
let root = PathBuf::from(&rec.root);
if !root.exists() {
return Err(format!(
"venv '{}' root does not exist: {}",
name,
root.display()
));
}
return Ok((
Venv {
name: name.to_string(),
root,
profile,
},
global_path,
));
}
}
Err(format!("venv '{}' not found", name))
}
pub fn create_venv(
name: &str,
root: &Path,
profile: VenvProfile,
registry_path: &Path,
) -> Result<(), String> {
if name.is_empty() {
return Err("venv name cannot be empty".to_string());
}
let root = root
.canonicalize()
.map_err(|e| format!("root {}: {}", root.display(), e))?;
if !root.is_dir() {
return Err(format!("root is not a directory: {}", root.display()));
}
let mut venvs = load_venvs(registry_path);
if venvs.contains_key(name) {
return Err(format!("venv '{}' already exists", name));
}
venvs.insert(
name.to_string(),
VenvRecord {
root: root.to_string_lossy().to_string(),
profile: profile.as_str().to_string(),
},
);
save_venvs(registry_path, &venvs)
}
pub fn list_venvs(project_base: &Path) -> Vec<(String, VenvRecord)> {
let mut out: Vec<(String, VenvRecord)> = Vec::new();
let proj_path = project_registry_path(project_base);
let proj = load_venvs(&proj_path);
for (k, v) in proj {
out.push((k, v));
}
if let Some(global_path) = global_registry_path() {
if global_path != proj_path {
for (k, v) in load_venvs(&global_path) {
if !out.iter().any(|(n, _)| n == &k) {
out.push((k, v));
}
}
}
}
out.sort_by(|a, b| a.0.cmp(&b.0));
out
}
pub fn show_venv(name: &str, project_base: &Path) -> Result<(VenvRecord, PathBuf), String> {
let proj_path = project_registry_path(project_base);
let venvs = load_venvs(&proj_path);
if let Some(rec) = venvs.get(name) {
return Ok((rec.clone(), proj_path));
}
if let Some(global_path) = global_registry_path() {
let venvs = load_venvs(&global_path);
if let Some(rec) = venvs.get(name) {
return Ok((rec.clone(), global_path));
}
}
Err(format!("venv '{}' not found", name))
}
pub fn delete_venv(name: &str, project_base: &Path) -> Result<bool, String> {
let proj_path = project_registry_path(project_base);
let mut venvs = load_venvs(&proj_path);
if venvs.remove(name).is_some() {
save_venvs(&proj_path, &venvs)?;
return Ok(true);
}
if let Some(global_path) = global_registry_path() {
let mut venvs = load_venvs(&global_path);
if venvs.remove(name).is_some() {
save_venvs(&global_path, &venvs)?;
return Ok(true);
}
}
Ok(false)
}
pub const STRICT_ALLOWED_NAMESPACES: &[&str] = &[
"chain", "crypto", "log", "config", "key", "auth", "evolve", "sync", "json", "test",
];