Merge branch 'test_improvement'

This commit is contained in:
Tom Alexander 2023-04-19 15:39:32 -04:00
commit 31da2e970c
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
9 changed files with 213 additions and 253 deletions

View File

@ -24,12 +24,16 @@ clean:
.PHONY: test
test:
> cargo test --lib
> cargo test --lib --test test_loader
.PHONY: integrationtest
integrationtest:
> cargo test --no-fail-fast --test test_loader
.PHONY: unittest
unittest:
> cargo test --lib
.PHONY: run
run:
> cargo run

View File

@ -22,17 +22,28 @@ fn main() {
.unwrap_or(false)
}
Err(_) => true,
}).collect::<Result<Vec<_>, _>>().unwrap();
})
.collect::<Result<Vec<_>, _>>()
.unwrap();
for test in test_files {
write_test(&mut test_file, &test);
}
}
fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
let test_name = test.path().strip_prefix("org_mode_samples/").expect("Paths should be under org_mode_samples/").to_string_lossy().to_lowercase().strip_suffix(".org").expect("Should have .org extension").replace("/", "_");
let test_name = test
.path()
.strip_prefix("org_mode_samples/")
.expect("Paths should be under org_mode_samples/")
.to_string_lossy()
.to_lowercase()
.strip_suffix(".org")
.expect("Should have .org extension")
.replace("/", "_");
if let Some(reason) = is_expect_fail(test_name.as_str()) {
write!(test_file, "#[ignore]\n").unwrap();
}
write!(
test_file,
include_str!("./tests/test_template"),
@ -56,3 +67,17 @@ use organic::sexp;
)
.unwrap();
}
fn is_expect_fail(name: &str) -> Option<&str> {
match name {
"drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"element_container_priority_drawer_dynamic_block" => Some("Keyword needs to be implemented."),
"element_container_priority_dynamic_block_dynamic_block" => Some("Keyword needs to be implemented."),
"element_container_priority_footnote_definition_dynamic_block" => Some("Keyword needs to be implemented."),
"element_container_priority_greater_block_dynamic_block" => Some("Keyword needs to be implemented."),
"element_container_priority_section_dynamic_block" => Some("Keyword needs to be implemented."),
"exit_matcher_investigation_table_list" => Some("Table needs to be implemented."),
"element_container_priority_readme" => Some("Table needs to be implemented."),
_ => None,
}
}

View File

@ -0,0 +1,27 @@
#!/usr/bin/env bash
#
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
samples_dir=$(readlink -f "org_mode_samples")
function get_test_names {
for test_file in "$@"
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")
without_extension="${relative_to_samples%.org}"
echo "${without_extension/\//_}"
else
echo "$test_file"
fi
done
}
get_test_names "$@" | while read test; do
cargo test --no-fail-fast --test test_loader "$test"
done

View File

