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 presence_probe: Option<fn() -> bool>,
}
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: !cfg!(windows),
needs_resolved_binary: cfg!(windows),
post_install_note: None,
presence_probe: 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).",
),
presence_probe: None,
};
pub const REASONIX: Target = Target {
name: "reasonix",
display_name: "Reasonix",
restart_noun: "Reasonix",
default_config_path: crate::install::reasonix::default_config_path,
hook_command: crate::install::reasonix::hook_command,
merge_install: crate::install::reasonix::merge_install,
merge_uninstall: crate::install::reasonix::merge_uninstall,
needs_path_warning: false,
needs_resolved_binary: true,
post_install_note: None,
presence_probe: Some(crate::install::reasonix::detect_installed),
};
pub const TARGETS: &[&Target] = &[&CLAUDE, &CODEX, &REASONIX];
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 {
match t.presence_probe {
Some(probe) => probe(),
None => 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_eq!(by_name("reasonix").unwrap().name, "reasonix");
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));
}
}