use std::{fmt, str::FromStr};
use crate::{Error, Result};
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum StaffTextKind {
DCAlCoda,
DCAlFine,
DCAl1st,
DCAl2nd,
DCAl3rd,
DSAlCoda,
DSAlFine,
DSAl1st,
DSAl2nd,
DSAl3rd,
Fine,
Free(String),
Repeat(u32),
}
impl fmt::Display for StaffTextKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use StaffTextKind::*;
let text = match self {
DCAlCoda => "D.C. al Coda".into(),
DCAlFine => "D.C. al Fine".into(),
DCAl1st => "D.C. al 1st End".into(),
DCAl2nd => "D.C. al 2nd End".into(),
DCAl3rd => "D.C. al 3rd End".into(),
DSAlCoda => "D.S. al Coda".into(),
DSAlFine => "D.S. al Fine".into(),
DSAl1st => "D.S. al 1st End".into(),
DSAl2nd => "D.S. al 2nd End".into(),
DSAl3rd => "D.S. al 3rd End".into(),
Fine => "Fine".into(),
Free(text) => text.clone(),
Repeat(n) => format!("{n}x"),
};
write!(f, "{text}")
}
}
impl FromStr for StaffTextKind {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
use StaffTextKind::*;
let res = match s {
"D.C. al Coda" => DCAlCoda,
"D.C. al Fine" => DCAlFine,
"D.C. al 1st End" => DCAl1st,
"D.C. al 2nd End" => DCAl2nd,
"D.C. al 3rd End" => DCAl3rd,
"D.S. al Coda" => DSAlCoda,
"D.S. al Fine" => DSAlFine,
"D.S. al 1st End" => DSAl1st,
"D.S. al 2nd End" => DSAl2nd,
"D.S. al 3rd End" => DSAl3rd,
"Fine" => Fine,
_ => {
if s.ends_with('x') {
let num_str = &s[0..s.len() - 1];
match num_str.parse() {
Ok(n) => Repeat(n),
_ => Free(s.into()),
}
} else {
Free(s.into())
}
}
};
Ok(res)
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct StaffText {
pub text: StaffTextKind,
pub position: Option<u8>,
}
impl StaffText {
pub fn new(text: StaffTextKind) -> Self {
Self {
text,
position: None,
}
}
pub fn set_position(&mut self, position: Option<u8>) {
self.position = position;
}
}
impl fmt::Display for StaffText {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pos = self.position.map(|p| format!("*{p}")).unwrap_or_default();
let text = &self.text;
write!(f, "<{pos}{text}>")
}
}
impl FromStr for StaffText {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if !s.starts_with('<') || !s.ends_with('>') {
return Err(Error::InvalidStaffText);
}
let s = &s[1..s.len() - 1];
let (pos, text) = if let Some(s) = s.strip_prefix('*') {
let digit_count = s.chars().take_while(|c| c.is_ascii_digit()).count();
let (number_str, trailing_str) = s.split_at(digit_count);
(number_str.parse().ok(), trailing_str)
} else {
(None, s)
};
let mut res = Self::new(text.parse()?);
res.set_position(pos);
Ok(res)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_string() {
let mut s = StaffText::new(StaffTextKind::Free("Some text".into()));
s.set_position(Some(12));
assert_eq!(s.to_string(), "<*12Some text>");
let st: StaffText = "<*12Some text>".parse().unwrap();
assert_eq!(st, s);
}
}