vmf_vmt 0.1.0

Valve Map Format and Valve Material parsing/building library.
Documentation
use nom::{
    branch::alt, bytes::complete::is_not, character::complete::{multispace0, none_of, one_of}, combinator::recognize, error::ParseError, multi::{many0, many1}, sequence::{delimited, separated_pair}, IResult
};

#[derive(PartialEq,Debug)]
pub enum Valve {
    Class {
        class_name: String,
        data: Vec<Valve>,
    },
    PropertyValue(String, String),
}

pub type VmfVmt = Vec<Valve>;

pub fn build_vmf_vmt(data: VmfVmt) -> String {
    data.into_iter().map(|class| build_valve(0,class)).collect::<Vec<String>>().concat()
}

pub fn build_valve(indent: usize, data: Valve) -> String {
    match data {
        Valve::Class { class_name, data } => {
            let mut out = String::new();
            for _ in 0..indent {out.push('\t')};
            out.push_str(&class_name);
            out.push_str("\n");
            for _ in 0..indent {out.push('\t')};
            out.push_str("{\n");
            for subvalue in data {
                out.push_str(&build_valve(indent+1,subvalue));
            }
            for _ in 0..indent {out.push('\t')};
            out.push_str("}\n");
            out
        },
        Valve::PropertyValue(property, value) => {
            let mut out = String::new();
            for _ in 0..indent {out.push('\t')};
            out.push_str("\"");
            out.push_str(&property);
            out.push_str("\" \"");
            out.push_str(&value);
            out.push_str("\"\n");
            out
        },
    }
}

pub fn parse_vmf_vmt(input: &str) -> IResult<&str,VmfVmt> {
    many1(ws(parse_class))(input)
}

fn parse_valve(input: &str) -> IResult<&str, Valve> {
    alt((parse_class, parse_property_value))(input)
}

fn parse_class(input: &str) -> IResult<&str, Valve> {
    let (input, class_name) = parse_unquoted(input)?;
    let (input, data) = delimited(
        ws(one_of("{")),
        many0(ws(parse_valve)),
        ws(one_of("}")),
    )(input)?;
    Ok((input, Valve::Class { class_name, data }))
}

fn parse_property_value(input: &str) -> IResult<&str, Valve> {
    let (input, (property, value)) =
        separated_pair(parse_string, multispace0, parse_string)(input)?;
    Ok((input, Valve::PropertyValue(property, value)))
}

fn parse_string(input: &str) -> IResult<&str, String> {
    alt((parse_unquoted, parse_quoted))(input)
}

fn parse_unquoted(input: &str) -> IResult<&str, String> {
    let (input, string) = is_not(" \t\r\n\"{{}}")(input)?;
    Ok((input, string.to_string()))
}

fn parse_quoted(input: &str) -> IResult<&str, String> {
    let (input, string) = delimited(
        recognize(one_of("\"")),
        recognize(many0(none_of("\""))),
        recognize(one_of("\"")),
    )(input)?;
    Ok((input, string.to_string()))
}

fn ws<'a, F: 'a, O, E: ParseError<&'a str>>(
    inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
    F: Fn(&'a str) -> IResult<&'a str, O, E>,
{
    delimited(multispace0, inner, multispace0)
}