use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[allow(clippy::upper_case_acronyms)]
pub enum LineEnding {
LF,
CRLF,
CR,
}
pub type LineEndingScores = HashMap<LineEnding, usize>;
impl From<&str> for LineEnding {
fn from(s: &str) -> Self {
let scores = Self::score_mixed_types(s);
let crlf_score = *scores.get(&Self::CRLF).unwrap_or(&0);
let cr_score = *scores.get(&Self::CR).unwrap_or(&0);
let lf_score = *scores.get(&Self::LF).unwrap_or(&0);
let max_score = crlf_score.max(cr_score).max(lf_score);
if max_score == 0 || crlf_score == max_score {
Self::CRLF
} else if cr_score == max_score {
Self::CR
} else {
Self::LF
}
}
}
impl LineEnding {
pub fn from_current_platform() -> Self {
if cfg!(windows) {
Self::CRLF
} else {
Self::LF
}
}
pub fn score_mixed_types(s: &str) -> LineEndingScores {
let crlf_score = Self::CRLF.split_with(s).len().saturating_sub(1);
let cr_score = Self::CR.split_with(s).len().saturating_sub(1) - crlf_score;
let lf_score = Self::LF.split_with(s).len().saturating_sub(1) - crlf_score;
[
(LineEnding::CRLF, crlf_score),
(LineEnding::CR, cr_score),
(LineEnding::LF, lf_score),
]
.into_iter()
.collect()
}
pub fn as_str(&self) -> &'static str {
match self {
Self::LF => "\n",
Self::CRLF => "\r\n",
Self::CR => "\r",
}
}
pub fn as_char(&self) -> char {
match self {
Self::LF => '\n',
Self::CR => '\r',
Self::CRLF => panic!("CRLF cannot be represented as a single character"),
}
}
pub fn normalize(s: &str) -> String {
s.replace("\r\n", "\n").replace("\r", "\n")
}
pub fn denormalize(&self, s: &str) -> String {
s.replace("\n", self.as_str())
}
pub fn split(s: &str) -> Vec<String> {
let line_ending = Self::from(s).as_str();
s.split(line_ending).map(String::from).collect()
}
pub fn split_with(&self, s: &str) -> Vec<String> {
s.split(self.as_str()).map(String::from).collect()
}
pub fn join(&self, lines: Vec<String>) -> String {
lines.join(self.as_str())
}
pub fn apply(&self, s: &str) -> String {
let normalized = Self::normalize(s);
normalized.replace("\n", self.as_str())
}
}