1use crate::structs::{Header, Line, Note};
2use regex::Regex;
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6error_chain! {
7 errors {
8 #[doc="duplicate header tag was found"]
9 DuplicateHeader(line: u32, tag: &'static str) {
10 description("duplicate header")
11 display("additional {} tag found in line: {}", line, tag)
12 }
13 #[doc="an essential header is missing"]
14 MissingEssential {
15 description("essential header is missing")
16 }
17
18 #[doc="value could not be parsed"]
19 ValueError(line: u32, field: &'static str) {
20 description("could not parse value")
21 display("could not parse {} in line: {}", line, field)
22 }
23 #[doc="an unknown note type was found"]
24 UnknownNoteType(line: u32) {
25 description("unknown note type")
26 display("unknown note type in line: {}", line)
27 }
28 #[doc="could not parse the line at all"]
29 ParserFailure(line: u32) {
30 description("could not parse line")
31 display("could not parse line: {}", line)
32 }
33 #[doc="song is missing the end terminator"]
34 MissingEndIndicator {
35 description("missing end indicator")
36 }
37 #[doc="song file uses a feature that is not implemented"]
38 NotImplemented(line: u32, feature: &'static str) {
39 description("not implemented")
40 display("the feature {} in line {} is not implemented", line, feature)
41 }
42 }
43}
44
45pub fn parse_txt_header_str(txt_str: &str) -> Result<Header> {
51 let mut opt_title = None;
52 let mut opt_artist = None;
53 let mut opt_bpm = None;
54 let mut opt_audio_path = None;
55
56 let mut opt_gap = None;
57 let mut opt_cover_path = None;
58 let mut opt_background_path = None;
59 let mut opt_video_path = None;
60 let mut opt_video_gap = None;
61 let mut opt_genre = None;
62 let mut opt_edition = None;
63 let mut opt_language = None;
64 let mut opt_year = None;
65 let mut opt_relative = None;
66 let mut opt_unknown: Option<HashMap<String, String>> = None;
67
68 lazy_static! {
69 static ref RE: Regex = Regex::new(r"#([A-Z3a-z]*):(.*)").unwrap();
70 }
71
72 for (line, line_count) in txt_str.lines().zip(1..) {
73 let cap = match RE.captures(line) {
74 Some(x) => x,
75 None => break,
76 };
77 let key = cap.get(1).unwrap().as_str();
78 let value = cap.get(2).unwrap().as_str();
79
80 if value == "" {
81 continue;
83 }
84
85 match key {
86 "TITLE" => {
87 if opt_title.is_none() {
88 opt_title = Some(String::from(value));
89 } else {
90 bail!(ErrorKind::DuplicateHeader(line_count, "TITLE"));
91 }
92 }
93 "ARTIST" => {
94 if opt_artist.is_none() {
95 opt_artist = Some(String::from(value));
96 } else {
97 bail!(ErrorKind::DuplicateHeader(line_count, "ARTIST"));
98 }
99 }
100 "MP3" => {
101 if opt_audio_path.is_none() {
102 opt_audio_path = Some(PathBuf::from(value));
103 } else {
104 bail!(ErrorKind::DuplicateHeader(line_count, "MP3"));
105 }
106 }
107 "BPM" => {
108 if opt_bpm.is_none() {
109 opt_bpm = match value.replace(",", ".").parse() {
110 Ok(x) => Some(x),
111 Err(_) => {
112 bail!(ErrorKind::ValueError(line_count, "BPM"));
113 }
114 };
115 } else {
116 bail!(ErrorKind::DuplicateHeader(line_count, "BPM"));
117 }
118 }
119
120 "GAP" => {
122 if opt_gap.is_none() {
123 opt_gap = match value.replace(",", ".").parse() {
124 Ok(x) => Some(x),
125 Err(_) => {
126 bail!(ErrorKind::ValueError(line_count, "GAP"));
127 }
128 };
129 } else {
130 bail!(ErrorKind::DuplicateHeader(line_count, "GAP"));
131 }
132 }
133 "COVER" => {
134 if opt_cover_path.is_none() {
135 opt_cover_path = Some(PathBuf::from(value));
136 } else {
137 bail!(ErrorKind::DuplicateHeader(line_count, "COVER"));
138 }
139 }
140 "BACKGROUND" => {
141 if opt_background_path.is_none() {
142 opt_background_path = Some(PathBuf::from(value));
143 } else {
144 bail!(ErrorKind::DuplicateHeader(line_count, "BACKGROUND"));
145 }
146 }
147 "VIDEO" => {
148 if opt_video_path.is_none() {
149 opt_video_path = Some(PathBuf::from(value));
150 } else {
151 bail!(ErrorKind::DuplicateHeader(line_count, "VIDEO"));
152 }
153 }
154 "VIDEOGAP" => {
155 if opt_video_gap.is_none() {
156 opt_video_gap = match value.replace(",", ".").parse() {
157 Ok(x) => Some(x),
158 Err(_) => {
159 bail!(ErrorKind::ValueError(line_count, "VIDEOGAP"));
160 }
161 };
162 } else {
163 bail!(ErrorKind::DuplicateHeader(line_count, "VIDEOGAP"));
164 }
165 }
166 "GENRE" => {
167 if opt_genre.is_none() {
168 opt_genre = Some(String::from(value));
169 } else {
170 bail!(ErrorKind::DuplicateHeader(line_count, "GENRE"));
171 }
172 }
173 "EDITION" => {
174 if opt_edition.is_none() {
175 opt_edition = Some(String::from(value));
176 } else {
177 bail!(ErrorKind::DuplicateHeader(line_count, "EDITION"));
178 }
179 }
180 "LANGUAGE" => {
181 if opt_language.is_none() {
182 opt_language = Some(String::from(value));
183 } else {
184 bail!(ErrorKind::DuplicateHeader(line_count, "LANGUAGE"));
185 }
186 }
187 "YEAR" => {
188 if opt_year.is_none() {
189 opt_year = match value.parse() {
190 Ok(x) => Some(x),
191 Err(_) => {
192 bail!(ErrorKind::ValueError(line_count, "YEAR"));
193 }
194 };
195 } else {
196 bail!(ErrorKind::DuplicateHeader(line_count, "YEAR"));
197 }
198 }
199 "RELATIVE" => {
201 if opt_relative.is_none() {
202 opt_relative = match value {
203 "YES" | "yes" => Some(true),
204 "NO" | "no" => Some(false),
205 _ => {
206 bail!(ErrorKind::ValueError(line_count, "RELATIVE"));
207 }
208 }
209 } else {
210 bail!(ErrorKind::DuplicateHeader(line_count, "RELATIVE"));
211 }
212 }
213 k => {
215 opt_unknown = match opt_unknown {
216 Some(mut x) => {
217 if !x.contains_key(k) {
218 x.insert(String::from(k), String::from(value));
219 Some(x)
220 } else {
221 bail!(ErrorKind::DuplicateHeader(line_count, "UNKNOWN"));
222 }
223 }
224 None => {
225 let mut unknown = HashMap::new();
226 unknown.insert(String::from(k), String::from(value));
227 Some(unknown)
228 }
229 };
230 }
231 };
232 }
233
234 if let (Some(title), Some(artist), Some(bpm), Some(audio_path)) =
236 (opt_title, opt_artist, opt_bpm, opt_audio_path)
237 {
238 let header = Header {
239 title,
240 artist,
241 bpm,
242 audio_path,
243
244 gap: opt_gap,
245 cover_path: opt_cover_path,
246 background_path: opt_background_path,
247 video_path: opt_video_path,
248 video_gap: opt_video_gap,
249 genre: opt_genre,
250 edition: opt_edition,
251 language: opt_language,
252 year: opt_year,
253 relative: opt_relative,
254 unknown: opt_unknown,
255 };
256 Ok(header)
258 } else {
259 bail!(ErrorKind::MissingEssential)
261 }
262}
263
264pub fn parse_txt_lines_str(txt_str: &str) -> Result<Vec<Line>> {
270 lazy_static! {
271 static ref LINE_RE: Regex = Regex::new("^-\\s?(-?[0-9]+)\\s*$").unwrap();
272 static ref LREL_RE: Regex = Regex::new("^-\\s?(-?[0-9]+)\\s+(-?[0-9]+)").unwrap();
273 static ref NOTE_RE: Regex =
274 Regex::new("^(.)\\s*(-?[0-9]+)\\s+(-?[0-9]+)\\s+(-?[0-9]+)\\s?(.*)").unwrap();
275 static ref DUET_RE: Regex = Regex::new("^P\\s?(-?[0-9]+)").unwrap();
276 }
277
278 let mut lines_vec = Vec::new();
279 let mut current_line = Line {
280 start: 0,
281 rel: None,
282 notes: Vec::new(),
283 };
284
285 let mut found_end_indicator = false;
286 for (line, line_count) in txt_str.lines().zip(1..) {
287 let first_char = match line.chars().nth(0) {
288 Some(x) => x,
289 None => bail!(ErrorKind::ParserFailure(line_count)),
290 };
291
292 if first_char == '#' {
294 continue;
295 }
296
297 if first_char == 'B' {
299 bail!(ErrorKind::NotImplemented(line_count, "variable bpm"));
300 }
301
302 if first_char == 'E' {
304 lines_vec.push(current_line);
305 found_end_indicator = true;
306 break;
307 }
308
309 if NOTE_RE.is_match(line) {
311 let cap = NOTE_RE.captures(line).unwrap();
312
313 let note_start = match cap.get(2).unwrap().as_str().parse() {
314 Ok(x) => x,
315 Err(_) => {
316 bail!(ErrorKind::ValueError(line_count, "note start"));
317 }
318 };
319 let note_duration = match cap.get(3).unwrap().as_str().parse() {
320 Ok(x) => {
321 if x >= 0 {
322 x
323 } else {
324 bail!(ErrorKind::ValueError(line_count, "note duration"));
325 }
326 }
327 Err(_) => {
328 bail!(ErrorKind::ValueError(line_count, "note duration"));
329 }
330 };
331 let note_pitch = match cap.get(4).unwrap().as_str().parse() {
332 Ok(x) => x,
333 Err(_) => {
334 bail!(ErrorKind::ValueError(line_count, "note pitch"));
335 }
336 };
337 let note_text = cap.get(5).unwrap().as_str();
338
339 let note = match cap.get(1).unwrap().as_str() {
340 ":" => Note::Regular {
341 start: note_start,
342 duration: note_duration,
343 pitch: note_pitch,
344 text: String::from(note_text),
345 },
346 "*" => Note::Golden {
347 start: note_start,
348 duration: note_duration,
349 pitch: note_pitch,
350 text: String::from(note_text),
351 },
352 "F" => Note::Freestyle {
353 start: note_start,
354 duration: note_duration,
355 pitch: note_pitch,
356 text: String::from(note_text),
357 },
358 _ => bail!(ErrorKind::UnknownNoteType(line_count)),
359 };
360
361 current_line.notes.push(note);
362 continue;
363 }
364
365 if LINE_RE.is_match(line) {
367 lines_vec.push(current_line);
369 let cap = LINE_RE.captures(line).unwrap();
370 let line_start = match cap.get(1).unwrap().as_str().parse() {
371 Ok(x) => x,
372 Err(_) => {
373 bail!(ErrorKind::ValueError(line_count, "line start"));
374 }
375 };
376 current_line = Line {
377 start: line_start,
378 rel: None,
379 notes: Vec::new(),
380 };
381 continue;
382 }
383
384 if LREL_RE.is_match(line) {
386 lines_vec.push(current_line);
388 let cap = LREL_RE.captures(line).unwrap();
389 let line_start = match cap.get(1).unwrap().as_str().parse() {
390 Ok(x) => x,
391 Err(_) => {
392 bail!(ErrorKind::ValueError(line_count, "line start"));
393 }
394 };
395 let line_rel = match cap.get(2).unwrap().as_str().parse() {
396 Ok(x) => x,
397 Err(_) => {
398 bail!(ErrorKind::ValueError(line_count, "line rel"));
399 }
400 };
401 current_line = Line {
402 start: line_start,
403 rel: Some(line_rel),
404 notes: Vec::new(),
405 };
406 continue;
407 }
408
409 if DUET_RE.is_match(line) {
410 let cap = DUET_RE.captures(line).unwrap();
411 let note = match cap.get(1).unwrap().as_str().parse() {
412 Ok(x) => {
413 if x >= 1 && x <= 3 {
414 Note::PlayerChange { player: x }
415 } else {
416 bail!(ErrorKind::ValueError(line_count, "player change"));
417 }
418 }
419 Err(_) => {
420 bail!(ErrorKind::ValueError(line_count, "player change"));
421 }
422 };
423 current_line.notes.push(note);
424 continue;
425 } else {
426 bail!(ErrorKind::ParserFailure(line_count));
428 }
429 }
430 if found_end_indicator {
431 Ok(lines_vec)
432 } else {
433 bail!(ErrorKind::MissingEndIndicator);
434 }
435}