oxicode_hashline/
normalize.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum LineEnding {
10 Crlf,
12 Lf,
14}
15
16pub fn detect_line_ending(content: &str) -> LineEnding {
19 let crlf_idx = content.find("\r\n");
20 let lf_idx = content.find('\n');
21 match (crlf_idx, lf_idx) {
22 (None, None) | (_, None) => LineEnding::Lf,
23 (None, Some(_)) => LineEnding::Lf,
24 (Some(c), Some(l)) => {
25 if c < l {
26 LineEnding::Crlf
27 } else {
28 LineEnding::Lf
29 }
30 }
31 }
32}
33
34pub fn normalize_to_lf(text: &str) -> String {
36 text.replace("\r\n", "\n").replace('\r', "\n")
37}
38
39pub fn restore_line_endings(text: &str, ending: LineEnding) -> String {
41 match ending {
42 LineEnding::Crlf => text.replace('\n', "\r\n"),
43 LineEnding::Lf => text.to_string(),
44 }
45}
46
47pub struct BomResult<'a> {
49 pub bom: &'a str,
51 pub text: &'a str,
53}
54
55pub fn strip_bom(content: &str) -> BomResult<'_> {
57 if let Some(rest) = content.strip_prefix('\u{feff}') {
58 BomResult {
59 bom: "\u{feff}",
60 text: rest,
61 }
62 } else {
63 BomResult {
64 bom: "",
65 text: content,
66 }
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn detect_endings() {
76 assert_eq!(detect_line_ending("a\r\nb"), LineEnding::Crlf);
77 assert_eq!(detect_line_ending("a\nb"), LineEnding::Lf);
78 assert_eq!(detect_line_ending("no newlines"), LineEnding::Lf);
79 assert_eq!(detect_line_ending("a\nb\r\nc"), LineEnding::Lf);
80 }
81
82 #[test]
83 fn normalize_crlf() {
84 assert_eq!(normalize_to_lf("a\r\nb\r\nc"), "a\nb\nc");
85 assert_eq!(normalize_to_lf("a\rb"), "a\nb");
86 }
87
88 #[test]
89 fn restore_crlf() {
90 assert_eq!(restore_line_endings("a\nb", LineEnding::Crlf), "a\r\nb");
91 assert_eq!(restore_line_endings("a\nb", LineEnding::Lf), "a\nb");
92 }
93
94 #[test]
95 fn strip_utf8_bom() {
96 let r = strip_bom("\u{feff}hello");
97 assert_eq!(r.bom, "\u{feff}");
98 assert_eq!(r.text, "hello");
99
100 let r = strip_bom("hello");
101 assert_eq!(r.bom, "");
102 assert_eq!(r.text, "hello");
103 }
104}