poe_data_tools 1.0.0

A library for working with Path of Exile game data
Documentation
use anyhow::anyhow;
use winnow::{
    Parser,
    ascii::{dec_uint as U, float as F, space1},
    combinator::{alt, opt, preceded as P, repeat},
    token::literal,
};

use super::types::*;
use crate::file_parsers::{
    VersionedResult, VersionedResultExt,
    shared::{
        lift::{SliceParser, lift},
        winnow::{
            WinnowParser, filename, quoted, quoted_str, safe_u32, unquoted_str, version_line,
        },
    },
};

fn line1<'a>() -> impl WinnowParser<&'a str, Line1> {
    winnow::trace!(
        "line1",
        (
            F, //
            opt(P(space1, U)),
            opt(P(space1, U)),
        )
            .map(|(scale, uint1, uint2)| Line1 {
                scale,
                uint1,
                uint2,
            })
    )
}

fn group_header<'a>(
    name_parser: impl WinnowParser<&'a str, String>,
) -> impl WinnowParser<&'a str, (String, Option<String>, Option<f32>)> {
    winnow::trace!(
        "group_header",
        (
            name_parser, //
            opt(P(space1, unquoted_str)),
            opt(P(space1, F)),
        )
    )
}

fn object<'a>() -> impl WinnowParser<&'a str, Object> {
    winnow::trace!(
        "object",
        (
            alt((literal("All").map(|_| Weight::All), F.map(Weight::Float))),
            P(space1, quoted('"').and_then(filename("ao"))),
            opt(P(space1, safe_u32)),
            opt(P(space1, literal("D").map(String::from))),
            opt(P(space1, F)),
        )
            .map(|(weight, ao_file, uint1, d, float1)| Object {
                weight,
                ao_file,
                uint1,
                d,
                float1,
            })
    )
}

fn group<'a>(
    name_parser: impl WinnowParser<&'a str, String>,
) -> impl SliceParser<'a, &'a str, Group> {
    winnow::trace!(
        "group",
        (
            lift(group_header(name_parser)), //
            repeat(0.., lift(object())),
        )
            .map(|((name, d, float1), objects)| Group {
                name,
                d,
                float1,
                objects,
            })
    )
}

pub fn parse_ddt_str(contents: &str) -> VersionedResult<DDTFile> {
    let lines = contents
        .lines()
        .map(|l| l.trim())
        .filter(|l| !l.is_empty() && !l.starts_with("//"))
        .collect::<Vec<_>>();
    let mut lines = lines.as_slice();

    let version = lift(version_line())
        .parse_next(&mut lines)
        .map_err(|e| anyhow!("Failed to parse file: {e:?}"))?;

    let mut parser = (
        lift(line1()),
        opt(lift(U)),
        group(unquoted_str),
        repeat(0.., group(quoted_str)),
    )
        .map(|(line1, uint1, default_group, mut groups)| {
            Vec::insert(&mut groups, 0, default_group); // for type inference
            DDTFile {
                version,
                line1,
                uint1,
                groups,
            }
        });

    parser
        .parse(lines)
        .map_err(|e| anyhow!("Failed to parse file: {e:?}"))
        .with_version(Some(version))
}