ireal_parser/
staff_text.rs1use std::{fmt, str::FromStr};
2
3use crate::{Error, Result};
4
5#[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#[derive(Debug, Eq, PartialEq, Clone)]
84pub struct StaffText {
85 pub text: StaffTextKind,
86 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}