M README.textile => README.textile +1 -3
@@ 26,6 26,4 @@ Find any "lice (bugs)":https://en.wikipedia.org/wiki/Clothes_iron#Hygiene ? I al
h3. TODO
* Parsing
-** DONE!
-* Rendering
-** Everything
+** Punctuation transforms
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:
-!images/flatiron.png(optional alt text)!
+!=images/flatiron.png(optional alt text)!
# Librarians rule
# Yes they do
M src/lib.rs => src/lib.rs +3 -7
@@ 1,14 1,10 @@
-#![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!"),
- }
+pub fn convert(input: String) -> String {
+ let (_, textile) = complete(parse::textile)(&input).unwrap();
+ format!("{}", textile)
}
M src/main.rs => src/main.rs +1 -1
@@ 4,6 4,6 @@ use std::fs;
fn main() {
let textile = fs::read_to_string("samples/textism.textile")
.expect("Something went wrong while reading the file");
- let html = convert(textile).unwrap();
+ let html = convert(textile);
print!("{}", html);
}
M src/parse/block.rs => src/parse/block.rs +108 -54
@@ 2,7 2,9 @@ use crate::parse::{
alignment, attributes::attributes, from_num, list::list, opt_either_order,
phrase::phrase, table::table,
};
-use crate::structs::{Align, Attributes, BlockKind, BlockTag, Indent};
+use crate::structs::{
+ Align, Attributes, BlockHeader, BlockKind, BlockTag, Indent,
+};
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag, take_while1},
@@ 56,18 58,46 @@ pub fn block(input: &str) -> IResult<&str, BlockTag> {
let (rest, _) = alt((eof, end_of_block))(&rest[i..])?;
match kind {
- BlockKind::Paragraph
- | BlockKind::Header(_)
- | BlockKind::Footnote(_)
- | BlockKind::BlockQuote => {
+ BlockKind::Paragraph | BlockKind::Header(_) | BlockKind::BlockQuote => {
+ let (_, content) = complete(phrase)(body)?;
+ Ok((
+ rest,
+ BlockTag::Basic {
+ kind,
+ header: BlockHeader {
+ indent,
+ align,
+ attributes,
+ },
+ content,
+ },
+ ))
+ }
+ BlockKind::Footnote(n) => {
+ let mut attributes = attributes.unwrap_or(Attributes {
+ class: None,
+ id: None,
+ style: None,
+ language: None,
+ });
+ attributes.class = match attributes.class {
+ Some(s) => Some(format!("footnote {}", s)),
+ None => Some(String::from("footnote")),
+ };
+ attributes.id = match attributes.id {
+ Some(s) => Some(format!("fn{} {}", n, s)),
+ None => Some(format!("fn{}", n)),
+ };
let (_, content) = complete(phrase)(body)?;
Ok((
rest,
BlockTag::Basic {
kind,
- indent,
- align,
- attributes,
+ header: BlockHeader {
+ indent,
+ align,
+ attributes: Some(attributes),
+ },
content,
},
))
@@ 81,9 111,11 @@ pub fn block(input: &str) -> IResult<&str, BlockTag> {
rest,
BlockTag::Preformatted {
kind,
- indent,
- align,
- attributes,
+ header: BlockHeader {
+ indent,
+ align,
+ attributes,
+ },
content,
},
))
@@ 195,9 227,11 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
+ header: BlockHeader {
+ indent: None,
+ align: None,
+ attributes: None,
+ },
content: InlineTag::Plaintext(String::from(
"Untagged paragraph"
))
@@ 216,9 250,11 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
+ header: BlockHeader {
+ indent: None,
+ align: None,
+ attributes: None,
+ },
content: InlineTag::Plaintext(String::from(
"Tagged paragraph"
))
@@ 237,9 273,11 @@ mod tests {
"",
BlockTag::Preformatted {
kind: BlockKind::Preformatted,
- indent: None,
- align: None,
- attributes: None,
+ header: BlockHeader {
+ indent: None,
+ align: None,
+ attributes: None,
+ },
content: String::from("Preformatted _paragraph_")
}
))
@@ 270,9 308,11 @@ mod tests {
"p. Hell if I know.",
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
+ header: BlockHeader {
+ indent: None,
+ align: None,
+ attributes: None,
+ },
content: InlineTag::Phrase {
kind: None,
attributes: None,
@@ 302,14 342,16 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Header(2),
- indent: None,
- align: None,
- attributes: Some(Attributes {
- class: Some(String::from("class")),
- id: None,
- style: Some(String::from("color:green")),
- language: None,
- }),
+ header: BlockHeader {
+ indent: None,
+ align: None,
+ attributes: Some(Attributes {
+ class: Some(String::from("class")),
+ id: None,
+ style: Some(String::from("color:green")),
+ language: None,
+ }),
+ },
content: InlineTag::Plaintext(String::from(
"This is a title"
))
@@ 328,9 370,11 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Header(2),
- indent: None,
- align: Some(Align::Center),
- attributes: None,
+ header: BlockHeader {
+ indent: None,
+ align: Some(Align::Center),
+ attributes: None,
+ },
content: InlineTag::Plaintext(String::from(
"This is a title"
))
@@ 349,9 393,11 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Header(2),
- indent: Some(Indent { left: 1, right: 1 }),
- align: None,
- attributes: None,
+ header: BlockHeader {
+ indent: Some(Indent { left: 1, right: 1 }),
+ align: None,
+ attributes: None,
+ },
content: InlineTag::Plaintext(String::from(
"This is a title"
))
@@ 370,9 416,11 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: Some(Indent { left: 0, right: 2 }),
- align: Some(Align::Right),
- attributes: None,
+ header: BlockHeader {
+ indent: Some(Indent { left: 0, right: 2 }),
+ align: Some(Align::Right),
+ attributes: None,
+ },
content: InlineTag::Plaintext(String::from("I am a fish!"))
}
))
@@ 389,9 437,11 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: Some(Indent { left: 0, right: 2 }),
- align: Some(Align::Right),
- attributes: None,
+ header: BlockHeader {
+ indent: Some(Indent { left: 0, right: 2 }),
+ align: Some(Align::Right),
+ attributes: None,
+ },
content: InlineTag::Plaintext(String::from(
"I am a transmitter!"
))
@@ 410,14 460,16 @@ mod tests {
"",
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: Some(Indent { left: 1, right: 1 }),
- align: Some(Align::Right),
- attributes: Some(Attributes {
- class: Some(String::from("greeting")),
- id: None,
- style: Some(String::from("color:green")),
- language: Some(String::from("fr")),
- }),
+ header: BlockHeader {
+ indent: Some(Indent { left: 1, right: 1 }),
+ align: Some(Align::Right),
+ attributes: Some(Attributes {
+ class: Some(String::from("greeting")),
+ id: None,
+ style: Some(String::from("color:green")),
+ language: Some(String::from("fr")),
+ }),
+ },
content: InlineTag::Plaintext(String::from("Salut!"))
}
))
@@ 445,9 497,11 @@ mod tests {
"",
BlockTag::Preformatted {
kind: BlockKind::BlockCode,
- indent: None,
- align: None,
- attributes: None,
+ header: BlockHeader {
+ indent: None,
+ align: None,
+ attributes: None,
+ },
content: String::from(
"This is supposed to be a block of textile code.
M src/parse/image.rs => src/parse/image.rs +18 -15
@@ 1,5 1,5 @@
use crate::parse::{attributes::attributes, link::link_suffix};
-use crate::structs::{ImageAlign, InlineTag};
+use crate::structs::{ImageAlign, ImageHeader, InlineTag};
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag},
@@ 24,8 24,7 @@ pub fn image(input: &str) -> IResult<&str, InlineTag> {
opt(link_suffix),
))(input)?;
let img = InlineTag::Image {
- attributes,
- align,
+ header: ImageHeader { attributes, align },
url,
alt,
};
@@ 93,8 92,10 @@ mod tests {
Ok((
"",
InlineTag::Image {
- attributes: None,
- align: None,
+ header: ImageHeader {
+ attributes: None,
+ align: None,
+ },
url: String::from(
"https://github.com/autumnull/flatiron/raw/main/images/flatiron.png",
),
@@ 114,8 115,8 @@ mod tests {
Ok((
"",
InlineTag::Image {
- attributes: None,
- align: None,
+ header: ImageHeader {attributes: None,
+ align: None,},
url: String::from(
"https://github.com/autumnull/flatiron/raw/main/images/flatiron.png",
),
@@ 135,8 136,10 @@ mod tests {
Ok((
"",
InlineTag::Image {
- attributes: None,
- align: None,
+ header: ImageHeader {
+ attributes: None,
+ align: None,
+ },
url: String::from(
"https://github.com/autumnull/flatiron/raw/main/images/flatiron.png(parentheses)",
),
@@ 156,8 159,8 @@ mod tests {
Ok((
"",
InlineTag::Image {
- attributes: None,
- align: None,
+ header: ImageHeader {attributes: None,
+ align: None,},
url: String::from(
"https://github.com/autumnull/flatiron/raw/main/images/flatiron.png!!",
),
@@ 187,8 190,8 @@ mod tests {
title: None,
url: String::from("https://github.com/autumnull/flatiron"),
content: Box::new(InlineTag::Image {
- attributes: None,
- align: None,
+ header: ImageHeader {attributes: None,
+ align: None,},
url: String::from("https://github.com/autumnull/flatiron/raw/main/images/flatiron.png"),
alt: None,
})
@@ 210,8 213,8 @@ mod tests {
title: None,
url: String::from("https://github.com/autumnull/flatiron"),
content: Box::new(InlineTag::Image {
- attributes: None,
- align: None,
+ header: ImageHeader {attributes: None,
+ align: None,},
url: String::from("https://github.com/autumnull/flatiron/raw/main/images/flatiron.png"),
alt: None,
})
M src/parse/list.rs => src/parse/list.rs +8 -4
@@ 3,7 3,9 @@ use crate::parse::{
block::{end_of_block, indent_align},
phrase::phrase,
};
-use crate::structs::{BlockTag, InlineTag, List, ListItem, ListKind};
+use crate::structs::{
+ BlockHeader, BlockTag, InlineTag, List, ListItem, ListKind,
+};
use nom::{
branch::alt,
character::complete::{char, line_ending},
@@ 46,9 48,11 @@ pub fn list(input: &str) -> IResult<&str, BlockTag> {
Ok(l) => Ok((
input,
BlockTag::List {
- indent,
- align,
- attributes,
+ header: BlockHeader {
+ attributes,
+ indent,
+ align,
+ },
content: l,
},
)),
M src/parse/mod.rs => src/parse/mod.rs +205 -158
@@ 137,8 137,9 @@ where
mod tests {
use super::*;
use crate::structs::{
- Attributes, BlockKind, BlockTag, CellKind, List, ListItem, ListKind,
- TableCell, TableRow, VerticalAlign,
+ Attributes, BlockHeader, BlockKind, BlockTag, CellKind, ImageAlign,
+ ImageHeader, List, ListItem, ListKind, TableCell, TableHeader,
+ TableRow, VerticalAlign,
};
#[test]
@@ 154,23 155,27 @@ This is a paragraph with some _emphasized text_.";
Textile(vec![
BlockTag::Basic {
kind: BlockKind::Header(2),
- indent: None,
- align: None,
- attributes: Some(Attributes {
- class: None,
- id: None,
- style: Some(String::from("color:green")),
- language: None,
- }),
+ header: BlockHeader {
+ attributes: Some(Attributes {
+ class: None,
+ id: None,
+ style: Some(String::from("color:green")),
+ language: None,
+ }),
+ indent: None,
+ align: None,
+ },
content: InlineTag::Plaintext(String::from(
"This is a title"
))
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
+ header: BlockHeader {
+ attributes: None,
+ indent: None,
+ align: None,
+ },
content: InlineTag::Phrase {
kind: None,
attributes: None,
@@ 201,63 206,71 @@ This is a paragraph with some _emphasized text_.";
let blocks = vec![
BlockTag::Basic {
kind: BlockKind::Header(2),
- indent: None,
- align: None,
- attributes: Some(Attributes {
- class: None,
- id: None,
- style: Some(String::from("color:green")),
- language: None
- }),
+ header: BlockHeader {
+ attributes: Some(Attributes {
+ class: None,
+ id: None,
+ style: Some(String::from("color:green")),
+ language: None
+ }),
+ indent: None,
+ align: None,
+ },
content: InlineTag::Plaintext(String::from("This is a title"))
},
BlockTag::Basic {
kind: BlockKind::Header(3),
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("This is a subhead"))
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: Some(Attributes {
- class: None,
- id: None,
- style: Some(String::from("color:red")),
- language: None
- }),
+ header: BlockHeader {
+ attributes: Some(Attributes {
+ class: None,
+ id: None,
+ style: Some(String::from("color:red")),
+ language: None
+ }),
+ indent: None,
+ align: None,
+ },
content: InlineTag::Plaintext(String::from("This is some text of dubious character. Isn't the use of \"quotes\" just lazy writing -- and theft of 'intellectual property' besides? I think the time has come to see a block quote."))
},
BlockTag::Basic {
kind: BlockKind::BlockQuote,
- indent: None,
- align: None,
- attributes: Some(Attributes {
- class: None,
- id: None,
- style: None,
- language: Some(String::from("fr"))
- }),
+ header: BlockHeader {
+ attributes: Some(Attributes {
+ class: None,
+ id: None,
+ style: None,
+ language: Some(String::from("fr"))
+ }),
+ indent: None,
+ align: None,
+ },
content: InlineTag::Plaintext(String::from("This is a block quote. I'll admit it's not the most exciting block quote ever devised."))
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("Simple list:"))
},
BlockTag::List{
- indent: None,
- align: None,
- attributes: Some(Attributes {
- class: None,
- id: None,
- style: Some(String::from("color:blue")),
- language: None
- }),
+ header: BlockHeader {
+ attributes: Some(Attributes {
+ class: None,
+ id: None,
+ style: Some(String::from("color:blue")),
+ language: None
+ }),
+ indent: None,
+ align: None,
+ },
content: List {
kind: ListKind::Numeric,
items: vec![
@@ 278,15 291,15 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("Multi-level list:"))
},
BlockTag::List{
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: List {
kind: ListKind::Numeric,
items: vec![
@@ 335,15 348,15 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("Mixed list:"))
},
BlockTag::List{
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: List {
kind: ListKind::Bulleted,
items: vec![
@@ 392,9 405,9 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Phrase {
kind: None,
attributes: None,
@@ 407,9 420,9 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Link {
attributes: None,
title: Some(String::from("optional title")),
@@ 420,156 433,186 @@ This is a paragraph with some _emphasized text_.";
}
},
BlockTag::Table {
- attributes: Some(Attributes {
- class: None,
- id: None,
- style: Some(String::from("border:1px solid black")),
- language: None
- }),
- indent: None,
- align: None,
+ header: BlockHeader {
+ attributes: Some(Attributes {
+ class: None,
+ id: None,
+ style: Some(String::from("border:1px solid black")),
+ language: None
+ }),
+ indent: None,
+ align: None,
+ },
rows: vec![
TableRow {
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
cells: vec![
TableCell {
kind: CellKind::Header,
col_span: None,
row_span: None,
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("this"))
},
TableCell {
kind: CellKind::Header,
col_span: None,
row_span: None,
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("is"))
},
TableCell {
kind: CellKind::Header,
col_span: None,
row_span: None,
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("a"))
},
TableCell {
kind: CellKind::Header,
col_span: None,
row_span: None,
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("header"))
},
]
},
TableRow {
- attributes: Some(Attributes{
- class: None,
- id: None,
- style: Some(String::from("background:gray")),
- language: None,
- }),
- h_align: Some(Align::Left),
- v_align: None,
+ header: TableHeader {
+ attributes: Some(Attributes{
+ class: None,
+ id: None,
+ style: Some(String::from("background:gray")),
+ language: None,
+ }),
+ h_align: Some(Align::Left),
+ v_align: None,
+ },
cells: vec![
TableCell {
kind: CellKind::Data,
col_span: Some(2),
row_span: None,
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("this is"))
},
TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
- attributes: Some(Attributes{
- class: None,
- id: None,
- style: Some(String::from("background:red;width:200px")),
- language: None,
- }),
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: Some(Attributes{
+ class: None,
+ id: None,
+ style: Some(String::from("background:red;width:200px")),
+ language: None,
+ }),
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("a"))
},
TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
- attributes: Some(Attributes{
- class: None,
- id: None,
- style: Some(String::from("height:200px")),
- language: None,
- }),
- h_align: Some(Align::Justify),
- v_align: Some(VerticalAlign::Top),
+ header: TableHeader {
+ attributes: Some(Attributes{
+ class: None,
+ id: None,
+ style: Some(String::from("height:200px")),
+ language: None,
+ }),
+ h_align: Some(Align::Justify),
+ v_align: Some(VerticalAlign::Top),
+ },
content: InlineTag::Plaintext(String::from("row"))
},
]
},
TableRow {
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
cells: vec![
TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("this"))
},
TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
- attributes: Some(Attributes{
- class: None,
- id: None,
- style: Some(String::from("padding:10px")),
- language: None,
- }),
- h_align: Some(Align::Justify),
- v_align: None,
+ header: TableHeader {
+ attributes: Some(Attributes{
+ class: None,
+ id: None,
+ style: Some(String::from("padding:10px")),
+ language: None,
+ }),
+ h_align: Some(Align::Justify),
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("is"))
},
TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
- attributes: None,
- h_align: None,
- v_align: Some(VerticalAlign::Top),
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: Some(VerticalAlign::Top),
+ },
content: InlineTag::Plaintext(String::from("another"))
},
TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
- attributes: Some(Attributes{
- class: Some(String::from("bob")),
- id: Some(String::from("bob")),
- style: None,
- language: None,
- }),
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: Some(Attributes{
+ class: Some(String::from("bob")),
+ id: Some(String::from("bob")),
+ style: None,
+ language: None,
+ }),
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from("row"))
},
]
@@ 578,27 621,31 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("An image:"))
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Image {
+ header: BlockHeader {
attributes: None,
+ indent: None,
align: None,
+ },
+ content: InlineTag::Image {
+ header: ImageHeader {
+ attributes: None,
+ align: Some(ImageAlign::Center),
+ },
url: String::from("images/flatiron.png"),
alt: Some(String::from("optional alt text"))
}
},
BlockTag::List{
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: List {
kind: ListKind::Numeric,
items: vec![
@@ 619,9 666,9 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Phrase {
kind: None,
attributes: None,
@@ 660,16 707,16 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Basic {
kind: BlockKind::Header(3),
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("Coding"))
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Phrase {
kind: None,
attributes: None,
@@ 682,29 729,29 @@ This is a paragraph with some _emphasized text_.";
},
BlockTag::Preformatted {
kind: BlockKind::BlockCode,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: String::from("$text = str_replace(\"<p>%::%</p>\",\"\",$text);\n$text = str_replace(\"%::%</p>\",\"\",$text);\n$text = str_replace(\"%::%\",\"\",$text);")
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("This isn't code."))
},
BlockTag::Basic {
kind: BlockKind::Paragraph,
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: InlineTag::Plaintext(String::from("So you see, my friends:"))
},
BlockTag::List {
+ header: BlockHeader {attributes: None,
indent: None,
- align: None,
- attributes: None,
+ align: None,},
content: List {
kind: ListKind::Bulleted,
items: vec![
M src/parse/phrase.rs => src/parse/phrase.rs +5 -3
@@ 202,7 202,7 @@ pub fn footnote_ref(input: &str) -> IResult<&str, InlineTag> {
#[cfg(test)]
mod tests {
use super::*;
- use crate::structs::Attributes;
+ use crate::structs::{Attributes, ImageHeader};
#[test]
fn empty_phrase() {
@@ 263,8 263,10 @@ mod tests {
content: vec![
InlineTag::Plaintext(String::from("Textist: ")),
InlineTag::Image {
- attributes: None,
- align: None,
+ header: ImageHeader {
+ attributes: None,
+ align: None,
+ },
url: String::from("/common/textist.gif"),
alt: Some(String::from("Textist")),
}
M src/parse/table.rs => src/parse/table.rs +32 -20
@@ 6,8 6,8 @@ use crate::parse::{
phrase::phrase,
};
use crate::structs::{
- Align, Attributes, BlockTag, CellKind, Indent, InlineTag, TableCell,
- TableRow, VerticalAlign,
+ Align, Attributes, BlockHeader, BlockTag, CellKind, Indent, InlineTag,
+ TableCell, TableHeader, TableRow, VerticalAlign,
};
use nom::{
branch::alt,
@@ 26,9 26,11 @@ pub fn table(input: &str) -> IResult<&str, BlockTag> {
Ok((
rest,
BlockTag::Table {
- indent,
- align,
- attributes,
+ header: BlockHeader {
+ attributes,
+ indent,
+ align,
+ },
rows,
},
))
@@ 56,9 58,11 @@ fn table_row(input: &str) -> IResult<&str, TableRow> {
Ok((
rest,
TableRow {
- attributes,
- h_align,
- v_align,
+ header: TableHeader {
+ attributes,
+ h_align,
+ v_align,
+ },
cells,
},
))
@@ 94,9 98,11 @@ fn table_cell(input: &str) -> IResult<&str, TableCell> {
kind,
col_span,
row_span,
- attributes,
- h_align,
- v_align,
+ header: TableHeader {
+ attributes,
+ h_align,
+ v_align,
+ },
content,
},
))
@@ 194,20 200,26 @@ mod tests {
Ok((
"",
BlockTag::Table {
- indent: None,
- align: None,
- attributes: None,
- rows: vec![TableRow {
+ header: BlockHeader {
attributes: None,
- h_align: None,
- v_align: None,
+ indent: None,
+ align: None,
+ },
+ rows: vec![TableRow {
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
cells: vec![TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
- attributes: None,
- h_align: None,
- v_align: None,
+ header: TableHeader {
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ },
content: InlineTag::Plaintext(String::from(
"hello"
))
M src/render.rs => src/render.rs +269 -54
@@ 21,53 21,59 @@ impl fmt::Display for BlockTag {
match self {
BlockTag::Basic {
kind,
- indent,
- align,
- attributes,
+ header,
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{}><sup>{}</sup>{}<a href=\"#fnr{}\">&21A9;</a></p>",
+ header, 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)
+ BlockKind::Paragraph
+ | BlockKind::BlockQuote
+ | BlockKind::Header(_) => {
+ write!(f, "<{}{}>{}</{}>", kind, header, content, kind)
+ }
+ _ => Err(fmt::Error),
},
BlockTag::Preformatted {
kind,
- indent,
- align,
- attributes,
+ header,
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
+ BlockKind::Preformatted => {
+ write!(f, "<pre{}>{}</pre>", header, html_escape(content))
+ }
+ BlockKind::BlockCode => write!(
+ f,
+ "<pre{}><code>{}</code></pre>",
+ header,
+ 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")?;
+ BlockTag::List { header, content } => {
+ let tag_name = match content.kind {
+ ListKind::Numeric => "ol",
+ ListKind::Bulleted => "ul",
+ };
+ write!(f, "<{}{}>\n", tag_name, header)?;
+ for item in content.items.iter() {
+ write!(f, "{}\n", item)?;
+ }
+ write!(f, "</{}>", tag_name)?;
+ Ok(())
+ }
+ BlockTag::Table { header, rows } => {
+ write!(f, "<table {}>\n", header)?;
for row in rows {
- write!(f, "{}\n", row);
+ write!(f, "{}\n", row)?;
}
- write!(f, "</table>");
+ write!(f, "</table>")?;
Ok(())
- },
+ }
BlockTag::NoTextile(s) => {
write!(f, "{}", s)
}
@@ 75,6 81,84 @@ impl fmt::Display for BlockTag {
}
}
+impl fmt::Display for BlockKind {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ BlockKind::Paragraph => write!(f, "p"),
+ BlockKind::Header(n) => write!(f, "h{}", n),
+ BlockKind::BlockQuote => write!(f, "blockquote"),
+ _ => Err(fmt::Error), // should never be called
+ }
+ }
+}
+
+impl fmt::Display for BlockHeader {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.indent.is_none() && self.align.is_none() {
+ match &self.attributes {
+ Some(attributes) => write!(f, "{}", attributes),
+ None => Ok(()),
+ }
+ } else {
+ let indent_style = match &self.indent {
+ Some(indent) => format!("{}", indent),
+ None => String::new(),
+ };
+ let align_style = match &self.align {
+ Some(align) => format!("{}", align),
+ None => String::new(),
+ };
+ match &self.attributes {
+ Some(attributes) => {
+ if let Some(s) = &attributes.class {
+ write!(f, " class=\"{}\"", s)?;
+ }
+ if let Some(s) = &attributes.id {
+ write!(f, " id=\"{}\"", s)?;
+ }
+ if let Some(s) = &attributes.style {
+ write!(
+ f,
+ " style=\"{}{} {}\"",
+ indent_style, align_style, s
+ )?;
+ }
+ if let Some(s) = &attributes.language {
+ write!(f, " lang=\"{}\"", s)?;
+ }
+ }
+ None => {
+ write!(f, " style=\"{}{}\"", indent_style, align_style)?;
+ }
+ }
+ Ok(())
+ }
+ }
+}
+
+impl fmt::Display for Indent {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.left > 0 {
+ write!(f, "padding-left: {}em; ", self.left)?;
+ }
+ if self.right > 0 {
+ write!(f, "padding-left: {}em; ", self.right)?;
+ }
+ Ok(())
+ }
+}
+
+impl fmt::Display for Align {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Align::Left => write!(f, "text-align: left;"),
+ Align::Right => write!(f, "text-align: right;"),
+ Align::Center => write!(f, "text-align: center;"),
+ Align::Justify => write!(f, "text-align: justify;"),
+ }
+ }
+}
+
impl fmt::Display for InlineTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ 97,24 181,33 @@ impl fmt::Display for InlineTag {
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 {
+ } => {
+ let attrs = match attributes {
+ Some(attributes) => format!("{}", attributes),
+ None => format!(""),
+ };
+ match title {
+ Some(title) => write!(
+ f,
+ "<a{} href=\"{}\" title=\"{}\">{}</a>",
+ attrs, url, title, content
+ ),
+ None => write!(
+ f,
+ "<a{} href=\"{}\">{}</a>",
+ attrs, url, content
+ ),
+ }
+ }
+ InlineTag::Image { header, url, alt } => match alt {
Some(alt) => {
- write!(f, "<img src=\"{}\" alt=\"{}\" />", url, alt)
+ write!(
+ f,
+ "<img{} src=\"{}\" alt=\"{}\" />",
+ header, url, alt
+ )
}
- None => write!(f, "<img src=\"{}\" />", url),
+ None => write!(f, "<img {} src=\"{}\" />", header, url),
},
InlineTag::Phrase {
kind,
@@ 122,7 215,10 @@ impl fmt::Display for InlineTag {
content,
} => match kind {
Some(k) => {
- write!(f, "<{}>", k)?;
+ match attributes {
+ Some(attributes) => write!(f, "<{}{}>", k, attributes)?,
+ None => write!(f, "<{}>", k)?,
+ };
for phrase in content {
write!(f, "{}", phrase)?;
}
@@ 158,6 254,63 @@ impl fmt::Display for PhraseKind {
}
}
+impl fmt::Display for Attributes {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(s) = &self.class {
+ write!(f, " class=\"{}\"", s)?;
+ }
+ if let Some(s) = &self.id {
+ write!(f, " id=\"{}\"", s)?;
+ }
+ if let Some(s) = &self.style {
+ write!(f, " style=\"{}\"", s)?;
+ }
+ if let Some(s) = &self.language {
+ write!(f, " lang=\"{}\"", s)?;
+ }
+ Ok(())
+ }
+}
+
+impl fmt::Display for ImageHeader {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self.align {
+ Some(align) => match &self.attributes {
+ Some(attributes) => {
+ if let Some(s) = &attributes.class {
+ write!(f, " class=\"{}\"", s)?;
+ }
+ if let Some(s) = &attributes.id {
+ write!(f, " id=\"{}\"", s)?;
+ }
+ if let Some(s) = &attributes.style {
+ write!(f, " style=\"{} {}\"", align, s)?;
+ }
+ if let Some(s) = &attributes.language {
+ write!(f, " lang=\"{}\"", s)?;
+ }
+ Ok(())
+ }
+ None => write!(f, " style=\"{}\"", align),
+ },
+ None => match &self.attributes {
+ Some(attributes) => write!(f, "{}", attributes),
+ None => Ok(()),
+ },
+ }
+ }
+}
+
+impl fmt::Display for ImageAlign {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ ImageAlign::Left => write!(f, "float: left;"),
+ ImageAlign::Right => write!(f, "float: right;"),
+ ImageAlign::Center => write!(f, "display: block; margin: auto;"),
+ }
+ }
+}
+
impl fmt::Display for List {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let tag_name = match self.kind {
@@ 186,7 339,7 @@ impl fmt::Display for ListItem {
impl fmt::Display for TableRow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(f, "<tr>\n")?;
+ write!(f, "<tr{}>\n", self.header)?;
for cell in &self.cells {
write!(f, "{}\n", cell)?;
}
@@ 201,7 354,69 @@ impl fmt::Display for TableCell {
CellKind::Header => "th",
CellKind::Data => "td",
};
- write!(f, "<{}>{}</{}>", tag_name, self.content, tag_name)?;
+ let colspan = match self.col_span {
+ Some(n) => format!(" colspan={}", n),
+ None => String::new(),
+ };
+ let rowspan = match self.row_span {
+ Some(n) => format!(" rowspan={}", n),
+ None => String::new(),
+ };
+ write!(
+ f,
+ "<{}{}{}{}>{}</{}>",
+ tag_name, colspan, rowspan, self.header, self.content, tag_name
+ )?;
Ok(())
}
}
+
+impl fmt::Display for TableHeader {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.h_align.is_none() && self.v_align.is_none() {
+ match &self.attributes {
+ Some(attributes) => write!(f, "{}", attributes),
+ None => Ok(()),
+ }
+ } else {
+ let h_align = match &self.h_align {
+ Some(align) => format!("{}", align),
+ None => String::new(),
+ };
+ let v_align = match &self.v_align {
+ Some(align) => format!("{}", align),
+ None => String::new(),
+ };
+ match &self.attributes {
+ Some(attributes) => {
+ if let Some(s) = &attributes.class {
+ write!(f, " class=\"{}\"", s)?;
+ }
+ if let Some(s) = &attributes.id {
+ write!(f, " id=\"{}\"", s)?;
+ }
+ if let Some(s) = &attributes.style {
+ write!(f, " style=\"{}{} {}\"", h_align, v_align, s)?;
+ }
+ if let Some(s) = &attributes.language {
+ write!(f, " lang=\"{}\"", s)?;
+ }
+ }
+ None => {
+ write!(f, " style=\"{}{}\"", h_align, v_align)?;
+ }
+ }
+ Ok(())
+ }
+ }
+}
+
+impl fmt::Display for VerticalAlign {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ VerticalAlign::Top => write!(f, "vertical-align: top;"),
+ VerticalAlign::Middle => write!(f, "vertical-align: middle;"),
+ VerticalAlign::Bottom => write!(f, "vertical-align: bottom;"),
+ }
+ }
+}
M src/structs.rs => src/structs.rs +25 -18
@@ 5,28 5,20 @@ pub struct Textile(pub Vec<BlockTag>);
pub enum BlockTag {
Basic {
kind: BlockKind,
- indent: Option<Indent>,
- align: Option<Align>,
- attributes: Option<Attributes>,
+ header: BlockHeader,
content: InlineTag,
},
Preformatted {
kind: BlockKind,
- indent: Option<Indent>,
- align: Option<Align>,
- attributes: Option<Attributes>,
+ header: BlockHeader,
content: String,
},
List {
- indent: Option<Indent>,
- align: Option<Align>,
- attributes: Option<Attributes>,
+ header: BlockHeader,
content: List,
},
Table {
- indent: Option<Indent>,
- align: Option<Align>,
- attributes: Option<Attributes>,
+ header: BlockHeader,
rows: Vec<TableRow>,
},
NoTextile(String),
@@ 44,6 36,13 @@ pub enum BlockKind {
}
#[derive(Debug, PartialEq)]
+pub struct BlockHeader {
+ pub attributes: Option<Attributes>,
+ pub indent: Option<Indent>,
+ pub align: Option<Align>,
+}
+
+#[derive(Debug, PartialEq)]
pub struct List {
pub kind: ListKind,
pub items: Vec<ListItem>,
@@ 63,10 62,15 @@ pub enum ListKind {
#[derive(Debug, PartialEq)]
pub struct TableRow {
+ pub header: TableHeader,
+ pub cells: Vec<TableCell>,
+}
+
+#[derive(Debug, PartialEq)]
+pub struct TableHeader {
pub attributes: Option<Attributes>,
pub h_align: Option<Align>,
pub v_align: Option<VerticalAlign>,
- pub cells: Vec<TableCell>,
}
#[derive(Debug, PartialEq)]
@@ 74,9 78,7 @@ pub struct TableCell {
pub kind: CellKind,
pub col_span: Option<usize>,
pub row_span: Option<usize>,
- pub attributes: Option<Attributes>,
- pub h_align: Option<Align>,
- pub v_align: Option<VerticalAlign>,
+ pub header: TableHeader,
pub content: InlineTag,
}
@@ 123,8 125,7 @@ pub enum InlineTag {
FootnoteRef(usize),
LineBreak,
Image {
- attributes: Option<Attributes>,
- align: Option<ImageAlign>,
+ header: ImageHeader,
url: String,
alt: Option<String>,
},
@@ 145,6 146,12 @@ pub enum InlineTag {
},
}
+#[derive(Debug, PartialEq)]
+pub struct ImageHeader {
+ pub attributes: Option<Attributes>,
+ pub align: Option<ImageAlign>,
+}
+
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ImageAlign {
Left,