use padlock_core::ir::{optimal_order, StructLayout};
use similar::{ChangeTag, TextDiff};
pub fn render_diff(layout: &StructLayout) -> String {
let original = fields_to_text(layout.fields.iter().map(|f| f.name.as_str()));
let optimal = optimal_order(layout);
let optimized = fields_to_text(optimal.iter().map(|f| f.name.as_str()));
text_diff(&original, &optimized)
}
pub fn text_diff(original: &str, updated: &str) -> String {
if original == updated {
return String::from("(no changes)\n");
}
let diff = TextDiff::from_lines(original, updated);
let mut out = String::new();
for change in diff.iter_all_changes() {
let prefix = match change.tag() {
ChangeTag::Delete => "-",
ChangeTag::Insert => "+",
ChangeTag::Equal => " ",
};
out.push_str(&format!("{prefix} {}", change.value()));
if !change.value().ends_with('\n') {
out.push('\n');
}
}
out
}
fn fields_to_text<'a>(names: impl Iterator<Item = &'a str>) -> String {
names.map(|n| format!("{n}\n")).collect()
}
#[cfg(test)]
mod tests {
use super::*;
use padlock_core::ir::test_fixtures::{connection_layout, packed_layout};
#[test]
fn diff_misaligned_is_nonempty() {
let out = render_diff(&connection_layout());
assert_ne!(out, "(no changes)\n");
assert!(out.contains("timeout") || out.contains("port"));
}
#[test]
fn diff_already_optimal_is_no_changes() {
let out = render_diff(&packed_layout());
assert_eq!(out, "(no changes)\n");
}
#[test]
fn text_diff_shows_plus_minus() {
let out = text_diff("a\nb\n", "a\nc\n");
assert!(out.contains("- b") || out.contains("-b"));
assert!(out.contains("+ c") || out.contains("+c"));
}
#[test]
fn text_diff_identical_is_no_changes() {
assert_eq!(text_diff("x\n", "x\n"), "(no changes)\n");
}
}