use std::path::Path;
use afterburner_core::{AfterburnerError, OutputValue, Result, ScriptOutcome};
pub use afterburner_core::Language;
#[derive(Debug, Clone, PartialEq)]
pub struct Outcome {
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub output: OutputValue,
pub ok: bool,
}
impl Outcome {
pub fn stdout_str(&self) -> String {
String::from_utf8_lossy(&self.stdout).into_owned()
}
pub fn stderr_str(&self) -> String {
String::from_utf8_lossy(&self.stderr).into_owned()
}
}
fn no_output() -> OutputValue {
OutputValue::Json(serde_json::Value::Null)
}
impl From<ScriptOutcome> for Outcome {
fn from(s: ScriptOutcome) -> Self {
Self {
stdout: s.stdout,
stderr: s.stderr,
output: no_output(),
ok: s.exit_code == 0,
}
}
}
#[cfg(feature = "wasm")]
impl From<afterburner_wasi::pyodide_runner::PyodideRunOutput> for Outcome {
fn from(p: afterburner_wasi::pyodide_runner::PyodideRunOutput) -> Self {
Self {
stdout: p.stdout,
stderr: p.stderr,
output: p.output,
ok: p.exit_code == 0,
}
}
}
#[cfg(feature = "wasm")]
impl From<afterburner_wasi::ruby_runner::RubyRunOutput> for Outcome {
fn from(r: afterburner_wasi::ruby_runner::RubyRunOutput) -> Self {
Self {
stdout: r.stdout,
stderr: r.stderr,
output: r.output,
ok: r.exit_code == 0,
}
}
}
pub(crate) fn dispatch_run_source(
ab: &crate::Afterburner,
lang: Language,
source: &str,
) -> Result<Outcome> {
match lang {
Language::Js => {
let outcome = ab.run_script(source)?;
Ok(Outcome::from(outcome))
}
#[cfg(feature = "ts")]
Language::Ts => {
let js = crate::ts::transpile(source, Path::new("<run_source>.ts"))
.map_err(|e| AfterburnerError::Engine(format!("TypeScript transpile: {e}")))?;
let outcome = ab.run_script(&js)?;
Ok(Outcome::from(outcome))
}
#[cfg(not(feature = "ts"))]
Language::Ts => Err(AfterburnerError::Engine(
"TypeScript requires the `ts` cargo feature \
(rebuild with `--features ts`)."
.into(),
)),
#[cfg(feature = "wasm")]
Language::Python => {
let out = afterburner_wasi::pyodide_runner::run_python(source)?;
Ok(Outcome::from(out))
}
#[cfg(not(feature = "wasm"))]
Language::Python => Err(AfterburnerError::Engine(
"Python requires the `wasm` feature to be enabled".into(),
)),
#[cfg(feature = "wasm")]
Language::Ruby => {
let out = afterburner_wasi::ruby_runner::run_ruby(source)?;
Ok(Outcome::from(out))
}
#[cfg(not(feature = "wasm"))]
Language::Ruby => Err(AfterburnerError::Engine(
"Ruby requires the `wasm` feature to be enabled".into(),
)),
Language::Rust | Language::Go | Language::C | Language::Cpp => {
Err(AfterburnerError::Engine(format!(
"{lang:?} does not have a source-interpreter path. \
Compile the source to a wasm32-wasip1 module with the \
appropriate toolchain, then use \
`Afterburner::register_precompiled` to register and run it."
)))
}
}
}
pub(crate) fn language_for_path(path: &Path) -> Result<Language> {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
Language::from_extension(ext).ok_or_else(|| {
AfterburnerError::Engine(format!(
"cannot detect language for {:?}: unsupported extension {:?}. \
Supported extensions: rb, py, js, ts, rs, go, c, cc, cpp.",
path.display(),
ext
))
})
}