osu_file_parser/osu_file/colours/
mod.rs1pub 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#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
77#[non_exhaustive]
78pub enum Colour {
79 Combo(i32, Rgb),
81 SliderTrackOverride(Rgb),
83 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 let err = Rgb::from_str(i, MIN_VERSION).unwrap_err();
161 return Err(err.into());
162 }
163 }
164 }
165
166 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}