@@ 1,10 1,13 @@
-use crate::structs::{InlineTag, PhraseKind, Textile};
+use crate::structs::{Align, InlineTag, PhraseKind, Textile};
use nom::{
+ branch::alt,
bytes::complete::tag,
character::complete::line_ending,
+ combinator::{fail, value},
+ error::ParseError,
multi::many0,
sequence::{preceded, terminated},
- IResult,
+ IResult, Parser,
};
mod acronym;
@@ 81,11 84,61 @@ fn from_num(input: &str) -> Result<usize, std::num::ParseIntError> {
usize::from_str_radix(input, 10)
}
+pub fn alignment(input: &str) -> IResult<&str, Align> {
+ alt((
+ value(Align::Justify, tag("<>")),
+ value(Align::Left, tag("<")),
+ value(Align::Right, tag(">")),
+ value(Align::Center, tag("=")),
+ ))(input)
+}
+
+fn opt_either_order<I, O, P, E, F, G>(
+ mut f: F,
+ mut g: G,
+) -> impl FnMut(I) -> IResult<I, (Option<O>, Option<P>)>
+where
+ I: Clone,
+ F: Parser<I, O, E>,
+ G: Parser<I, P, E>,
+ E: ParseError<I>,
+{
+ move |mut input: I| {
+ let (mut opt_f, mut opt_g) = (None, None);
+ loop {
+ if let Ok((rest, i)) = f.parse(input.clone()) {
+ if opt_f.is_none() {
+ opt_f = Some(i);
+ input = rest;
+ } else {
+ return Ok((input, (opt_f, opt_g)));
+ }
+ } else if let Ok((rest, a)) = g.parse(input.clone()) {
+ if opt_g.is_none() {
+ opt_g = Some(a);
+ input = rest;
+ } else {
+ return Ok((input, (opt_f, opt_g)));
+ }
+ } else {
+ break;
+ }
+ }
+
+ if opt_f.is_some() || opt_g.is_some() {
+ Ok((input, (opt_f, opt_g)))
+ } else {
+ fail(input)
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
use crate::structs::{
- Attributes, BlockKind, BlockTag, List, ListItem, ListKind,
+ Attributes, BlockKind, BlockTag, CellKind, List, ListItem, ListKind,
+ TableCell, TableRow, VerticalAlign,
};
#[test]
@@ 145,427 198,543 @@ This is a paragraph with some _emphasized text_.";
fn textile_sample() {
let input = include_str!("../../samples/textism.textile");
let result = textile(input);
- assert_eq!(
- result,
- Ok((
- "",
- 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
- }),
- content: InlineTag::Plaintext(String::from("This is a title"))
- },
- BlockTag::Basic {
- kind: BlockKind::Header(3),
- indent: None,
- align: None,
+ 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
+ }),
+ content: InlineTag::Plaintext(String::from("This is a title"))
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Header(3),
+ indent: None,
+ align: None,
+ attributes: 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
+ }),
+ 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"))
+ }),
+ 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,
+ indent: None,
+ align: None,
+ attributes: 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
+ }),
+ content: List {
+ kind: ListKind::Numeric,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("one")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("two")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("three")),
+ sublist: None,
+ },
+ ]
+ }
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Plaintext(String::from("Multi-level list:"))
+ },
+ BlockTag::List{
+ indent: None,
+ align: None,
+ attributes: None,
+ content: List {
+ kind: ListKind::Numeric,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("one")),
+ sublist: Some(List {
+ kind: ListKind::Numeric,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("aye")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("bee")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("see")),
+ sublist: None,
+ }
+ ],
+ }),
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("two")),
+ sublist: Some(List {
+ kind: ListKind::Numeric,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("x")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("y")),
+ sublist: None,
+ },
+ ],
+ }),
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("three")),
+ sublist: None,
+ },
+ ]
+ }
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Plaintext(String::from("Mixed list:"))
+ },
+ BlockTag::List{
+ indent: None,
+ align: None,
+ attributes: None,
+ content: List {
+ kind: ListKind::Bulleted,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Point one")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Point two")),
+ sublist: Some(List {
+ kind: ListKind::Numeric,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Step 1")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Step 2")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Step 3")),
+ sublist: None,
+ }
+ ],
+ }),
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Point three")),
+ sublist: Some(List {
+ kind: ListKind::Bulleted,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Sub point 1")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Sub point 2")),
+ sublist: None,
+ },
+ ],
+ }),
+ },
+ ]
+ }
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Phrase {
+ kind: None,
+ attributes: None,
+ content: vec![
+ InlineTag::Plaintext(String::from("Well, that went well. How about we insert an ")),
+ InlineTag::NoTextile(String::from("<a href=\"http://www.textism.com/\" title=\"watch out\">old-fashioned hypertext link</a>")),
+ InlineTag::Plaintext(String::from("? Will the quote marks in the tags get messed up? No!")),
+ ]
+ }
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Link {
+ attributes: None,
+ title: Some(String::from("optional title")),
+ url: String::from("http://www.textism.com"),
+ content: Box::new(
+ InlineTag::Plaintext(String::from("This is a link"))
+ )
+ }
+ },
+ BlockTag::Table {
+ 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,
- content: InlineTag::Plaintext(String::from("This is a subhead"))
+ 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,
+ content: InlineTag::Plaintext(String::from("this"))
+ },
+ TableCell {
+ kind: CellKind::Header,
+ col_span: None,
+ row_span: None,
+ 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,
+ content: InlineTag::Plaintext(String::from("a"))
+ },
+ TableCell {
+ kind: CellKind::Header,
+ col_span: None,
+ row_span: None,
+ attributes: None,
+ h_align: None,
+ v_align: None,
+ content: InlineTag::Plaintext(String::from("header"))
+ },
+ ]
},
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: Some(Attributes {
- class: None,
- id: None,
- style: Some(String::from("color:red")),
- language: 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 {
+ TableRow {
+ attributes: Some(Attributes{
class: None,
id: None,
- style: None,
- language: Some(String::from("fr"))
+ style: Some(String::from("background:gray")),
+ language: None,
}),
- content: InlineTag::Plaintext(String::from("This is a block quote. I'll admit it's not the most exciting block quote ever devised."))
+ 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,
+ 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,
+ 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),
+ content: InlineTag::Plaintext(String::from("row"))
+ },
+ ]
},
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
+ TableRow {
attributes: 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
- }),
- content: List {
- kind: ListKind::Numeric,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("one")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("two")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("three")),
- sublist: None,
- },
- ]
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Plaintext(String::from("Multi-level list:"))
- },
- BlockTag::List{
- indent: None,
- align: None,
- attributes: None,
- content: List {
- kind: ListKind::Numeric,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("one")),
- sublist: Some(List {
- kind: ListKind::Numeric,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("aye")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("bee")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("see")),
- sublist: None,
- }
- ],
- }),
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("two")),
- sublist: Some(List {
- kind: ListKind::Numeric,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("x")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("y")),
- sublist: None,
- },
- ],
- }),
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("three")),
- sublist: None,
- },
- ]
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Plaintext(String::from("Mixed list:"))
- },
- BlockTag::List{
- indent: None,
- align: None,
- attributes: None,
- content: List {
- kind: ListKind::Bulleted,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("Point one")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("Point two")),
- sublist: Some(List {
- kind: ListKind::Numeric,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("Step 1")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("Step 2")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("Step 3")),
- sublist: None,
- }
- ],
- }),
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("Point three")),
- sublist: Some(List {
- kind: ListKind::Bulleted,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("Sub point 1")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("Sub point 2")),
- sublist: None,
- },
- ],
- }),
- },
- ]
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Phrase {
- kind: None,
- attributes: None,
- content: vec![
- InlineTag::Plaintext(String::from("Well, that went well. How about we insert an ")),
- InlineTag::NoTextile(String::from("<a href=\"http://www.textism.com/\" title=\"watch out\">old-fashioned hypertext link</a>")),
- InlineTag::Plaintext(String::from("? Will the quote marks in the tags get messed up? No!")),
- ]
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Link {
- attributes: None,
- title: Some(String::from("optional title")),
- url: String::from("http://www.textism.com"),
- content: Box::new(
- InlineTag::Plaintext(String::from("This is a link"))
- )
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Phrase {
- kind: None,
- attributes: None,
- content: vec![
- InlineTag::Plaintext(String::from("table{border:1px solid black}.")),
- InlineTag::LineBreak,
- InlineTag::Plaintext(String::from("|")),
- InlineTag::Phrase {
- kind: Some(PhraseKind::Emphasis),
- attributes: None,
- content: vec![
- InlineTag::Plaintext(String::from(". this|"))
- ]
- },
- InlineTag::Plaintext(String::from(". is|")),
- InlineTag::Phrase {
- kind: Some(PhraseKind::Emphasis),
- attributes: None,
- content: vec![
- InlineTag::Plaintext(String::from(". a|"))
- ]
- },
- InlineTag::Plaintext(String::from(". header|")),
- InlineTag::LineBreak,
- InlineTag::Plaintext(String::from("<{background:gray}. |\\2. this is|{background:red;width:200px}. a|")),
- InlineTag::Phrase {
- kind: Some(PhraseKind::Superscript),
- attributes: None,
- content: vec![
- InlineTag::Plaintext(String::from("<>{height:200px}. row|")),
- InlineTag::LineBreak,
- InlineTag::Plaintext(String::from("|this|<>{padding:10px}. is|"))
- ]
- },
- InlineTag::Plaintext(String::from(". another|(bob#bob). row|"))
- ]
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Plaintext(String::from("An image:"))
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Image {
- attributes: None,
- align: None,
- url: String::from("/textist.gif"),
- alt: Some(String::from("optional alt text"))
- }
- },
- BlockTag::List{
- indent: None,
- align: None,
- attributes: None,
- content: List {
- kind: ListKind::Numeric,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("Librarians rule")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("Yes they do")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("But you knew that")),
- sublist: None,
- },
- ]
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Phrase {
- kind: 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,
+ 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,
+ 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),
+ 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,
+ content: InlineTag::Plaintext(String::from("row"))
+ },
+ ]
+ }
+ ],
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Plaintext(String::from("An image:"))
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Image {
+ attributes: None,
+ align: None,
+ url: String::from("/textist.gif"),
+ alt: Some(String::from("optional alt text"))
+ }
+ },
+ BlockTag::List{
+ indent: None,
+ align: None,
+ attributes: None,
+ content: List {
+ kind: ListKind::Numeric,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Librarians rule")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("Yes they do")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("But you knew that")),
+ sublist: None,
+ },
+ ]
+ }
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Phrase {
+ kind: None,
+ attributes: None,
+ content: vec![
+ InlineTag::Plaintext(String::from("Some more text of dubious character. Here is a noisome string of ")),
+ InlineTag::Acronym {
+ title: None,
+ content: String::from("CAPITAL")
+ },
+ InlineTag::Plaintext(String::from(" letters. Here is something we want to ")),
+ InlineTag::Phrase {
+ kind: Some(PhraseKind::Emphasis),
attributes: None,
content: vec![
- InlineTag::Plaintext(String::from("Some more text of dubious character. Here is a noisome string of ")),
- InlineTag::Acronym {
- title: None,
- content: String::from("CAPITAL")
- },
- InlineTag::Plaintext(String::from(" letters. Here is something we want to ")),
- InlineTag::Phrase {
- kind: Some(PhraseKind::Emphasis),
- attributes: None,
- content: vec![
- InlineTag::Plaintext(String::from("emphasize"))
- ]
- },
- InlineTag::Plaintext(String::from(".")),
- InlineTag::LineBreak,
- InlineTag::Plaintext(String::from("That was a linebreak. And something to indicate ")),
- InlineTag::Phrase {
- kind: Some(PhraseKind::Strong),
- attributes: None,
- content: vec![
- InlineTag::Plaintext(String::from("strength"))
- ]
- },
- InlineTag::Plaintext(String::from(". Of course I could use <em>my own ")),
- InlineTag::Acronym {
- title: None,
- content: String::from("HTML")
- },
- InlineTag::Plaintext(String::from(" tags</em> if I <strong>felt</strong> like it."))
+ InlineTag::Plaintext(String::from("emphasize"))
]
- }
- },
- BlockTag::Basic {
- kind: BlockKind::Header(3),
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Plaintext(String::from("Coding"))
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Phrase {
- kind: None,
+ },
+ InlineTag::Plaintext(String::from(".")),
+ InlineTag::LineBreak,
+ InlineTag::Plaintext(String::from("That was a linebreak. And something to indicate ")),
+ InlineTag::Phrase {
+ kind: Some(PhraseKind::Strong),
attributes: None,
content: vec![
- InlineTag::Plaintext(String::from("This ")),
- InlineTag::Code(String::from("is some code, \"isn't it\"")),
- InlineTag::Plaintext(String::from(". Watch those quote marks! Now for some preformatted text:"))
- ]
- }
- },
- BlockTag::Preformatted {
- kind: BlockKind::BlockCode,
- indent: None,
- align: None,
- attributes: 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,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Plaintext(String::from("This isn't code."))
- },
- BlockTag::Basic {
- kind: BlockKind::Paragraph,
- indent: None,
- align: None,
- attributes: None,
- content: InlineTag::Plaintext(String::from("So you see, my friends:"))
- },
- BlockTag::List {
- indent: None,
- align: None,
- attributes: None,
- content: List {
- kind: ListKind::Bulleted,
- items: vec![
- ListItem {
- content: InlineTag::Plaintext(String::from("The time is now")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("The time is not later")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("The time is not yesterday")),
- sublist: None,
- },
- ListItem {
- content: InlineTag::Plaintext(String::from("We must act")),
- sublist: None,
- },
+ InlineTag::Plaintext(String::from("strength"))
]
- }
- }
- ])
- ))
- );
+ },
+ InlineTag::Plaintext(String::from(". Of course I could use <em>my own ")),
+ InlineTag::Acronym {
+ title: None,
+ content: String::from("HTML")
+ },
+ InlineTag::Plaintext(String::from(" tags</em> if I <strong>felt</strong> like it."))
+ ]
+ }
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Header(3),
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Plaintext(String::from("Coding"))
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Phrase {
+ kind: None,
+ attributes: None,
+ content: vec![
+ InlineTag::Plaintext(String::from("This ")),
+ InlineTag::Code(String::from("is some code, \"isn't it\"")),
+ InlineTag::Plaintext(String::from(". Watch those quote marks! Now for some preformatted text:"))
+ ]
+ }
+ },
+ BlockTag::Preformatted {
+ kind: BlockKind::BlockCode,
+ indent: None,
+ align: None,
+ attributes: 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,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Plaintext(String::from("This isn't code."))
+ },
+ BlockTag::Basic {
+ kind: BlockKind::Paragraph,
+ indent: None,
+ align: None,
+ attributes: None,
+ content: InlineTag::Plaintext(String::from("So you see, my friends:"))
+ },
+ BlockTag::List {
+ indent: None,
+ align: None,
+ attributes: None,
+ content: List {
+ kind: ListKind::Bulleted,
+ items: vec![
+ ListItem {
+ content: InlineTag::Plaintext(String::from("The time is now")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("The time is not later")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("The time is not yesterday")),
+ sublist: None,
+ },
+ ListItem {
+ content: InlineTag::Plaintext(String::from("We must act")),
+ sublist: None,
+ },
+ ]
+ }
+ }
+ ];
+ match result {
+ Ok((_, Textile(v))) => {
+ for i in 0..v.len() {
+ assert_eq!(v[i], blocks[i]);
+ }
+ }
+ _ => assert_eq!(result, Ok(("", Textile(vec![])))),
+ }
}
}
@@ 1,5 1,177 @@
-use crate::structs::VerticalAlign;
-use nom::{branch::alt, bytes::complete::tag, combinator::value, IResult};
+use crate::parse::{
+ alignment,
+ attributes::attributes,
+ block::{end_of_block, indent_align},
+ from_num, opt_either_order,
+ phrase::phrase,
+};
+use crate::structs::{
+ Align, Attributes, BlockTag, CellKind, Indent, InlineTag, TableCell,
+ TableRow, VerticalAlign,
+};
+use nom::{
+ branch::alt,
+ bytes::complete::{escaped_transform, tag, take_while1},
+ character::complete::{char, line_ending, none_of},
+ combinator::{complete, fail, map_res, opt, value},
+ multi::{many1, many_till},
+ sequence::{preceded, terminated, tuple},
+ IResult,
+};
+
+pub fn table(input: &str) -> IResult<&str, BlockTag> {
+ let (rest, (opt_header, rows)) =
+ tuple((opt(table_head), table_body))(input)?;
+ let (attributes, align, indent) = opt_header.unwrap_or((None, None, None));
+ Ok((
+ rest,
+ BlockTag::Table {
+ indent,
+ align,
+ attributes,
+ rows,
+ },
+ ))
+}
+
+fn table_head(
+ input: &str,
+) -> IResult<&str, (Option<Attributes>, Option<Align>, Option<Indent>)> {
+ let (rest, (attributes, indent_align)) = terminated(
+ tuple((opt(attributes), opt(indent_align))),
+ tuple((char('.'), line_ending)),
+ )(input)?;
+ let (indent, align) = indent_align.unwrap_or((None, None));
+ Ok((rest, (attributes, align, indent)))
+}
+
+fn table_body(input: &str) -> IResult<&str, Vec<TableRow>> {
+ many1(table_row)(input)
+}
+
+fn table_row(input: &str) -> IResult<&str, TableRow> {
+ let (rest, (opt_header, cells)) = tuple((opt(row_head), row_body))(input)?;
+ let (attributes, h_align, v_align) =
+ opt_header.unwrap_or((None, None, None));
+ Ok((
+ rest,
+ TableRow {
+ attributes,
+ h_align,
+ v_align,
+ cells,
+ },
+ ))
+}
+
+fn row_head(
+ input: &str,
+) -> IResult<&str, (Option<Attributes>, Option<Align>, Option<VerticalAlign>)> {
+ let (rest, (attributes, aligns)) = terminated(
+ tuple((opt(attributes), opt(table_aligns))),
+ tag(". "),
+ )(input)?;
+ let (h_align, v_align) = aligns.unwrap_or((None, None));
+ Ok((rest, (attributes, h_align, v_align)))
+}
+
+fn row_body(input: &str) -> IResult<&str, Vec<TableCell>> {
+ let (rest, (cells, _end)) = many_till(
+ table_cell,
+ tuple((char('|'), alt((line_ending, end_of_block)))),
+ )(input)?;
+ Ok((rest, cells))
+}
+
+fn table_cell(input: &str) -> IResult<&str, TableCell> {
+ let (rest, (opt_header, content)) =
+ preceded(char('|'), tuple((opt(cell_head), cell_body)))(input)?;
+ let (kind, col_span, row_span, attributes, h_align, v_align) =
+ opt_header.unwrap_or((CellKind::Data, None, None, None, None, None));
+ Ok((
+ rest,
+ TableCell {
+ kind,
+ col_span,
+ row_span,
+ attributes,
+ h_align,
+ v_align,
+ content,
+ },
+ ))
+}
+
+fn cell_head(
+ input: &str,
+) -> IResult<
+ &str,
+ (
+ CellKind,
+ Option<usize>,
+ Option<usize>,
+ Option<Attributes>,
+ Option<Align>,
+ Option<VerticalAlign>,
+ ),
+> {
+ let (rest, (kind, spans, attributes, aligns)) = terminated(
+ tuple((
+ alt((
+ value(CellKind::Header, char('_')),
+ value(CellKind::Data, tag("")),
+ )),
+ opt(cell_spans),
+ opt(attributes),
+ opt(table_aligns),
+ )),
+ tag(". "),
+ )(input)?;
+ let (h_align, v_align) = aligns.unwrap_or((None, None));
+ let (col_span, row_span) = spans.unwrap_or((None, None));
+ Ok((
+ rest,
+ (kind, col_span, row_span, attributes, h_align, v_align),
+ ))
+}
+
+fn cell_body(input: &str) -> IResult<&str, InlineTag> {
+ let (rest, body) = escaped_transform(
+ none_of("\\|"),
+ '\\',
+ alt((value("\\", tag("\\")), value("|", tag("|")))),
+ )(input)?;
+ let phrase_result = complete(phrase)(&*body);
+ if let Ok((_, content)) = phrase_result {
+ Ok((rest, content))
+ } else {
+ fail(input)
+ }
+}
+
+fn table_aligns(
+ input: &str,
+) -> IResult<&str, (Option<Align>, Option<VerticalAlign>)> {
+ opt_either_order(alignment, vertical_alignment)(input)
+}
+
+fn cell_spans(input: &str) -> IResult<&str, (Option<usize>, Option<usize>)> {
+ opt_either_order(col_span, row_span)(input)
+}
+
+fn col_span(input: &str) -> IResult<&str, usize> {
+ preceded(
+ char('\\'),
+ map_res(take_while1(|c: char| c.is_ascii_digit()), from_num),
+ )(input)
+}
+
+fn row_span(input: &str) -> IResult<&str, usize> {
+ preceded(
+ char('/'),
+ map_res(take_while1(|c: char| c.is_ascii_digit()), from_num),
+ )(input)
+}
fn vertical_alignment(input: &str) -> IResult<&str, VerticalAlign> {
alt((
@@ 8,3 180,41 @@ fn vertical_alignment(input: &str) -> IResult<&str, VerticalAlign> {
value(VerticalAlign::Bottom, tag("~")),
))(input)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn table_basic() {
+ let input = "|hello|";
+ let result = table(input);
+ assert_eq!(
+ result,
+ Ok((
+ "",
+ BlockTag::Table {
+ indent: None,
+ align: None,
+ attributes: None,
+ rows: vec![TableRow {
+ 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,
+ content: InlineTag::Plaintext(String::from(
+ "hello"
+ ))
+ }]
+ }]
+ }
+ ))
+ );
+ }
+}