ireal_parser/
staff_text.rs

1use std::{fmt, str::FromStr};
2
3use crate::{Error, Result};
4
5/// Represents a staff text
6#[derive(Debug, Eq, PartialEq, Clone)]
7pub enum StaffTextKind {
8    DCAlCoda,
9    DCAlFine,
10    DCAl1st,
11    DCAl2nd,
12    DCAl3rd,
13    DSAlCoda,
14    DSAlFine,
15    DSAl1st,
16    DSAl2nd,
17    DSAl3rd,
18    Fine,
19    Free(String),
20    Repeat(u32),
21}
22
23impl fmt::Display for StaffTextKind {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        use StaffTextKind::*;
26
27        let text = match self {
28            DCAlCoda => "D.C. al Coda".into(),
29            DCAlFine => "D.C. al Fine".into(),
30            DCAl1st => "D.C. al 1st End".into(),
31            DCAl2nd => "D.C. al 2nd End".into(),
32            DCAl3rd => "D.C. al 3rd End".into(),
33            DSAlCoda => "D.S. al Coda".into(),
34            DSAlFine => "D.S. al Fine".into(),
35            DSAl1st => "D.S. al 1st End".into(),
36            DSAl2nd => "D.S. al 2nd End".into(),
37            DSAl3rd => "D.S. al 3rd End".into(),
38            Fine => "Fine".into(),
39            Free(text) => text.clone(),
40            Repeat(n) => format!("{n}x"),
41        };
42
43        write!(f, "{text}")
44    }
45}
46
47impl FromStr for StaffTextKind {
48    type Err = Error;
49
50    fn from_str(s: &str) -> Result<Self> {
51        use StaffTextKind::*;
52
53        let res = match s {
54            "D.C. al Coda" => DCAlCoda,
55            "D.C. al Fine" => DCAlFine,
56            "D.C. al 1st End" => DCAl1st,
57            "D.C. al 2nd End" => DCAl2nd,
58            "D.C. al 3rd End" => DCAl3rd,
59            "D.S. al Coda" => DSAlCoda,
60            "D.S. al Fine" => DSAlFine,
61            "D.S. al 1st End" => DSAl1st,
62            "D.S. al 2nd End" => DSAl2nd,
63            "D.S. al 3rd End" => DSAl3rd,
64            "Fine" => Fine,
65            _ => {
66                if s.ends_with('x') {
67                    let num_str = &s[0..s.len() - 1];
68                    match num_str.parse() {
69                        Ok(n) => Repeat(n),
70                        _ => Free(s.into()),
71                    }
72                } else {
73                    Free(s.into())
74                }
75            }
76        };
77
78        Ok(res)
79    }
80}
81
82/// Represents some staff text
83#[derive(Debug, Eq, PartialEq, Clone)]
84pub struct StaffText {
85    pub text: StaffTextKind,
86    /// You can move the text upwards relative to the current chord by adding a
87    /// * followed by two digit number between 00 (below the system) and 74
88    /// (above the system)
89    pub position: Option<u8>,
90}
91
92impl StaffText {
93    pub fn new(text: StaffTextKind) -> Self {
94        Self {
95            text,
96            position: None,
97        }
98    }
99
100    pub fn set_position(&mut self, position: Option<u8>) {
101        self.position = position;
102    }
103}
104
105impl fmt::Display for StaffText {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        let pos = self.position.map(|p| format!("*{p}")).unwrap_or_default();
108        let text = &self.text;
109
110        write!(f, "<{pos}{text}>")
111    }
112}
113
114impl FromStr for StaffText {
115    type Err = Error;
116
117    fn from_str(s: &str) -> Result<Self> {
118        if !s.starts_with('<') || !s.ends_with('>') {
119            return Err(Error::InvalidStaffText);
120        }
121
122        let s = &s[1..s.len() - 1];
123
124        let (pos, text) = if let Some(s) = s.strip_prefix('*') {
125            let digit_count = s.chars().take_while(|c| c.is_ascii_digit()).count();
126            let (number_str, trailing_str) = s.split_at(digit_count);
127            (number_str.parse().ok(), trailing_str)
128        } else {
129            (None, s)
130        };
131
132        let mut res = Self::new(text.parse()?);
133        res.set_position(pos);
134        Ok(res)
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_to_string() {
144        let mut s = StaffText::new(StaffTextKind::Free("Some text".into()));
145        s.set_position(Some(12));
146        assert_eq!(s.to_string(), "<*12Some text>");
147
148        let st: StaffText = "<*12Some text>".parse().unwrap();
149        assert_eq!(st, s);
150    }
151}