intext 0.0.0

In-text text formatting language.
Documentation
use crate::nodes::Node;
use crate::parsers::common::*;
use crate::parsers::tags::{tag_start_delimiter, tags};
use crate::plugins::PluginDispatch;
use crate::state::SharedState;
use chonk::prelude::*;

pub fn inline<'a>(state: SharedState) -> impl Parser<'a, Vec<Node>, Message<'a>> {
    move |ctx| {
        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()),
                    )),
                ),
            )),
        )
        .parse(ctx)
        .map_result(|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
        })
    }
}

pub fn tagged<'a, C>(state: SharedState, mut callback: C) -> impl Parser<'a, Node, Message<'a>>
where
    C: Parser<'a, Node, Message<'a>>,
{
    move |ctx| match callback.parse(ctx)? {
        (ctx, result) => match tag_start_delimiter().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, Message<'a>> {
    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, Message<'a>> {
    move |ctx| {
        delimited(is('*'), inline(state.clone()), is('*'))
            .parse(ctx)
            .map_result(|words| {
                state.borrow_mut().plugins.on_node_parsed(Node::Element {
                    name: "em".into(),
                    attributes: None,
                    tags: None,
                    children: Some(words),
                })
            })
    }
}

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

#[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, Message<'a>> {
    move |ctx: ParserContext<'a>| match find_when(is('`'), take(1.., is('`'))).parse(ctx.clone())? {
        (_, delimiter) => delimited(is(delimiter), take(1.., isnt(delimiter)), is(delimiter))
            .parse(ctx)
            .map_result(|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(),
                    }]),
                })
            }),
    }
}

#[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, Message<'a>> {
    move |ctx| {
        find_when(is('"'), delimited(is('"'), inline(state.clone()), is('"')))
            .parse(ctx)
            .map_result(|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)
                    },
                })
            })
    }
}

#[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, Message<'a>> {
    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, Message<'a>> {
    move |ctx| {
        take(1.., is(word_character)).parse(ctx).map_result(|word| {
            state
                .borrow_mut()
                .plugins
                .on_node_parsed(Node::Word { word: word.into() })
        })
    }
}

#[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();
}