livesplit_core/run/parser/
shit_split.rs

1//! Provides the parser for ShitSplit splits files.
2
3use crate::{timing, GameTime, Run, Segment, TimeSpan};
4use core::{num::ParseIntError, result::Result as StdResult};
5use snafu::{OptionExt, ResultExt};
6
7/// The Error type for splits files that couldn't be parsed by the ShitSplit
8/// Parser.
9#[derive(Debug, snafu::Snafu)]
10#[snafu(context(suffix(false)))]
11pub enum Error {
12    /// An empty splits file was provided.
13    Empty,
14    /// Expected the name of the category, but didn't find it.
15    ExpectedCategoryName,
16    /// Expected the attempt count, but didn't find it.
17    ExpectedAttemptCount,
18    /// Failed to parse the attempt count.
19    ParseAttemptCount {
20        /// The underlying error.
21        source: ParseIntError,
22    },
23    /// Expected the name of a world, but didn't find it.
24    ExpectedWorldName,
25    /// Expected the time of a world, but didn't find it.
26    ExpectedWorldTime,
27    /// Failed to parse the time of a world.
28    ParseWorldTime {
29        /// The underlying error.
30        source: timing::ParseError,
31    },
32}
33
34/// The Result type for the ShitSplit Parser.
35pub type Result<T> = StdResult<T, Error>;
36
37/// Attempts to parse a ShitSplit splits file.
38pub fn parse(source: &str) -> Result<Run> {
39    let mut lines = source.lines();
40
41    let line = lines.next().context(Empty)?;
42
43    let mut splits = line.split('|');
44    let category_name = splits.next().context(ExpectedCategoryName)?;
45
46    let mut run = Run::new();
47
48    run.set_category_name(
49        category_name
50            .strip_prefix('#')
51            .ok_or(Error::ExpectedCategoryName)?,
52    );
53    run.set_attempt_count(
54        splits
55            .next()
56            .context(ExpectedAttemptCount)?
57            .parse()
58            .context(ParseAttemptCount)?,
59    );
60    let mut total_time = TimeSpan::zero();
61    let mut next_line = lines.next();
62    while let Some(line) = next_line {
63        if line.is_empty() {
64            break;
65        }
66        let mut splits = line.split('|');
67        let world_name = splits.next().context(ExpectedWorldName)?;
68        total_time += splits
69            .next()
70            .context(ExpectedWorldTime)?
71            .parse()
72            .context(ParseWorldTime)?;
73        next_line = lines.next();
74        let mut has_acts = false;
75        while let Some(line) = next_line {
76            if let Some(segment_name) = line.strip_prefix('*') {
77                run.push_segment(Segment::new(segment_name));
78                has_acts = true;
79                next_line = lines.next();
80            } else {
81                next_line = Some(line);
82                break;
83            }
84        }
85        let time = GameTime(Some(total_time)).into();
86        if has_acts {
87            run.segments_mut()
88                .last_mut()
89                .unwrap()
90                .set_personal_best_split_time(time);
91        } else {
92            let mut segment = Segment::new(world_name);
93            segment.set_personal_best_split_time(time);
94            run.push_segment(segment);
95        }
96    }
97
98    Ok(run)
99}