use anyhow::{Context, Result};
use par_term_config::{Profile, ProfileManager};
use std::path::PathBuf;
pub fn profiles_path() -> PathBuf {
dirs::config_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("par-term")
.join("profiles.yaml")
}
pub fn load_profiles() -> Result<ProfileManager> {
load_profiles_from(profiles_path())
}
pub fn load_profiles_from(path: PathBuf) -> Result<ProfileManager> {
crate::debug_info!("PROFILE", "Loading profiles from {:?}", path);
if !path.exists() {
crate::debug_info!(
"PROFILE",
"No profiles file found at {:?}, starting with empty profiles",
path
);
return Ok(ProfileManager::new());
}
let contents = std::fs::read_to_string(&path)
.with_context(|| format!("Failed to read profiles from {:?}", path))?;
crate::debug_info!(
"PROFILE",
"Read {} bytes from profiles file",
contents.len()
);
if contents.trim().is_empty() {
crate::debug_info!(
"PROFILE",
"Profiles file is empty, starting with empty profiles"
);
return Ok(ProfileManager::new());
}
let profiles: Vec<Profile> = serde_yaml_ng::from_str(&contents)
.with_context(|| format!("Failed to parse profiles from {:?}", path))?;
crate::debug_info!(
"PROFILE",
"Parsed {} profiles from {:?}",
profiles.len(),
path
);
for p in &profiles {
crate::debug_info!("PROFILE", " - {}: {}", p.id, p.name);
}
Ok(ProfileManager::from_profiles(profiles))
}
pub fn save_profiles(manager: &ProfileManager) -> Result<()> {
save_profiles_to(manager, profiles_path())
}
pub fn save_profiles_to(manager: &ProfileManager, path: PathBuf) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("Failed to create config directory {:?}", parent))?;
}
let profiles = manager.to_vec();
let contents = serde_yaml_ng::to_string(&profiles).context("Failed to serialize profiles")?;
std::fs::write(&path, contents)
.with_context(|| format!("Failed to write profiles to {:?}", path))?;
log::info!("Saved {} profiles to {:?}", profiles.len(), path);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_load_nonexistent_file() {
let temp = tempdir().expect("failed to create temp dir");
let path = temp.path().join("nonexistent.yaml");
let manager = load_profiles_from(path)
.expect("loading from nonexistent path should return empty manager");
assert!(manager.is_empty());
}
#[test]
fn test_load_empty_file() {
let temp = tempdir().expect("failed to create temp dir");
let path = temp.path().join("empty.yaml");
std::fs::write(&path, "").expect("failed to write empty file");
let manager =
load_profiles_from(path).expect("loading empty file should return empty manager");
assert!(manager.is_empty());
}
#[test]
fn test_save_and_load_roundtrip() {
let temp = tempdir().expect("failed to create temp dir");
let path = temp.path().join("profiles.yaml");
let mut manager = ProfileManager::new();
manager.add(
Profile::new("Test Profile 1")
.working_directory("/home/user")
.command("bash")
.tab_name("Test Tab")
.icon("🔧"),
);
manager.add(
Profile::new("Test Profile 2")
.command("ssh")
.command_args(vec![
"user@server".to_string(),
"-p".to_string(),
"22".to_string(),
]),
);
save_profiles_to(&manager, path.clone()).expect("failed to save profiles");
let loaded = load_profiles_from(path).expect("failed to load saved profiles");
assert_eq!(loaded.len(), 2);
let profiles: Vec<_> = loaded.profiles_ordered().into_iter().collect();
assert_eq!(profiles[0].name, "Test Profile 1");
assert_eq!(profiles[0].working_directory.as_deref(), Some("/home/user"));
assert_eq!(profiles[0].command.as_deref(), Some("bash"));
assert_eq!(profiles[0].tab_name.as_deref(), Some("Test Tab"));
assert_eq!(profiles[0].icon.as_deref(), Some("🔧"));
assert_eq!(profiles[1].name, "Test Profile 2");
assert_eq!(profiles[1].command.as_deref(), Some("ssh"));
assert_eq!(
profiles[1].command_args,
Some(vec![
"user@server".to_string(),
"-p".to_string(),
"22".to_string()
])
);
}
#[test]
fn test_save_creates_parent_directory() {
let temp = tempdir().expect("failed to create temp dir");
let path = temp.path().join("nested").join("dir").join("profiles.yaml");
let manager = ProfileManager::new();
save_profiles_to(&manager, path.clone()).expect("failed to save profiles to nested dir");
assert!(path.exists());
}
#[test]
fn test_load_corrupt_file_returns_error() {
let temp = tempdir().expect("failed to create temp dir");
let path = temp.path().join("corrupt.yaml");
std::fs::write(&path, "not: valid: yaml: [[[").expect("failed to write corrupt file");
let result = load_profiles_from(path);
assert!(result.is_err());
}
}