~autumnull/flatiron

15d28c1129feee242ed1a31f6f3541c627a06faf — Autumn! 2 years ago f8872c9
Added parsing for lists
3 files changed, 140 insertions(+), 25 deletions(-)

M README.textile
M src/parse.rs
M tests/textile.rs
M README.textile => README.textile +0 -1
@@ 26,7 26,6 @@ Find any "lice (bugs)":https://en.wikipedia.org/wiki/Clothes_iron#Hygiene ? I al
h3. TODO

* Parsing
** Lists
** Tables
* Rendering
** Everything

M src/parse.rs => src/parse.rs +116 -9
@@ 2,8 2,10 @@ use nom::{
    branch::alt,
    bytes::complete::{escaped_transform, tag, take_while1, take_while_m_n},
    character::complete::{char, line_ending, none_of, satisfy},
    combinator::{complete, eof, fail, map_res, opt, value},
    multi::{fold_many1, fold_many_m_n, many0, many0_count, many_till},
    combinator::{complete, eof, fail, map, map_res, opt, value},
    multi::{
        fold_many1, fold_many_m_n, many0, many0_count, many1_count, many_till,
    },
    sequence::{delimited, preceded, terminated, tuple},
    IResult,
};


@@ 59,7 61,7 @@ pub struct ListItem {
    pub sublist: Option<List>,
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum ListKind {
    Numeric,
    Bulleted,


@@ 71,7 73,7 @@ pub struct Indent {
    pub right: usize,
}

#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Align {
    Left,
    Right,


@@ 79,7 81,7 @@ pub enum Align {
    Justify,
}

#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum VerticalAlign {
    Top,
    Middle,


@@ 195,6 197,11 @@ pub fn block(input: &str) -> IResult<&str, BlockTag> {
    if input.is_empty() {
        return fail(input);
    }

    if let Ok((rest, list)) = list(input) {
        return Ok((rest, list));
    }

    let (rest, opt_header) = opt(block_header)(input)?;
    let (kind, attributes, opt_indent_align, extended) =
        opt_header.unwrap_or((BlockKind::Paragraph, None, None, false));


@@ 266,6 273,102 @@ pub fn block(input: &str) -> IResult<&str, BlockTag> {
    }
}

fn list(input: &str) -> IResult<&str, BlockTag> {
    let (rest, (kind, attributes, opt_indent_align)) = terminated(
        tuple((
            alt((
                value(ListKind::Numeric, char('#')),
                value(ListKind::Bulleted, char('*')),
            )),
            opt(attributes),
            opt(indent_align),
        )),
        char(' '),
    )(input)?;
    let (indent, align) = opt_indent_align.unwrap_or((None, None));

    let mut all_items: Vec<(ListKind, usize, InlineTag)> = Vec::new();
    // get first item
    let (rest, first_content) = list_item_content(rest)?;
    all_items.push((kind, 1, first_content));

    // collect all items with depth information
    let mut input = rest;
    while let Err(_) = end_of_block(input) {
        let (rest, (item_kind, depth)) = list_item_head(input)?;
        let (rest, content) = list_item_content(rest)?;
        all_items.push((item_kind, depth, content));
        input = rest;
    }

    // convert flat items list to nested list
    match list_items_to_nested_list(&mut all_items) {
        Ok(l) => Ok((
            input,
            BlockTag::List {
                indent,
                align,
                attributes,
                content: l,
            },
        )),
        Err(_) => fail(input),
    }
}

fn list_item_head(input: &str) -> IResult<&str, (ListKind, usize)> {
    delimited(
        line_ending,
        alt((
            map(many1_count(char('#')), |n| (ListKind::Numeric, n)),
            map(many1_count(char('*')), |n| (ListKind::Bulleted, n)),
        )),
        char(' '),
    )(input)
}

fn list_item_content(input: &str) -> IResult<&str, InlineTag> {
    let mut i = 0;
    while let Err(_) = end_list_item(&input[i..]) {
        i += 1
    }
    let (_, content) = phrase(&input[..i])?;
    Ok((&input[i..], content))
}

fn end_list_item(input: &str) -> IResult<&str, &str> {
    alt((value("", list_item_head), end_of_block))(input)
}

fn list_items_to_nested_list(
    list: &mut Vec<(ListKind, usize, InlineTag)>,
) -> Result<List, String> {
    let mut items: Vec<ListItem> = Vec::new();
    let kind = list[0].0;
    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
            items.last_mut().unwrap().sublist =
                Some(list_items_to_nested_list(list)?);
        } else if list[0].1 < depth {
            break;
        } else if list[0].0 != kind {
            return Err(format!(
                "List uses mixed markers on same level: {:?}",
                list[0].2
            ));
        } else {
            let (_, _, content) = list.remove(0);
            items.push(ListItem {
                content,
                sublist: None,
            })
        }
    }
    Ok(List { kind, items })
}

