chart_file_parser/
song.rs

1use std::fmt::Display;
2
3use nom::{bytes::complete::tag, combinator::map_res, multi::many1, sequence::preceded, IResult};
4
5use crate::{
6    components::{curlied, spaced},
7    song_property::SongProperty,
8};
9
10#[derive(Debug, PartialEq, Eq)]
11pub(crate) struct Song<'a> {
12    resolution: u32,
13    properties: Vec<SongProperty<'a>>,
14}
15
16impl<'a> Song<'a> {
17    #[must_use]
18    pub(crate) fn new(resolution: u32, properties: Vec<SongProperty<'a>>) -> Self {
19        Self {
20            resolution,
21            properties,
22        }
23    }
24
25    pub(crate) fn multiply(&mut self, factor: u32) {
26        self.resolution *= factor;
27    }
28
29    #[inline]
30    pub(crate) fn parse(input: &str) -> IResult<&str, Song> {
31        map_res(
32            preceded(
33                spaced(tag("[Song]")),
34                curlied(many1(spaced(SongProperty::parse))),
35            ),
36            Song::try_from,
37        )(input)
38    }
39}
40
41impl<'a> TryFrom<Vec<SongProperty<'a>>> for Song<'a> {
42    type Error = &'static str;
43
44    fn try_from(value: Vec<SongProperty<'a>>) -> Result<Self, Self::Error> {
45        let resolution_entry = value
46            .iter()
47            .find(|x| x.name() == "Resolution")
48            .ok_or("resolution not found")?;
49        let resolution = resolution_entry
50            .value()
51            .parse::<u32>()
52            .map_err(|_| "invalid resolution")?;
53        let properties = value
54            .into_iter()
55            .filter(|x| x.name() != "Resolution")
56            .collect();
57        Ok(Self::new(resolution, properties))
58    }
59}
60
61impl<'a> Display for Song<'a> {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        let resolution_string = self.resolution.to_string();
64        write!(
65            f,
66            "[Song]\n{{\n{}{}}}",
67            SongProperty::new("Resolution", &resolution_string),
68            self.properties
69                .iter()
70                .map(SongProperty::to_string)
71                .collect::<String>()
72        )
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    #![allow(clippy::unwrap_used)]
79    use super::*;
80
81    #[test]
82    fn test_song() {
83        Song::parse(
84            r#"[Song]
85{
86  Name = "Second Sight"
87  Artist = "Adagio"
88  Charter = "Peddy"
89  Album = "Sanctus Ignis"
90  Year = ", 2001"
91  Offset = 0
92  Resolution = 192
93  Player2 = bass
94  Difficulty = 0
95  PreviewStart = 0
96  PreviewEnd = 0
97  Genre = "Neoclassical Metal"
98  MediaType = "cd"
99  MusicStream = "song.ogg"
100}"#,
101        )
102        .unwrap();
103    }
104}