@ -1,4 +1,6 @@
use super::sexp::Token;
use super::util::assert_bounds;
use super::util::assert_name;
use crate::compare::util::get_offsets;
use crate::parser::Comment;
use crate::parser::Document;
@ -18,6 +20,7 @@ use crate::DynamicBlock;
pub struct DiffResult {
status: DiffStatus,
name: String,
message: Option<String>,
children: Vec<DiffResult>,
}
@ -71,13 +74,11 @@ pub fn compare_document<'s>(
rust: &'s Document<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children.first().ok_or("Should have at least one child.")?;
let first_child_text = first_child.as_atom()?;
if first_child_text != "org-data" {
return Err("Document should correspond to an org-data cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "org-data").is_err() {
this_status = DiffStatus::Bad;
}
// Skipping "org-data" and the first parameter which is often nil
for (i, token) in children.iter().skip(2).enumerate() {
@ -112,6 +113,7 @@ pub fn compare_document<'s>(
Ok(DiffResult {
status: this_status,
name: "document".to_owned(),
message: None,
children: child_status,
})
}
@ -122,29 +124,13 @@ fn compare_section<'s>(
rust: &'s Section<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children.first().ok_or("Should have at least one child.")?;
let first_child_text = first_child.as_atom()?;
if first_child_text != "section" {
return Err("Section should correspond to a section cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
if assert_name(emacs, "section").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -155,6 +141,7 @@ fn compare_section<'s>(
Ok(DiffResult {
status: this_status,
name: "section".to_owned(),
message: None,
children: child_status,
})
}
@ -165,29 +152,13 @@ fn compare_heading<'s>(
rust: &'s Heading<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children.first().ok_or("Should have at least one child.")?;
let first_child_text = first_child.as_atom()?;
if first_child_text != "headline" {
return Err("Heading should correspond to a headline cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "headline").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -205,6 +176,7 @@ fn compare_heading<'s>(
Ok(DiffResult {
status: this_status,
name: "heading".to_owned(),
message: None,
children: child_status,
})
}
@ -231,31 +203,13 @@ fn compare_paragraph<'s>(
rust: &'s Paragraph<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != "paragraph" {
return Err("Paragraph should correspond to a paragraph cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "paragraph").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -264,6 +218,7 @@ fn compare_paragraph<'s>(
Ok(DiffResult {
status: this_status,
name: "paragraph".to_owned(),
message: None,
children: child_status,
})
}
@ -274,31 +229,13 @@ fn compare_plain_list<'s>(
rust: &'s PlainList<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != "plain-list" {
return Err("Paragraph should correspond to a paragraph cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "plain-list").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -309,6 +246,7 @@ fn compare_plain_list<'s>(
Ok(DiffResult {
status: this_status,
name: "plain-list".to_owned(),
message: None,
children: child_status,
})
}
@ -319,31 +257,13 @@ fn compare_plain_list_item<'s>(
rust: &'s PlainListItem<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != "item" {
return Err("PlainListItem should correspond to an item cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "item").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -354,6 +274,7 @@ fn compare_plain_list_item<'s>(
Ok(DiffResult {
status: this_status,
name: "plain-list-item".to_owned(),
message: None,
children: child_status,
})
}
@ -364,43 +285,20 @@ fn compare_greater_block<'s>(
rust: &'s GreaterBlock<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
match rust.name.to_lowercase().as_str() {
"center" => {
if first_child != "center-block" {
return Err(
"Center greater blocks should correspond to a center-block cell.".into(),
);
}
}
"quote" => {
if first_child != "quote-block" {
return Err("Quote greater blocks should correspond to a quote-block cell.".into());
}
}
_ => todo!(),
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(
emacs,
match rust.name.to_lowercase().as_str() {
"center" => "center-block",
"quote" => "quote-block",
_ => todo!(),
},
).is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -411,6 +309,7 @@ fn compare_greater_block<'s>(
Ok(DiffResult {
status: this_status,
name: "greater-block".to_owned(),
message: None,
children: child_status,
})
}
@ -421,31 +320,13 @@ fn compare_dynamic_block<'s>(
rust: &'s DynamicBlock<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != "dynamic-block" {
return Err("Dynamic block should correspond to a dynamic-block cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "dynamic-block").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -456,6 +337,7 @@ fn compare_dynamic_block<'s>(
Ok(DiffResult {
status: this_status,
name: "dynamic-block".to_owned(),
message: None,
children: child_status,
})
}
@ -466,31 +348,13 @@ fn compare_footnote_definition<'s>(
rust: &'s FootnoteDefinition<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != "footnote-definition" {
return Err("Paragraph should correspond to a paragraph cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "footnote-definition").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -501,6 +365,7 @@ fn compare_footnote_definition<'s>(
Ok(DiffResult {
status: this_status,
name: "footnote-definition".to_owned(),
message: None,
children: child_status,
})
}
@ -511,37 +376,20 @@ fn compare_comment<'s>(
rust: &'s Comment<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != "comment" {
return Err("Comment should correspond to a comment cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "comment").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
Ok(DiffResult {
status: this_status,
name: "comment".to_owned(),
message: None,
children: child_status,
})
}
@ -552,31 +400,13 @@ fn compare_drawer<'s>(
rust: &'s Drawer<'s>,
) -> Result<DiffResult, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != "drawer" {
return Err("Drawer should correspond to a drawer cell.".into());
}
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
if assert_name(emacs, "drawer").is_err() {
this_status = DiffStatus::Bad;
}
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
if assert_bounds(source, emacs, rust).is_err() {
this_status = DiffStatus::Bad;
}
@ -587,6 +417,7 @@ fn compare_drawer<'s>(
Ok(DiffResult {
status: this_status,
name: "drawer".to_owned(),
message: None,
children: child_status,
})
}

View File

@ -1,5 +1,7 @@
use crate::parser::Source;
use super::sexp::Token;
/// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize;
@ -19,3 +21,46 @@ pub fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (u
let end = offset + rust_object_source.len();
(offset, end)
}
pub fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_child != name {
Err(format!(
"Expected a {expected} cell, but found a {found} cell.",
expected = name,
found = first_child
))?;
}
Ok(())
}
pub fn assert_bounds<'s, S: Source<'s>>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s S,
) -> Result<(), Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let begin = attributes_map
.get(":begin")
.ok_or("Missing :begin attribute.")?
.as_atom()?;
let end = attributes_map
.get(":end")
.ok_or("Missing :end attribute.")?
.as_atom()?;
let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
}
Ok(())
}

