livesplit_core/run/parser/
splitterz.rs1use crate::{timing, RealTime, Run, Segment, TimeSpan};
4use alloc::borrow::Cow;
5use core::{num::ParseIntError, result::Result as StdResult};
6use snafu::ResultExt;
7
8#[derive(Debug, snafu::Snafu)]
11#[snafu(context(suffix(false)))]
12pub enum Error {
13 Empty,
15 ExpectedCategoryName,
17 ExpectedAttemptCount,
19 ParseAttemptCount {
21 source: ParseIntError,
23 },
24 ExpectedSegmentName,
26 ExpectedSplitTime,
28 ParseSplitTime {
30 source: timing::ParseError,
32 },
33 ExpectedBestSegment,
35 ParseBestSegment {
37 source: timing::ParseError,
39 },
40}
41
42pub type Result<T> = StdResult<T, Error>;
44
45fn unescape(text: &str) -> Cow<'_, str> {
46 if text.contains('‡') {
47 text.replace('‡', ",").into()
48 } else {
49 text.into()
50 }
51}
52
53pub fn parse(source: &str, #[allow(unused)] load_icons: bool) -> Result<Run> {
59 let mut run = Run::new();
60
61 #[cfg(feature = "std")]
62 let mut icon_buf = Vec::new();
63
64 let mut lines = source.lines();
65 let line = lines.next().ok_or(Error::Empty)?;
66 let mut splits = line.split(',');
67 run.set_category_name(unescape(splits.next().ok_or(Error::ExpectedCategoryName)?));
68 run.set_attempt_count(
69 splits
70 .next()
71 .ok_or(Error::ExpectedAttemptCount)?
72 .parse()
73 .context(ParseAttemptCount)?,
74 );
75
76 for line in &mut lines {
77 if line.is_empty() {
78 break;
79 }
80
81 let mut splits = line.split(',');
82
83 let mut segment = Segment::new(unescape(splits.next().ok_or(Error::ExpectedSegmentName)?));
84
85 let time: TimeSpan = splits
86 .next()
87 .ok_or(Error::ExpectedSplitTime)?
88 .parse()
89 .context(ParseSplitTime)?;
90 if time != TimeSpan::zero() {
91 segment.set_personal_best_split_time(RealTime(Some(time)).into());
92 }
93
94 let time: TimeSpan = splits
95 .next()
96 .ok_or(Error::ExpectedBestSegment)?
97 .parse()
98 .context(ParseBestSegment)?;
99 if time != TimeSpan::zero() {
100 segment.set_best_segment_time(RealTime(Some(time)).into());
101 }
102
103 #[cfg(feature = "std")]
104 if load_icons {
105 if let Some(icon_path) = splits.next() {
106 if !icon_path.is_empty() {
107 if let Ok(image) = crate::settings::Image::from_file(
108 unescape(icon_path).as_ref(),
109 &mut icon_buf,
110 ) {
111 segment.set_icon(image);
112 }
113 }
114 }
115 }
116
117 run.push_segment(segment);
118 }
119
120 for line in lines {
121 if let Some(counter) = line.split(',').next() {
122 run.metadata_mut()
123 .custom_variable_mut(unescape(counter))
124 .permanent()
125 .set_value("0");
126 }
127 }
131
132 Ok(run)
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn counters() {
141 const RUN: &str = r#"Run Title:,1
142SegmentName,0:00:00.00,0.00
143SegmentName,0:00:00.00,0.00
144SegmentName,0:00:00.00,0.00
145
146Counter,1,True
147Counter2,1,True
148"#;
149
150 let run = parse(RUN, false).unwrap();
151 assert_eq!(run.len(), 3);
152 assert_eq!(run.metadata.custom_variable_value("Counter"), Some("0"));
153 assert_eq!(run.metadata.custom_variable_value("Counter2"), Some("0"));
154 }
155}