use clap::{Parser, Subcommand};
use std::path::PathBuf;
use telltale_bridge::equivalence::EquivalenceChecker;
use telltale_bridge::import::json_to_global;
use telltale_bridge::runner::LeanRunner;
#[derive(Parser)]
#[command(name = "golden")]
#[command(about = "Golden file management for Rust-Lean equivalence testing")]
struct Cli {
#[command(subcommand)]
command: Commands,
#[arg(long, default_value = "golden")]
golden_dir: PathBuf,
}
#[derive(Subcommand)]
enum Commands {
Regenerate,
Check,
Add {
name: String,
json: String,
},
List,
}
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let golden_dir = if cli.golden_dir.is_absolute() {
cli.golden_dir
} else {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(&cli.golden_dir)
};
match cli.command {
Commands::Regenerate => cmd_regenerate(&golden_dir),
Commands::Check => cmd_check(&golden_dir),
Commands::Add { name, json } => cmd_add(&golden_dir, &name, &json),
Commands::List => cmd_list(&golden_dir),
}
}
fn cmd_regenerate(golden_dir: &PathBuf) -> anyhow::Result<()> {
require_lean()?;
let checker = EquivalenceChecker::with_lean(golden_dir)?;
let projection_dir = golden_dir.clone();
if !projection_dir.exists() {
println!("No golden files to regenerate");
return Ok(());
}
let mut count = 0;
for entry in std::fs::read_dir(&projection_dir)? {
let entry = entry?;
if !entry.file_type()?.is_dir() {
continue;
}
let test_name = entry.file_name().to_string_lossy().to_string();
let input_path = entry.path().join("input.json");
if !input_path.exists() {
println!("Skipping {} (no input.json)", test_name);
continue;
}
let input: serde_json::Value =
serde_json::from_str(&std::fs::read_to_string(&input_path)?)?;
let global = json_to_global(&input)?;
let bundle = checker.generate_golden_bundle(&global)?;
checker.write_golden_bundle(&test_name, &bundle)?;
println!("Regenerated: {}", test_name);
count += 1;
}
println!("\nRegenerated {} test cases", count);
Ok(())
}
fn cmd_check(golden_dir: &PathBuf) -> anyhow::Result<()> {
require_lean()?;
let checker = EquivalenceChecker::with_lean(golden_dir)?;
let drifted = checker.check_golden_drift()?;
if drifted.is_empty() {
println!("✓ All golden files are up-to-date");
Ok(())
} else {
println!("✗ Golden file drift detected:");
for item in &drifted {
println!(" - {}", item);
}
println!("\nRun 'cargo run -p telltale-bridge --bin golden -- regenerate' to update");
Err(anyhow::anyhow!("golden file drift detected"))
}
}
fn cmd_add(golden_dir: &PathBuf, name: &str, json: &str) -> anyhow::Result<()> {
require_lean()?;
let input: serde_json::Value = serde_json::from_str(json)?;
let global = json_to_global(&input)?;
let checker = EquivalenceChecker::with_lean(golden_dir)?;
let bundle = checker.generate_golden_bundle(&global)?;
checker.write_golden_bundle(name, &bundle)?;
println!("Added test case: {}", name);
println!(
" Roles: {}",
bundle
.projections
.keys()
.cloned()
.collect::<Vec<_>>()
.join(", ")
);
Ok(())
}
fn cmd_list(golden_dir: &std::path::Path) -> anyhow::Result<()> {
let projection_dir = golden_dir.to_path_buf();
if !projection_dir.exists() {
println!("No golden files found at {:?}", projection_dir);
return Ok(());
}
println!("Golden test cases:");
println!();
for entry in std::fs::read_dir(&projection_dir)? {
let entry = entry?;
if !entry.file_type()?.is_dir() {
continue;
}
let test_name = entry.file_name().to_string_lossy().to_string();
let mut roles = Vec::new();
for file in std::fs::read_dir(entry.path())? {
let file = file?;
let name = file.file_name().to_string_lossy().to_string();
if name.ends_with(".expected.json") {
roles.push(name.trim_end_matches(".expected.json").to_string());
}
}
println!(" {} (roles: {})", test_name, roles.join(", "));
}
Ok(())
}
fn require_lean() -> anyhow::Result<()> {
if !LeanRunner::is_available() {
return Err(anyhow::anyhow!(
"Lean runner not available. Build with `cd lean && lake build telltale_validator`."
));
}
Ok(())
}