txtpp 0.2.4

A simple-to-use general purpose preprocessor for text files.
Documentation
//! Wrapper to perform file system operations

use crate::error::PathError;
use crate::fs::normalize_path;
use error_stack::{Result, ResultExt};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;

pub const CRLF: &str = "\r\n";
pub const LF: &str = "\n";
#[cfg(windows)]
pub const OS_LINE_ENDING: &str = CRLF;
#[cfg(not(windows))]
pub const OS_LINE_ENDING: &str = LF;

pub trait GetLineEnding {
    fn get_line_ending(&self) -> Result<&'static str, PathError>;
}

impl<P> GetLineEnding for P
where
    P: AsRef<Path>,
{
    fn get_line_ending(&self) -> Result<&'static str, PathError> {
        let mut buf = vec![];
        let len = File::open(self)
            .map(BufReader::new)
            .and_then(|mut r| r.read_until(b'\n', &mut buf))
            .change_context_lazy(|| PathError::from(self))
            .attach_printable_lazy(|| {
                format!(
                    "Failed to get line ending for file: {}",
                    normalize_path(&self.as_ref().display().to_string())
                )
            })?;
        Ok(get_line_ending_from_buf(&buf, len))
    }
}

fn get_line_ending_from_buf(buf: &[u8], len: usize) -> &'static str {
    match len {
        0 => OS_LINE_ENDING,
        1 => {
            if buf[0] == b'\n' {
                LF
            } else {
                OS_LINE_ENDING
            }
        }
        _ => {
            if buf[len - 1] == b'\n' {
                if buf[len - 2] == b'\r' {
                    CRLF
                } else {
                    LF
                }
            } else {
                OS_LINE_ENDING
            }
        }
    }
}

#[cfg(test)]
mod ut {
    use super::*;

    macro_rules! test_line_ending {
        ($expected:expr, $buf:literal) => {
            let buf = $buf.to_string();
            assert_eq!(
                $expected,
                get_line_ending_from_buf(buf.as_bytes(), buf.len())
            );
        };
    }

    #[test]
    fn test_empty() {
        test_line_ending!(OS_LINE_ENDING, "");
    }

    #[test]
    fn test_text_nonewline() {
        test_line_ending!(OS_LINE_ENDING, "something");
    }

    #[test]
    fn test_text_lf() {
        test_line_ending!(LF, "something\n");
    }

    #[test]
    fn test_lf() {
        test_line_ending!(LF, "\n");
    }

    #[test]
    fn test_text_crlf() {
        test_line_ending!(CRLF, "something\r\n");
    }

    #[test]
    fn test_crlf() {
        test_line_ending!(CRLF, "\r\n");
    }

    #[test]
    fn test_cr() {
        test_line_ending!(OS_LINE_ENDING, "\r");
    }
}