From 6ac1ef0ca50d9703a0b36f278862433a2dc12103 Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Sun, 15 Sep 2024 18:09:04 +0200 Subject: [PATCH] Support the new JSON based Flitter splits files (#841) Flitter got rewritten in Rust and the splits files are now JSON based. This adds support for parsing those files. The old file format is no longer supported, because Flitter itself also no longer supports it. This also changes how we parse time spans in general, making them more strict in what they accept. Previously ridiculous values like `100:100:100:100:100` got accepted. --- src/run/parser/flitter.rs | 134 ++++++++ src/run/parser/flitter/mod.rs | 78 ----- src/run/parser/flitter/s_expressions.rs | 419 ------------------------ src/settings/layout_background.rs | 2 +- src/timing/mod.rs | 2 + src/timing/time_span.rs | 182 +++++++--- src/util/ascii_char.rs | 27 +- tests/run_files/flitter.json | 81 +++++ tests/run_files/mod.rs | 3 +- tests/split_parsing.rs | 8 - 10 files changed, 359 insertions(+), 577 deletions(-) create mode 100644 src/run/parser/flitter.rs delete mode 100644 src/run/parser/flitter/mod.rs delete mode 100644 src/run/parser/flitter/s_expressions.rs create mode 100644 tests/run_files/flitter.json diff --git a/src/run/parser/flitter.rs b/src/run/parser/flitter.rs new file mode 100644 index 00000000..d9af8f10 --- /dev/null +++ b/src/run/parser/flitter.rs @@ -0,0 +1,134 @@ +//! Provides the parser for Flitter splits files. + +use crate::{ + platform::prelude::*, + timing::{parse_custom, CustomParser}, + Run, Segment, Time, +}; +use alloc::borrow::Cow; +use core::result::Result as StdResult; +use serde_derive::Deserialize; +use serde_json::Error as JsonError; +use snafu::ensure; + +/// The Error type for splits files that couldn't be parsed by the Flitter +/// Parser. +#[derive(Debug, snafu::Snafu)] +#[snafu(context(suffix(false)))] +pub enum Error { + /// Failed to parse JSON. + Json { + /// The underlying error. + #[cfg_attr(not(feature = "std"), snafu(source(false)))] + source: JsonError, + }, + /// The split names can't be empty. + SplitNamesEmpty, + /// The personal best can't be empty. + PersonalBestEmpty, + /// The golds can't be empty. + GoldsEmpty, + /// The split name count does not match the gold count. + GoldCountMismatch, + /// The split name count does not match the personal best split count. + PersonalBestCountMismatch, + /// The last split of the personal best can't be null. + LastSplitNull, +} + +/// The Result type for the Flitter Parser. +pub type Result = StdResult; + +#[derive(Deserialize)] +struct Splits<'a> { + #[serde(borrow)] + title: Cow<'a, str>, + #[serde(borrow)] + category: Cow<'a, str>, + attempts: u32, + // completed: u32, + #[serde(borrow)] + split_names: Vec>, + #[serde(borrow)] + golds: Vec>>, + #[serde(borrow)] + personal_best: PersonalBest<'a>, +} + +#[derive(Deserialize)] +struct Gold<'a> { + #[serde(borrow)] + duration: Cow<'a, str>, +} + +#[derive(Deserialize)] +struct PersonalBest<'a> { + // attempt: u32, + #[serde(borrow)] + splits: Vec>>, +} + +#[derive(Deserialize)] +struct Split<'a> { + #[serde(borrow)] + time: Cow<'a, str>, +} + +struct FlitterParser; + +impl CustomParser for FlitterParser { + const ASCII_ONLY: bool = true; + const ALLOW_NEGATIVE: bool = false; + const WITH_DAYS: bool = true; +} + +fn parse_time(real_time: &str) -> Option