use std::path::{Path, PathBuf};
use crate::extensions::preexec::{create_tmpdir, first_rendered_html, run_preexec, tmpdir_path};
use crate::extensions::{ExtensionDef, ExtensionError, TemplateErrorKind};
use super::template::{references_rendered_basename, references_tmpdir};
use super::{expand_command_host, expand_preexec_args, ExpandedInvocation, MatchContext};
thread_local! {
static LAST_CREATED_TMPDIR: std::cell::RefCell<Option<PathBuf>> = const { std::cell::RefCell::new(None) };
}
#[doc(hidden)]
#[must_use]
pub fn last_created_tmpdir() -> Option<PathBuf> {
LAST_CREATED_TMPDIR.with(|cell| cell.borrow().clone())
}
fn ensure_preexec_output_parents(args: &[String]) -> Result<(), ExtensionError> {
for window in args.windows(2) {
if window[0] == "--output" {
if let Some(parent) = Path::new(&window[1]).parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent).map_err(|err| ExtensionError::Io {
message: format!(
"could not create preexec output parent '{}': {err}",
parent.display()
),
source: Some(Box::new(err)),
})?;
}
}
}
}
Ok(())
}
pub fn expand_and_validate(
ext: &ExtensionDef,
ctx: &MatchContext<'_>,
) -> Result<ExpandedInvocation, ExtensionError> {
let mut ctx = ctx.clone();
let temp_guard = if references_tmpdir(ext) {
let dir = create_tmpdir()?;
ctx.tmpdir = Some(tmpdir_path(&dir));
LAST_CREATED_TMPDIR.with(|cell| {
*cell.borrow_mut() = ctx.tmpdir.clone();
});
Some(dir)
} else {
None
};
if let Some(pre) = ext.preexec.as_ref() {
let (cmd, args) = match expand_preexec_args(pre, ext, &ctx) {
Ok(pair) => pair,
Err(err) => {
drop(temp_guard);
return Err(err);
}
};
ensure_preexec_output_parents(&args)?;
let stdout_capture = ext.preexec.as_ref().and_then(|p| p.stdout);
match run_preexec(&cmd, &args, stdout_capture) {
Ok(stdout) => ctx.preexec_stdout = stdout,
Err(err) => {
drop(temp_guard);
return Err(err);
}
}
if references_rendered_basename(ext) {
let tmp = ctx.tmpdir.as_deref().ok_or_else(|| {
ExtensionError::template(
TemplateErrorKind::Unavailable,
"{rendered_basename} requires {tmpdir}",
)
})?;
match first_rendered_html(tmp) {
Ok(name) => ctx.rendered_basename = Some(name),
Err(err) => {
drop(temp_guard);
return Err(err);
}
}
}
}
let (command, host_overrides) = match expand_command_host(ext, &ctx) {
Ok(pair) => pair,
Err(err) => {
drop(temp_guard);
return Err(err);
}
};
if let Err(source) = wyvern_schema::validate(&command) {
drop(temp_guard);
return Err(ExtensionError::InvalidCommand { source });
}
Ok(ExpandedInvocation {
command,
host_overrides,
temp_guard,
})
}