livesplit_core/run/parser/
composite.rs

1//! The composite parser can be used, if the file type of the splits file is not
2//! known, which tries to figure out which splits file format is used and parses
3//! it with the parser for that format.
4//!
5//! # Examples
6//!
7//! Using the composite parser to parse a splits file of an unknown file format.
8//!
9//! ```no_run
10//! use livesplit_core::run::parser::composite;
11//! use std::fs;
12//! use std::path::Path;
13//!
14//! // Load the file.
15//! let path = Path::new("path/to/splits_file");
16//! let file = fs::read(path).expect("Failed reading the file.");
17//!
18//! // Actually parse the file. We also pass the path to load additional files from
19//! // the file system, like segment icons.
20//! let result = composite::parse(&file, Some(path));
21//! let parsed = result.expect("Not a valid splits file.");
22//!
23//! // Print out the detected file format.
24//! println!("Splits File Format: {}", parsed.kind);
25//!
26//! // Get out the Run object.
27//! let run = parsed.run;
28//! ```
29
30use super::{
31    face_split, flitter, livesplit, llanfair, llanfair_gered, portal2_live_timer, shit_split,
32    source_live_timer, speedrun_igt, splits_io, splitterino, splitterz, splitty,
33    time_split_tracker, urn, wsplit, TimerKind,
34};
35use crate::{platform::path::Path, Run};
36use core::{result::Result as StdResult, str};
37
38/// The Error type for splits files that couldn't be parsed by the Composite
39/// Parser.
40#[derive(Debug, snafu::Snafu)]
41#[snafu(context(suffix(false)))]
42pub enum Error {
43    /// No parser was able to parse the splits file.
44    NoParserParsedIt,
45}
46
47/// The Result type for the Composite Parser.
48pub type Result<T> = StdResult<T, Error>;
49
50/// A run parsed by the Composite Parser. This contains the Run itself and
51/// information about which parser parsed it.
52pub struct ParsedRun<'a> {
53    /// The parsed run.
54    pub run: Run,
55    /// The parser that parsed it.
56    pub kind: TimerKind<'a>,
57}
58
59impl ParsedRun<'_> {
60    /// Returns an owned version of the parsed run.
61    pub fn into_owned(self) -> ParsedRun<'static> {
62        ParsedRun {
63            run: self.run,
64            kind: self.kind.into_owned(),
65        }
66    }
67}
68
69const fn parsed(run: Run, kind: TimerKind<'_>) -> ParsedRun<'_> {
70    ParsedRun { run, kind }
71}
72
73/// Attempts to parse and fix a splits file by invoking the corresponding parser
74/// for the file format detected. Additionally you can provide the path of the
75/// splits file so additional files, like external images, can be loaded. If you
76/// are using livesplit-core in a server-like environment, set this to `None`.
77/// Only client-side applications should provide a path here. Unlike the normal
78/// parsing function, it also fixes problems in the Run, such as decreasing
79/// times and missing information.
80pub fn parse_and_fix<'source>(
81    source: &'source [u8],
82    load_files_path: Option<&Path>,
83) -> Result<ParsedRun<'source>> {
84    let mut run = parse(source, load_files_path)?;
85    run.run.fix_splits();
86    Ok(run)
87}
88
89/// Attempts to parse a splits file by invoking the corresponding parser for the
90/// file format detected. Additionally you can provide the path of the splits
91/// file so additional files, like external images, can be loaded. If you are
92/// using livesplit-core in a server-like environment, set this to `None`. Only
93/// client-side applications should provide a path here.
94pub fn parse<'source>(
95    source: &'source [u8],
96    load_files_path: Option<&Path>,
97) -> Result<ParsedRun<'source>> {
98    if let Ok(source) = simdutf8::basic::from_utf8(source) {
99        if let Ok(run) = livesplit::parse(source) {
100            return Ok(parsed(run, TimerKind::LiveSplit));
101        }
102
103        if let Ok(run) = wsplit::parse(source, load_files_path.is_some()) {
104            return Ok(parsed(run, TimerKind::WSplit));
105        }
106
107        if let Ok(run) = splitterz::parse(source, load_files_path.is_some()) {
108            return Ok(parsed(run, TimerKind::SplitterZ));
109        }
110
111        if let Ok(run) = shit_split::parse(source) {
112            return Ok(parsed(run, TimerKind::ShitSplit));
113        }
114
115        if let Ok(run) = splitty::parse(source) {
116            return Ok(parsed(run, TimerKind::Splitty));
117        }
118
119        if let Ok(run) = time_split_tracker::parse(source, load_files_path) {
120            return Ok(parsed(run, TimerKind::TimeSplitTracker));
121        }
122
123        if let Ok(run) = portal2_live_timer::parse(source) {
124            return Ok(parsed(run, TimerKind::Portal2LiveTimer));
125        }
126
127        if let Ok(run) = face_split::parse(source, load_files_path.is_some()) {
128            return Ok(parsed(run, TimerKind::FaceSplit));
129        }
130
131        // Should be parsed after LiveSplit's parser, as it also parses all
132        // LiveSplit files with the current implementation.
133        if let Ok(run) = llanfair_gered::parse(source) {
134            return Ok(parsed(run, TimerKind::LlanfairGered));
135        }
136
137        if let Ok((run, timer)) = splits_io::parse(source) {
138            return Ok(parsed(run, TimerKind::Generic(timer)));
139        }
140
141        // Splitterino, SourceLiveTimer, Flitter, and SpeedRunIGT need to be
142        // before Urn because of a false positive due to the nature of parsing
143        // JSON files.
144        if let Ok(run) = splitterino::parse(source) {
145            return Ok(parsed(run, TimerKind::Splitterino));
146        }
147
148        if let Ok(run) = flitter::parse(source) {
149            return Ok(parsed(run, TimerKind::Flitter));
150        }
151
152        if let Ok(run) = source_live_timer::parse(source) {
153            return Ok(parsed(run, TimerKind::SourceLiveTimer));
154        }
155
156        if let Ok(run) = speedrun_igt::parse(source) {
157            return Ok(parsed(run, TimerKind::SpeedRunIGT));
158        }
159
160        // Urn accepts entirely empty JSON files.
161        if let Ok(run) = urn::parse(source) {
162            return Ok(parsed(run, TimerKind::Urn));
163        }
164    }
165
166    if let Ok(run) = llanfair::parse(source) {
167        return Ok(parsed(run, TimerKind::Llanfair));
168    }
169
170    Err(Error::NoParserParsedIt)
171}