str-utils 0.3.7

This crate provides some traits to extend `[u8]`, `str` and `Cow<str>`.
Documentation
use alloc::{borrow::Cow, string::String, vec::Vec};

pub(crate) fn newline_sequence_len(bytes: &[u8], p: usize, length: usize) -> Option<usize> {
    if p == length {
        return None;
    }

    match bytes[p] {
        b'\n' | 0x0B | 0x0C => Some(1),
        b'\r' => {
            if p < length - 1 && bytes[p + 1] == b'\n' {
                Some(2)
            } else {
                Some(1)
            }
        },
        0xC2 if p < length - 1 && bytes[p + 1] == 0x85 => Some(2),
        0xE2 if p < length - 2 && bytes[p + 1] == 0x80 && matches!(bytes[p + 2], 0xA8 | 0xA9) => {
            Some(3)
        },
        _ => None,
    }
}

pub(crate) fn replace_newlines_with<'a>(s: &'a str, replacement: u8) -> Cow<'a, str> {
    let bytes = s.as_bytes();
    let length = bytes.len();

    let mut p = 0;

    let first_len = loop {
        if p == length {
            return Cow::Borrowed(s);
        }

        if let Some(len) = newline_sequence_len(bytes, p, length) {
            // A one-byte newline that already equals the replacement can stay borrowed until another sequence needs a real change.
            if len == 1 && bytes[p] == replacement {
                p += 1;

                continue;
            }

            break len;
        }

        p += 1;
    };

    let mut new_v = Vec::with_capacity(bytes.len());

    new_v.extend_from_slice(&bytes[..p]);
    new_v.push(replacement);

    p += first_len;

    let mut start = p;

    loop {
        if p == length {
            break;
        }

        if let Some(len) = newline_sequence_len(bytes, p, length) {
            // A one-byte newline that already equals the replacement is kept in the next copied slice.
            if len == 1 && bytes[p] == replacement {
                p += 1;

                continue;
            }

            new_v.extend_from_slice(&bytes[start..p]);
            new_v.push(replacement);

            p += len;
            start = p;
        } else {
            p += 1;
        }
    }

    new_v.extend_from_slice(&bytes[start..p]);

    Cow::Owned(unsafe { String::from_utf8_unchecked(new_v) })
}