use std::fs;
use std::path::Path;
use evorule_mcu::RulePack;
use crate::error::CliError;
use crate::io_util;
pub fn run(rules_file: &Path, output: &Path) -> Result<(), CliError> {
let rules = io_util::load_rules_file(rules_file)?;
let rule_count = rules.len();
let pack = RulePack::from_rules(rules)
.map_err(|e| CliError::other(format!("cannot compile rules: {e}")))?;
let bytes = pack
.to_evopack()
.map_err(|e| CliError::other(format!("cannot encode evopack: {e}")))?;
fs::write(output, &bytes)?;
println!("=== Compiling evopack ===");
println!("Rules: {}", rule_count);
println!("Output: {} ({} bytes)", output.display(), bytes.len());
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
use super::*;
use std::fs;
fn make_temp_dir(name: &str) -> std::path::PathBuf {
let dir =
std::env::temp_dir().join(format!("evorule-cli-pack-{}-{}", name, std::process::id()));
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).unwrap();
dir
}
fn cleanup(dir: &std::path::Path) {
let _ = fs::remove_dir_all(dir);
}
const SAMPLE_RULES: &str = r#"[
{
"type": "branch",
"params": {
"domain": {"type": "instruction", "instruction_type": "increment"},
"on_true": [
{"type": "set", "params": {"attr": "x", "operation": "add",
"value": "__exec__.instruction.params.delta"}}
]
}
},
{"type": "set", "params": {"attr": "y", "operation": "set", "value": 7}}
]"#;
#[test]
fn test_compile_deterministic_same_bytes() {
let dir = make_temp_dir("deterministic");
let rules_file = dir.join("rules.json");
fs::write(&rules_file, SAMPLE_RULES).unwrap();
let out_a = dir.join("a.evopack");
let out_b = dir.join("b.evopack");
run(&rules_file, &out_a).unwrap();
run(&rules_file, &out_b).unwrap();
let bytes_a = fs::read(&out_a).unwrap();
let bytes_b = fs::read(&out_b).unwrap();
assert!(!bytes_a.is_empty());
assert_eq!(bytes_a, bytes_b, "同输入必须同字节(确定性)");
let pack = RulePack::from_evopack(&bytes_a).unwrap();
assert_eq!(pack.rules().len(), 2);
cleanup(&dir);
}
#[test]
fn test_compile_rejects_rule_count_over_limit() {
let dir = make_temp_dir("over-limit");
let rules_file = dir.join("rules.json");
let mut json = String::from("[");
for i in 0..65 {
if i > 0 {
json.push(',');
}
json.push_str(&format!(
r#"{{"type":"set","params":{{"attr":"x","operation":"set","value":{i}}}}}"#
));
}
json.push(']');
fs::write(&rules_file, json).unwrap();
let out = dir.join("out.evopack");
let err = run(&rules_file, &out).unwrap_err();
assert!(
err.to_string().contains("exceeds transform limit"),
"got: {err}"
);
assert!(!out.exists(), "编译失败不得产出文件");
cleanup(&dir);
}
#[test]
fn test_compile_invalid_json_fails() {
let dir = make_temp_dir("bad-json");
let rules_file = dir.join("bad.json");
fs::write(&rules_file, "not json").unwrap();
let out = dir.join("out.evopack");
let err = run(&rules_file, &out).unwrap_err();
assert!(err.to_string().contains("Invalid JSON"), "got: {err}");
cleanup(&dir);
}
}