mod manager;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
use log::{debug, info, warn};
pub use manager::EnvironmentManager;
use crate::{
config::{BASE_ENV_NAME, EnvVarsConfig, EnvironmentConfig},
error::{EnvMgrError, EnvMgrResult},
};
pub struct Environment {
pub key: String,
pub name: String,
pub env_vars: Vec<EnvVarsConfig>,
pub one_password_ssh:
Option<crate::integrations::one_password_ssh_agent::OnePasswordSSHAgentConfig>,
pub gh_cli: Option<crate::integrations::gh_cli::GhCliConfig>,
pub tailscale: Option<crate::integrations::tailscale::TailscaleConfig>,
}
impl Environment {
fn load_from_config(key: &str, config: &EnvironmentConfig) -> Self {
debug!("Loading environment: {} ({key})", config.name);
Self {
key: key.to_string(),
name: config.name.clone(),
env_vars: config.env_vars.clone(),
one_password_ssh: config.op_ssh.clone(),
gh_cli: config.gh_cli.clone(),
tailscale: config.tailscale.clone(),
}
}
pub fn load_base_environment() -> EnvMgrResult<Self> {
let base_env_config = EnvironmentConfig::load_base_config()?;
Ok(Self::load_from_config(BASE_ENV_NAME, &base_env_config))
}
pub fn load_environment_by_key(key: &str) -> EnvMgrResult<Self> {
let env_config = EnvironmentConfig::load_env_config_by_key(key)?;
Ok(Self::load_from_config(key, &env_config))
}
fn env_dir(&self) -> PathBuf {
if self.key == BASE_ENV_NAME {
EnvironmentConfig::get_base_env_dir()
} else {
EnvironmentConfig::get_env_dir_by_key(&self.key)
}
}
fn files_dir(&self) -> PathBuf {
self.env_dir().join("files")
}
pub fn files_to_link(&self) -> EnvMgrResult<HashMap<PathBuf, PathBuf>> {
let mut file_map = HashMap::new();
let files_dir = self.files_dir();
if files_dir.exists() && files_dir.is_dir() {
let files = discover_files_in_dir(&files_dir)?;
for file in files {
if let Ok(target_path) = file.strip_prefix(&files_dir) {
let target_full_path = dirs::home_dir()
.ok_or(EnvMgrError::DirError("home".into()))?
.join(target_path);
debug!(
"Mapping file for linking: {} -> {}",
target_full_path.display(),
file.display()
);
file_map.insert(target_full_path, file);
} else {
warn!(
"File {} is not under the files directory {}",
file.display(),
files_dir.display()
);
}
}
} else {
info!(
"No files directory found for environment {} (files dir: {})",
self.key,
files_dir.display()
);
}
Ok(file_map)
}
}
fn discover_files_in_dir(dir: &Path) -> EnvMgrResult<Vec<PathBuf>> {
let mut files = Vec::new();
if dir.exists() && dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
files.push(path);
} else if path.is_dir() {
files.extend(discover_files_in_dir(&path)?);
}
}
}
Ok(files)
}
#[cfg(test)]
mod tests {
use std::fs;
use super::*;
#[test]
fn test_discover_files_in_dir_empty() {
let temp_dir = std::env::temp_dir().join("envmgr_test_empty");
let _ = fs::remove_dir_all(&temp_dir);
fs::create_dir_all(&temp_dir).unwrap();
let files = discover_files_in_dir(&temp_dir).unwrap();
assert_eq!(files.len(), 0);
fs::remove_dir_all(&temp_dir).unwrap();
}
#[test]
fn test_discover_files_in_dir_single_file() {
let temp_dir = std::env::temp_dir().join("envmgr_test_single");
let _ = fs::remove_dir_all(&temp_dir);
fs::create_dir_all(&temp_dir).unwrap();
fs::write(temp_dir.join("file1.txt"), "content").unwrap();
let files = discover_files_in_dir(&temp_dir).unwrap();
assert_eq!(files.len(), 1);
assert!(files[0].ends_with("file1.txt"));
fs::remove_dir_all(&temp_dir).unwrap();
}
#[test]
fn test_discover_files_in_dir_nested() {
let temp_dir = std::env::temp_dir().join("envmgr_test_nested");
let _ = fs::remove_dir_all(&temp_dir);
fs::create_dir_all(&temp_dir).unwrap();
fs::create_dir_all(temp_dir.join("subdir")).unwrap();
fs::write(temp_dir.join("file1.txt"), "content1").unwrap();
fs::write(temp_dir.join("subdir").join("file2.txt"), "content2").unwrap();
let files = discover_files_in_dir(&temp_dir).unwrap();
assert_eq!(files.len(), 2);
fs::remove_dir_all(&temp_dir).unwrap();
}
#[test]
fn test_discover_files_in_dir_nonexistent() {
let temp_dir = std::env::temp_dir().join("envmgr_test_nonexistent_dir");
let _ = fs::remove_dir_all(&temp_dir);
let files = discover_files_in_dir(&temp_dir).unwrap();
assert_eq!(files.len(), 0);
}
}