~autumnull/flatiron

2e8fc0b3e010583af6ae91777978468db923d1b9 — Autumn! 2 years ago ae4095b
Implemented basic renderer
M README.textile => README.textile +1 -1
@@ 1,6 1,6 @@
!images/flatiron.png!

h1.  Flatiron
h1. Flatiron

A parser and HTML renderer for the "Textile markup language":https://web.archive.org/web/20021226034931/http://textism.com/article/648/ , written in rust.


M samples/textism.textile => samples/textism.textile +1 -1
@@ 46,7 46,7 @@ Well, that went well. How about we insert an ==<a href="http://www.textism.com/"

An image:

!/textist.gif(optional alt text)!
!images/flatiron.png(optional alt text)!

# Librarians rule
# Yes they do

M src/lib.rs => src/lib.rs +9 -0
@@ 1,5 1,14 @@
#![allow(dead_code)]
use nom::combinator::complete;

mod parse;
mod render;
mod structs;

pub fn convert(input: String) -> Result<String, &'static str> {
    let parse_result = complete(parse::textile)(&input);
    match parse_result {
        Ok((_, s)) => Ok(format!("{}", s)),
        Err(_) => Err("Could not parse textile!"),
    }
}

M src/main.rs => src/main.rs +7 -1
@@ 1,3 1,9 @@
use flatiron::convert;
use std::fs;

fn main() {
    println!("Hello, world!");
    let textile = fs::read_to_string("samples/textism.textile")
        .expect("Something went wrong while reading the file");
    let html = convert(textile).unwrap();
    print!("{}", html);
}

M src/parse/block.rs => src/parse/block.rs +3 -0
@@ 44,6 44,9 @@ pub fn block(input: &str) -> IResult<&str, BlockTag> {
            }
            _ => {
                i += 1;
                while !rest.is_char_boundary(i) {
                    i += 1;
                }
            }
        }
    }

M src/parse/list.rs => src/parse/list.rs +5 -2
@@ 70,7 70,10 @@ fn list_item_head(input: &str) -> IResult<&str, (ListKind, usize)> {
fn list_item_content(input: &str) -> IResult<&str, InlineTag> {
    let mut i = 0;
    while let Err(_) = end_list_item(&input[i..]) {
        i += 1
        i += 1;
        while !input.is_char_boundary(i) {
            i += 1;
        }
    }
    let (_, content) = phrase(&input[..i])?;
    Ok((&input[i..], content))


@@ 88,7 91,7 @@ fn list_items_to_nested_list(
    let depth = list[0].1;
    while !list.is_empty() {
        if list[0].1 > depth {
            // unwrap should be safe because depth can only be greater after 1st item
            // unwrap should be safe because depth can only change after 1st item
            items.last_mut().unwrap().sublist =
                Some(list_items_to_nested_list(list)?);
        } else if list[0].1 < depth {

M src/parse/mod.rs => src/parse/mod.rs +1 -1
@@ 591,7 591,7 @@ This is a paragraph with some _emphasized text_.";
                content: InlineTag::Image {
                    attributes: None,
                    align: None,
                    url: String::from("/textist.gif"),
                    url: String::from("images/flatiron.png"),
                    alt: Some(String::from("optional alt text"))
                }
            },

M src/parse/phrase.rs => src/parse/phrase.rs +6 -0
@@ 86,6 86,9 @@ pub fn phrase(input: &str) -> IResult<&str, InlineTag> {
            let c = input.chars().nth(i).unwrap();
            preceding_space = !c.is_alphanumeric();
            i += 1;
            while !input.is_char_boundary(i) {
                i += 1;
            }
        }
    }
}


@@ 161,6 164,9 @@ fn tagged_phrase(
                let c = input.chars().nth(i).unwrap();
                preceding_space = !c.is_alphanumeric();
                i += 1;
                while !rest.is_char_boundary(i) {
                    i += 1;
                }
            }
        }
    }

M src/render.rs => src/render.rs +206 -0
@@ 1,1 1,207 @@
use crate::structs::*;
use std::fmt;

fn html_escape(s: &String) -> String {
    s.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
}

impl fmt::Display for Textile {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for block in self.0.iter() {
            write!(f, "{}\n", block)?
        }
        Ok(())
    }
}

