159 lines
4.9 KiB
Rust
159 lines
4.9 KiB
Rust
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<OrgSource<'s>, 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<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, ()))
|
|
}
|
|
|
|
#[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, ()))
|
|
}
|
|
|
|
#[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>, 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>, 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>, OrgSource<'s>> {
|
|
recognize(many_till(
|
|
verify(anychar, |c| {
|
|
*c != '/' && (c.is_ascii_punctuation() || c.is_whitespace())
|
|
}),
|
|
one_of(" \t\r\n()[]<>"),
|
|
))(input)
|
|
}
|