livesplit_core/run/parser/
splitty.rs

1//! Provides the parser for Splitty splits files.
2
3use crate::{platform::prelude::*, Run, Segment, Time, TimeSpan, TimingMethod};
4use alloc::borrow::Cow;
5use core::result::Result as StdResult;
6use serde::Deserialize;
7use serde_json::Error as JsonError;
8
9/// The Error type for splits files that couldn't be parsed by the Splitty
10/// Parser.
11#[derive(Debug, snafu::Snafu)]
12#[snafu(context(suffix(false)))]
13pub enum Error {
14    /// Failed to parse JSON.
15    Json {
16        /// The underlying error.
17        #[cfg_attr(not(feature = "std"), snafu(source(false)))]
18        source: JsonError,
19    },
20}
21
22/// The Result type for the Splitty Parser.
23pub type Result<T> = StdResult<T, Error>;
24
25#[derive(Deserialize)]
26struct Splits<'a> {
27    #[serde(borrow)]
28    run_name: Cow<'a, str>,
29    start_delay: f64,
30    run_count: u32,
31    splits: Vec<Split<'a>>,
32    timer_type: u8,
33}
34
35#[derive(Deserialize)]
36struct Split<'a> {
37    #[serde(borrow)]
38    name: Cow<'a, str>,
39    pb_split: Option<f64>,
40    split_best: Option<f64>,
41}
42
43fn parse_time(milliseconds: Option<f64>, method: TimingMethod) -> Time {
44    let mut time = Time::new();
45
46    if let Some(milliseconds) = milliseconds {
47        time[method] = Some(TimeSpan::from_milliseconds(milliseconds));
48    }
49
50    time
51}
52
53/// Attempts to parse a Splitty splits file.
54pub fn parse(source: &str) -> Result<Run> {
55    let splits: Splits<'_> =
56        serde_json::from_str(source).map_err(|source| Error::Json { source })?;
57
58    let mut run = Run::new();
59
60    run.set_game_name(splits.run_name);
61    run.set_attempt_count(splits.run_count);
62    run.set_offset(TimeSpan::from_milliseconds(-splits.start_delay));
63
64    let method = if splits.timer_type == 0 {
65        TimingMethod::RealTime
66    } else {
67        TimingMethod::GameTime
68    };
69
70    run.segments_mut()
71        .extend(splits.splits.into_iter().map(|split| {
72            let mut segment = Segment::new(split.name);
73            segment.set_personal_best_split_time(parse_time(split.pb_split, method));
74            segment.set_best_segment_time(parse_time(split.split_best, method));
75
76            segment
77        }));
78
79    Ok(run)
80}