subtext 0.3.5

A parser for the Subtext file format
Documentation
use crate::sequence::Sequence;

pub const CARRIAGE_RETURN_NEW_LINE_SEQUENCE: &[char] = &['\r', '\n'];
pub const SLASH_LINK_SEQUENCE: &[char] = &['/'];
pub const WIKI_LINK_OPEN_SEQUENCE: &[char] = &['[', '['];
pub const WIKI_LINK_CLOSE_SEQUENCE: &[char] = &[']', ']'];
pub const HTTPS_LINK_SEQUENCE: &[char] = &['h', 't', 't', 'p', 's', ':', '/', '/'];
pub const HTTP_LINK_SEQUENCE: &[char] = &['h', 't', 't', 'p', ':', '/', '/'];
pub const IPFS_LINK_SEQUENCE: &[char] = &['i', 'p', 'f', 's', ':', '/', '/'];

pub fn sequence_to_predicate(mut sequence: Sequence) -> impl FnMut(&char) -> Option<usize> + '_ {
    move |token: &char| {
        sequence.go_to(token);
        match sequence.is_complete() {
            true => Some(sequence.len()),
            false => None,
        }
    }
}

pub fn first_predicate(
    mut predicates: Vec<impl FnMut(&char) -> Option<usize>>,
) -> impl FnMut(&char) -> Option<usize> {
    move |token: &char| {
        for predicate in predicates.iter_mut() {
            if let result @ Some(_) = predicate(&token) {
                return result;
            }
        }

        None
    }
}

pub fn tab_or_space_predicate() -> impl FnMut(&char) -> Option<usize> {
    move |token: &char| match token {
        ' ' | '\t' => Some(1usize),
        _ => None,
    }
}

pub fn new_line_predicate() -> impl FnMut(&char) -> Option<usize> {
    let mut is_carriage_return =
        sequence_to_predicate(Sequence::new(CARRIAGE_RETURN_NEW_LINE_SEQUENCE, None));

    move |token: &char| {
        if let matched @ Some(_) = is_carriage_return(token) {
            return matched;
        }

        if *token == '\n' {
            return Some(1usize);
        }

        None
    }
}

pub fn white_space_predicate() -> impl FnMut(&char) -> Option<usize> {
    let mut is_new_line = new_line_predicate();
    let mut is_white_space = tab_or_space_predicate();

    move |token: &char| {
        if let matched @ Some(_) = is_new_line(token) {
            return matched;
        }

        if let matched @ Some(_) = is_white_space(token) {
            return matched;
        }

        None
    }
}

pub fn wiki_link_delimiter_predicate() -> impl FnMut(&char) -> Option<usize> {
    let mut is_new_line = new_line_predicate();
    let mut is_wiki_link_close =
        sequence_to_predicate(Sequence::new(WIKI_LINK_CLOSE_SEQUENCE, None));

    move |token: &char| {
        if let matched @ Some(_) = is_new_line(token) {
            return matched;
        }

        if let matched @ Some(_) = is_wiki_link_close(token) {
            return matched;
        }

        None
    }
}

pub fn hyper_link_predicate() -> impl FnMut(&char) -> Option<usize> {
    first_predicate(Vec::from([
        sequence_to_predicate(Sequence::new(HTTP_LINK_SEQUENCE, Some(' '))),
        sequence_to_predicate(Sequence::new(HTTPS_LINK_SEQUENCE, Some(' '))),
        sequence_to_predicate(Sequence::new(IPFS_LINK_SEQUENCE, Some(' '))),
    ]))
}

pub fn wiki_link_open_predicate() -> impl FnMut(&char) -> Option<usize> {
    sequence_to_predicate(Sequence::new(WIKI_LINK_OPEN_SEQUENCE, Some(' ')))
}

pub fn slash_link_predicate() -> impl FnMut(&char) -> Option<usize> {
    sequence_to_predicate(Sequence::new(SLASH_LINK_SEQUENCE, Some(' ')))
}

#[derive(Debug)]
pub enum ParseLinkAs {
    SlashLink,
    HyperLink,
    WikiLink,
}

pub fn link_predicate() -> impl FnMut(&char) -> Option<(usize, ParseLinkAs)> {
    let mut is_hyper_link = hyper_link_predicate();
    let mut is_slash_link = slash_link_predicate();
    let mut is_wiki_link_open = wiki_link_open_predicate();

    move |token: &char| {
        if let Some(steps) = is_hyper_link(token) {
            return Some((steps, ParseLinkAs::HyperLink));
        }

        if let Some(steps) = is_slash_link(token) {
            return Some((steps, ParseLinkAs::SlashLink));
        }

        if let Some(steps) = is_wiki_link_open(token) {
            return Some((steps, ParseLinkAs::WikiLink));
        }

        None
    }
}