flatiron 1.0.5

A parser and HTML renderer for the Textile markup language
Documentation
use crate::structs::Attributes;
use nom::{
    branch::alt,
    bytes::complete::{escaped_transform, tag},
    character::complete::{char, none_of},
    combinator::{fail, opt, value},
    sequence::{delimited, preceded, tuple},
    IResult,
};

pub fn attributes(mut input: &str) -> IResult<&str, Attributes> {
    let mut attributes = Attributes {
        class: None,
        id: None,
        style: None,
        language: None,
    };
    loop {
        if let Ok((rest, (class, id))) = class_and_id(input) {
            if attributes.class.is_none() {
                attributes.class = class;
            } else if class.is_some() {
                return Ok((input, attributes));
            }
            if attributes.id.is_none() {
                attributes.id = id;
            } else if id.is_some() {
                return Ok((input, attributes));
            }
            input = rest;
        } else if let Ok((rest, style)) = style(input) {
            if attributes.style.is_none() {
                attributes.style = Some(style);
            } else {
                return Ok((input, attributes));
            }
            input = rest;
        } else if let Ok((rest, language)) = language(input) {
            if attributes.language.is_none() {
                attributes.language = Some(language);
            } else {
                return Ok((input, attributes));
            }
            input = rest;
        } else {
            break;
        }
    }
    if attributes.class.is_some()
        || attributes.id.is_some()
        || attributes.style.is_some()
        || attributes.language.is_some()
    {
        Ok((input, attributes))
    } else {
        fail(input)
    }
}

fn class_and_id(
    input: &str,
) -> IResult<&str, (Option<String>, Option<String>)> {
    let (rest, (opt_class, opt_id)) = delimited(
        char('('),
        tuple((opt(class_name), opt(preceded(char('#'), class_name)))),
        char(')'),
    )(input)?;
    if opt_class.is_some() || opt_id.is_some() {
        Ok((rest, (opt_class, opt_id)))
    } else {
        fail(rest)
    }
}

fn class_name(input: &str) -> IResult<&str, String> {
    escaped_transform(
        none_of("\\#()"),
        '\\',
        alt((
            value("\\", tag("\\")),
            value("(", tag("(")),
            value(")", tag(")")),
            value("#", tag("#")),
        )),
    )(input)
}

fn style(input: &str) -> IResult<&str, String> {
    delimited(
        char('{'),
        escaped_transform(
            none_of("\\{}"),
            '\\',
            alt((
                value("\\", tag("\\")),
                value("{", tag("{")),
                value("}", tag("}")),
            )),
        ),
        char('}'),
    )(input)
}

fn language(input: &str) -> IResult<&str, String> {
    delimited(
        char('['),
        escaped_transform(
            none_of("\\[]"),
            '\\',
            alt((
                value("\\", tag("\\")),
                value("[", tag("[")),
                value("]", tag("]")),
            )),
        ),
        char(']'),
    )(input)
}

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

    #[test]
    fn attributes_csl() {
        let input = "(class#id){style}[language]";
        let result = attributes(input);
        assert_eq!(
            result,
            Ok((
                "",
                Attributes {
                    class: Some(String::from("class")),
                    id: Some(String::from("id")),
                    style: Some(String::from("style")),
                    language: Some(String::from("language")),
                }
            ))
        );
    }

    #[test]
    fn attributes_scl() {
        let input = "{style}(class#id)[language]";
        let result = attributes(input);
        assert_eq!(
            result,
            Ok((
                "",
                Attributes {
                    class: Some(String::from("class")),
                    id: Some(String::from("id")),
                    style: Some(String::from("style")),
                    language: Some(String::from("language")),
                }
            ))
        );
    }

    #[test]
    fn attributes_lcs() {
        let input = "[language]{style}(class#id)";
        let result = attributes(input);
        assert_eq!(
            result,
            Ok((
                "",
                Attributes {
                    class: Some(String::from("class")),
                    id: Some(String::from("id")),
                    style: Some(String::from("style")),
                    language: Some(String::from("language")),
                }
            ))
        );
    }

    #[test]
    fn attributes_class() {
        let input = "(class)";
        let result = attributes(input);
        assert_eq!(
            result,
            Ok((
                "",
                Attributes {
                    class: Some(String::from("class")),
                    id: None,
                    style: None,
                    language: None,
                }
            ))
        );
    }
}