use std::path::Path;
use speccoder_core::extensions::{Contribution, ExtensionChanges};
use speccoder_core::hooks::{HookFile, TrustState};
use super::Loaded;
use crate::hooks::trust as store;
#[must_use]
pub fn key(name: &str) -> String {
format!("extension:{name}")
}
#[must_use]
pub fn state(dir: &Path, e: &Loaded) -> TrustState {
store::state(dir, &key(&e.name), &e.digest)
}
#[must_use]
pub fn changes(dir: &Path, e: &Loaded) -> Option<ExtensionChanges> {
let before = store::last_trusted(dir, &key(&e.name))?;
if before.digest == e.digest {
return None;
}
let files_before: Vec<HookFile> =
serde_json::from_value(before.definition["files"].clone()).unwrap_or_default();
let seen: Vec<Contribution> =
serde_json::from_value(before.definition["contributions"].clone()).unwrap_or_default();
let mut files: Vec<String> = e
.files
.iter()
.filter(|f| !files_before.contains(f))
.map(|f| f.path.clone())
.chain(
files_before
.iter()
.filter(|b| !e.files.iter().any(|f| f.path == b.path))
.map(|b| format!("{} (gone)", b.path)),
)
.collect();
files.sort();
let (widened, gone) = speccoder_core::extensions::widened(&seen, &e.contributions());
Some(ExtensionChanges {
files,
widened,
gone,
})
}
pub fn record(dir: &Path, e: &Loaded, by: &str, via: &str) {
store::record_as(
dir,
&key(&e.name),
&e.digest,
serde_json::json!({ "files": e.files, "contributions": e.contributions() }),
by,
via,
);
}
pub fn parse_flag(value: &str) -> Result<(String, String), String> {
match value.split_once('=') {
Some((n, d)) if !n.is_empty() && d.starts_with("sha256:") => {
Ok((n.to_owned(), d.to_owned()))
}
_ => Err(format!(
"--trust-extension {value}: give <name>=sha256:<digest>, as `speccoder extensions` prints it"
)),
}
}
pub fn apply_flags(
dir: &Path,
all: &[Loaded],
flags: &[(String, String)],
by: &str,
) -> Result<(), String> {
for (name, digest) in flags {
let Some(e) = all.iter().find(|e| &e.name == name) else {
return Err(format!(
"--trust-extension {name}: there is no extension `{name}`"
));
};
if !e.usable() {
return Err(format!(
"--trust-extension {name}: it cannot be loaded: {}",
e.problems.join("; ")
));
}
if &e.digest != digest {
return Err(format!(
"--trust-extension {name}: the extension is now {}, not {digest} — review it (speccoder extensions) before trusting",
e.digest
));
}
if state(dir, e) != TrustState::Trusted {
record(dir, e, by, "ci");
}
}
Ok(())
}