use std::path::{Path, PathBuf};
use anyhow::Result;
pub struct MergeOutcome {
pub content: String,
pub changed: bool,
}
pub struct Target {
pub name: &'static str,
pub display_name: &'static str,
pub restart_noun: &'static str,
pub default_config_path: fn() -> PathBuf,
pub hook_command: fn(resolved: &Path) -> Result<String>,
pub merge_install: fn(content: &str, hook_cmd: &str) -> Result<MergeOutcome>,
pub merge_uninstall: fn(content: &str) -> Result<MergeOutcome>,
pub needs_path_warning: bool,
pub needs_resolved_binary: bool,
pub post_install_note: Option<&'static str>,
}
pub const BACKUP_SUFFIX: &str = "pixtuoid.bak";
pub const CLAUDE: Target = Target {
name: "claude",
display_name: "Claude Code",
restart_noun: "Claude Code",
default_config_path: crate::install::claude::default_config_path,
hook_command: crate::install::claude::hook_command,
merge_install: crate::install::claude::merge_install,
merge_uninstall: crate::install::claude::merge_uninstall,
needs_path_warning: true,
needs_resolved_binary: false,
post_install_note: None,
};
pub const CODEX: Target = Target {
name: "codex",
display_name: "Codex",
restart_noun: "Codex",
default_config_path: crate::install::codex::default_config_path,
hook_command: crate::install::codex::hook_command,
merge_install: crate::install::codex::merge_install,
merge_uninstall: crate::install::codex::merge_uninstall,
needs_path_warning: false,
needs_resolved_binary: true,
post_install_note: Some(
"note: comments and formatting in config.toml are not preserved (restore from the backup if needed).",
),
};
pub const TARGETS: &[&Target] = &[&CLAUDE, &CODEX];
pub fn by_name(name: &str) -> Option<&'static Target> {
TARGETS.iter().copied().find(|t| t.name == name)
}
pub fn config_present(path: &Path) -> bool {
crate::install::io::resolve_symlink(path).exists()
}
pub fn is_present(t: &Target) -> bool {
config_present((t.default_config_path)().as_path())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn by_name_resolves_claude_and_rejects_unknown() {
assert_eq!(by_name("claude").unwrap().name, "claude");
assert_eq!(by_name("codex").unwrap().name, "codex");
assert!(by_name("nope").is_none());
assert!(by_name("all").is_none()); }
#[test]
fn config_present_checks_file_existence() {
let dir = tempfile::TempDir::new().unwrap();
let p = dir.path().join("x.json");
assert!(!config_present(&p));
std::fs::write(&p, "{}").unwrap();
assert!(config_present(&p));
}
}