osu_file_parser/osu_file/colours/
mod.rs

1pub mod error;
2pub mod types;
3
4use nom::{
5    branch::alt,
6    bytes::complete::tag,
7    character::complete::{char, digit1, space0},
8    combinator::{cut, fail, map_res, rest},
9    error::context,
10    sequence::{preceded, tuple},
11    Finish, Parser,
12};
13
14use crate::parsers::comma;
15
16pub use error::*;
17pub use types::*;
18
19use super::{Error, Version, VersionedDefault, VersionedFromStr, VersionedToString, MIN_VERSION};
20
21#[derive(Clone, Debug, Hash, PartialEq, Eq)]
22pub struct Colours(pub Vec<Colour>);
23
24impl VersionedFromStr for Colours {
25    type Err = Error<ParseError>;
26
27    fn from_str(s: &str, version: Version) -> std::result::Result<Option<Self>, Self::Err> {
28        match version {
29            MIN_VERSION..=4 => Ok(None),
30            _ => {
31                let mut colours = Vec::new();
32
33                for (line_index, s) in s.lines().enumerate() {
34                    if s.trim().is_empty() {
35                        continue;
36                    }
37
38                    let colour =
39                        Error::new_from_result_into(Colour::from_str(s, version), line_index)?;
40                    if let Some(colour) = colour {
41                        colours.push(colour);
42                    }
43                }
44
45                Ok(Some(Colours(colours)))
46            }
47        }
48    }
49}
50
51impl VersionedToString for Colours {
52    fn to_string(&self, version: Version) -> Option<String> {
53        match version {
54            MIN_VERSION..=4 => None,
55            _ => Some(
56                self.0
57                    .iter()
58                    .filter_map(|c| c.to_string(version))
59                    .collect::<Vec<_>>()
60                    .join("\n"),
61            ),
62        }
63    }
64}
65
66impl VersionedDefault for Colours {
67    fn default(version: Version) -> Option<Self> {
68        match version {
69            MIN_VERSION..=4 => None,
70            _ => Some(Colours(Vec::new())),
71        }
72    }
73}
74
75/// Struct representing a single `colour` component in the `Colours` section.
76#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
77#[non_exhaustive]
78pub enum Colour {
79    /// Additive combo colours.
80    Combo(i32, Rgb),
81    /// Additive slider track colour.
82    SliderTrackOverride(Rgb),
83    /// Slider border colour.
84    SliderBorder(Rgb),
85}
86
87impl VersionedFromStr for Colour {
88    type Err = ParseColourError;
89
90    fn from_str(s: &str, version: Version) -> Result<Option<Self>, Self::Err> {
91        let s = s.trim();
92
93        let separator = || tuple((space0, tag(":"), space0));
94        let combo_type = tag("Combo");
95        let combo_count = map_res(digit1, |s: &str| s.parse());
96        let slider_track_override_type = tag("SliderTrackOverride");
97        let slider_border_type = tuple((tag("Slider"), alt((char('B'), char('b'))), tag("order")));
98        let rgb_parse_error = "rgb_parse_error";
99        let rgb = || {
100            context(
101                rgb_parse_error,
102                map_res(rest, |s: &str| {
103                    Rgb::from_str(s, version).map(|rgb| rgb.unwrap())
104                }),
105            )
106        };
107
108        let combo = tuple((
109            preceded(
110                combo_type,
111                context(ParseColourError::InvalidComboCount.into(), cut(combo_count)),
112            ),
113            cut(preceded(
114                context(ParseColourError::InvalidColonSeparator.into(), separator()),
115                rgb(),
116            )),
117        ))
118        .map(|(combo, rgb)| Colour::Combo(combo, rgb));
119
120        let slide_track_override = preceded(
121            tuple((
122                slider_track_override_type,
123                context(
124                    ParseColourError::InvalidColonSeparator.into(),
125                    cut(separator()),
126                ),
127            )),
128            cut(rgb()),
129        )
130        .map(Colour::SliderTrackOverride);
131
132        let slider_border = preceded(
133            tuple((
134                slider_border_type,
135                context(
136                    ParseColourError::InvalidColonSeparator.into(),
137                    cut(separator()),
138                ),
139            )),
140            cut(rgb()),
141        )
142        .map(Colour::SliderBorder);
143
144        let colour_res: Result<(_, _), nom::error::VerboseError<&str>> = alt((
145            combo,
146            slide_track_override,
147            slider_border,
148            context(ParseColourError::UnknownColourType.into(), fail),
149        ))(s)
150        .finish();
151
152        match colour_res {
153            Ok((_, colour)) => Ok(Some(colour)),
154            Err(e) => {
155                for (i, e) in &e.errors {
156                    if let nom::error::VerboseErrorKind::Context(context) = e {
157                        if context == &rgb_parse_error {
158                            // re-parse to get actual error message
159
160                            let err = Rgb::from_str(i, MIN_VERSION).unwrap_err();
161                            return Err(err.into());
162                        }
163                    }
164                }
165
166                // else just return the error
167                Err(nom::Err::Error(e).into())
168            }
169        }
170    }
171}
172
173impl VersionedToString for Colour {
174    fn to_string(&self, version: Version) -> Option<String> {
175        let colour_str = match self {
176            Colour::Combo(num, rgb) => format!("Combo{num} : {}", rgb.to_string(version).unwrap()),
177            Colour::SliderTrackOverride(rgb) => {
178                format!("SliderTrackOverride : {}", rgb.to_string(version).unwrap())
179            }
180            Colour::SliderBorder(rgb) => {
181                format!("SliderBorder : {}", rgb.to_string(version).unwrap())
182            }
183        };
184
185        Some(colour_str)
186    }
187}