From be553aefb1068cdce1853c96ddcecab32f121f25 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 01:34:56 -0400 Subject: [PATCH 01/16] Add test showing plain links cannot be templates. --- org_mode_samples/object/plain_link/template.org | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 org_mode_samples/object/plain_link/template.org diff --git a/org_mode_samples/object/plain_link/template.org b/org_mode_samples/object/plain_link/template.org new file mode 100644 index 0000000..2a644ef --- /dev/null +++ b/org_mode_samples/object/plain_link/template.org @@ -0,0 +1,8 @@ +# None of these end up as links. + +#+LINK: foo https://foo.bar/baz#%s +foo::lorem + +cat::bat +#+LINK: cat https://dog%s +cat:bat From 592e773920c902f6c10f6c611dbd6ad5b6b6ee17 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 01:38:33 -0400 Subject: [PATCH 02/16] Add tests for plain link search options and relative paths. --- org_mode_samples/object/plain_link/relative_path.org | 4 ++++ org_mode_samples/object/plain_link/search_option.org | 8 ++++++++ 2 files changed, 12 insertions(+) create mode 100644 org_mode_samples/object/plain_link/relative_path.org create mode 100644 org_mode_samples/object/plain_link/search_option.org diff --git a/org_mode_samples/object/plain_link/relative_path.org b/org_mode_samples/object/plain_link/relative_path.org new file mode 100644 index 0000000..addba92 --- /dev/null +++ b/org_mode_samples/object/plain_link/relative_path.org @@ -0,0 +1,4 @@ +# These do not become links +./simple.org::foo +../simple.org::foo +/simple.org::foo diff --git a/org_mode_samples/object/plain_link/search_option.org b/org_mode_samples/object/plain_link/search_option.org new file mode 100644 index 0000000..489153b --- /dev/null +++ b/org_mode_samples/object/plain_link/search_option.org @@ -0,0 +1,8 @@ +file:simple.org::foo + +file:simple.org::#foo +file:simple.org::foo bar +file:simple.org::foo +bar +file:simple.org::foo::bar +file:simple.org::/foo/ From 6973d5a2c0dbba3486cfa458e53f8dbae6522c59 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 01:46:22 -0400 Subject: [PATCH 03/16] Since value and path are always the same for radio links, I removed the extra value. --- src/compare/diff.rs | 2 +- src/parser/radio_link.rs | 7 ++----- src/types/object.rs | 7 ++++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index c08e559..8fd64b3 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2860,7 +2860,7 @@ fn compare_radio_link<'b, 's>( ), ( EmacsField::Required(":raw-link"), - |r| Some(&r.raw_link), + |r| Some(r.get_raw_link()), compare_property_quoted_string ), ( diff --git a/src/parser/radio_link.rs b/src/parser/radio_link.rs index 5edd8b6..5955344 100644 --- a/src/parser/radio_link.rs +++ b/src/parser/radio_link.rs @@ -41,7 +41,6 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>( source: source.into(), children: rematched_target, path: path.into(), - raw_link: path.into(), }, )); } @@ -194,8 +193,7 @@ mod tests { &Object::RadioLink(RadioLink { source: "bar ", children: vec![Object::PlainText(PlainText { source: "bar" })], - path: "bar".into(), - raw_link: "bar".into() + path: "bar".into() }) ); } @@ -238,8 +236,7 @@ mod tests { source: "*bar* ", children: vec![Object::PlainText(PlainText { source: "bar" })] })], - path: "*bar* ".into(), - raw_link: "*bar* ".into() + path: "*bar* ".into() }) ); } diff --git a/src/types/object.rs b/src/types/object.rs index ea884f0..107ee0e 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -98,7 +98,6 @@ pub struct RadioTarget<'s> { pub struct RadioLink<'s> { pub source: &'s str, pub path: &'s str, - pub raw_link: &'s str, // TODO: Is this always the same as path? If so, this could be a function instead of a field. pub children: Vec>, } @@ -720,3 +719,9 @@ impl<'s> RegularLink<'s> { }) } } + +impl<'s> RadioLink<'s> { + pub fn get_raw_link(&self) -> &'s str { + self.path + } +} From a55694176ccb15e4dff7d6e7fe2ae037217f2b04 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 02:22:36 -0400 Subject: [PATCH 04/16] Compare the properties of plain links. --- .../all_default_links_with_search_option.org | 25 ++++++ .../{with_parenthesis.org => parenthesis.org} | 0 .../object/plain_link/search_option.org | 3 + src/compare/diff.rs | 51 ++++++++++++- src/parser/plain_link.rs | 76 +++++++++++++++++-- src/types/object.rs | 4 +- 6 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 org_mode_samples/object/plain_link/all_default_links_with_search_option.org rename org_mode_samples/object/plain_link/{with_parenthesis.org => parenthesis.org} (100%) diff --git a/org_mode_samples/object/plain_link/all_default_links_with_search_option.org b/org_mode_samples/object/plain_link/all_default_links_with_search_option.org new file mode 100644 index 0000000..1e15e65 --- /dev/null +++ b/org_mode_samples/object/plain_link/all_default_links_with_search_option.org @@ -0,0 +1,25 @@ +non-link text::foo +eww://foo::foo +rmail://foo::foo +mhe://foo::foo +irc://foo::foo +info://foo::foo +gnus://foo::foo +docview://foo::foo +bibtex://foo::foo +bbdb://foo::foo +w3m://foo::foo +doi://foo::foo +file+sys://foo::foo +file+emacs://foo::foo +shell://foo::foo +news://foo::foo +mailto://foo::foo +https://foo::foo +http://foo::foo +ftp://foo::foo +help://foo::foo +file://foo::foo +elisp://foo::foo +randomfakeprotocl://foo::foo +non-link text::foo diff --git a/org_mode_samples/object/plain_link/with_parenthesis.org b/org_mode_samples/object/plain_link/parenthesis.org similarity index 100% rename from org_mode_samples/object/plain_link/with_parenthesis.org rename to org_mode_samples/object/plain_link/parenthesis.org diff --git a/org_mode_samples/object/plain_link/search_option.org b/org_mode_samples/object/plain_link/search_option.org index 489153b..b2e0853 100644 --- a/org_mode_samples/object/plain_link/search_option.org +++ b/org_mode_samples/object/plain_link/search_option.org @@ -6,3 +6,6 @@ file:simple.org::foo bar file:simple.org::foo::bar file:simple.org::/foo/ + +# Does not become a search option because it is inside parenthesis. +https://en.wikipedia.org/wiki/Shebang_(Uni::x) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 8fd64b3..16902dd 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2936,10 +2936,55 @@ fn compare_plain_link<'b, 's>( emacs: &'b Token<'s>, rust: &'b PlainLink<'s>, ) -> Result, Box> { - let this_status = DiffStatus::Good; - let message = None; + let mut this_status = DiffStatus::Good; + let mut message = None; - // TODO: Compare :type :path :format :raw-link :application :search-option + if let Some((new_status, new_message)) = compare_properties!( + emacs, + rust, + ( + EmacsField::Required(":type"), + |r| { + match &r.link_type { + LinkType::File => Some(Cow::Borrowed("file")), + LinkType::Protocol(protocol) => Some(protocol.clone()), + LinkType::Id => Some(Cow::Borrowed("id")), + LinkType::CustomId => Some(Cow::Borrowed("custom-id")), + LinkType::CodeRef => Some(Cow::Borrowed("coderef")), + LinkType::Fuzzy => Some(Cow::Borrowed("fuzzy")), + } + }, + compare_property_quoted_string + ), + ( + EmacsField::Required(":path"), + |r| Some(r.path), + compare_property_quoted_string + ), + ( + EmacsField::Required(":format"), + |_| Some("plain"), + compare_property_unquoted_atom + ), + ( + EmacsField::Required(":raw-link"), + |r| Some(r.raw_link), + compare_property_quoted_string + ), + ( + EmacsField::Required(":application"), + compare_identity, + compare_property_always_nil + ), + ( + EmacsField::Required(":search-option"), + |r| r.search_option, + compare_property_quoted_string + ) + )? { + this_status = new_status; + message = new_message; + } Ok(DiffResult { status: this_status, diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index fe97fbe..ac205dc 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -1,11 +1,15 @@ use nom::branch::alt; +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; +use nom::combinator::consumed; use nom::combinator::eof; +use nom::combinator::map; use nom::combinator::not; +use nom::combinator::opt; use nom::combinator::peek; use nom::combinator::recognize; use nom::combinator::verify; @@ -30,6 +34,7 @@ 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::LinkType; use crate::types::PlainLink; #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -38,9 +43,7 @@ pub(crate) fn plain_link<'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)?; + 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)?; @@ -49,12 +52,22 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>( remaining, PlainLink { source: source.into(), - link_type: proto.into(), - path: path.into(), + link_type: path_plain.link_type, + path: path_plain.path, + raw_link: path_plain.raw_link, + search_option: path_plain.search_option, }, )) } +#[derive(Debug)] +struct PathPlain<'s> { + link_type: LinkType<'s>, + path: &'s str, + raw_link: &'s str, + search_option: Option<&'s str>, +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn pre<'b, 'g, 'r, 's>( _context: RefContext<'b, 'g, 'r, 's>, @@ -84,6 +97,59 @@ fn post<'b, 'g, 'r, 's>( Ok((remaining, ())) } +fn parse_path_plain<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, 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, 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, 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, + }, + )) +} + #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn protocol<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, diff --git a/src/types/object.rs b/src/types/object.rs index 107ee0e..ac8620e 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -104,8 +104,10 @@ pub struct RadioLink<'s> { #[derive(Debug, PartialEq)] pub struct PlainLink<'s> { pub source: &'s str, - pub link_type: &'s str, + pub link_type: LinkType<'s>, pub path: &'s str, + pub raw_link: &'s str, + pub search_option: Option<&'s str>, } #[derive(Debug, PartialEq)] From c58b8505700ba6f8ab979a140c2c2788cde9b711 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 02:42:07 -0400 Subject: [PATCH 05/16] Add support for search options. --- src/parser/plain_link.rs | 55 ++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index ac205dc..ae90402 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -111,9 +111,15 @@ fn file_path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, PathPlain<'s>> { + let path_plain_end = path_plain_end(input.get_parenthesis_depth(), true); + 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, (raw_link, (_, path, search_option))) = consumed(tuple(( tag("file:"), - parser_with_context!(path_plain)(context), + parser_with_context!(path_plain)(&parser_context), opt(map( tuple((tag("::"), is_not(" \t\r\n"))), |(_, search_option)| search_option, @@ -134,10 +140,16 @@ fn protocol_path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, PathPlain<'s>> { + let path_plain_end = path_plain_end(input.get_parenthesis_depth(), false); + 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, (raw_link, (protocol, _, path))) = consumed(tuple(( - parser_with_context!(protocol)(context), + parser_with_context!(protocol)(&parser_context), tag(":"), - parser_with_context!(path_plain)(context), + parser_with_context!(path_plain)(&parser_context), )))(input)?; Ok(( remaining, @@ -175,24 +187,28 @@ fn path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, ) -> Res, OrgSource<'s>> { - let path_plain_end = path_plain_end(input.get_parenthesis_depth()); - let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { - class: ExitClass::Gamma, - exit_matcher: &path_plain_end, - }); - let parser_context = context.with_additional_node(&parser_context); - + // The caller needs to put an instance of path_plain_end on the context before calling this. let (remaining, _components) = many1(alt(( - parser_with_context!(path_plain_no_parenthesis)(&parser_context), - parser_with_context!(path_plain_parenthesis)(&parser_context), + parser_with_context!(path_plain_no_parenthesis)(context), + parser_with_context!(path_plain_parenthesis)(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) +fn path_plain_end( + starting_parenthesis_depth: BracketDepth, + enable_search_option: bool, +) -> impl ContextMatcher { + move |context, input: OrgSource<'_>| { + _path_plain_end( + context, + input, + starting_parenthesis_depth, + enable_search_option, + ) + } } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -200,7 +216,16 @@ fn _path_plain_end<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, starting_parenthesis_depth: BracketDepth, + enable_search_option: bool, ) -> Res, OrgSource<'s>> { + let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth; + if enable_search_option && current_depth == 0 { + let search_option = peek(tag("::"))(input); + if search_option.is_ok() { + return search_option; + } + } + let (remaining, _leading_punctuation) = many0(verify(anychar, |c| { !" \t\r\n[]<>()/".contains(*c) && c.is_ascii_punctuation() }))(input)?; @@ -210,7 +235,6 @@ fn _path_plain_end<'b, 'g, 'r, 's>( return disallowed_character; } - let current_depth = remaining.get_parenthesis_depth() - starting_parenthesis_depth; if current_depth == 0 { let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError>>(")")(remaining); @@ -227,7 +251,6 @@ fn _path_plain_end<'b, 'g, 'r, 's>( } } - // many0 punctuation Err(nom::Err::Error(CustomError::MyError(MyError( "No path plain end".into(), )))) From ddb09a18051a3878e0d84371c6fb491f764900f9 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 03:00:40 -0400 Subject: [PATCH 06/16] Add support for application in plain links. --- src/compare/diff.rs | 4 ++-- src/parser/plain_link.rs | 25 +++++++++++++++++++++++-- src/types/object.rs | 1 + 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 16902dd..6141b29 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2973,8 +2973,8 @@ fn compare_plain_link<'b, 's>( ), ( EmacsField::Required(":application"), - compare_identity, - compare_property_always_nil + |r| r.application, + compare_property_quoted_string ), ( EmacsField::Required(":search-option"), diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index ae90402..2d2c82b 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -8,10 +8,12 @@ use nom::character::complete::one_of; use nom::combinator::consumed; use nom::combinator::eof; use nom::combinator::map; +use nom::combinator::map_parser; use nom::combinator::not; use nom::combinator::opt; use nom::combinator::peek; use nom::combinator::recognize; +use nom::combinator::rest; use nom::combinator::verify; use nom::multi::many0; use nom::multi::many1; @@ -56,6 +58,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>( path: path_plain.path, raw_link: path_plain.raw_link, search_option: path_plain.search_option, + application: path_plain.application, }, )) } @@ -66,6 +69,7 @@ struct PathPlain<'s> { path: &'s str, raw_link: &'s str, search_option: Option<&'s str>, + application: Option<&'s str>, } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -107,6 +111,17 @@ fn parse_path_plain<'b, 'g, 'r, 's>( ))(input) } +fn parse_file_and_application<'s>( + input: OrgSource<'s>, +) -> Res, Option>> { + let (remaining, _) = tag("file")(input)?; + let (remaining, application) = + opt(map(tuple((tag("+"), rest)), |(_, application)| application))(remaining)?; + // Assert we consumed the entire protocol. + not(anychar)(remaining)?; + Ok((remaining, application)) +} + fn file_path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, @@ -117,8 +132,12 @@ fn file_path_plain<'b, 'g, 'r, 's>( exit_matcher: &path_plain_end, }); let parser_context = context.with_additional_node(&parser_context); - let (remaining, (raw_link, (_, path, search_option))) = consumed(tuple(( - tag("file:"), + let (remaining, (raw_link, (application, _, path, search_option))) = consumed(tuple(( + map_parser( + parser_with_context!(protocol)(&parser_context), + parse_file_and_application, + ), + tag(":"), parser_with_context!(path_plain)(&parser_context), opt(map( tuple((tag("::"), is_not(" \t\r\n"))), @@ -132,6 +151,7 @@ fn file_path_plain<'b, 'g, 'r, 's>( path: path.into(), raw_link: raw_link.into(), search_option: search_option.map(Into::<&str>::into), + application: application.map(Into::<&str>::into), }, )) } @@ -158,6 +178,7 @@ fn protocol_path_plain<'b, 'g, 'r, 's>( path: path.into(), raw_link: raw_link.into(), search_option: None, + application: None, }, )) } diff --git a/src/types/object.rs b/src/types/object.rs index ac8620e..a8d2edb 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -108,6 +108,7 @@ pub struct PlainLink<'s> { pub path: &'s str, pub raw_link: &'s str, pub search_option: Option<&'s str>, + pub application: Option<&'s str>, } #[derive(Debug, PartialEq)] From 0c34df159f12bacc6d6691d8b8ce7d06f680f7c2 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 03:03:57 -0400 Subject: [PATCH 07/16] Add test showing we are not handling application in regular links properly. --- org_mode_samples/object/regular_link/application.org | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 org_mode_samples/object/regular_link/application.org diff --git a/org_mode_samples/object/regular_link/application.org b/org_mode_samples/object/regular_link/application.org new file mode 100644 index 0000000..06149c6 --- /dev/null +++ b/org_mode_samples/object/regular_link/application.org @@ -0,0 +1,2 @@ +[[file+sys://foo]] +[[file+emacs://foo]] From 7196e10b69b846509bf45868bec2c205ec89b4ea Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 03:14:16 -0400 Subject: [PATCH 08/16] Set up regular links for adding application. --- .../object/regular_link/search_option.org | 2 ++ src/compare/diff.rs | 4 ++-- src/parser/plain_link.rs | 2 +- src/parser/regular_link.rs | 12 ++++++++++++ src/types/object.rs | 1 + 5 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 org_mode_samples/object/regular_link/search_option.org diff --git a/org_mode_samples/object/regular_link/search_option.org b/org_mode_samples/object/regular_link/search_option.org new file mode 100644 index 0000000..aba6af0 --- /dev/null +++ b/org_mode_samples/object/regular_link/search_option.org @@ -0,0 +1,2 @@ +# Does not become a search option because it is inside parenthesis. +[[https://en.wikipedia.org/wiki/Shebang_(Uni::x)]] diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 6141b29..04ee74c 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -2802,8 +2802,8 @@ fn compare_regular_link<'b, 's>( ), ( EmacsField::Required(":application"), - compare_identity, - compare_property_always_nil + |r| r.application.as_ref(), + compare_property_quoted_string ), ( EmacsField::Required(":search-option"), diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index 2d2c82b..5ef46f2 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -111,7 +111,7 @@ fn parse_path_plain<'b, 'g, 'r, 's>( ))(input) } -fn parse_file_and_application<'s>( +pub(crate) fn parse_file_and_application<'s>( input: OrgSource<'s>, ) -> Res, Option>> { let (remaining, _) = tag("file")(input)?; diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs index e0812fe..0ea5db4 100644 --- a/src/parser/regular_link.rs +++ b/src/parser/regular_link.rs @@ -69,6 +69,7 @@ fn regular_link_without_description<'b, 'g, 'r, 's>( raw_link: path.raw_link, search_option: path.search_option, children: Vec::new(), + application: path.application, }, )) } @@ -95,6 +96,7 @@ fn regular_link_with_description<'b, 'g, 'r, 's>( raw_link: path.raw_link, search_option: path.search_option, children: description, + application: path.application, }, )) } @@ -105,6 +107,7 @@ struct PathReg<'s> { path: Cow<'s, str>, raw_link: Cow<'s, str>, search_option: Option>, + application: Option>, } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] @@ -161,6 +164,7 @@ fn parse_path_reg<'b, 'g, 'r, 's>( 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()), + application: link.application.map(|s| s.into_owned().into()), }, )) } else { @@ -240,6 +244,8 @@ fn apply_link_templates<'b, 'g, 'r, 's>( } fn file_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { + // TODO: Update this to parse out the application like plain link does. + // TODO: Handle parenthesis like plain link does. let (remaining, (raw_link, (_, path, search_option))) = consumed(tuple(( alt((tag("file:"), peek(tag(".")), peek(tag("/")))), recognize(many_till(anychar, alt((peek(tag("::")), eof)))), @@ -256,6 +262,7 @@ fn file_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { search_option: search_option .map(Into::<&str>::into) .map(Into::>::into), + application: todo!(), }, )) } @@ -269,6 +276,7 @@ fn id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { path: path.into(), raw_link: raw_link.into(), search_option: None, + application: None, }, )) } @@ -282,6 +290,7 @@ fn custom_id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s path: path.into(), raw_link: raw_link.into(), search_option: None, + application: None, }, )) } @@ -299,6 +308,7 @@ fn code_ref_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s> path: path.into(), raw_link: raw_link.into(), search_option: None, + application: None, }, )) } @@ -319,6 +329,7 @@ fn protocol_path_reg<'b, 'g, 'r, 's>( path: path.into(), raw_link: raw_link.into(), search_option: None, + application: None, }, )) } @@ -332,6 +343,7 @@ fn fuzzy_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { path: body.into(), raw_link: body.into(), search_option: None, + application: None, }, )) } diff --git a/src/types/object.rs b/src/types/object.rs index a8d2edb..16e69df 100644 --- a/src/types/object.rs +++ b/src/types/object.rs @@ -85,6 +85,7 @@ pub struct RegularLink<'s> { pub raw_link: Cow<'s, str>, pub search_option: Option>, pub children: Vec>, + pub application: Option>, } #[derive(Debug, PartialEq)] From 3aa84c17438b5310c0ac807cb656c3ea1582d679 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 03:23:45 -0400 Subject: [PATCH 09/16] Add test showing we are not handling puncutation-only search options properly. --- org_mode_samples/object/plain_link/search_option.org | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/org_mode_samples/object/plain_link/search_option.org b/org_mode_samples/object/plain_link/search_option.org index b2e0853..d9e1631 100644 --- a/org_mode_samples/object/plain_link/search_option.org +++ b/org_mode_samples/object/plain_link/search_option.org @@ -9,3 +9,8 @@ file:simple.org::/foo/ # Does not become a search option because it is inside parenthesis. https://en.wikipedia.org/wiki/Shebang_(Uni::x) + + +file:simple.org::* foo +file:simple.org::*bar +file:simple.org::b*az From 20a86838948924a576a18d8ec5d6d092fa34ac3d Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sat, 7 Oct 2023 03:25:01 -0400 Subject: [PATCH 10/16] Handle puncuation-only search options. --- src/parser/plain_link.rs | 9 ++++++++- src/parser/regular_link.rs | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index 5ef46f2..09eb6b0 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -140,7 +140,14 @@ fn file_path_plain<'b, 'g, 'r, 's>( tag(":"), parser_with_context!(path_plain)(&parser_context), opt(map( - tuple((tag("::"), is_not(" \t\r\n"))), + tuple(( + tag("::"), + verify(is_not(" \t\r\n"), |search_option| { + Into::<&str>::into(search_option) + .chars() + .any(char::is_alphanumeric) + }), + )), |(_, search_option)| search_option, )), )))(input)?; diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs index 0ea5db4..b87a2a3 100644 --- a/src/parser/regular_link.rs +++ b/src/parser/regular_link.rs @@ -262,7 +262,7 @@ fn file_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { search_option: search_option .map(Into::<&str>::into) .map(Into::>::into), - application: todo!(), + application: None, // TODO }, )) } From 0030ef44592dfdf55b4a3f835ab1e623a20e0154 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 8 Oct 2023 09:10:47 -0400 Subject: [PATCH 11/16] Parse application and handle parenthesis in regular links. --- src/parser/plain_link.rs | 4 +- src/parser/regular_link.rs | 95 +++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index 09eb6b0..1fec0e1 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -230,7 +230,7 @@ fn path_plain_end( enable_search_option: bool, ) -> impl ContextMatcher { move |context, input: OrgSource<'_>| { - _path_plain_end( + impl_path_plain_end( context, input, starting_parenthesis_depth, @@ -240,7 +240,7 @@ fn path_plain_end( } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn _path_plain_end<'b, 'g, 'r, 's>( +fn impl_path_plain_end<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, starting_parenthesis_depth: BracketDepth, diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs index b87a2a3..bd6568b 100644 --- a/src/parser/regular_link.rs +++ b/src/parser/regular_link.rs @@ -21,13 +21,17 @@ use nom::sequence::tuple; use nom::InputTake; use super::object_parser::regular_link_description_set_object; +use super::org_source::BracketDepth; use super::org_source::OrgSource; +use super::plain_link::parse_file_and_application; 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 super::util::text_until_exit; use crate::context::parser_with_context; use crate::context::ContextElement; +use crate::context::ContextMatcher; use crate::context::ExitClass; use crate::context::ExitMatcherNode; use crate::context::RefContext; @@ -136,7 +140,7 @@ fn parse_path_reg<'b, 'g, 'r, '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, + parser_with_context!(file_path_reg)(context), id_path_reg, custom_id_path_reg, code_ref_path_reg, @@ -169,7 +173,7 @@ fn parse_path_reg<'b, 'g, 'r, 's>( )) } else { alt(( - file_path_reg, + parser_with_context!(file_path_reg)(context), id_path_reg, custom_id_path_reg, code_ref_path_reg, @@ -243,16 +247,47 @@ fn apply_link_templates<'b, 'g, 'r, 's>( Some(ret) } -fn file_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { - // TODO: Update this to parse out the application like plain link does. - // TODO: Handle parenthesis like plain link does. - 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 - })), +fn file_path_reg<'b, 'g, 'r, 's>( + context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, +) -> Res, PathReg<'s>> { + let path_reg_end = path_reg_end(input.get_parenthesis_depth(), true); + let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Gamma, + exit_matcher: &path_reg_end, + }); + let parser_context = context.with_additional_node(&parser_context); + + let (remaining, (raw_link, (application, path, search_option))) = consumed(tuple(( + alt(( + map( + tuple(( + peek(tag("file")), + map_parser( + parser_with_context!(protocol)(&parser_context), + parse_file_and_application, + ), + tag(":"), + )), + |(_, application, _)| application, + ), + map(peek(tag(".")), |_| None), + map(peek(tag("/")), |_| None), + )), + parser_with_context!(text_until_exit)(&parser_context), + opt(map( + tuple(( + tag("::"), + verify(rest, |search_option| { + Into::<&str>::into(search_option) + .chars() + .any(char::is_alphanumeric) + }), + )), + |(_, search_option)| search_option, + )), )))(input)?; + Ok(( remaining, PathReg { @@ -262,7 +297,9 @@ fn file_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { search_option: search_option .map(Into::<&str>::into) .map(Into::>::into), - application: None, // TODO + application: application + .map(Into::<&str>::into) + .map(Into::>::into), }, )) } @@ -376,3 +413,37 @@ fn description_end<'b, 'g, 'r, 's>( ) -> Res, OrgSource<'s>> { tag("]]")(input) } + +fn path_reg_end( + starting_parenthesis_depth: BracketDepth, + enable_search_option: bool, +) -> impl ContextMatcher { + move |context, input: OrgSource<'_>| { + impl_path_reg_end( + context, + input, + starting_parenthesis_depth, + enable_search_option, + ) + } +} + +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] +fn impl_path_reg_end<'b, 'g, 'r, 's>( + _context: RefContext<'b, 'g, 'r, 's>, + input: OrgSource<'s>, + starting_parenthesis_depth: BracketDepth, + enable_search_option: bool, +) -> Res, OrgSource<'s>> { + let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth; + if enable_search_option && current_depth == 0 { + let search_option = peek(tag("::"))(input); + if search_option.is_ok() { + return search_option; + } + } + + Err(nom::Err::Error(CustomError::MyError(MyError( + "No path reg end".into(), + )))) +} From ba8115c648efb031034f3790094d251be8ce41dc Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 8 Oct 2023 09:16:48 -0400 Subject: [PATCH 12/16] Remove "//" from file paths. --- src/parser/plain_link.rs | 4 +++- src/parser/regular_link.rs | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index 1fec0e1..84a5462 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -132,12 +132,14 @@ fn file_path_plain<'b, 'g, 'r, 's>( exit_matcher: &path_plain_end, }); let parser_context = context.with_additional_node(&parser_context); - let (remaining, (raw_link, (application, _, path, search_option))) = consumed(tuple(( + let (remaining, (raw_link, (_, application, _, _, path, search_option))) = consumed(tuple(( + peek(tag("file")), map_parser( parser_with_context!(protocol)(&parser_context), parse_file_and_application, ), tag(":"), + opt(tag("//")), parser_with_context!(path_plain)(&parser_context), opt(map( tuple(( diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs index bd6568b..b80e5cc 100644 --- a/src/parser/regular_link.rs +++ b/src/parser/regular_link.rs @@ -268,8 +268,9 @@ fn file_path_reg<'b, 'g, 'r, 's>( parse_file_and_application, ), tag(":"), + opt(tag("//")), )), - |(_, application, _)| application, + |(_, application, _, _)| application, ), map(peek(tag(".")), |_| None), map(peek(tag("/")), |_| None), From 1bbe8fc6889d6b607f6e6ec2a11c9304fa8cce29 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 8 Oct 2023 09:28:09 -0400 Subject: [PATCH 13/16] Do not strip "//" from plain link path. --- src/parser/plain_link.rs | 9 ++++++--- src/parser/regular_link.rs | 8 ++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index 84a5462..1d0bda6 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -101,6 +101,7 @@ fn post<'b, 'g, 'r, 's>( Ok((remaining, ())) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn parse_path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, @@ -111,6 +112,7 @@ fn parse_path_plain<'b, 'g, 'r, 's>( ))(input) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn parse_file_and_application<'s>( input: OrgSource<'s>, ) -> Res, Option>> { @@ -122,6 +124,7 @@ pub(crate) fn parse_file_and_application<'s>( Ok((remaining, application)) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn file_path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, @@ -132,15 +135,14 @@ fn file_path_plain<'b, 'g, 'r, 's>( exit_matcher: &path_plain_end, }); let parser_context = context.with_additional_node(&parser_context); - let (remaining, (raw_link, (_, application, _, _, path, search_option))) = consumed(tuple(( + let (remaining, (raw_link, (_, application, _, path, search_option))) = consumed(tuple(( peek(tag("file")), map_parser( parser_with_context!(protocol)(&parser_context), parse_file_and_application, ), tag(":"), - opt(tag("//")), - parser_with_context!(path_plain)(&parser_context), + recognize(opt(parser_with_context!(path_plain)(&parser_context))), opt(map( tuple(( tag("::"), @@ -165,6 +167,7 @@ fn file_path_plain<'b, 'g, 'r, 's>( )) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn protocol_path_plain<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs index b80e5cc..fdb7fae 100644 --- a/src/parser/regular_link.rs +++ b/src/parser/regular_link.rs @@ -133,6 +133,7 @@ fn pathreg<'b, 'g, 'r, 's>( Ok((remaining, path)) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn parse_path_reg<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, @@ -188,6 +189,7 @@ enum ParserState { Percent, } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn apply_link_templates<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, @@ -247,6 +249,7 @@ fn apply_link_templates<'b, 'g, 'r, 's>( Some(ret) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn file_path_reg<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, @@ -305,6 +308,7 @@ fn file_path_reg<'b, 'g, 'r, 's>( )) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, (raw_link, (_, path))) = consumed(tuple((tag("id:"), rest)))(input)?; Ok(( @@ -319,6 +323,7 @@ fn id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { )) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn custom_id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, (raw_link, (_, path))) = consumed(tuple((tag("#"), rest)))(input)?; Ok(( @@ -333,6 +338,7 @@ fn custom_id_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s )) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn code_ref_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, (raw_link, (_, path, _))) = consumed(tuple(( tag("("), @@ -351,6 +357,7 @@ fn code_ref_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s> )) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn protocol_path_reg<'b, 'g, 'r, 's>( context: RefContext<'b, 'g, 'r, 's>, input: OrgSource<'s>, @@ -372,6 +379,7 @@ fn protocol_path_reg<'b, 'g, 'r, 's>( )) } +#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn fuzzy_path_reg<'s>(input: OrgSource<'s>) -> Res, PathReg<'s>> { let (remaining, body) = rest(input)?; Ok(( From 038535174bf52cf6444f4d3ccd656a55b214b522 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 8 Oct 2023 09:34:47 -0400 Subject: [PATCH 14/16] Do not allow empty path in plain links and do not remove "//" in regular links. --- src/parser/plain_link.rs | 2 +- src/parser/regular_link.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index 1d0bda6..ff74535 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -142,7 +142,7 @@ fn file_path_plain<'b, 'g, 'r, 's>( parse_file_and_application, ), tag(":"), - recognize(opt(parser_with_context!(path_plain)(&parser_context))), + parser_with_context!(path_plain)(&parser_context), opt(map( tuple(( tag("::"), diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs index fdb7fae..caeefd8 100644 --- a/src/parser/regular_link.rs +++ b/src/parser/regular_link.rs @@ -271,9 +271,8 @@ fn file_path_reg<'b, 'g, 'r, 's>( parse_file_and_application, ), tag(":"), - opt(tag("//")), )), - |(_, application, _, _)| application, + |(_, application, _)| application, ), map(peek(tag(".")), |_| None), map(peek(tag("/")), |_| None), From bbcafef8bf240fa41841a2f56a5f854fd1ddb2dd Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 8 Oct 2023 09:51:12 -0400 Subject: [PATCH 15/16] Handle 3 or more slashes in the path for plain links. --- .../object/plain_link/multiple_slashes.org | 13 +++++++++++++ src/parser/plain_link.rs | 9 ++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 org_mode_samples/object/plain_link/multiple_slashes.org diff --git a/org_mode_samples/object/plain_link/multiple_slashes.org b/org_mode_samples/object/plain_link/multiple_slashes.org new file mode 100644 index 0000000..f8064d8 --- /dev/null +++ b/org_mode_samples/object/plain_link/multiple_slashes.org @@ -0,0 +1,13 @@ +file:foo +file:/bar +file://baz +file:///lorem +file:////ipsum +file://///dolar + +https:foo +https:/bar +https://baz +https:///lorem +https:////ipsum +https://///dolar diff --git a/src/parser/plain_link.rs b/src/parser/plain_link.rs index ff74535..a226209 100644 --- a/src/parser/plain_link.rs +++ b/src/parser/plain_link.rs @@ -2,11 +2,13 @@ use nom::branch::alt; use nom::bytes::complete::is_not; use nom::bytes::complete::tag; use nom::bytes::complete::tag_no_case; +use nom::bytes::complete::take; use nom::character::complete::anychar; use nom::character::complete::none_of; use nom::character::complete::one_of; use nom::combinator::consumed; use nom::combinator::eof; +use nom::combinator::flat_map; use nom::combinator::map; use nom::combinator::map_parser; use nom::combinator::not; @@ -17,6 +19,7 @@ use nom::combinator::rest; use nom::combinator::verify; use nom::multi::many0; use nom::multi::many1; +use nom::multi::many1_count; use nom::multi::many_till; use nom::sequence::tuple; @@ -135,13 +138,17 @@ fn file_path_plain<'b, 'g, 'r, 's>( exit_matcher: &path_plain_end, }); let parser_context = context.with_additional_node(&parser_context); - let (remaining, (raw_link, (_, application, _, path, search_option))) = consumed(tuple(( + let (remaining, (raw_link, (_, application, _, _, path, search_option))) = consumed(tuple(( peek(tag("file")), map_parser( parser_with_context!(protocol)(&parser_context), parse_file_and_application, ), tag(":"), + opt(flat_map( + peek(map(verify(many1_count(tag("/")), |c| *c >= 3), |c| c - 1)), + take, + )), parser_with_context!(path_plain)(&parser_context), opt(map( tuple(( From 120a06055f55aee7eb89c4b1bdcd8b6b063fcc39 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Sun, 8 Oct 2023 09:54:10 -0400 Subject: [PATCH 16/16] Handle 3 or more slashes in the path for regular links. --- .../object/regular_link/multiple_slashes.org | 20 +++++++++++++++++++ src/parser/regular_link.rs | 9 ++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 org_mode_samples/object/regular_link/multiple_slashes.org diff --git a/org_mode_samples/object/regular_link/multiple_slashes.org b/org_mode_samples/object/regular_link/multiple_slashes.org new file mode 100644 index 0000000..bb29c8c --- /dev/null +++ b/org_mode_samples/object/regular_link/multiple_slashes.org @@ -0,0 +1,20 @@ +[[file:foo]] +[[file:/bar]] +[[file://baz]] +[[file:///lorem]] +[[file:////ipsum]] +[[file://///dolar]] + +[[foo]] +[[/bar]] +[[//baz]] +[[///lorem]] +[[////ipsum]] +[[/////dolar]] + +[[https:foo]] +[[https:/bar]] +[[https://baz]] +[[https:///lorem]] +[[https:////ipsum]] +[[https://///dolar]] diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs index caeefd8..0946ca2 100644 --- a/src/parser/regular_link.rs +++ b/src/parser/regular_link.rs @@ -4,11 +4,13 @@ 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; 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::flat_map; use nom::combinator::map; use nom::combinator::map_parser; use nom::combinator::opt; @@ -16,6 +18,7 @@ use nom::combinator::peek; use nom::combinator::recognize; use nom::combinator::rest; use nom::combinator::verify; +use nom::multi::many1_count; use nom::multi::many_till; use nom::sequence::tuple; use nom::InputTake; @@ -261,7 +264,7 @@ fn file_path_reg<'b, 'g, 'r, 's>( }); let parser_context = context.with_additional_node(&parser_context); - let (remaining, (raw_link, (application, path, search_option))) = consumed(tuple(( + let (remaining, (raw_link, (application, _, path, search_option))) = consumed(tuple(( alt(( map( tuple(( @@ -277,6 +280,10 @@ fn file_path_reg<'b, 'g, 'r, 's>( map(peek(tag(".")), |_| None), map(peek(tag("/")), |_| None), )), + opt(flat_map( + peek(map(verify(many1_count(tag("/")), |c| *c >= 3), |c| c - 1)), + take, + )), parser_with_context!(text_until_exit)(&parser_context), opt(map( tuple((