chordlib 0.8.1

Work with chord-and-lyrics songs: parse, transform, and render them to multiple formats
Documentation
use crate::error::Error;
use crate::types::Part;

pub struct PartIterator<'a> {
    content_chord: Option<&'a str>,
    content_text: Option<&'a str>,
}

impl<'a> PartIterator<'a> {
    pub fn new(content: &'a str) -> Self {
        let vec = content.split('\n').collect::<Vec<&str>>();
        if vec.len() > 1 {
            Self {
                content_chord: Some(vec[0]),
                content_text: Some(vec[1]),
            }
        } else if vec.len() == 1 {
            if vec[0].contains("[ch]") {
                Self {
                    content_chord: Some(vec[0]),
                    content_text: None,
                }
            } else {
                Self {
                    content_chord: None,
                    content_text: Some(vec[0]),
                }
            }
        } else {
            Self {
                content_chord: None,
                content_text: None,
            }
        }
    }
}

impl<'a> Iterator for PartIterator<'a> {
    type Item = Result<Part, Error>;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(mut content_chord) = self.content_chord {
            let old_content_chord = content_chord;
            let start = content_chord.find("[ch]")?;
            if start > 0
                && let Some(content_text) = self.content_text
            {
                let byte_index = content_text
                    .chars()
                    .take(start)
                    .map(|b| b.len_utf8())
                    .sum::<usize>();
                let text = &content_text[..byte_index];
                self.content_text = Some(&content_text[byte_index..]);
                self.content_chord = Some(&content_chord[start..]);
                return Some(("", text).try_into());
            }
            content_chord = &content_chord[start + 4..];
            let end = content_chord.find("[/ch]")?;
            let chord = &content_chord[..end];
            let next_start = content_chord.find("[ch]").unwrap_or(content_chord.len());
            content_chord = &content_chord[next_start..];
            self.content_chord = Some(content_chord);
            if let Some(content_text) = self.content_text {
                let text = if !content_chord.is_empty() {
                    let char_index =
                        old_content_chord.chars().count() - content_chord.chars().count() - 9;
                    let byte_index = content_text
                        .chars()
                        .take(char_index)
                        .map(|b| b.len_utf8())
                        .sum::<usize>();
                    let text = &content_text[..byte_index];
                    self.content_text = Some(&content_text[byte_index..]);
                    text
                } else {
                    let text = content_text;
                    self.content_text = None;
                    text
                };
                Some((chord, text).try_into())
            } else {
                Some((chord, "").try_into())
            }
        } else {
            Some(("", self.content_text.take()?).try_into())
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn part_iterator() {
        let inputs = [
            "",
            "[ch]A[/ch]",
            "[ch]A[/ch] [ch]B[/ch]",
            "[ch]A[/ch]    [ch]B[/ch]\nHeyƶ you",
            "[ch]A[/ch]          [ch]B[/ch]\nHey",
            "    [ch]A[/ch]\nHey you",
            "Hello World",
        ];
        let outputs = [
            vec![("", "").try_into().unwrap()],
            vec![("A", "").try_into().unwrap()],
            vec![("A", "").try_into().unwrap(), ("B", "").try_into().unwrap()],
            vec![
                ("A", "Heyƶ ").try_into().unwrap(),
                ("B", "you").try_into().unwrap(),
            ],
            vec![
                ("A", "Hey").try_into().unwrap(),
                ("B", "").try_into().unwrap(),
            ],
            vec![
                ("", "Hey ").try_into().unwrap(),
                ("A", "you").try_into().unwrap(),
            ],
            vec![("", "Hello World").try_into().unwrap()],
        ];
        for (input, output) in inputs.iter().zip(outputs.iter()) {
            assert_eq!(
                &PartIterator::new(input)
                    .collect::<Result<Vec<Part>, Error>>()
                    .unwrap(),
                output
            );
        }
    }
}