use std::borrow::Cow; use nom::branch::alt; use nom::bytes::complete::escaped; use nom::bytes::complete::is_a; use nom::bytes::complete::tag; use nom::bytes::complete::take_till1; use nom::bytes::complete::take_until; use nom::character::complete::anychar; use nom::combinator::consumed; use nom::combinator::eof; use nom::combinator::map; use nom::combinator::map_parser; use nom::combinator::opt; use nom::combinator::peek; use nom::combinator::recognize; use nom::combinator::rest; use nom::combinator::verify; use nom::multi::many_till; use nom::sequence::tuple; use nom::InputTake; use super::object_parser::regular_link_description_set_object; use super::org_source::OrgSource; use super::plain_link::protocol; use super::util::exit_matcher_parser; use super::util::get_consumed; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; 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::types::LinkType; use crate::types::Object; use crate::types::RegularLink; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn regular_link<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, RegularLink<'s>> { alt(( parser_with_context!(regular_link_without_description)(context), parser_with_context!(regular_link_with_description)(context), ))(input) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn regular_link_without_description<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, RegularLink<'s>> { let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, path) = pathreg(context, remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _trailing_whitespace) = maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; let source = get_consumed(input, remaining); Ok(( remaining, RegularLink { source: source.into(), link_type: path.link_type, path: path.path, raw_link: path.raw_link, search_option: path.search_option, }, )) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn regular_link_with_description<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, RegularLink<'s>> { let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, path) = pathreg(context, remaining)?; let (remaining, _closing_bracket) = tag("][")(remaining)?; let (remaining, _description) = description(context, remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _trailing_whitespace) = maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; let source = get_consumed(input, remaining); Ok(( remaining, RegularLink { source: source.into(), link_type: path.link_type, path: path.path, raw_link: path.raw_link, search_option: path.search_option, }, )) } #[derive(Debug)] struct PathReg<'s> { link_type: LinkType<'s>, path: Cow<'s, str>, raw_link: Cow<'s, str>, search_option: Option>, } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn pathreg<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, PathReg<'s>> { let (remaining, path) = map_parser( escaped( take_till1(|c| match c { '\\' | '[' | ']' => true, _ => false, }), '\\', anychar, ), parser_with_context!(parse_path_reg)(context), )(input)?; Ok((remaining, path)) } fn parse_path_reg<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, PathReg<'s>> { if let Some(replaced_link) = apply_link_templates(context, input) { let replaced_input = Into::>::into(replaced_link.as_str()); let (_remaining, link) = alt(( file_path_reg, id_path_reg, custom_id_path_reg, code_ref_path_reg, parser_with_context!(protocol_path_reg)(context), fuzzy_path_reg, ))(replaced_input) .map_err(|_| { nom::Err::Error(CustomError::MyError(MyError( "No pathreg match after replacement.", ))) })?; let remaining = input.take(input.len()); let link_type = match link.link_type { LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()), LinkType::File => LinkType::File, LinkType::Id => LinkType::Id, LinkType::CustomId => LinkType::CustomId, LinkType::CodeRef => LinkType::CodeRef, LinkType::Fuzzy => LinkType::Fuzzy, }; Ok(( remaining, PathReg { link_type, path: link.path.into_owned().into(), raw_link: link.raw_link.into_owned().into(), search_option: link.search_option.map(|s| s.into_owned().into()), }, )) } else { alt(( file_path_reg, id_path_reg, custom_id_path_reg, code_ref_path_reg, parser_with_context!(protocol_path_reg)(context), fuzzy_path_reg, ))(input) } } enum ParserState { Normal, Percent, } fn apply_link_templates<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Option { let (remaining, key) = opt(map( tuple(( recognize(take_until::<_, _, nom::error::Error<_>>(":")), is_a(":"), )), |(key, _)| key, ))(input) .expect("opt ensures this cannot error."); let key = match key { Some(key) => key, None => { return None; } }; let replacement_template = match context.get_global_settings().link_templates.get(key.into()) { Some(template) => template, None => { return None; } }; let inject_value = Into::<&str>::into(remaining); let mut ret = String::with_capacity(replacement_template.len() + inject_value.len()); let mut state = ParserState::Normal; let mut injected_value = false; for c in replacement_template.chars() { state = match (&state, c) { (ParserState::Normal, '%') => ParserState::Percent, (ParserState::Normal, _) => { ret.push(c); ParserState::Normal } (ParserState::Percent, 's') => { ret.push_str(inject_value); injected_value = true; ParserState::Normal } (ParserState::Percent, _) => { panic!("Unhandled percent value: {}", c) } }; } // Handle lingering state match state { ParserState::Percent => { ret.push('%'); } _ => {} } if !injected_value { ret.push_str(inject_value); } Some(ret) } fn file_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, (raw_link, (_, path, search_option))) = consumed(tuple(( alt((tag("file:"), peek(tag(".")), peek(tag("/")))), recognize(many_till(anychar, alt((peek(tag("::")), eof)))), opt(map(tuple((tag("::"), rest)), |(_, search_option)| { search_option })), )))(input)?; Ok(( remaining, PathReg { link_type: LinkType::File, path: path.into(), raw_link: raw_link.into(), search_option: search_option .map(Into::<&str>::into) .map(Into::>::into), }, )) } fn id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, (raw_link, (_, path))) = consumed(tuple((tag("id:"), rest)))(input)?; Ok(( remaining, PathReg { link_type: LinkType::Id, path: path.into(), raw_link: raw_link.into(), search_option: None, }, )) } fn custom_id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, (raw_link, (_, path))) = consumed(tuple((tag("#"), rest)))(input)?; Ok(( remaining, PathReg { link_type: LinkType::CustomId, path: path.into(), raw_link: raw_link.into(), search_option: None, }, )) } fn code_ref_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, (raw_link, (_, path, _))) = consumed(tuple(( tag("("), recognize(many_till(anychar, peek(tuple((tag(")"), eof))))), tag(")"), )))(input)?; Ok(( remaining, PathReg { link_type: LinkType::CodeRef, path: path.into(), raw_link: raw_link.into(), search_option: None, }, )) } fn protocol_path_reg<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, PathReg<'s>> { let (remaining, (raw_link, (protocol, _, path))) = consumed(tuple(( parser_with_context!(protocol)(context), tag(":"), rest, )))(input)?; Ok(( remaining, PathReg { link_type: LinkType::Protocol(protocol.into()), path: path.into(), raw_link: raw_link.into(), search_option: None, }, )) } fn fuzzy_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, body) = rest(input)?; Ok(( remaining, PathReg { link_type: LinkType::Fuzzy, path: body.into(), raw_link: body.into(), search_option: None, }, )) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn description<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, Vec>> { let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { class: ExitClass::Beta, exit_matcher: &description_end, }); let parser_context = context.with_additional_node(&parser_context); let (remaining, (children, _exit_contents)) = verify( many_till( parser_with_context!(regular_link_description_set_object)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context), ), |(children, _exit_contents)| !children.is_empty(), )(input)?; Ok((remaining, children)) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn description_end<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { tag("]]")(input) }