rstmt-core 0.1.0

the core modules for the rstmt framework
Documentation
/*
    Appellation: pitch_type <module>
    Created At: 2025.12.23:14:35:22
    Contrib: @FL03
*/

/// [`Accidental`] is a sealed marker trait used to designate various _kinds_ of musical notes,
/// i.e., sharp, flat, natural, etc.
pub trait Accidental
where
    Self: 'static + AsRef<str> + Send + Sync + core::fmt::Debug + core::fmt::Display,
{
    private! {}

    fn new() -> Self
    where
        Self: Sized;

    #[allow(clippy::should_implement_trait)]
    fn from_str(s: &str) -> Result<Self, crate::error::Error>
    where
        Self: Sized + core::str::FromStr<Err = crate::error::Error>,
    {
        s.parse::<Self>()
    }

    fn name(&self) -> &str;

    fn symbol(&self) -> char;
}

/*
 ************* Implementations *************
*/
macro_rules! accidental {
    (@impl $(#[$meta:meta])* $vis:vis $type:ident $name:ident = $sym:literal $(;)?) => {
        unit_type! {
            $(#[$meta])*
            #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
            $vis $type $name
        }

        impl $name {
            pub const NAME: &'static str = stringify!($name);
            /// compares some static type `T` against `Self`
            pub fn of<T>() -> bool
            where
                T: 'static,
            {
                ::core::any::TypeId::of::<T>() == ::core::any::TypeId::of::<Self>()
            }
            /// returns the symbol of the accidental
            pub const fn symbol(&self) -> char {
                $sym
            }
            /// returns the name of the accidental
            pub fn name(&self) -> &str {
                stringify!($name)
            }
        }

        impl $crate::pitch::Accidental for $name {
            seal! {}

            fn new() -> Self {
                Self
            }

            fn name(&self) -> &str {
                self.name()
            }

            fn symbol(&self) -> char {
                self.symbol()
            }
        }

        impl AsRef<str> for $name {
            fn as_ref(&self) -> &str {
                stringify!($name)
            }
        }

        impl ::core::fmt::Debug for $name {
            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                write!(f, "{}", self.symbol())
            }
        }

        impl ::core::fmt::Display for $name {
            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
                write!(f, "{}", self.symbol())
            }
        }
    };
    ($($vis:vis $type:ident $name:ident $(= $sym:literal)?);* $(;)?) => {
        $(accidental! { @impl $vis $type $name $(= $sym)? })*
    };
}

accidental! {
    pub struct Flat = '♭';
    pub struct Sharp = '♯';
}

#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(
    feature = "serde",
    derive(serde::Deserialize, serde::Serialize),
    serde(rename_all = "snake_case")
)]
#[repr(transparent)]
pub struct Natural;

impl Natural {
    pub const fn new() -> Self {
        Self
    }
    /// compares some static type `T` against `Self`
    pub fn of<T>() -> bool
    where
        T: 'static,
    {
        ::core::any::TypeId::of::<T>() == ::core::any::TypeId::of::<Self>()
    }

    pub const fn name(&self) -> &str {
        "Natural"
    }

    pub const fn symbol(&self) -> char {
        ''
    }
}

impl Accidental for Natural {
    seal! {}

    fn new() -> Self {
        Self::new()
    }

    fn name(&self) -> &str {
        self.name()
    }

    fn symbol(&self) -> char {
        self.symbol()
    }
}

impl AsRef<str> for Natural {
    fn as_ref(&self) -> &str {
        "Natural"
    }
}

impl AsRef<char> for Natural {
    fn as_ref(&self) -> &char {
        &''
    }
}

impl core::borrow::Borrow<str> for Natural {
    fn borrow(&self) -> &str {
        self.as_ref()
    }
}

impl core::borrow::Borrow<char> for Natural {
    fn borrow(&self) -> &char {
        self.as_ref()
    }
}

impl core::ops::Deref for Natural {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.as_ref()
    }
}

impl core::str::FromStr for Natural {
    type Err = crate::error::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.is_empty() || s.to_lowercase() == "natural" || s == "" {
            Ok(Natural)
        } else {
            Err(anyhow::anyhow!("Unable to parse a natural note : {}", s).into())
        }
    }
}

impl core::fmt::Debug for Natural {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(self.name())
    }
}

impl core::fmt::Display for Natural {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}", self.symbol())
    }
}

#[cfg(feature = "alloc")]
impl ::core::str::FromStr for Flat {
    type Err = crate::error::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.to_lowercase() == "flat" || s == "" || s == "b" {
            Ok(Self)
        } else {
            Err(anyhow::anyhow!("Invalid accidental string: {}", s).into())
        }
    }
}

#[cfg(feature = "alloc")]
impl ::core::str::FromStr for Sharp {
    type Err = crate::error::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        if s.to_lowercase() == "sharp" || s == "" || s == "#" {
            Ok(Self)
        } else {
            Err(anyhow::anyhow!("Invalid accidental string: {}", s).into())
        }
    }
}