1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
//! A crate to parse an osu! beatmap file.
//!
//! # How to use
//!
//! ```
//! use osu_file_parser::*;
//!
//! let osu_file_str = include_str!("./tests/osu_files/files/acid_rain.osu");
//! // parse the .osu file
//! let mut osu_file = osu_file_str.parse::<OsuFile>().unwrap();
//!
//! let osb_str = include_str!("./tests/osu_files/files/acid_rain.osb");
//! // .osb file can also be parsed and appended to the `OsuFile` instance
//! osu_file.append_osb(osb_str).unwrap();
//!
//! // you can use `assert_eq_osu_str` to assert that the parsed .osu file is equal to the original .osu file
//! assert_eq_osu_str(&osu_file.to_string(), osu_file_str);
//! assert_eq_osu_str(&osu_file.osb_to_string().unwrap(), osb_str);
//! ```
//!
//! # General information
//!
//! ## Alternative traits
//! - Most of the types in the crate uses the `VersionedToString`, `VersionedFromStr` and `VersionedDefault` traits as replacements for the `Display`, `FromStr` and `Default` traits.
//! - Those traits take an extra `version` parameter to choose what version output to use.
//! - If the type doesn't exist in certain versions, the output will be `None`.
//!
//! ## Errors
//! - Structs that takes lines of string as input can return errors containing information of where the error occurred and what the error was.
//! - The error type is wrapped in [`Error`] in those cases.
//! - [`Error`] has methods that tells you where the error happened in the input string and what the error was.
#[cfg(test)]
mod tests;
mod helper;
pub mod osu_file;
pub use osu_file::*;
mod parsers;
/// Trims the given osu file string into something that can be tested for equality.
/// - Ignores all empty lines and key value pair's spacing between the key and comma.
/// - Deletes `\u{feff}` characters.
pub fn osu_str_trimmer(s: &str) -> String {
let mut builder = Vec::new();
let mut section_values_inner = Vec::new();
let mut in_sections = false;
let mut first_line = true;
let mut section_coloned = true;
let section_values_sort_and_push =
|section_values_inner: &mut Vec<String>, builder: &mut Vec<String>| {
section_values_inner.sort();
builder.append(section_values_inner);
};
for line in s.lines() {
let line = line.trim().replace('\u{feff}', "");
if line.is_empty() {
continue;
}
if !in_sections {
if line.starts_with('[') && line.ends_with(']') {
in_sections = true;
} else {
// data before the sections are irrelevant after first line with file format specifier
if first_line && line.starts_with("osu file format v") {
builder.push(line);
}
first_line = false;
continue;
}
builder.push(line);
continue;
} else {
if line.starts_with('[') && line.ends_with(']') {
section_values_sort_and_push(&mut section_values_inner, &mut builder);
builder.push(line);
section_coloned = true;
continue;
}
if line.contains(':') {
let mut header = true;
let mut value = false;
let line = line
.chars()
.filter(|c| {
if !header && !value && *c == ':' {
header = false;
value = true;
return true;
}
if header && c.is_whitespace() {
return false;
}
true
})
.collect::<String>();
if section_coloned {
section_values_inner.push(line);
} else {
builder.push(line);
}
} else {
section_coloned = false;
builder.append(&mut section_values_inner);
builder.push(line);
}
}
}
section_values_sort_and_push(&mut section_values_inner, &mut builder);
builder.join("\n")
}
/// Tests equality of two osu file strings.
pub fn osu_str_eq<L: AsRef<str>, R: AsRef<str>>(left: L, right: R) -> bool {
let left = osu_str_trimmer(left.as_ref());
let right = osu_str_trimmer(right.as_ref());
left == right
}
/// Asserts that two osu file strings are equal.
pub fn assert_eq_osu_str<L: AsRef<str>, R: AsRef<str>>(left: L, right: R) {
let left = osu_str_trimmer(left.as_ref());
let right = osu_str_trimmer(right.as_ref());
pretty_assertions::assert_eq!(left, right)
}