rgchart 0.0.12

A library for parsing and writing rhythm game charts.
Documentation
use std::str::FromStr;
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq)]
pub struct Metadata {
    pub title: String,
    
    pub title_unicode: String,
    
    pub artist: String,
    
    pub artist_unicode: String,
    
    pub creator: String,
    
    pub version: String,
    
    pub source: String,
    
    pub tags: Vec<String>,
    
    pub beatmap_id: i32,
    
    pub beatmap_set_id: i32,
}

impl Default for Metadata {
    fn default() -> Self {
        Self {
            title: String::new(),
            title_unicode: String::new(),
            artist: String::new(),
            artist_unicode: String::new(),
            creator: String::new(),
            version: String::new(),
            source: String::new(),
            tags: Vec::new(),
            beatmap_id: 0,
            beatmap_set_id: 0,
        }
    }
}

impl FromStr for Metadata {
    type Err = String;
    
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut metadata = Metadata::default();
        
        let mut key_values = HashMap::new();
        for line in s.lines() {
            let line = line.trim();
            if line.is_empty() || line.starts_with("//") {
                continue;
            }
            
            if let Some((key, value)) = line.split_once(':') {
                let key = key.trim();
                let value = value.trim();
                key_values.insert(key.to_string(), value.to_string());
            }
        }
        
        if let Some(value) = key_values.get("Title") {
            metadata.title = value.clone();
        }
        
        if let Some(value) = key_values.get("TitleUnicode") {
            metadata.title_unicode = value.clone();
        }
        
        if let Some(value) = key_values.get("Artist") {
            metadata.artist = value.clone();
        }
        
        if let Some(value) = key_values.get("ArtistUnicode") {
            metadata.artist_unicode = value.clone();
        }
        
        if let Some(value) = key_values.get("Creator") {
            metadata.creator = value.clone();
        }
        
        if let Some(value) = key_values.get("Version") {
            metadata.version = value.clone();
        }
        
        if let Some(value) = key_values.get("Source") {
            metadata.source = value.clone();
        }
        
        if let Some(value) = key_values.get("Tags") {
            metadata.tags = value
                .split_whitespace()
                .map(|tag| tag.to_string())
                .collect();
        }
        
        if let Some(value) = key_values.get("BeatmapID") {
            metadata.beatmap_id = value.parse::<i32>()
                .map_err(|_| format!("Invalid BeatmapID value: {}", value))?;
        }
        
        if let Some(value) = key_values.get("BeatmapSetID") {
            metadata.beatmap_set_id = value.parse::<i32>()
                .map_err(|_| format!("Invalid BeatmapSetID value: {}", value))?;
        }
        
        Ok(metadata)
    }
}

impl Metadata {
    pub fn new(
        title: String,
        title_unicode: String,
        artist: String,
        artist_unicode: String,
        creator: String,
        version: String,
        source: String,
        tags: Vec<String>,
        beatmap_id: i32,
        beatmap_set_id: i32,
    ) -> Self {
        Self {
            title,
            title_unicode,
            artist,
            artist_unicode,
            creator,
            version,
            source,
            tags,
            beatmap_id,
            beatmap_set_id,
        }
    }
    
    pub fn display_title(&self) -> &str {
        if !self.title_unicode.is_empty() {
            &self.title_unicode
        } else {
            &self.title
        }
    }
    
    pub fn display_artist(&self) -> &str {
        if !self.artist_unicode.is_empty() {
            &self.artist_unicode
        } else {
            &self.artist
        }
    }
    
    pub fn add_tag(&mut self, tag: String) {
        if !self.tags.contains(&tag) {
            self.tags.push(tag);
        }
    }
    
    pub fn remove_tag(&mut self, tag: &str) {
        self.tags.retain(|t| t != tag);
    }
    
    pub fn has_tag(&self, tag: &str) -> bool {
        self.tags.iter().any(|t| t.eq_ignore_ascii_case(tag))
    }
    
    pub fn tags_string(&self) -> String {
        self.tags.join(" ")
    }
    
    pub fn set_tags_from_string(&mut self, tags_str: &str) {
        self.tags = tags_str
            .split_whitespace()
            .map(|tag| tag.to_string())
            .collect();
    }
    
    pub fn is_submitted(&self) -> bool {
        self.beatmap_id > 0 && self.beatmap_set_id > 0
    }
    
    pub fn has_unicode(&self) -> bool {
        !self.title_unicode.is_empty() || !self.artist_unicode.is_empty()
    }
    
    pub fn to_str(&self) -> String {
        let mut lines = Vec::new();
        
        lines.push(format!("Title: {}", self.title));
        lines.push(format!("TitleUnicode: {}", self.title_unicode));
        lines.push(format!("Artist: {}", self.artist));
        lines.push(format!("ArtistUnicode: {}", self.artist_unicode));
        lines.push(format!("Creator: {}", self.creator));
        lines.push(format!("Version: {}", self.version));
        lines.push(format!("Source: {}", self.source));
        lines.push(format!("Tags: {}", self.tags_string()));
        lines.push(format!("BeatmapID: {}", self.beatmap_id));
        lines.push(format!("BeatmapSetID: {}", self.beatmap_set_id));
        
        lines.join("\n")
    }
}