use std::borrow::Cow;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineEnding {
Lf,
Crlf,
Mixed,
}
pub fn detect_line_ending_enum(content: &str) -> LineEnding {
let bytes = content.as_bytes();
let mut has_crlf = false;
let mut has_standalone_lf = false;
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'\r' && i + 1 < bytes.len() && bytes[i + 1] == b'\n' {
has_crlf = true;
i += 2;
} else if bytes[i] == b'\n' {
has_standalone_lf = true;
i += 1;
} else {
i += 1;
}
if has_crlf && has_standalone_lf {
return LineEnding::Mixed;
}
}
match (has_crlf, has_standalone_lf) {
(true, true) => LineEnding::Mixed,
(true, false) => LineEnding::Crlf,
(false, _) => LineEnding::Lf,
}
}
pub fn detect_line_ending(content: &str) -> &'static str {
let crlf_count = content.matches("\r\n").count();
let lf_count = content.matches('\n').count() - crlf_count;
if crlf_count > lf_count { "\r\n" } else { "\n" }
}
pub fn normalize_line_ending<'a>(content: &'a str, target: LineEnding) -> Cow<'a, str> {
match target {
LineEnding::Lf => {
if !content.contains('\r') {
Cow::Borrowed(content)
} else {
Cow::Owned(content.replace("\r\n", "\n"))
}
}
LineEnding::Crlf => {
let normalized = content.replace("\r\n", "\n");
Cow::Owned(normalized.replace('\n', "\r\n"))
}
LineEnding::Mixed => Cow::Borrowed(content),
}
}
pub fn ensure_consistent_line_endings(original: &str, modified: &str) -> String {
let original_ending = detect_line_ending_enum(original);
let target_ending = if original_ending == LineEnding::Mixed {
let crlf_count = original.matches("\r\n").count();
let lf_count = original.matches('\n').count() - crlf_count;
if crlf_count > lf_count {
LineEnding::Crlf
} else {
LineEnding::Lf
}
} else {
original_ending
};
let modified_ending = detect_line_ending_enum(modified);
if target_ending != modified_ending {
normalize_line_ending(modified, target_ending).into_owned()
} else {
modified.to_string()
}
}
pub fn get_line_ending_str(ending: LineEnding) -> &'static str {
match ending {
LineEnding::Lf => "\n",
LineEnding::Crlf => "\r\n",
LineEnding::Mixed => "\n", }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_line_ending_enum() {
assert_eq!(detect_line_ending_enum("hello\nworld"), LineEnding::Lf);
assert_eq!(detect_line_ending_enum("hello\r\nworld"), LineEnding::Crlf);
assert_eq!(detect_line_ending_enum("hello\r\nworld\nmixed"), LineEnding::Mixed);
assert_eq!(detect_line_ending_enum("no line endings"), LineEnding::Lf);
}
#[test]
fn test_detect_line_ending() {
assert_eq!(detect_line_ending("hello\nworld"), "\n");
assert_eq!(detect_line_ending("hello\r\nworld"), "\r\n");
assert_eq!(detect_line_ending("hello\r\nworld\nmixed"), "\n"); assert_eq!(detect_line_ending("no line endings"), "\n");
}
#[test]
fn test_normalize_line_ending() {
assert_eq!(normalize_line_ending("hello\r\nworld", LineEnding::Lf), "hello\nworld");
assert_eq!(
normalize_line_ending("hello\nworld", LineEnding::Crlf),
"hello\r\nworld"
);
assert_eq!(
normalize_line_ending("hello\r\nworld\nmixed", LineEnding::Lf),
"hello\nworld\nmixed"
);
}
#[test]
fn test_ensure_consistent_line_endings() {
let original = "hello\r\nworld";
let modified = "hello\nworld\nextra";
assert_eq!(
ensure_consistent_line_endings(original, modified),
"hello\r\nworld\r\nextra"
);
let original = "hello\nworld";
let modified = "hello\r\nworld\r\nextra";
assert_eq!(
ensure_consistent_line_endings(original, modified),
"hello\nworld\nextra"
);
}
}