ittech 0.3.0

Impulse Tracker file parser and writer
Documentation
use crate::error::OutOfRangeError;
use std::convert::TryFrom;
use std::fmt::{self, Write};

#[derive(Clone, Copy)]
pub struct Name {
    pub bytes: [u8; 26],
}

#[derive(Clone, Copy)]
pub struct DosFilename {
    pub bytes: [u8; 13],
}

#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
pub struct RangedU8<const LOW: u8, const HIGH: u8>(u8);


impl<const LOW: u8, const HIGH: u8> RangedU8<LOW, HIGH> {
    pub fn as_u8(self) -> u8 {
        self.0
    }
}

impl<const LOW: u8, const HIGH: u8> TryFrom<u8> for RangedU8<LOW, HIGH> {
    type Error = OutOfRangeError<LOW, HIGH>;

    fn try_from(raw: u8) -> Result<Self, Self::Error> {
        if (LOW..=HIGH).contains(&raw) {
            Ok(RangedU8(raw))
        } else {
            Err(OutOfRangeError(raw))
        }
    }
}

impl<const LOW: u8, const HIGH: u8> From<RangedU8<LOW, HIGH>> for u8 {
    fn from(ranged: RangedU8<LOW, HIGH>) -> u8 {
        ranged.as_u8()
    }
}

fn null_terminated(bytes: &[u8]) -> &[u8] {
    let null_pos = bytes.iter()
        .position(|&b| b == 0)
        .unwrap_or(bytes.len());
    &bytes[..null_pos]
}

fn debug_bytestring(bytes: &[u8], f: &mut fmt::Formatter) -> fmt::Result {
    f.write_char('"')?;
    for &byte in null_terminated(bytes) {
        if byte.is_ascii_graphic() || byte == b' ' {
            f.write_char(byte.into())?;
        } else {
            write!(f, "<0x{:02X}>", byte)?;
        }
    }
    f.write_char('"')
}

impl fmt::Debug for Name {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        debug_bytestring(&self.bytes, f)
    }
}

impl fmt::Debug for DosFilename {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        debug_bytestring(&self.bytes, f)
    }
}

impl fmt::Display for Name {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        String::from_utf8_lossy(null_terminated(&self.bytes)).fmt(f)
    }
}

impl fmt::Display for DosFilename {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        String::from_utf8_lossy(null_terminated(&self.bytes)).fmt(f)
    }
}

impl<const LOW: u8, const HIGH: u8> fmt::Debug for RangedU8<LOW, HIGH> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.as_u8().fmt(f)
    }
}