use std::process::Command;
fn which_guile() -> Option<std::path::PathBuf> {
let path = std::env::var_os("PATH")?;
std::env::split_paths(&path)
.map(|d| d.join("guile"))
.find(|p| p.is_file())
}
pub fn validate(source: &str) -> Result<(), crate::Error> {
let guile = which_guile().ok_or_else(|| crate::Error::Invalid {
field: "guile".into(),
reason: "guile not found in PATH".into(),
})?;
let script = r#"(let loop () (let ((x (read))) (unless (eof-object? x) (loop))))"#;
let mut child = Command::new(guile)
.args(["-c", script])
.stdin(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|e| crate::Error::Invalid {
field: "guile".into(),
reason: e.to_string(),
})?;
use std::io::Write;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(source.as_bytes()).ok();
}
let out = child
.wait_with_output()
.map_err(|e| crate::Error::Invalid {
field: "guile".into(),
reason: e.to_string(),
})?;
if out.status.success() {
Ok(())
} else {
Err(crate::Error::Invalid {
field: "source".into(),
reason: String::from_utf8_lossy(&out.stderr).trim().to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_valid_and_rejects_invalid() {
if which_guile().is_none() {
eprintln!("skipping: no guile");
return;
}
assert!(validate("(list (channel (name 'a)))").is_ok());
assert!(validate("(list (channel").is_err());
}
}