use nom::branch::alt; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; use nom::character::complete::anychar; use nom::character::complete::none_of; use nom::character::complete::one_of; use nom::combinator::eof; use nom::combinator::peek; use nom::combinator::recognize; use nom::combinator::verify; use nom::multi::many_till; use super::org_source::OrgSource; use crate::context::parser_with_context; use crate::context::ContextElement; use crate::context::ExitClass; use crate::context::ExitMatcherNode; use crate::context::RefContext; use crate::error::CustomError; use crate::error::MyError; use crate::error::Res; use crate::parser::util::exit_matcher_parser; use crate::parser::util::get_consumed; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::types::PlainLink; // TODO: Make this a user-provided variable corresponding to elisp's org-link-parameters const ORG_LINK_PARAMETERS: [&'static str; 23] = [ "id", "eww", "rmail", "mhe", "irc", "info", "gnus", "docview", "bibtex", "bbdb", "w3m", "doi", "file+sys", "file+emacs", "shell", "news", "mailto", "https", "http", "ftp", "help", "file", "elisp", ]; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn plain_link<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, 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: source.into(), link_type: proto.into(), path: path.into(), }, )) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn pre<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, ()> { let preceding_character = input.get_preceding_character(); 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.".into(), )))); } }; Ok((input, ())) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn post<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, ()> { let (remaining, _) = alt((eof, recognize(none_of(WORD_CONSTITUENT_CHARACTERS))))(input)?; Ok((remaining, ())) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub fn protocol<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { // TODO: This should be defined by org-link-parameters for link_parameter in ORG_LINK_PARAMETERS { let result = tag_no_case::<_, _, CustomError<_>>(link_parameter)(input); match result { Ok((remaining, ent)) => { return Ok((remaining, ent)); } Err(_) => {} } } Err(nom::Err::Error(CustomError::MyError(MyError( "NoLinkProtocol".into(), )))) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { // 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" let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Gamma, exit_matcher: &path_plain_end, }); let parser_context = context.with_additional_node(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let (remaining, path) = recognize(verify( many_till(anychar, peek(exit_matcher)), |(children, _exit_contents)| !children.is_empty(), ))(input)?; Ok((remaining, path)) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn path_plain_end<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { recognize(many_till( verify(anychar, |c| { *c != '/' && (c.is_ascii_punctuation() || c.is_whitespace()) }), one_of(" \t\r\n()[]<>"), ))(input) }