View File

@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::take_while;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::multi::many_till;
@ -17,6 +18,7 @@ use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@ -24,6 +26,8 @@ use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::parser::util::start_of_line;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::parser::Drawer;
use crate::parser::Element;
use crate::parser::Paragraph;
#[tracing::instrument(ret, level = "debug")]
pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Drawer<'s>> {
@ -51,8 +55,17 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
let element_matcher = parser_with_context!(element)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining,
vec![Element::Paragraph(Paragraph::of_text(whitespace))],
),
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, _end) = drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =

View File

@ -83,10 +83,18 @@ fn footnote_definition_end<'r, 's>(
input: &'s str,
) -> Res<&'s str, &'s str> {
let start_of_line_matcher = parser_with_context!(start_of_line)(context);
let footnote_definition_matcher = parser_with_context!(footnote_definition)(context);
let allow_nesting_context =
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
if immediate_in_section(context, "footnote definition") {
&allow_nesting_context
} else {
context
},
);
let maybe_consume_trailing_whitespace_matcher =
parser_with_context!(maybe_consume_trailing_whitespace)(context);
alt((
let (remaining, source) = alt((
recognize(tuple((
maybe_consume_trailing_whitespace_matcher,
footnote_definition_matcher,
@ -95,7 +103,9 @@ fn footnote_definition_end<'r, 's>(
start_of_line_matcher,
verify(many1(blank_line), |lines: &Vec<&str>| lines.len() >= 2),
))),
))(input)
))(input)?;
Ok((remaining, source))
}
#[cfg(test)]

View File

@ -35,11 +35,6 @@ pub fn greater_block<'r, 's>(
input: &'s str,
) -> Res<&'s str, GreaterBlock<'s>> {
// TODO: Do I need to differentiate between different greater block types.
if immediate_in_section(context, "greater block") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element",
))));
}
start_of_line(context, input)?;
let (remaining, _leading_whitespace) = space0(input)?;
// TODO: Not handling indentation before start of block
@ -50,11 +45,21 @@ pub fn greater_block<'r, 's>(
_ => true,
}),
))(remaining)?;
let context_name = match name.to_lowercase().as_str() {
"center" => "center block",
"quote" => "quote block",
_ => "greater block",
};
if immediate_in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element",
))));
}
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?;
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("greater block"))
.with_additional_node(ContextElement::Context(context_name))
.with_additional_node(ContextElement::GreaterBlock(name))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,

View File

@ -1,5 +1,5 @@
:firstdrawer:
:seconddrawer:
foo
:end:
:drawername:
:end: