use std::str::FromStr;
use nom::{
branch::alt,
bytes::complete::{tag, take_till, take_until, take_while},
character::complete::multispace0,
character::complete::{char, space0},
combinator::{eof, map_res, rest},
error::{FromExternalError, ParseError},
multi::{many0, separated_list0},
sequence::{delimited, preceded, terminated, tuple},
IResult,
};
use crate::osu_file::{Version, VersionedFromStr};
pub fn get_colon_field_value_lines(s: &str) -> IResult<&str, Vec<(&str, &str, &str, &str)>> {
let field_name = take_till(|c| c == ':' || c == '\n');
let field_separator = char(':');
let field_value = take_till(|c| c == '\r' || c == '\n');
let field_line = tuple((
terminated(field_name, field_separator),
space0,
field_value,
multispace0,
));
many0(field_line)(s)
}
pub fn pipe_vec_versioned_map<'a, E, T>(
version: Version,
) -> impl FnMut(&'a str) -> IResult<&'a str, Vec<T>, E>
where
E: ParseError<&'a str> + nom::error::FromExternalError<&'a str, <T as VersionedFromStr>::Err>,
T: VersionedFromStr,
{
let item = take_while(|c: char| !['|', ',', '\r', '\n'].contains(&c));
let item = map_res(item, move |s: &str| {
T::from_str(s, version).map(|v| v.unwrap())
});
separated_list0(tag("|"), item)
}
pub fn comma<'a, E>() -> impl FnMut(&'a str) -> IResult<&'a str, &str, E>
where
E: ParseError<&'a str>,
{
tag(",")
}
pub fn comma_field<'a, E>() -> impl FnMut(&'a str) -> IResult<&str, &str, E>
where
E: ParseError<&'a str>,
{
take_while(|c: char| c != ',')
}
pub fn comma_field_type<'a, E, T>() -> impl FnMut(&'a str) -> IResult<&str, T, E>
where
E: ParseError<&'a str> + FromExternalError<&'a str, <T as FromStr>::Err>,
T: FromStr,
{
map_res(comma_field(), |i| i.parse())
}
pub fn comma_field_versioned_type<'a, E, T>(
version: Version,
) -> impl FnMut(&'a str) -> IResult<&str, T, E>
where
E: ParseError<&'a str> + FromExternalError<&'a str, <T as VersionedFromStr>::Err>,
T: VersionedFromStr,
{
map_res(comma_field(), move |i| {
T::from_str(i, version).map(|i| i.unwrap())
})
}
pub fn consume_rest_type<'a, E, T>() -> impl FnMut(&'a str) -> IResult<&str, T, E>
where
E: ParseError<&'a str> + FromExternalError<&'a str, <T as FromStr>::Err>,
T: FromStr,
{
map_res(rest, |s: &str| s.parse())
}
pub fn consume_rest_versioned_type<'a, E, T>(
version: Version,
) -> impl FnMut(&'a str) -> IResult<&str, T, E>
where
E: ParseError<&'a str> + FromExternalError<&'a str, <T as VersionedFromStr>::Err>,
T: VersionedFromStr,
{
map_res(rest, move |s: &str| {
T::from_str(s, version).map(|s| s.unwrap())
})
}
pub fn nothing<'a, E>() -> impl FnMut(&'a str) -> IResult<&str, &str, E>
where
E: ParseError<&'a str>,
{
preceded(space0, eof)
}
pub fn square_section<'a>(
) -> impl FnMut(&'a str) -> IResult<&'a str, (&str, &str, &str, &str), nom::error::Error<&str>> {
let section_open = tag("[");
let section_close = tag("]");
let section_name_inner = take_till(|c: char| c == ']' || c == '\n');
let section_name = delimited(section_open, section_name_inner, section_close);
let section_until = alt((take_until("\n["), rest));
tuple((multispace0, section_name, multispace0, section_until))
}