Parses songs the usual way

This commit is contained in:
Daniel Flanagan 2024-07-07 12:39:34 -05:00
parent f487d2f23d
commit 529536199e

View file

@ -59,16 +59,28 @@ impl FromStr for Song {
return Err(SongParseError::EmptyString); return Err(SongParseError::EmptyString);
} }
// TODO: some way to encode comments in a song struct so that if/when we
// serialize it back into a string format they are preserved?
// would probably best be done with an actual AST
static COMMENT_REGEX: OnceLock<Regex> = OnceLock::new();
let comment_re = COMMENT_REGEX.get_or_init(|| Regex::new(r"(?s)#[^\n]*").unwrap());
let s = comment_re.replace_all(s, "").into_owned();
dbg!(&s);
static HUNK_REGEX: OnceLock<Regex> = OnceLock::new(); static HUNK_REGEX: OnceLock<Regex> = OnceLock::new();
let re = HUNK_REGEX.get_or_init(|| Regex::new(r"\s*[\n\r]\s*[\n\r]\s*").unwrap()); let hunk_re = HUNK_REGEX.get_or_init(|| Regex::new(r"\s*[\n\r]\s*[\n\r]\s*").unwrap());
let mut hunks = VecDeque::new(); let mut hunks = VecDeque::new();
let mut last_end: usize = 0; let mut last_end: usize = 0;
for m in re.find_iter(s) { for m in hunk_re.find_iter(&s) {
hunks.push_back(&s[last_end..m.start()]); hunks.push_back(s[last_end..m.start()].trim());
last_end = m.end(); last_end = m.end();
} }
hunks.push_back(&s[last_end..s.len()]); hunks.push_back(s[last_end..s.len()].trim());
// process header // process header
let mut header_lines = hunks.pop_front().unwrap().lines().map(|s| s.trim()); let mut header_lines = hunks.pop_front().unwrap().lines().map(|s| s.trim());
@ -104,10 +116,16 @@ impl FromStr for Song {
// process verses // process verses
for hunk in hunks { for hunk in hunks {
let mut verse_contents = hunk; if hunk.starts_with('(') {
if hunk.ends_with(')') && !hunk.contains('\n') {
default_plan.push_back(hunk[1..hunk.len() - 1].to_owned());
continue;
}
}
let mut verse_contents: &str = hunk;
let end_i = hunk.find('\n').unwrap_or(hunk.len()); let end_i = hunk.find('\n').unwrap_or(hunk.len());
let verse_name: String = if let Some(i) = &hunk[0..end_i].find(':') { let verse_name: String = if let Some(i) = &hunk[0..end_i].find(':') {
verse_contents = &hunk[end_i + 1..]; verse_contents = &&hunk[end_i + 1..];
String::from(&hunk[0..*i]) String::from(&hunk[0..*i])
} else { } else {
format!("Generated Verse {}", verses.len() + 1).to_owned() format!("Generated Verse {}", verses.len() + 1).to_owned()
@ -134,6 +152,26 @@ mod test {
fn parses_simple_song() { fn parses_simple_song() {
let song: Song = r#"Song Title let song: Song = r#"Song Title
A verse"#
.parse()
.unwrap();
assert_eq!(song.name, "Song Title");
assert_eq!(
song.verses.get("Generated Verse 1"),
Some(&Verse {
content: "A verse".to_owned()
})
);
assert_eq!(song.verses.len(), 1);
assert_eq!(song.default_plan[0], "Generated Verse 1");
assert_eq!(song.default_plan.len(), 1);
}
#[test]
fn parses_song_with_comments() {
let song: Song = r#"Song Title
# this is a comment
A verse"# A verse"#
.parse() .parse()
.unwrap(); .unwrap();
@ -187,7 +225,7 @@ mod test {
v1: v1:
v1 v1content
v2: v2:
@ -199,24 +237,25 @@ mod test {
.parse() .parse()
.unwrap(); .unwrap();
assert_eq!(song.name, "Song Title"); assert_eq!(song.name, "Title");
assert_eq!( assert_eq!(
song.verses.get("Generated Verse 1"), song.verses.get("v1"),
Some(&Verse { Some(&Verse {
content: "A verse".to_owned() content: "v1content".to_owned()
}) })
); );
assert_eq!(song.verses.len(), 1); assert_eq!(song.verses.len(), 2);
assert_eq!(song.default_plan[0], "Generated Verse 1"); assert_eq!(song.default_plan[0], "v1");
assert_eq!(song.default_plan.len(), 1); assert_eq!(song.default_plan.len(), 4);
dbg!(&song.other_plans); dbg!(&song.other_plans);
assert_eq!( assert_eq!(
song.other_plans.get("another_plan"), song.default_plan,
Some(&VecDeque::from(vec![ VecDeque::from(vec![
"Generated Verse 1".to_owned(), "v1".to_owned(),
"Generated Verse 1".to_owned(), "v2".to_owned(),
"Generated Verse 1".to_owned() "v2".to_owned(),
])) "v1".to_owned(),
])
); );
} }
} }