etch 0.4.2

Not just a text formatter, don't mark it down, etch it.
Documentation
use crate::nodes::Node;
use crate::parsers::common::*;
use crate::parsers::tags::tags;
use crate::plugins::PluginDispatch;
use crate::state::SharedState;
use glue::prelude::*;
use glue::combinators::structures::*;

pub fn inline<'a>(state: SharedState) -> impl Parser<'a, Vec<Node>> {
    move |ctx| {
        map_result(
            find(
                1..,
                find_all((
                    take_all((
                        optional(find_when_not(take(2.., is('\n')), is('\n'))),
                        indent(0..),
                    )),
                    tagged(
                        state.clone(),
                        find_any((
                            emphasis(state.clone()),
                            quoted(state.clone()),
                            preformatted(state.clone()),
                            span(state.clone()),
                            word(state.clone()),
                        )),
                    ),
                )),
            ),
            |children| {
                let mut items = Vec::new();

                for child in children {
                    if child.0.len() > 0 {
                        items.push(Node::Whitespace {
                            space: child.0.into(),
                        });
                    }

                    items.push(child.1);
                }

                items
            },
        ).parse(ctx)
    }
}

pub fn tagged<'a, C>(state: SharedState, mut callback: C) -> impl Parser<'a, Node>
where
    C: Parser<'a, Node>,
{
    move |ctx| match callback.parse(ctx)? {
        (ctx, result) => match is('[').parse(ctx.clone()) {
            Ok((_, _)) => match tags(state.clone()).parse(ctx)? {
                (ctx, tags) => Ok((
                    ctx,
                    state
                        .borrow_mut()
                        .plugins
                        .on_node_tagged(result.with_tags(tags)),
                )),
            },
            Err((ctx, _)) => Ok((ctx, result)),
        },
    }
}

pub fn emphasis<'a>(state: SharedState) -> impl Parser<'a, Node> {
    move |ctx| {
        find_when(
            is('*'),
            find_any((
                strong_emphasis(state.clone()),
                normal_emphasis(state.clone()),
            )),
        ).parse(ctx)
    }
}

pub fn normal_emphasis<'a>(state: SharedState) -> impl Parser<'a, Node> {
    move |ctx| {
        map_result(
            delimited(is('*'), inline(state.clone()), is('*')),
            |words| {
                state.borrow_mut().plugins.on_node_parsed(Node::Element {
                    name: "em".into(),
                    attributes: None,
                    tags: None,
                    children: Some(words),
                })
            },
        ).parse(ctx)
    }
}

pub fn strong_emphasis<'a>(state: SharedState) -> impl Parser<'a, Node> {
    move |ctx| {
        map_result(
            delimited(is("**"), inline(state.clone()), is("**")),
            |words| {
                state.borrow_mut().plugins.on_node_parsed(Node::Element {
                    name: "strong".into(),
                    attributes: None,
                    tags: None,
                    children: Some(words),
                })
            },
        ).parse(ctx)
    }
}

#[test]
fn emphasis_parses() {
    use crate::state::State;

    let state = State::shareable();

    tagged(state.clone(), emphasis(state.clone()))
        .parse("**foo**[neat]")
        .map_result(|result| {
            assert_eq!(
                result,
                Node::Element {
                    name: "strong".into(),
                    attributes: None,
                    tags: Some(vec!["neat".into()]),
                    children: Some(vec![Node::Word { word: "foo".into() }])
                }
            )
        })
        .unwrap();

    tagged(state.clone(), emphasis(state.clone()))
        .parse("*foo*[neat]")
        .map_result(|result| {
            assert_eq!(
                result,
                Node::Element {
                    name: "em".into(),
                    attributes: None,
                    tags: Some(vec!["neat".into()]),
                    children: Some(vec![Node::Word { word: "foo".into() }])
                }
            )
        })
        .unwrap();
}