fn block_header(
    input: &str,
) -> IResult<


@@ 347,6 450,7 @@ fn indent_align(
            break;
        }
    }

    if opt_indent.is_some() || opt_align.is_some() {
        Ok((input, (opt_indent, opt_align)))
    } else {


@@ 483,7 587,11 @@ fn language(input: &str) -> IResult<&str, String> {
}

fn end_of_block(input: &str) -> IResult<&str, &str> {
    alt((eof, value("", tuple((line_ending, line_ending)))))(input)
    alt((
        eof,
        value("", tuple((line_ending, line_ending))),
        value("", tuple((line_ending, eof))),
    ))(input)
}

fn strip_flatiron_extended(input: &str) -> IResult<&str, String> {


@@ 492,11 600,10 @@ fn strip_flatiron_extended(input: &str) -> IResult<&str, String> {

pub fn phrase(input: &str) -> IResult<&str, InlineTag> {
    let mut input_string = input.to_string();
    if !(input.contains("\n") && !input.contains("\n|")) {
        input_string = input.replace("\n|", "\n");
    if let Ok((_, stripped)) = complete(strip_flatiron_extended)(input) {
        input_string = stripped;
    }
    let mut input = input_string.as_str();
    println!("{}", input);

    let mut content: Vec<InlineTag> = Vec::new();


M tests/textile.rs => tests/textile.rs +24 -15
@@ 274,7 274,14 @@ fn textile_sample() {
                    indent: None,
                    align: None,
                    attributes: None,
                    content: InlineTag::Plaintext(String::from("\"This is a link (optional title)\":http://www.textism.com"))
                    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,


@@ 285,7 292,9 @@ fn textile_sample() {
                        kind: None,
                        attributes: None,
                        content: vec![
                            InlineTag::Plaintext(String::from("table{border:1px solid black}.\n|")),
                            InlineTag::Plaintext(String::from("table{border:1px solid black}.")),
                            InlineTag::LineBreak,
                            InlineTag::Plaintext(String::from("|")),
                            InlineTag::Phrase {
                                kind: Some(PhraseKind::Emphasis),
                                attributes: None,


@@ 301,12 310,16 @@ fn textile_sample() {
                                    InlineTag::Plaintext(String::from(". a|"))
                                ]
                            },
                            InlineTag::Plaintext(String::from(". header|\n<{background:gray}. |\\2. this is|{background:red;width:200px}. 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|\n|this|<>{padding:10px}. is|"))
                                    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|"))


@@ 325,17 338,11 @@ fn textile_sample() {
                    indent: None,
                    align: None,
                    attributes: None,
                    content: InlineTag::Phrase {
                        kind: None,
                    content: InlineTag::Image {
                        attributes: None,
                        content: vec![
                            InlineTag::Image {
                                attributes: None,
                                align: None,
                                url: String::from("/textist.gif"),
                                alt: Some(String::from("optional alt text"))
                            }
                        ]
                        align: None,
                        url: String::from("/textist.gif"),
                        alt: Some(String::from("optional alt text"))
                    }
                },
                BlockTag::List{


@@ 382,7 389,9 @@ fn textile_sample() {
                                    InlineTag::Plaintext(String::from("emphasize"))
                                ]
                            },
                            InlineTag::Plaintext(String::from(".\nThat was a linebreak. And something to indicate ")),
                            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,