xiv-emote-parser 0.1.8

Parses log messages for emotes from FFXIV into a sequence of message parts
Documentation
use std::error::Error;

use thiserror::Error;
use xiv_emote_parser::log_message::{
    condition::{Character, Gender, LogMessageAnswers},
    process_log_message, EmoteTextError,
};

// #[test]
// fn can_parse_en() {
//     let log_msg = "<Clickable(<If(Equal(ObjectParameter(1),ObjectParameter(2)))>you<Else/><If(PlayerParameter(7))><SheetEn(ObjStr,2,PlayerParameter(7),1,1)/><Else/>ObjectParameter(2)</If></If>)/> <If(Equal(ObjectParameter(1),ObjectParameter(2)))>look<Else/>looks</If> at <If(Equal(ObjectParameter(1),ObjectParameter(3)))><If(PlayerParameter(8))><SheetEn(ObjStr,2,PlayerParameter(8),1,1)/><Else/>you</If><Else/><If(PlayerParameter(8))><SheetEn(ObjStr,2,PlayerParameter(8),1,1)/><Else/>ObjectParameter(3)</If></If> in surprise.";

//     let parse = LogMessageParser::parse(Rule::message, log_msg).unwrap();
//     println!("{:#?}", parse);
//     let root = parse.single().unwrap();
//     let message = LogMessageParser::message(root);
//     println!("{:#?}", message);

//     assert!(message.is_ok(), "did not parse correctly");
// }

// #[test]
// fn can_parse_jp() {
//     let log_msg = "<If(PlayerParameter(7))><Sheet(ObjStr,PlayerParameter(7),0)/><Else/>ObjectParameter(2)</If>はおどろいた。";

//     let parse = LogMessageParser::parse(Rule::message, log_msg).unwrap();
//     println!("{:#?}", parse);
//     let root = parse.single().unwrap();
//     let message = LogMessageParser::message(root);
//     println!("{:#?}", message);

//     assert!(message.is_ok(), "did not parse correctly");
// }

#[test]
fn can_parse_en_with_ast() -> Result<(), impl Error> {
    let log_msg = "<Clickable(<If(Equal(ObjectParameter(1),ObjectParameter(2)))>you<Else/><If(PlayerParameter(7))><SheetEn(ObjStr,2,PlayerParameter(7),1,1)/><Else/>ObjectParameter(2)</If></If>)/> <If(Equal(ObjectParameter(1),ObjectParameter(2)))>look<Else/>looks</If> at <If(Equal(ObjectParameter(1),ObjectParameter(3)))><If(PlayerParameter(8))><SheetEn(ObjStr,2,PlayerParameter(8),1,1)/><Else/>you</If><Else/><If(PlayerParameter(8))><SheetEn(ObjStr,2,PlayerParameter(8),1,1)/><Else/>ObjectParameter(3)</If></If> in surprise.";

    let origin = Character::new("K'haldru Alaba", Gender::Female, true, true);
    let target = Character::new("Puruo Jelly", Gender::Male, true, false);
    let text = process_log_message(log_msg, &LogMessageAnswers::new(origin, target).unwrap());
    println!("{:?}", text);
    text.map(|_| ())
}

#[test]
fn can_parse_en_with_ast_gendered_speaker() -> Result<(), impl Error> {
    let log_msg = "<Clickable(<If(Equal(ObjectParameter(1),ObjectParameter(2)))>you<Else/><If(PlayerParameter(7))><SheetEn(ObjStr,2,PlayerParameter(7),1,1)/><Else/>ObjectParameter(2)</If></If>)/> <If(Equal(ObjectParameter(1),ObjectParameter(2)))>express<Else/>expresses</If> <If(Equal(ObjectParameter(1),ObjectParameter(2)))>your<Else/><If(PlayerParameter(7))><If(<Sheet(BNpcName,PlayerParameter(7),6)/>)>her<Else/>his</If><Else/><If(PlayerParameter(5))>her<Else/>his</If></If></If> annoyance with <If(Equal(ObjectParameter(1),ObjectParameter(3)))><If(PlayerParameter(8))><SheetEn(ObjStr,2,PlayerParameter(8),1,1)/><Else/>you</If><Else/><If(PlayerParameter(8))><SheetEn(ObjStr,2,PlayerParameter(8),1,1)/><Else/>ObjectParameter(3)</If></If>.";

    let origin = Character::new("K'haldru Alaba", Gender::Female, true, true);
    let target = Character::new("Puruo Jelly", Gender::Male, true, false);
    let text = process_log_message(log_msg, &LogMessageAnswers::new(origin, target).unwrap());
    println!("{:?}", text);
    text.map(|_| ())
}

