rgchart 0.0.13

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

#[allow(unused)]
#[inline]
pub fn parse_key_value(raw_str: &str) -> (&str, &str) {
    let colon_pos = raw_str.find(':').unwrap();
    (raw_str[..colon_pos].trim(), raw_str[colon_pos + 1..].trim())
}

#[inline(always)]
pub fn trim_split_iter<'a, I>(split_iter: I, remove_whitespace: bool) -> Vec<&'a str>
where
    I: Iterator<Item = &'a str>,
{
    if remove_whitespace {
        split_iter.map(|s| s.trim()).filter(|s| !s.is_empty()).collect()
    } else {
        split_iter.map(|s| s.trim()).collect()
    }
}

pub fn remove_comments(string: &str, comment_begin: &str) -> String {
    let mut result = String::with_capacity(string.len());
    for line in string.lines() {
        let (content, _) = line.split_once(comment_begin).unwrap_or((line, ""));
        if content.chars().any(|c| !c.is_whitespace()) {
            result.push_str(content);
            result.push('\n');
        }
    }
    result.pop();
    result
}

#[inline]
pub fn add_key_value_template(template: &mut String, key: &str, sep: &str, value: &str, end: &str) {
    template.reserve(key.len() + value.len() + 3);
    template.push_str(key);
    template.push_str(sep);
    template.push_str(value);
    template.push_str(end);
}

#[allow(unused)]
#[inline]
pub fn add_key_value_template_escaped(template: &mut String, key: &str, sep: &str, value: &str, end: &str) {
    let has_esc = value.chars().any(|c| match c {
        '"' | '\\' | '\n' | '\r' | '\t' => true,
        c if c.is_control() => true,
        _ => false,
    });
    
    if has_esc {
        template.reserve(key.len() + sep.len() + end.len() + value.len() * 2 + 2);
        
        template.push_str(key);
        template.push_str(sep);
        template.push('"');
        
        for c in value.chars() {
            match c {
                '"' => template.push_str("\\\""),
                '\\' => template.push_str("\\\\"),
                '\n' => template.push_str("\\n"),
                '\r' => template.push_str("\\r"),
                '\t' => template.push_str("\\t"),
                c if c.is_control() => {
                    template.push_str(&format!("\\u{:04x}", c as u32));
                }
                c => template.push(c),
            }
        }
        
        template.push('"');
    } else {
        template.reserve(key.len() + sep.len() + end.len() + value.len());
        
        template.push_str(key);
        template.push_str(sep);
        template.push_str(value);
    }
    
    template.push_str(end);
}

pub trait StrDefaultExtension {
    fn or_default_empty(&self, default: &str) -> String;
}

impl StrDefaultExtension for str {
    fn or_default_empty(&self, default: &str) -> String {
        let trimmed = self.trim();
        if trimmed.is_empty() {
            default.to_string()
        } else {
            trimmed.to_string()
        }
    }
}

pub trait StrNumericDefaultExtension {
    fn or_default_empty_as<D: Default + ToString + FromStr>(&self, default: D) -> D;
}

impl StrNumericDefaultExtension for str {
    fn or_default_empty_as<D: Default + ToString + FromStr>(&self, default: D) -> D {
        let s = self.trim();
        if s.is_empty() {
            return default;
        }

        s.parse().unwrap_or_else(|_| {
            default.to_string().parse().unwrap_or_else(|_| {
                panic!(
                    "Failed to parse '{}' or default '{}' as requested numeric type",
                    s,
                    default.to_string()
                )
            })
        })
    }
}