use std::fs;
use std::path::{Path, PathBuf};
use clap::Parser;
use crate::generate::archetypes::ALL_ARCHETYPES;
use crate::generate::emit::{generate, GenerationPlan, TemplateKind};
use crate::op_registry::all_specs;
use crate::OpSpec;
#[derive(Debug, Parser)]
pub struct Args {
#[arg(long, conflicts_with = "all")]
pub op: Option<String>,
#[arg(long)]
pub all: bool,
#[arg(long)]
pub plan: Option<PathBuf>,
#[arg(long)]
pub out: PathBuf,
}
pub fn run(args: Args) -> Result<(), Box<dyn std::error::Error>> {
let mut seed = 0x5659_5245_0005_0001u64;
let mut op_filter = args.op;
if let Some(plan_path) = args.plan {
let plan = PlanFile::read(&plan_path)?;
seed = plan.seed.unwrap_or(seed);
if op_filter.is_none() {
op_filter = plan.op;
}
}
let ops = select_ops(op_filter.as_deref(), args.all)?;
let plan = GenerationPlan {
ops,
archetypes: ALL_ARCHETYPES.to_vec(),
templates: all_templates(),
seed,
};
let report = generate(&plan, &args.out).map_err(|err: crate::generate::emit::GenError| {
format!("Fix: generated-test emission failed: {err}")
})?;
println!(
"generated {} file(s) into {}",
report.files.len(),
args.out.display()
);
Ok(())
}
struct PlanFile {
op: Option<String>,
seed: Option<u64>,
}
impl PlanFile {
fn read(path: &Path) -> Result<Self, String> {
let text = fs::read_to_string(path)
.map_err(|err| format!("Fix: failed to read plan `{}`: {err}", path.display()))?;
let mut op = None;
let mut seed = None;
for raw in text.lines() {
let line = raw.split('#').next().map(str::trim).unwrap_or("");
if line.is_empty() || line.starts_with('[') {
continue;
}
let Some((key, value)) = line.split_once('=') else {
return Err(format!(
"Fix: malformed plan line `{raw}`. Use key = value."
));
};
match key.trim() {
"op" => op = Some(unquote(value.trim()).to_string()),
"seed" => {
seed =
Some(value.trim().parse::<u64>().map_err(|err| {
format!("Fix: plan seed must be a u64 integer: {err}")
})?);
}
_ => {}
}
}
Ok(Self { op, seed })
}
}
fn select_ops(filter: Option<&str>, all: bool) -> Result<Vec<&'static OpSpec>, String> {
if filter.is_none() && !all {
return Err(
"Fix: specify --op NAME, --all, or --plan PATH with op = \"NAME\".".to_string(),
);
}
let specs = all_specs();
let selected: Vec<OpSpec> = if all {
specs
} else {
let Some(needle) = filter else {
return Err(
"Fix: plan file must contain `op = \"NAME\"` unless --all is used.".to_string(),
);
};
specs
.into_iter()
.filter(|spec| spec.id == needle || spec.id.rsplit('.').next() == Some(needle))
.collect()
};
if selected.is_empty() {
return Err(format!(
"Fix: no operation matched `{}` in op_registry::all_specs.",
filter.unwrap_or("<all>")
));
}
Ok(selected
.into_iter()
.map(|spec| Box::leak(Box::new(spec)) as &'static OpSpec)
.collect())
}
fn unquote(value: &str) -> &str {
value
.strip_prefix('"')
.and_then(|rest| rest.strip_suffix('"'))
.unwrap_or(value)
}
fn all_templates() -> Vec<TemplateKind> {
vec![
TemplateKind::OpCorrectness,
TemplateKind::Law,
TemplateKind::BackendEquiv,
TemplateKind::Archetype,
TemplateKind::Validation,
TemplateKind::MutationKill,
]
}