#[test]
fn can_parse_en_cry() -> Result<(), impl Error> {
    let log_msg = "<Clickable(<If(Equal(ObjectParameter(1),ObjectParameter(2)))>your<Else/><If(Equal(ObjectParameter(1),ObjectParameter(2)))>you<Else/><If(PlayerParameter(7))><SheetEn(ObjStr,2,PlayerParameter(7),1,1)/><Else/>ObjectParameter(2)</If></If>'s</If>)/> eyes brim over with tears.";

    let origin = Character::new("K'haldru Alaba", Gender::Female, true, true);
    let target = Character::new("Puruo Jelly", Gender::Male, true, false);
    let text = process_log_message(log_msg, &LogMessageAnswers::new(origin, target).unwrap());
    println!("{:?}", text);
    text.map(|_| ())
}

#[derive(Debug, Error)]
#[error("Failed to parse {name} ({error:?}) (original: {original})")]
struct MessageTestError {
    name: String,
    original: String,
    error: EmoteTextError,
}

#[test]
fn can_parse_all_emotes() -> Result<(), impl Error> {
    let data = [
        include_str!("../emote-221102-1.json"),
        include_str!("../emote-221102-2.json"),
        include_str!("../emote-221102-3.json"),
    ];
    let emotes: Vec<_> = data
        .into_iter()
        .map(|d| serde_json::from_str(d).expect("couldn't parse test json"))
        .flat_map(|v: serde_json::Value| {
            v["Results"]
                .as_array()
                .cloned()
                .expect("test json didn't contain Results array")
                .into_iter()
        })
        .collect();

    let char1 = Character::new("K'haldru Alaba", Gender::Female, true, true);
    let char2 = Character::new("Puruo Jelly", Gender::Male, true, false);
    let char3 = Character::new("Nanamo Ul Namo", Gender::Female, false, false);
    let answerses = [
        LogMessageAnswers::new(char1.clone(), char2.clone()),
        LogMessageAnswers::new(char2.clone(), char3.clone()),
        LogMessageAnswers::new(char3.clone(), char1.clone()),
    ]
    .map(|r| r.expect("couldn't set up answers"));

    emotes
        .into_iter()
        .map(|emote| {
            println!("{:?}", emote);
            let name = emote["Name"]
                .as_str()
                .expect("emote didn't have a name")
                .to_string();
            let messages = [
                &emote["LogMessageTargeted"]["Text_en"],
                &emote["LogMessageTargeted"]["Text_ja"],
                &emote["LogMessageUntargeted"]["Text_en"],
                &emote["LogMessageUntargeted"]["Text_ja"],
            ]
            .into_iter()
            .filter_map(|v| match v.as_str() {
                Some(s) => Some(s),
                None => {
                    eprintln!("skipping {} due to no messages", name);
                    None
                }
            })
            .collect::<Vec<_>>();

            for (i, message) in messages.iter().enumerate() {
                for answers in &answerses {
                    println!("testing {} {} {:?}", name, i, answers);
                    let text = process_log_message(message, answers);
                    match text {
                        Err(e) => {
                            return Err(MessageTestError {
                                name,
                                original: message.to_string(),
                                error: e,
                            });
                        }
                        Ok(t) => {
                            println!(
                                "{} ({:?}, {:?}): {}",
                                name,
                                answers.origin_character(),
                                answers.target_character(),
                                t
                            );
                        }
                    }
                }
            }
            Ok(())
        })
        .collect()
}