Files
hunjing/sandbox/src/codemode.rs
T
2026-08-21 16:00:13 +08:00

80 lines
3.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! code 模式转译:language + code → argv + 临时脚本文件。
//!
//! code 模式(`/v1/exec` 的 `code`/`language` 字段)由本模块转译为 argv,复用现有进程执行路径。
//! 走脚本文件而非 `-c`,避免多行 / 模块导入 / shebang 问题;脚本经 `tempfile` 写入 OS 临时目录,
//! 返回的 `CodeTranslation.script_path`(`TempPath`)由调用方持有至执行结束——drop 时自动删除。
//! 流式执行场景须把 `CodeTranslation` move 进 SSE 闭包,确保子进程 open 脚本前文件不被删。
use std::io::Write;
use tempfile::NamedTempFile;
/// code 模式转译结果:argv 用于执行,script_path 须由调用方持有至执行结束。
pub struct CodeTranslation {
pub argv: Vec<String>,
/// 临时脚本路径,持有至执行结束(RAII:drop 时自动删除)
#[allow(dead_code)]
pub script_path: tempfile::TempPath,
}
/// 语言 → (interpreter, 脚本扩展名)。P0 仅支持 python;node/java 留 P2。
pub fn interpreter_for(language: &str) -> Result<(&'static str, &'static str), String> {
match language {
"python" => Ok(("python3", "py")),
other => Err(format!("不支持的语言: {other}(P0 仅支持 python)")),
}
}
/// 写 code 到临时脚本文件,返回 argv 与 TempPath(调用方持有至执行结束)。
pub async fn translate(language: &str, code: &str) -> Result<CodeTranslation, String> {
let (interp, ext) = interpreter_for(language)?;
// NamedTempFile 是同步 IO,文件小、写一次,无需 tokio 化。
let mut tmp = NamedTempFile::with_suffix(format!(".{ext}"))
.map_err(|e| format!("创建临时脚本失败: {e}"))?;
tmp.write_all(code.as_bytes())
.map_err(|e| format!("写入临时脚本失败: {e}"))?;
// detach 成 TempPath:文件留在磁盘,TempPath drop 时删除。
let script_path = tmp.into_temp_path();
let argv = vec![
interp.to_string(),
script_path.to_string_lossy().into_owned(),
];
Ok(CodeTranslation { argv, script_path })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn interpreter_for_python_ok_others_rejected() {
assert_eq!(interpreter_for("python").unwrap(), ("python3", "py"));
assert!(interpreter_for("node").is_err());
assert!(interpreter_for("").is_err());
}
#[tokio::test]
async fn translate_python_writes_script_and_returns_argv() {
let code = "print(1+1)\nimport sys\nprint(sys.argv)";
let trans = translate("python", code).await.unwrap();
assert_eq!(trans.argv[0], "python3");
assert!(trans.argv[1].ends_with(".py"));
let on_disk = std::fs::read_to_string(&trans.argv[1]).unwrap();
assert_eq!(on_disk, code);
}
#[tokio::test]
async fn translate_unsupported_language_errors() {
assert!(translate("ruby", "puts 1").await.is_err());
}
#[tokio::test]
async fn translate_temp_path_keeps_file_until_drop() {
let trans = translate("python", "x = 1").await.unwrap();
let path = trans.argv[1].clone();
assert!(std::path::Path::new(&path).exists(), "持有 TempPath 时文件应存在");
drop(trans);
assert!(!std::path::Path::new(&path).exists(), "TempPath drop 后文件应删除");
}
}