impl fmt::Display for BlockTag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            BlockTag::Basic {
                kind,
                indent,
                align,
                attributes,
                content,
            } => match kind {
                BlockKind::Paragraph => write!(f, "<p>{}</p>", content),
                BlockKind::BlockQuote => write!(f, "<blockquote>{}</blockquote>", content),
                BlockKind::Header(n) => {
                    write!(f, "<h{}>{}</h{}>", n, content, n)
                }
                BlockKind::Footnote(n) => write!(
                    f,
                    "<p id=\"fn{}\" class=\"footnote\"><sup>{}</sup>{}<a href=\"#fnr{}\">&21A9;</a></p>",
                    n, n, content, n
                ),
                _ => Err(fmt::Error)
            },
            BlockTag::Preformatted {
                kind,
                indent,
                align,
                attributes,
                content,
            } => match kind {
                BlockKind::Preformatted => write!(f, "<pre>{}</pre>", html_escape(content)),
                BlockKind::BlockCode => write!(f, "<pre><code>{}</code></pre>", html_escape(content)),
                _ => Err(fmt::Error) // other kinds should not be possible here
            },
            BlockTag::List {
                indent,
                align,
                attributes,
                content,
            } => write!(f, "{}", content),
            BlockTag::Table {
                indent,
                align,
                attributes,
                rows,
            } => {
                write!(f, "<table>\n")?;
                for row in rows {
                    write!(f, "{}\n", row);
                }
                write!(f, "</table>");
                Ok(())
            },
            BlockTag::NoTextile(s) => {
                write!(f, "{}", s)
            }
        }
    }
}

impl fmt::Display for InlineTag {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            InlineTag::Plaintext(s) => write!(f, "{}", s),
            InlineTag::Code(s) => write!(f, "<code>{}</code>", html_escape(s)),
            InlineTag::NoTextile(s) => write!(f, "{}", s),
            InlineTag::Acronym { title, content } => match title {
                Some(title) => {
                    write!(f, "<abbr title=\"{}\">{}</abbr>", title, content)
                }
                None => write!(f, "<span class=\"caps\">{}</span>", content),
            },
            InlineTag::FootnoteRef(n) => write!(
                f,
                "<sup id=\"fnr{}\"><a href=\"#fn{}\">{}</a></sup>",
                n, n, n
            ),
            InlineTag::Link {
                attributes,
                title,
                url,
                content,
            } => match title {
                Some(title) => write!(
                    f,
                    "<a href=\"{}\" title=\"{}\">{}</a>",
                    url, title, content
                ),
                None => write!(f, "<a href=\"{}\">{}</a>", url, content),
            },
            InlineTag::Image {
                attributes,
                align,
                url,
                alt,
            } => match alt {
                Some(alt) => {
                    write!(f, "<img src=\"{}\" alt=\"{}\" />", url, alt)
                }
                None => write!(f, "<img src=\"{}\" />", url),
            },
            InlineTag::Phrase {
                kind,
                attributes,
                content,
            } => match kind {
                Some(k) => {
                    write!(f, "<{}>", k)?;
                    for phrase in content {
                        write!(f, "{}", phrase)?;
                    }
                    write!(f, "</{}>", k)?;
                    Ok(())
                }
                None => {
                    for phrase in content {
                        write!(f, "{}", phrase)?;
                    }
                    Ok(())
                }
            },
            InlineTag::LineBreak => write!(f, "<br />\n"),
        }
    }
}

impl fmt::Display for PhraseKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PhraseKind::Italic => write!(f, "i"),
            PhraseKind::Bold => write!(f, "b"),
            PhraseKind::Emphasis => write!(f, "em"),
            PhraseKind::Strong => write!(f, "strong"),
            PhraseKind::Citation => write!(f, "cite"),
            PhraseKind::Deleted => write!(f, "del"),
            PhraseKind::Inserted => write!(f, "ins"),
            PhraseKind::Superscript => write!(f, "sup"),
            PhraseKind::Subscript => write!(f, "sub"),
            PhraseKind::Span => write!(f, "span"),
        }
    }
}

impl fmt::Display for List {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let tag_name = match self.kind {
            ListKind::Numeric => "ol",
            ListKind::Bulleted => "ul",
        };
        write!(f, "<{}>\n", tag_name)?;
        for item in self.items.iter() {
            write!(f, "{}\n", item)?;
        }
        write!(f, "</{}>", tag_name)?;
        Ok(())
    }
}

impl fmt::Display for ListItem {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.sublist {
            Some(sublist) => {
                write!(f, "<li>{}\n{}\n</li>", self.content, sublist)
            }
            None => write!(f, "<li>{}</li>", self.content),
        }
    }
}

impl fmt::Display for TableRow {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "<tr>\n")?;
        for cell in &self.cells {
            write!(f, "{}\n", cell)?;
        }
        write!(f, "</tr>")?;
        Ok(())
    }
}

impl fmt::Display for TableCell {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let tag_name = match self.kind {
            CellKind::Header => "th",
            CellKind::Data => "td",
        };
        write!(f, "<{}>{}</{}>", tag_name, self.content, tag_name)?;
        Ok(())
    }
}