Skip to main content

shine_core/install/
line_endings.rs

1//! Line-ending-agnostic comparison helpers.
2//!
3//! Preset templates are embedded and written LF (see the repo `.gitattributes`),
4//! but a user's on-disk copy of an installed file may be CRLF — e.g. a Windows
5//! editor re-saving a PowerShell profile. Comparing those byte-exact would treat
6//! a pure CRLF↔LF difference as a real change, producing spurious "update"
7//! reports (and silent whole-file rewrites) on re-install. These helpers let the
8//! reconciliation logic compare content while ignoring line-ending style.
9
10/// Returns a copy of `bytes` with every `\r\n` and lone `\r` reduced to `\n`.
11///
12/// For input that is already LF-only this returns an identical byte sequence, so
13/// callers that compare via [`eol_eq`] see no behavior change for LF content.
14pub fn normalize_eol(bytes: &[u8]) -> Vec<u8> {
15    let mut out = Vec::with_capacity(bytes.len());
16    let mut i = 0;
17    while i < bytes.len() {
18        if bytes[i] == b'\r' {
19            out.push(b'\n');
20            // Collapse a `\r\n` pair into the single `\n` just pushed.
21            if bytes.get(i + 1) == Some(&b'\n') {
22                i += 1;
23            }
24        } else {
25            out.push(bytes[i]);
26        }
27        i += 1;
28    }
29    out
30}
31
32/// Compares two byte slices for equality, ignoring line-ending style
33/// (`\r\n`, lone `\r`, and `\n` are all treated as equivalent line breaks).
34pub fn eol_eq(a: &[u8], b: &[u8]) -> bool {
35    normalize_eol(a) == normalize_eol(b)
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn normalize_eol_converts_crlf_to_lf() {
44        assert_eq!(normalize_eol(b"a\r\nb\r\n"), b"a\nb\n");
45    }
46
47    #[test]
48    fn normalize_eol_converts_lone_cr_to_lf() {
49        assert_eq!(normalize_eol(b"a\rb\r"), b"a\nb\n");
50    }
51
52    #[test]
53    fn normalize_eol_handles_mixed_endings() {
54        assert_eq!(normalize_eol(b"a\r\nb\rc\nd"), b"a\nb\nc\nd");
55    }
56
57    #[test]
58    fn normalize_eol_is_noop_for_lf_only() {
59        let input = b"a\nb\nc";
60        assert_eq!(normalize_eol(input), input);
61    }
62
63    #[test]
64    fn eol_eq_ignores_line_ending_style() {
65        assert!(eol_eq(b"a\r\nb\r\n", b"a\nb\n"));
66        assert!(eol_eq(b"a\rb", b"a\nb"));
67        assert!(!eol_eq(b"a\r\nb", b"a\r\nc"));
68    }
69}