use std::fmt;
use color_eyre::Result;
pub trait Transform {
fn transform(&self, content: String) -> Result<String>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub enum ContentTransformer {
LineTerminator(LineTerminator),
}
impl Transform for ContentTransformer {
fn transform(&self, content: String) -> Result<String> {
match self {
Self::LineTerminator(lt) => lt.transform(content),
}
}
}
impl fmt::Display for ContentTransformer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Display::fmt(&self, f)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub enum LineTerminator {
LF,
CRLF,
}
impl Transform for LineTerminator {
fn transform(&self, mut content: String) -> Result<String> {
match self {
Self::LF => Ok(content.replace("\r\n", "\n")),
Self::CRLF => {
let lf_idxs = content.match_indices('\n');
let mut cr_idxs = content.match_indices('\r').peekable();
#[allow(clippy::needless_collect)]
let lf_idxs = lf_idxs
.filter_map(|(lf_idx, _)| {
while matches!(cr_idxs.peek(), Some((cr_idx,_)) if cr_idx + 1 < lf_idx) {
let _ = cr_idxs.next().expect("Failed to advance peeked iterator");
}
if matches!(cr_idxs.peek(), Some((cr_idx, _)) if cr_idx + 1 == lf_idx) {
let _ = cr_idxs.next().expect("Failed to advance peeked iterator");
None
} else {
Some(lf_idx)
}
})
.collect::<Vec<_>>();
for (offset, lf_idx) in lf_idxs.into_iter().enumerate() {
content.insert(lf_idx + offset, '\r');
}
Ok(content)
}
}
}
}
impl fmt::Display for LineTerminator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Debug::fmt(&self, f)
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn line_terminator_lf() -> Result<()> {
const CONTENT: &str = "Hello\r\nWorld\nHow\nare\r\nyou today?\r\r\r\nLast line\r\\n";
assert_eq!(
LineTerminator::LF.transform(String::from(CONTENT))?,
"Hello\nWorld\nHow\nare\nyou today?\r\r\nLast line\r\\n"
);
Ok(())
}
#[test]
fn line_terminator_crlf() -> Result<()> {
const CONTENT: &str = "Hello\r\nWorld\nHow\nare\r\nyou today?\r\r\r\nLast line\r\\n";
assert_eq!(
LineTerminator::CRLF.transform(String::from(CONTENT))?,
"Hello\r\nWorld\r\nHow\r\nare\r\nyou today?\r\r\r\nLast line\r\\n"
);
Ok(())
}
}