pub fn preformatted<'a>(state: SharedState) -> impl Parser<'a, Node> {
    move |ctx: ParserContext<'a>| match find_when(is('`'), take(1.., is('`'))).parse(ctx.clone())? {
        (_, delimiter) => map_result(
            delimited(is(delimiter), take(1.., isnt(delimiter)), is(delimiter)),
            |preformatted| {
                state.borrow_mut().plugins.on_node_parsed(Node::Element {
                    name: "code".into(),
                    attributes: None,
                    tags: None,
                    children: Some(vec![
                        Node::Text {
                            text: preformatted.into(),
                        },
                    ]),
                })
            },
        ).parse(ctx),
    }
}

#[test]
fn preformatted_parses() {
    use crate::state::State;

    let state = State::shareable();

    tagged(state.clone(), preformatted(state.clone()))
        .parse("`foo bar`[neat]")
        .map_result(|result| {
            assert_eq!(
                result,
                Node::Element {
                    name: "code".into(),
                    attributes: None,
                    tags: Some(vec!["neat".into()]),
                    children: Some(vec![
                        Node::Text {
                            text: "foo bar".into(),
                        },
                    ])
                }
            )
        })
        .unwrap();
}

pub fn quoted<'a>(state: SharedState) -> impl Parser<'a, Node> {
    move |ctx| {
        map_result(
            find_when(is('"'), delimited(is('"'), inline(state.clone()), is('"'))),
            |children| {
                state.borrow_mut().plugins.on_node_parsed(Node::Element {
                    name: "q".into(),
                    attributes: None,
                    tags: None,
                    children: if children.is_empty() {
                        None
                    } else {
                        Some(children)
                    },
                })
            },
        ).parse(ctx)
    }
}

#[test]
fn quoted_parses() {
    use crate::state::State;

    let state = State::shareable();

    tagged(state.clone(), quoted(state.clone()))
        .parse("\"foo bar\"[neat]")
        .map_result(|result| {
            assert_eq!(
                result,
                Node::Element {
                    name: "q".into(),
                    attributes: None,
                    tags: Some(vec!["neat".into()]),
                    children: Some(vec![
                        Node::Word { word: "foo".into() },
                        Node::Whitespace { space: " ".into() },
                        Node::Word { word: "bar".into() },
                    ]),
                }
            )
        })
        .unwrap();
}

pub fn span<'a>(state: SharedState) -> impl Parser<'a, Node> {
    move |ctx| {
        find_when(is('['), delimited(is('['), inline(state.clone()), is(']')))
            .parse(ctx)
            .map(|(ctx, children)| {
                (
                    ctx,
                    state.borrow_mut().plugins.on_node_parsed(Node::Span {
                        attributes: None,
                        tags: None,
                        children: if children.is_empty() {
                            None
                        } else {
                            Some(children)
                        },
                    }),
                )
            })
    }
}

#[test]
fn span_parses() {
    use crate::state::State;

    let state = State::shareable();

    tagged(state.clone(), span(state.clone()))
        .parse("[foo bar][neat]")
        .map_result(|result| {
            assert_eq!(
                result,
                Node::Span {
                    attributes: None,
                    tags: Some(vec!["neat".into()]),
                    children: Some(vec![
                        Node::Word { word: "foo".into() },
                        Node::Whitespace { space: " ".into() },
                        Node::Word { word: "bar".into() },
                    ])
                }
            )
        })
        .unwrap();
}

pub fn word_character(character: char) -> bool {
    !whitespace(character) && character != '[' && character != ']' && character != '*'
        && character != '`' && character != '"' && character != '#'
}

pub fn word<'a>(state: SharedState) -> impl Parser<'a, Node> {
    move |ctx| {
        map_result(take(1.., is(word_character)), |word| {
            state
                .borrow_mut()
                .plugins
                .on_node_parsed(Node::Word { word: word.into() })
        }).parse(ctx)
    }
}

#[test]
fn word_parses() {
    use crate::state::State;

    let state = State::shareable();

    word(state.clone())
        .parse("foo")
        .map_result(|result| assert_eq!(result, Node::Word { word: "foo".into() }))
        .unwrap();

    tagged(state.clone(), word(state.clone()))
        .parse("foo[neat]")
        .map_result(|result| {
            assert_eq!(
                result,
                Node::Span {
                    attributes: None,
                    tags: Some(vec!["neat".into()]),
                    children: Some(vec![Node::Word { word: "foo".into() }]),
                }
            )
        })
        .unwrap();
}