flatiron 1.0.5

A parser and HTML renderer for the Textile markup language
Documentation
use crate::parse::{attributes::attributes, link::link_suffix};
use crate::structs::{ImageAlign, ImageHeader, InlineTag};
use nom::{
    branch::alt,
    bytes::complete::{escaped_transform, tag},
    character::complete::{char, none_of},
    combinator::{opt, value},
    sequence::{delimited, tuple},
    IResult,
};

pub fn image(input: &str) -> IResult<&str, InlineTag> {
    let (rest, ((align, attributes, url, alt), opt_link)) = tuple((
        delimited(
            char('!'),
            tuple((
                opt(image_alignment),
                opt(attributes),
                image_url,
                opt(delimited(char('('), image_alt, char(')'))),
            )),
            char('!'),
        ),
        opt(link_suffix),
    ))(input)?;
    let img = InlineTag::Image {
        header: ImageHeader { attributes, align },
        url,
        alt,
    };
    Ok((
        rest,
        match opt_link {
            Some(link) => InlineTag::Link {
                attributes: None,
                title: None,
                url: link,
                content: Box::new(img),
            },
            None => img,
        },
    ))
}

fn image_alignment(input: &str) -> IResult<&str, ImageAlign> {
    alt((
        value(ImageAlign::Left, tag("<")),
        value(ImageAlign::Right, tag(">")),
        value(ImageAlign::Center, tag("=")),
    ))(input)
}

fn image_url(input: &str) -> IResult<&str, String> {
    let (rest, url) = escaped_transform(
        none_of("\\()! \r\n\t"),
        '\\',
        alt((
            value("\\", tag("\\")),
            value("(", tag("(")),
            value(")", tag(")")),
            value("!", tag("!")),
        )),
    )(input)?;
    Ok((rest, String::from(url)))
}

fn image_alt(input: &str) -> IResult<&str, String> {
    let (rest, alt) = escaped_transform(
        none_of("\\()!"),
        '\\',
        alt((
            value("\\", tag("\\")),
            value("(", tag("(")),
            value(")", tag(")")),
            value("!", tag("!")),
        )),
    )(input)?;
    Ok((rest, String::from(alt)))
}

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

    #[test]
    fn image_basic() {
        let input =
            "!https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png!";
        let result = image(input);
        assert_eq!(
            result,
            Ok((
                "",
                InlineTag::Image {
                    header: ImageHeader {
                        attributes: None,
                        align: None,
                    },
                    url: String::from(
                        "https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png",
                    ),
                    alt: None
                }
            ))
        );
    }

    #[test]
    fn image_with_alt() {
        let input =
            "!https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png(Flatiron logo)!";
        let result = image(input);
        assert_eq!(
            result,
            Ok((
                "",
                InlineTag::Image {
                    header: ImageHeader {attributes: None,
                    align: None,},
                    url: String::from(
                        "https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png",
                    ),
                    alt: Some(String::from("Flatiron logo")),
                }
            ))
        );
    }

    #[test]
    fn image_with_parentheses() {
        let input =
            "!https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png\\(parentheses\\)!";
        let result = image(input);
        assert_eq!(
            result,
            Ok((
                "",
                InlineTag::Image {
                    header: ImageHeader {
                        attributes: None,
                        align: None,
                    },
                    url: String::from(
                        "https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png(parentheses)",
                    ),
                    alt: None
                }
            ))
        );
    }

    #[test]
    fn image_with_exclamation() {
        let input =
            "!https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png\\!\\!!";
        let result = image(input);
        assert_eq!(
            result,
            Ok((
                "",
                InlineTag::Image {
                    header: ImageHeader {attributes: None,
                    align: None,},
                    url: String::from(
                        "https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png!!",
                    ),
                    alt: None,
                }
            ))
        );
    }

    #[test]
    #[should_panic]
    fn not_an_image() {
        let input = "Stop! This is not an image, leave me alone!";
        image(input).unwrap();
    }

    #[test]
    fn image_link() {
        let input = "!https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png!:https://git.sr.ht/~autumnull/flatiron";
        let result = image(input);
        assert_eq!(
            result,
            Ok((
                "",
                InlineTag::Link {
                    attributes: None,
                    title: None,
                    url: String::from("https://git.sr.ht/~autumnull/flatiron"),
                    content: Box::new(InlineTag::Image {
                        header: ImageHeader {attributes: None,
                        align: None,},
                        url: String::from("https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png"),
                        alt: None,
                    })
                }
            ))
        );
    }

    #[test]
    fn image_link_parenthesized() {
        let input = "!https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png!:(https://git.sr.ht/~autumnull/flatiron)";
        let result = image(input);
        assert_eq!(
            result,
            Ok((
                "",
                InlineTag::Link {
                    attributes: None,
                    title: None,
                    url: String::from("https://git.sr.ht/~autumnull/flatiron"),
                    content: Box::new(InlineTag::Image {
                        header: ImageHeader {attributes: None,
                        align: None,},
                        url: String::from("https://git.sr.ht/~autumnull/flatiron/blob/main/images/flatiron.png"),
                        alt: None,
                    })
                }
            ))
        );
    }
}