1use similar::{ChangeTag, TextDiff};
2
3pub fn is_binary(data: &[u8]) -> bool {
4 let probe = &data[..data.len().min(1024)];
5 if probe.contains(&0) {
6 return true;
7 }
8
9 std::str::from_utf8(probe).is_err()
10}
11
12pub fn unified_diff(old: &str, new: &str, old_label: &str, new_label: &str) -> String {
13 let diff = TextDiff::from_lines(old, new);
14 let mut output = String::new();
15 output.push_str(&format!("--- {old_label}\n"));
16 output.push_str(&format!("+++ {new_label}\n"));
17
18 for op in diff.ops() {
19 for change in diff.iter_changes(op) {
20 match change.tag() {
21 ChangeTag::Delete => output.push('-'),
22 ChangeTag::Insert => output.push('+'),
23 ChangeTag::Equal => output.push(' '),
24 }
25 output.push_str(&change.to_string());
26 }
27 }
28
29 output
30}
31
32#[cfg(test)]
33mod tests {
34 use super::{is_binary, unified_diff};
35
36 #[test]
37 fn line_diff_logic() {
38 let old = "A=1\nB=2\n";
39 let new = "A=1\nB=3\nC=4\n";
40 let rendered = unified_diff(old, new, "old/.env", "new/.env");
41
42 assert!(rendered.contains("--- old/.env"));
43 assert!(rendered.contains("+++ new/.env"));
44 assert!(rendered.contains("-B=2"));
45 assert!(rendered.contains("+B=3"));
46 assert!(rendered.contains("+C=4"));
47 }
48
49 #[test]
50 fn binary_detection() {
51 assert!(is_binary(&[0x00, 0x01, 0x02]));
52 assert!(is_binary(&[0xff, 0xfe, 0xfd]));
53 assert!(!is_binary(b"DATABASE_URL=postgres://localhost\n"));
54 }
55}