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}