organic/src/parser/plain_link.rs

318 lines
10 KiB
Rust
Raw Normal View History

use nom::branch::alt;
2023-10-07 02:22:36 -04:00
use nom::bytes::complete::is_not;
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;
2023-10-07 02:22:36 -04:00
use nom::combinator::consumed;
use nom::combinator::eof;
2023-10-07 02:22:36 -04:00
use nom::combinator::map;
use nom::combinator::not;
2023-10-07 02:22:36 -04:00
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
2023-09-21 16:34:24 -04:00
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
2023-09-03 11:05:34 -04:00
use crate::context::parser_with_context;
use crate::context::ContextElement;
2023-09-21 16:34:24 -04:00
use crate::context::ContextMatcher;
2023-09-03 11:05:34 -04:00
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::Matcher;
2023-09-03 11:05:34 -04:00
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
2023-07-13 18:18:07 -04:00
use crate::error::Res;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
2023-10-07 02:22:36 -04:00
use crate::types::LinkType;
2023-09-03 11:05:34 -04:00
use crate::types::PlainLink;
2023-07-13 18:18:07 -04:00
2023-08-10 20:04:59 -04:00
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
2023-09-11 13:13:28 -04:00
pub(crate) fn plain_link<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainLink<'s>> {
let (remaining, _) = pre(context, input)?;
2023-10-07 02:22:36 -04:00
let (remaining, path_plain) = parse_path_plain(context, remaining)?;
peek(parser_with_context!(post)(context))(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
2023-07-13 23:22:10 +00:00
Ok((
remaining,
PlainLink {
source: source.into(),
2023-10-07 02:22:36 -04:00
link_type: path_plain.link_type,
path: path_plain.path,
raw_link: path_plain.raw_link,
search_option: path_plain.search_option,
2023-07-13 23:22:10 +00:00
},
))
2023-07-13 18:18:07 -04:00
}
2023-10-07 02:22:36 -04:00
#[derive(Debug)]
struct PathPlain<'s> {
link_type: LinkType<'s>,
path: &'s str,
raw_link: &'s str,
search_option: Option<&'s str>,
}
2023-08-10 20:04:59 -04:00
#[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<OrgSource<'s>, ()> {
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, ()))
}
2023-08-10 20:04:59 -04:00
#[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<OrgSource<'s>, ()> {
let (remaining, _) = alt((eof, recognize(none_of(WORD_CONSTITUENT_CHARACTERS))))(input)?;
Ok((remaining, ()))
}
2023-10-07 02:22:36 -04:00
fn parse_path_plain<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PathPlain<'s>> {
alt((
parser_with_context!(file_path_plain)(context),
parser_with_context!(protocol_path_plain)(context),
))(input)
}
fn file_path_plain<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PathPlain<'s>> {
let (remaining, (raw_link, (_, path, search_option))) = consumed(tuple((
tag("file:"),
parser_with_context!(path_plain)(context),
opt(map(
tuple((tag("::"), is_not(" \t\r\n"))),
|(_, search_option)| search_option,
)),
)))(input)?;
Ok((
remaining,
PathPlain {
link_type: LinkType::File,
path: path.into(),
raw_link: raw_link.into(),
search_option: search_option.map(Into::<&str>::into),
},
))
}
fn protocol_path_plain<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PathPlain<'s>> {
let (remaining, (raw_link, (protocol, _, path))) = consumed(tuple((
parser_with_context!(protocol)(context),
tag(":"),
parser_with_context!(path_plain)(context),
)))(input)?;
Ok((
remaining,
PathPlain {
link_type: LinkType::Protocol(protocol.into()),
path: path.into(),
raw_link: raw_link.into(),
search_option: None,
},
))
}
2023-08-10 20:04:59 -04:00
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
2023-09-11 13:13:28 -04:00
pub(crate) fn protocol<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
for link_parameter in context.get_global_settings().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(),
))))
}
2023-08-10 20:04:59 -04:00
#[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>, OrgSource<'s>> {
let path_plain_end = path_plain_end(input.get_parenthesis_depth());
2023-09-03 11:05:34 -04:00
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &path_plain_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (remaining, _components) = many1(alt((
parser_with_context!(path_plain_no_parenthesis)(&parser_context),
parser_with_context!(path_plain_parenthesis)(&parser_context),
)))(input)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
fn path_plain_end(starting_parenthesis_depth: BracketDepth) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| _path_plain_end(context, input, starting_parenthesis_depth)
}
#[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>,
starting_parenthesis_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _leading_punctuation) = many0(verify(anychar, |c| {
!" \t\r\n[]<>()/".contains(*c) && c.is_ascii_punctuation()
}))(input)?;
let disallowed_character = recognize(one_of(" \t\r\n[]<>"))(remaining);
if disallowed_character.is_ok() {
return disallowed_character;
}
let current_depth = remaining.get_parenthesis_depth() - starting_parenthesis_depth;
if current_depth == 0 {
let close_parenthesis =
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
if close_parenthesis.is_ok() {
return close_parenthesis;
}
let open_parenthesis_without_match = recognize(tuple((
peek(tag("(")),
not(parser_with_context!(path_plain_parenthesis)(context)),
)))(remaining);
if open_parenthesis_without_match.is_ok() {
return open_parenthesis_without_match;
}
}
// many0 punctuation
Err(nom::Err::Error(CustomError::MyError(MyError(
"No path plain end".into(),
))))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_plain_no_parenthesis<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, path) = recognize(verify(
many_till(
anychar,
alt((
peek(path_plain_no_parenthesis_disallowed_character),
parser_with_context!(exit_matcher_parser)(context),
)),
),
|(children, _exit_contents)| !children.is_empty(),
))(input)?;
Ok((remaining, path))
}
2023-08-10 20:04:59 -04:00
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_plain_no_parenthesis_disallowed_character<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(anychar, |c| {
c.is_whitespace() || "()[]<>".contains(*c)
}))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_plain_parenthesis<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _opening) = tag("(")(input)?;
let starting_depth = remaining.get_parenthesis_depth();
let (remaining, _path) = recognize(verify(
many_till(
anychar,
alt((
peek(path_plain_parenthesis_end(starting_depth)),
parser_with_context!(exit_matcher_parser)(context),
)),
),
|(children, _exit_contents)| !children.is_empty(),
))(remaining)?;
let (remaining, _opening) = tag(")")(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
fn path_plain_parenthesis_end(starting_parenthesis_depth: BracketDepth) -> impl Matcher {
move |input: OrgSource<'_>| _path_plain_parenthesis_end(input, starting_parenthesis_depth)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _path_plain_parenthesis_end<'s>(
input: OrgSource<'s>,
starting_parenthesis_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the link.
unreachable!("Exceeded plain link parenthesis depth.")
}
if current_depth == 0 {
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
if close_parenthesis.is_ok() {
return close_parenthesis;
}
}
if current_depth == 1 {
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input);
if open_parenthesis.is_ok() {
return open_parenthesis;
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"No closing parenthesis".into(),
))))
}