1use std::borrow::Cow;
2use std::fmt;
3use std::num::{NonZero, ParseIntError};
4use std::str::FromStr;
5
6use crate::arguments::{ArgumentScanner, ExpectArg as _};
7use crate::parse::Decoder;
8
9#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub enum AudioRepetition {
14 Forever,
16 Count(NonZero<u32>),
18}
19
20impl Default for AudioRepetition {
21 fn default() -> Self {
23 Self::Count(NonZero::new(1).unwrap())
24 }
25}
26
27impl fmt::Display for AudioRepetition {
28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 match self {
30 Self::Forever => (-1).fmt(f),
31 Self::Count(amount) => amount.fmt(f),
32 }
33 }
34}
35
36impl FromStr for AudioRepetition {
37 type Err = ParseIntError;
38
39 fn from_str(s: &str) -> Result<Self, Self::Err> {
40 if s == "-1" {
41 return Ok(Self::Forever);
42 }
43 s.parse().map(Self::Count)
44 }
45}
46
47#[derive(Copy, Clone, Debug, PartialEq, Eq)]
70pub struct Sound<S = String> {
71 pub fname: S,
73 pub volume: u8,
75 pub repeat: AudioRepetition,
77 pub priority: u8,
82 pub class: Option<S>,
85 pub url: Option<S>,
89}
90
91impl<S: Default> Default for Sound<S> {
92 fn default() -> Self {
93 Self {
94 fname: S::default(),
95 volume: 100,
96 repeat: AudioRepetition::default(),
97 priority: 50,
98 class: None,
99 url: None,
100 }
101 }
102}
103
104impl<S: AsRef<str>> Sound<S> {
105 pub fn is_off(&self) -> bool {
108 self.fname.as_ref().eq_ignore_ascii_case("off") && self.url.is_none()
109 }
110}
111
112impl<S> Sound<S> {
113 pub fn map_text<T, F>(self, mut f: F) -> Sound<T>
115 where
116 F: FnMut(S) -> T,
117 {
118 Sound {
119 fname: f(self.fname),
120 volume: self.volume,
121 repeat: self.repeat,
122 priority: self.priority,
123 class: self.class.map(&mut f),
124 url: self.url.map(f),
125 }
126 }
127}
128
129impl_into_owned!(Sound);
130
131impl<S: AsRef<str>> Sound<S> {
132 pub fn borrow_text(&self) -> Sound<&str> {
134 Sound {
135 fname: self.fname.as_ref(),
136 volume: self.volume,
137 repeat: self.repeat,
138 class: self.class.as_ref().map(AsRef::as_ref),
139 url: self.url.as_ref().map(AsRef::as_ref),
140 priority: self.priority,
141 }
142 }
143}
144
145impl_partial_eq!(Sound);
146
147impl<S: AsRef<str>> Sound<S> {
148 pub(crate) fn scan<A>(mut scanner: A) -> crate::Result<Self>
149 where
150 A: ArgumentScanner<Output = S>,
151 {
152 let fname = scanner.decode_next_or("fname")?.expect_some("fname")?;
153 let volume = scanner.decode_next_or("v")?.expect_number()?.unwrap_or(100);
154 let repeat = scanner
155 .decode_next_or("l")?
156 .expect_number()?
157 .unwrap_or_default();
158 let priority = scanner.decode_next_or("p")?.expect_number()?.unwrap_or(50);
159 let class = scanner.decode_next_or("t")?;
160 let url = scanner.decode_next_or("u")?;
161 scanner.expect_end()?;
162 Ok(Self {
163 fname,
164 volume,
165 repeat,
166 priority,
167 class,
168 url,
169 })
170 }
171}
172
173impl_from_str!(Sound);
174
175impl<S: AsRef<str>> fmt::Display for Sound<S> {
176 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177 let Sound {
178 fname,
179 volume,
180 repeat,
181 priority,
182 class,
183 url,
184 } = self.borrow_text();
185 crate::display::ElementFormatter {
186 name: "SOUND",
187 arguments: &[
188 &fname,
189 &(volume, 100),
190 &(repeat, AudioRepetition::default()),
191 &(priority, 50),
192 &class,
193 &url,
194 ],
195 keywords: &[],
196 }
197 .fmt(f)
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204 use crate::test_utils::{StringPair, format_from_pairs, parse_from_pairs};
205
206 const AUDIO_REPETITION_PAIRS: &[StringPair<AudioRepetition>] = &[
207 (AudioRepetition::Forever, "-1"),
208 (AudioRepetition::Count(NonZero::new(10).unwrap()), "10"),
209 ];
210
211 #[test]
212 fn fmt_audio_repetition() {
213 let (actual, expected) = format_from_pairs(AUDIO_REPETITION_PAIRS);
214 assert_eq!(actual, expected);
215 }
216
217 #[test]
218 fn parse_audio_repetition() {
219 let (actual, expected) = parse_from_pairs(AUDIO_REPETITION_PAIRS);
220 assert_eq!(actual, expected);
221 }
222
223 #[test]
224 fn parse_audio_repetition_invalid() {
225 assert_eq!(
226 "0".parse::<AudioRepetition>(),
227 Err("0".parse::<NonZero<u32>>().unwrap_err())
228 );
229 }
230}