sac13 0.1.1

The reference implementation for the SAC13 calendar system.
Documentation
use core::num::NonZeroU8;

use crate::iterhelp::ByteSliceIter;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
/// A separator between date components in their textual representation. At most six ASCII characters long.
pub struct DateComponentSeparator {
    bytes: [u8; 6],
    len: NonZeroU8,
}

impl DateComponentSeparator {
    /// Length of the separator, in characters and bytes.
    #[allow(clippy::len_without_is_empty)] // It's never empty!
    pub fn len(&self) -> NonZeroU8 {
        self.len
    }

    /// Slice to raw separator data.
    pub fn as_bytes(&self) -> &[u8] {
        &self.bytes[0..self.len.get() as usize]
    }

    /// Separator as an ASCII str slice.
    pub fn as_str(&self) -> &str {
        str::from_utf8(self.as_bytes()).expect("Separator is ASCII only")
    }

    pub(crate) fn is_single_slash(&self) -> bool {
        let mut slash_found = false;

        for i in 0..self.len.get() as usize {
            let x = self.bytes[i];

            if x == b' ' {
                // ignore spaces
                continue;
            }

            if x != b'/' {
                // found something that was not a space and not a slash
                return false;
            }

            if slash_found {
                // there already was a slash so it can no longe be a "single_slash"
                return false;
            }

            slash_found = true;
        }

        true
    }

    pub(crate) fn parse(i: &mut ByteSliceIter) -> Option<Self> {
        let mut len: usize = 0;
        let mut data: [u8; 6] = [0; 6];

        loop {
            let Some(peeked) = i.peek() else {
                // reached the end, so we return
                break;
            };

            if peeked.is_ascii_alphanumeric() {
                // alphanumerics are not allowed in separators because
                // they represent component digits
                break;
            }

            if peeked == b'-'
                && len != 0
                && let Some(x) = i.peek_n(1)
                && x.is_ascii_alphanumeric()
            {
                break;
            }

            // Reached max size separator so we end parsing by returning None.
            if len >= data.len() {
                return None;
            }

            // if everything works out store data and continue
            data[len] = peeked;
            len += 1;

            _ = i.next();
        }

        Some(Self {
            len: NonZeroU8::new(len as u8)?,
            bytes: data,
        })
    }
}