diff --git a/build.rs b/build.rs index fe47ad4..bb99b66 100644 --- a/build.rs +++ b/build.rs @@ -78,9 +78,7 @@ fn is_expect_fail(name: &str) -> Option<&str> { "element_container_priority_footnote_definition_greater_block" => Some("Need to implement subscript."), "element_container_priority_greater_block_greater_block" => Some("Need to implement subscript."), "element_container_priority_section_greater_block" => Some("Need to implement subscript."), - "keyword_affiliated_keyword" => Some("Need to implement link."), "paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."), - "text_markup_opening_and_closing" => Some("Need to implement bold and link."), _ => None, } } diff --git a/org_mode_samples/regular_link/simple.org b/org_mode_samples/regular_link/simple.org new file mode 100644 index 0000000..ad63ddb --- /dev/null +++ b/org_mode_samples/regular_link/simple.org @@ -0,0 +1,3 @@ +[[https://fizz.buzz/]] + +[[https://fizz.buzz][super cool website]] diff --git a/scripts/run_integration_test.bash b/scripts/run_integration_test.bash index ca16183..7a96aab 100755 --- a/scripts/run_integration_test.bash +++ b/scripts/run_integration_test.bash @@ -4,6 +4,8 @@ set -euo pipefail IFS=$'\n\t' DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REALPATH=$(command -v uu-realpath || command -v realpath) + samples_dir=$(readlink -f "$DIR/../org_mode_samples") function get_test_names { @@ -11,7 +13,7 @@ function get_test_names { do if [ -e "$test_file" ]; then test_file_full_path=$(readlink -f "$test_file") - relative_to_samples=$(realpath --relative-to "$samples_dir" "$test_file_full_path") + relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path") without_extension="${relative_to_samples%.org}" echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]' else diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 1f13463..80df736 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -1071,13 +1071,23 @@ fn compare_strike_through<'s>( } fn compare_regular_link<'s>( - _source: &'s str, - _emacs: &'s Token<'s>, - _rust: &'s RegularLink<'s>, + source: &'s str, + emacs: &'s Token<'s>, + rust: &'s RegularLink<'s>, ) -> Result> { + let mut this_status = DiffStatus::Good; + let emacs_name = "link"; + if assert_name(emacs, emacs_name).is_err() { + this_status = DiffStatus::Bad; + } + + if assert_bounds(source, emacs, rust).is_err() { + this_status = DiffStatus::Bad; + } + Ok(DiffResult { - status: DiffStatus::Good, - name: "regular-link".to_owned(), + status: this_status, + name: emacs_name.to_owned(), message: None, children: Vec::new(), }) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0d5e2a6..208d9c6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -26,6 +26,7 @@ mod plain_list; mod plain_text; mod planning; mod property_drawer; +mod regular_link; pub mod sexp; mod source; mod table; diff --git a/src/parser/object.rs b/src/parser/object.rs index 5a3ec31..932a0bd 100644 --- a/src/parser/object.rs +++ b/src/parser/object.rs @@ -2,17 +2,14 @@ use super::source::Source; #[derive(Debug)] pub enum Object<'s> { + RegularLink(RegularLink<'s>), Bold(Bold<'s>), Italic(Italic<'s>), Underline(Underline<'s>), StrikeThrough(StrikeThrough<'s>), Code(Code<'s>), Verbatim(Verbatim<'s>), - PlainText(PlainText<'s>), - - #[allow(dead_code)] - RegularLink(RegularLink<'s>), } #[derive(Debug)] @@ -111,3 +108,9 @@ impl<'s> Source<'s> for Verbatim<'s> { self.source } } + +impl<'s> Source<'s> for RegularLink<'s> { + fn get_source(&'s self) -> &'s str { + self.source + } +} diff --git a/src/parser/object_parser.rs b/src/parser/object_parser.rs index a4ba457..ff2fea1 100644 --- a/src/parser/object_parser.rs +++ b/src/parser/object_parser.rs @@ -4,6 +4,7 @@ use nom::combinator::not; use super::parser_with_context::parser_with_context; use super::plain_text::plain_text; +use super::regular_link::regular_link; use super::Context; use crate::error::Res; use crate::parser::object::Object; @@ -19,6 +20,10 @@ pub fn standard_set_object<'r, 's>( alt(( parser_with_context!(text_markup)(context), + map( + parser_with_context!(regular_link)(context), + Object::RegularLink, + ), map(parser_with_context!(plain_text)(context), Object::PlainText), ))(input) } @@ -43,5 +48,20 @@ pub fn any_object_except_plain_text<'r, 's>( input: &'s str, ) -> Res<&'s str, Object<'s>> { // Used for exit matchers so this does not check exit matcher condition. - alt((parser_with_context!(text_markup)(context),))(input) + alt(( + parser_with_context!(text_markup)(context), + map( + parser_with_context!(regular_link)(context), + Object::RegularLink, + ), + ))(input) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn regular_link_description_object_set<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, Object<'s>> { + // TODO: minimal set of objects as well as export snippets, inline babel calls, inline source blocks, macros, and statistics cookies. It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]] + alt((parser_with_context!(minimal_set_object)(context),))(input) } diff --git a/src/parser/regular_link.rs b/src/parser/regular_link.rs new file mode 100644 index 0000000..321addd --- /dev/null +++ b/src/parser/regular_link.rs @@ -0,0 +1,98 @@ +use nom::branch::alt; +use nom::bytes::complete::escaped; +use nom::bytes::complete::tag; +use nom::bytes::complete::take_till1; +use nom::character::complete::one_of; +use nom::character::complete::space0; +use nom::combinator::verify; +use nom::multi::many_till; + +use super::parser_with_context::parser_with_context; +use super::util::get_consumed; +use super::Context; +use super::Object; +use super::RegularLink; +use crate::error::Res; +use crate::parser::exiting::ExitClass; +use crate::parser::object_parser::regular_link_description_object_set; +use crate::parser::parser_context::ContextElement; +use crate::parser::parser_context::ExitMatcherNode; +use crate::parser::util::exit_matcher_parser; + +#[tracing::instrument(ret, level = "debug")] +pub fn regular_link<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, RegularLink<'s>> { + alt(( + parser_with_context!(regular_link_without_description)(context), + parser_with_context!(regular_link_with_description)(context), + ))(input) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn regular_link_without_description<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, RegularLink<'s>> { + let (remaining, _opening_bracket) = tag("[[")(input)?; + let (remaining, _path) = pathreg(context, remaining)?; + let (remaining, _closing_bracket) = tag("]]")(remaining)?; + let (remaining, _trailing_whitespace) = space0(remaining)?; + let source = get_consumed(input, remaining); + Ok((remaining, RegularLink { source })) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn regular_link_with_description<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, 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) = space0(remaining)?; + let source = get_consumed(input, remaining); + Ok((remaining, RegularLink { source })) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn pathreg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + let (remaining, path) = escaped( + take_till1(|c| match c { + '\\' | ']' => true, + _ => false, + }), + '\\', + one_of(r#"]"#), + )(input)?; + Ok((remaining, path)) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn description<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, Vec>> { + let parser_context = + context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Beta, + exit_matcher: &description_end, + })); + let (remaining, (children, _exit_contents)) = verify( + many_till( + parser_with_context!(regular_link_description_object_set)(&parser_context), + parser_with_context!(exit_matcher_parser)(&parser_context), + ), + |(children, _exit_contents)| !children.is_empty(), + )(input)?; + + Ok((remaining, children)) +} + +#[tracing::instrument(ret, level = "debug")] +fn description_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + tag("]]")(input) +} diff --git a/src/parser/text_markup.rs b/src/parser/text_markup.rs index 1c36c51..18cb60b 100644 --- a/src/parser/text_markup.rs +++ b/src/parser/text_markup.rs @@ -25,6 +25,7 @@ use crate::parser::parser_with_context::parser_with_context; use crate::parser::util::exit_matcher_parser; use crate::parser::util::get_consumed; use crate::parser::util::get_one_before; +use crate::parser::util::preceded_by_whitespace; use crate::parser::Bold; use crate::parser::Code; use crate::parser::Italic; @@ -32,7 +33,6 @@ use crate::parser::Object; use crate::parser::StrikeThrough; use crate::parser::Underline; use crate::parser::Verbatim; -use crate::parser::util::preceded_by_whitespace; #[tracing::instrument(ret, level = "debug")] pub fn text_markup<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Object<'s>> { diff --git a/toy_language.txt b/toy_language.txt index d990b43..fc07a3b 100644 --- a/toy_language.txt +++ b/toy_language.txt @@ -1 +1,13 @@ -foo *bar baz * lorem* ipsum +prologue *goes here* I guess *bold +text* + +bold*wont* start *or stop*when there is text outside it + +I guess *regular + +text* + +[[foo][foo *bar]] baz* car + + +*nesting *bold entrances* and* exits