use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::bytes::complete::take_while; use nom::character::complete::none_of; use nom::combinator::eof; use nom::combinator::peek; use nom::combinator::recognize; use super::Context; use crate::error::CustomError; use crate::error::MyError; use crate::error::Res; use crate::parser::object::PlainLink; use crate::parser::parser_with_context::parser_with_context; use crate::parser::util::get_consumed; use crate::parser::util::get_one_before; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; #[tracing::instrument(ret, level = "debug")] pub fn plain_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainLink<'s>> { let (remaining, _) = pre(context, input)?; let (remaining, proto) = protocol(context, remaining)?; let (remaining, _separator) = tag(":")(remaining)?; let (remaining, path) = path_plain(context, remaining)?; peek(parser_with_context!(post)(context))(remaining)?; let source = get_consumed(input, remaining); Ok(( remaining, PlainLink { source, link_type: proto, path, }, )) } #[tracing::instrument(ret, level = "debug")] pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { let document_root = context.get_document_root().unwrap(); let preceding_character = get_one_before(document_root, input) .map(|slice| slice.chars().next()) .flatten(); match preceding_character { // If None, we are at the start of the file which is fine None => {} Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {} Some(_) => { // Not at start of line, cannot be a heading return Err(nom::Err::Error(CustomError::MyError(MyError( "Not a valid pre character for plain link.", )))); } }; Ok((input, ())) } #[tracing::instrument(ret, level = "debug")] pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> { let (remaining, _) = alt((eof, recognize(none_of(WORD_CONSTITUENT_CHARACTERS))))(input)?; Ok((remaining, ())) } #[tracing::instrument(ret, level = "debug")] pub fn protocol<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { // TODO: This should be defined by org-link-parameters let (remaining, proto) = alt(( alt(( tag_no_case("id"), tag_no_case("eww"), tag_no_case("rmail"), tag_no_case("mhe"), tag_no_case("irc"), tag_no_case("info"), tag_no_case("gnus"), tag_no_case("docview"), tag_no_case("bibtex"), tag_no_case("bbdb"), tag_no_case("w3m"), )), alt(( tag_no_case("doi"), tag_no_case("file+sys"), tag_no_case("file+emacs"), tag_no_case("shell"), tag_no_case("news"), tag_no_case("mailto"), tag_no_case("https"), tag_no_case("http"), tag_no_case("ftp"), tag_no_case("help"), tag_no_case("file"), tag_no_case("elisp"), )), ))(input)?; Ok((remaining, proto)) } #[tracing::instrument(ret, level = "debug")] pub fn path_plain<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { // TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring" take_while(|c| !" \t\r\n()[]<>".contains(c))(input) // recognize(many1(none_of(" \t\r\n()[]<>")))(input) }