use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct ExpectUpdate {
pub file: String,
pub line: u32,
pub column: u32,
pub actual: String,
}
impl ExpectUpdate {
pub fn load_all(target_dir: &PathBuf) -> Vec<Self> {
let dir = target_dir.join("expect_updates");
if !dir.exists() {
return Vec::new();
}
fs::read_dir(&dir)
.into_iter()
.flatten()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map_or(false, |ext| ext == "json"))
.filter_map(|e| {
let content = fs::read_to_string(e.path()).ok()?;
serde_json::from_str(&content).ok()
})
.collect()
}
pub fn clear_all(target_dir: &PathBuf) {
let dir = target_dir.join("expect_updates");
if dir.exists() {
fs::remove_dir_all(&dir).ok();
}
}
}
pub fn check(actual: &str, expected: &str, file: &str, line: u32, column: u32, manifest_dir: &str) {
let actual_norm = normalize(actual);
let expected_norm = normalize(expected);
if actual_norm == expected_norm {
return;
}
record_mismatch(file, line, column, actual, manifest_dir);
panic!(
"assert_display_is! mismatch at {}:{}:{}\n\n--- Expected ---\n{}\n\n--- Actual ---\n{}\n\nRun `cargo run --bin update-expects` to apply fixes.",
file, line, column, expected, actual
);
}
fn normalize(s: &str) -> String {
s.lines()
.map(|l| l.trim())
.collect::<Vec<_>>()
.join("\n")
.trim()
.to_string()
}
fn record_mismatch(file: &str, line: u32, column: u32, actual: &str, manifest_dir: &str) {
let workspace_root = find_workspace_root(manifest_dir);
let dir = workspace_root.join("target/expect_updates");
fs::create_dir_all(&dir).expect("Failed to create expect_updates directory");
let safe_name = file.replace(['/', '\\'], "_");
let filename = format!("{}_L{}_C{}.json", safe_name, line, column);
let path = dir.join(filename);
let abs_file = workspace_root.join(file);
let update = ExpectUpdate {
file: abs_file.to_string_lossy().to_string(),
line,
column,
actual: actual.to_string(),
};
let json = serde_json::to_string_pretty(&update).expect("Failed to serialize update");
fs::write(&path, json).expect("Failed to write update file");
}
fn find_workspace_root(manifest_dir: &str) -> PathBuf {
let mut dir = PathBuf::from(manifest_dir);
loop {
if dir.join("Cargo.toml").exists() {
let content = fs::read_to_string(dir.join("Cargo.toml")).unwrap_or_default();
if content.contains("[workspace]") {
return dir;
}
}
if !dir.pop() {
break;
}
}
PathBuf::from(manifest_dir)
.parent()
.unwrap_or(Path::new("."))
.to_path_buf()
}