use std::fs;
use std::path::Path;
use anyhow::{Result, bail};
use super::{HOOK_BACKUP, HookState, HostConfig, Outcome};
use crate::home::{Home, write_private};
const SOURCE: &str = include_str!("../../integrations/pi/openmoat.js");
const BINARY_LINE: &str = "const MOAT = __MOAT_BINARY__;";
const FIRST_LINE: &str = "// OpenMoat for pi.";
pub fn text(binary: &Path) -> Result<String> {
let quoted = serde_json::to_string(&binary.to_string_lossy())?;
if SOURCE.matches(BINARY_LINE).count() != 1 {
bail!("the pi extension source has no single `{BINARY_LINE}` line");
}
Ok(SOURCE.replacen(BINARY_LINE, &format!("const MOAT = {quoted};"), 1))
}
pub fn is_ours(text: &str) -> bool {
text.starts_with(FIRST_LINE)
}
pub fn install(home: &Home, config: &HostConfig, binary: &Path, dry_run: bool) -> Result<Outcome> {
let path = &config.settings_path;
let wanted = text(binary)?;
let current = fs::read_to_string(path).ok();
let outcome = match ¤t {
Some(text) if *text == wanted => Outcome::Unchanged,
Some(text) if is_ours(text) => Outcome::Updated,
_ => Outcome::Installed,
};
if outcome == Outcome::Unchanged || dry_run {
return Ok(outcome);
}
if current.as_deref().is_some_and(|text| !is_ours(text)) {
super::back_up(home, path, HOOK_BACKUP)?;
}
write_private(path, wanted.as_bytes())?;
Ok(outcome)
}
pub fn render(binary: &Path, current: &str) -> Result<Option<String>> {
let wanted = text(binary)?;
Ok((wanted != current).then_some(wanted))
}
pub fn state(config: &HostConfig, binary: &Path) -> HookState {
let current = match fs::read_to_string(&config.settings_path) {
Ok(text) => text,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return HookState::Missing,
Err(e) => {
return HookState::Unreadable(format!(
"reading {}: {e}",
config.settings_path.display()
));
}
};
if text(binary).is_ok_and(|wanted| wanted == current) {
return HookState::Installed;
}
if !is_ours(¤t) {
return HookState::Missing;
}
HookState::Stale {
command: binary_of(¤t).unwrap_or_else(|| "?".to_owned()),
}
}
fn binary_of(text: &str) -> Option<String> {
let line = text.lines().find(|l| l.starts_with("const MOAT = "))?;
let literal = line.strip_prefix("const MOAT = ")?.strip_suffix(';')?;
serde_json::from_str(literal).ok()
}
#[cfg(test)]
mod tests;