livesplit_core/run/parser/
urn.rs

1//! Provides the parser for Urn splits files.
2
3use crate::{platform::prelude::*, Run, Segment, Time, TimeSpan};
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 Urn
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 Urn Parser.
23pub type Result<T> = StdResult<T, Error>;
24
25#[derive(Deserialize)]
26struct Splits<'a> {
27    #[serde(borrow)]
28    title: Option<Cow<'a, str>>,
29    attempt_count: Option<u32>,
30    start_delay: Option<TimeSpan>,
31    splits: Option<Vec<Split<'a>>>,
32}
33
34#[derive(Deserialize)]
35struct Split<'a> {
36    #[serde(borrow)]
37    title: Option<Cow<'a, str>>,
38    time: Option<TimeSpan>,
39    best_time: Option<TimeSpan>,
40    best_segment: Option<TimeSpan>,
41}
42
43fn parse_time(real_time: TimeSpan) -> Time {
44    // Empty Time is stored as zero
45    let real_time = if real_time != TimeSpan::zero() {
46        Some(real_time)
47    } else {
48        None
49    };
50
51    Time::new().with_real_time(real_time)
52}
53
54/// Attempts to parse an Urn splits file.
55pub fn parse(source: &str) -> Result<Run> {
56    let splits: Splits<'_> =
57        serde_json::from_str(source).map_err(|source| Error::Json { source })?;
58
59    let mut run = Run::new();
60
61    if let Some(title) = splits.title {
62        run.set_category_name(title);
63    }
64    if let Some(attempt_count) = splits.attempt_count {
65        run.set_attempt_count(attempt_count);
66    }
67    if let Some(start_delay) = splits.start_delay {
68        run.set_offset(-start_delay);
69    }
70
71    // Best Split Times can be used for the Segment History Every single best
72    // split time should be included as its own run, since the best split times
73    // could be apart from each other less than the best segments, so we have to
74    // assume they are from different runs.
75    let mut attempt_history_index = 1;
76
77    if let Some(splits) = splits.splits {
78        for split in splits {
79            let mut segment = Segment::new(split.title.unwrap_or_default());
80            if let Some(time) = split.time {
81                segment.set_personal_best_split_time(parse_time(time));
82            }
83            if let Some(best_segment) = split.best_segment {
84                segment.set_best_segment_time(parse_time(best_segment));
85            }
86
87            if let Some(best_time) = split.best_time {
88                let best_split_time = parse_time(best_time);
89                if best_split_time.real_time.is_some() {
90                    run.add_attempt_with_index(
91                        Time::default(),
92                        attempt_history_index,
93                        None,
94                        None,
95                        None,
96                    );
97
98                    // Insert a new run that skips to the current split
99                    for already_inserted_segment in run.segments_mut() {
100                        already_inserted_segment
101                            .segment_history_mut()
102                            .insert(attempt_history_index, Time::default());
103                    }
104
105                    segment
106                        .segment_history_mut()
107                        .insert(attempt_history_index, best_split_time);
108
109                    attempt_history_index += 1;
110                }
111            }
112
113            run.push_segment(segment);
114        }
115    }
116
117    Ok(run)
118}