Merge branch 'lesser_block_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded

This commit is contained in:
Tom Alexander 2023-10-05 02:37:58 -04:00
commit 02af3d0081
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
82 changed files with 1624 additions and 172 deletions

View File

@ -46,6 +46,13 @@ dockertest:
> $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: buildtest
buildtest:
> cargo build --no-default-features
> cargo build --no-default-features --features compare
> cargo build --no-default-features --features tracing
> cargo build --no-default-features --features compare,tracing
.PHONY: foreign_document_test
foreign_document_test:
> $(MAKE) -C docker/organic_test run_foreign_document_test

View File

@ -0,0 +1,17 @@
#+name: foo
: bar
#+source: foo
: bar
#+tblname: foo
: bar
#+resname: foo
: bar
#+srcname: foo
: bar
#+label: foo
: bar

View File

@ -0,0 +1,8 @@
#+NAME: foo
bar
#+NaMe: baz
cat
#+name: lorem
ipsum

View File

@ -0,0 +1,6 @@
#+name: foo
#+source: bar
#+name: baz
#+tblname: lorem
#+label: ipsum
: dolar

View File

@ -0,0 +1,2 @@
#+tblname: foo
: bar

View File

@ -0,0 +1,10 @@
* Headline
before
#+NAME: foo
:candle:
inside
the drawer
:end:
after

View File

@ -0,0 +1,7 @@
#+NAME: foo
#+BEGIN: clocktable :scope file :maxlevel 2
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
| Headline | Time |
|--------------+--------|
| *Total time* | *0:00* |
#+END:

View File

@ -0,0 +1,8 @@
#+begin_center
#+end_center
#+begin_center
#+NAME: foo
#+end_center

View File

@ -0,0 +1,4 @@
#+NAME: foo
#+begin_center
#+end_center

View File

@ -0,0 +1,4 @@
#+NAME: foo
#+begin_quote
#+end_quote

View File

@ -0,0 +1,6 @@
#+NAME: foo
#+begin_defun
foo
{{{bar(baz)}}}
#+end_defun

View File

@ -0,0 +1,2 @@
#+NAME: foo
1. bar

View File

@ -0,0 +1,2 @@
#+NAME: foo
| foo | bar |

View File

@ -0,0 +1,2 @@
#+NAME: foo
#+call: foo(bar="baz")

View File

@ -0,0 +1,3 @@
CLOCK: [2023-04-21 Fri 19:32]--[2023-04-21 Fri 19:35] => 0:03
#+NAME: foo
CLOCK: [2023-04-21 Fri 19:43]

View File

@ -0,0 +1,2 @@
#+NAME: foo
# Comments cannot have affiliated keywords.

View File

@ -0,0 +1,2 @@
#+NAME: foo
%%(foo)

View File

@ -0,0 +1,2 @@
#+NAME: foo
: bar

View File

@ -0,0 +1,2 @@
#+NAME: foo
-----

View File

@ -0,0 +1,2 @@
#+NAME: foo
#+FOO: BAR

View File

@ -0,0 +1,4 @@
#+NAME: foo
\begin{foo}
bar
\end{foo}

View File

@ -0,0 +1,10 @@
#+begin_comment
,* foo
,,,** bar
,*** baz
lorem
, ipsum
,#+begin_src dolar
,#+end_src
#+end_comment

View File

@ -0,0 +1,4 @@
#+NAME: foo
#+begin_comment text
bar
#+end_comment

View File

@ -0,0 +1,4 @@
#+begin_comment
This is a comment
,* with an escaped line.
#+end_comment

View File

@ -0,0 +1,3 @@
#+begin_comment -n 20
foo
#+end_comment

View File

@ -0,0 +1,10 @@
#+begin_example
,* foo
,,,** bar
,*** baz
lorem
, ipsum
,#+begin_src dolar
,#+end_src
#+end_example

View File

@ -0,0 +1,7 @@
#+begin_example python :exports results
print("foo")
#+end_example
#+begin_example python -n :exports results
print("foo")
#+end_example

View File

@ -0,0 +1,3 @@
#+begin_example elisp -n 5
foo
#+end_example

View File

@ -0,0 +1,7 @@
#+begin_example -n -10
foo
#+end_example
#+begin_example +n -15
bar
#+end_example

View File

@ -0,0 +1,7 @@
#+begin_example -n 0
foo
#+end_example
#+begin_example +n 0
bar
#+end_example

View File

@ -0,0 +1,8 @@
#+begin_example -n 5
foo
#+end_example
# Line numbering starts at 15 for the example below since it uses +n.
#+begin_example +n 10
bar
#+end_example

View File

@ -0,0 +1,4 @@
#+NAME: foo
#+begin_example text
bar
#+end_example

View File

@ -0,0 +1,3 @@
#+begin_example foo -k
bar
#+end_example

View File

@ -0,0 +1,3 @@
#+begin_example foo -n bar -k baz
#+end_example

View File

@ -0,0 +1,15 @@
#+begin_example text -i
foo
#+end_example
#+begin_example text -n -i
foo
#+end_example
#+begin_example text
foo
#+end_example
#+begin_example text -n -r -k
foo
#+end_example

View File

@ -0,0 +1,6 @@
#+begin_example text
foo
bar (ref:here)
baz
#+end_example
Link to the reference: [[(here)]]

View File

@ -0,0 +1,2 @@
#+begin_example
#+end_example

View File

@ -0,0 +1,15 @@
#+BEGIN_EXAMPLE elisp -n -r -l "((%s))"
foo
#+END_EXAMPLE
#+BEGIN_EXAMPLE elisp -k -n -r -l "((%s))"
foo
#+END_EXAMPLE
#+BEGIN_EXAMPLE elisp -k 8 -n -r -l "((%s))"
foo
#+END_EXAMPLE
#+BEGIN_EXAMPLE elisp -n -r -k -l "((%s))"
foo
#+END_EXAMPLE

View File

@ -0,0 +1,3 @@
#+begin_example +n 10
foo
#+end_example

View File

@ -0,0 +1,10 @@
#+begin_export html
,* foo
,,,** bar
,*** baz
lorem
, ipsum
,#+begin_src dolar
,#+end_src
#+end_export

View File

@ -0,0 +1,4 @@
#+NAME: foo
#+begin_export text
bar
#+end_export

View File

@ -0,0 +1,3 @@
#+begin_export latex
This would be LaTeX code.
#+end_export

View File

@ -0,0 +1,6 @@
# Verse blocks are the only lesser blocks that contain objects
#+begin_verse
#+begin_comment
This is a comment.
#+end_comment
#+end_verse

View File

@ -0,0 +1,7 @@
#+begin_src python :exports results
print("foo")
#+end_src
#+begin_src python -n :exports results
print("foo")
#+end_src

View File

@ -0,0 +1,4 @@
#+NAME: foo
#+begin_src text
bar
#+end_src

View File

@ -0,0 +1,7 @@
#+begin_src python :exports results
print("foo")
#+end_src
#+begin_src python -n :exports results
print("foo")
#+end_src

View File

@ -0,0 +1,3 @@
#+begin_src foo -n bar -k baz
#+end_src

View File

@ -0,0 +1,15 @@
#+begin_src text -i
foo
#+end_src
#+begin_src text -n -i
foo
#+end_src
#+begin_src text
foo
#+end_src
#+begin_src text -n -r -k
foo
#+end_src

View File

@ -0,0 +1,6 @@
#+begin_src text
foo
bar (ref:here)
baz
#+end_src
Link to the reference: [[(here)]]

View File

@ -0,0 +1,4 @@
#+NAME: foo
#+begin_verse text
bar
#+end_verse

View File

@ -0,0 +1,2 @@
#+NAME: foo
bar

View File

@ -1,6 +1,7 @@
use std::path::Path;
use crate::compare::diff::compare_document;
use crate::compare::diff::DiffResult;
use crate::compare::parse::emacs_parse_anonymous_org_document;
use crate::compare::parse::emacs_parse_file_org_document;
use crate::compare::parse::get_emacs_version;
@ -43,6 +44,12 @@ pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
if diff_result.is_bad() {
Err("Diff results do not match.")?;
} else {
println!(
"{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0),
reset = DiffResult::reset_color(),
);
}
Ok(())
@ -83,6 +90,12 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
if diff_result.is_bad() {
Err("Diff results do not match.")?;
} else {
println!(
"{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0),
reset = DiffResult::reset_color(),
);
}
Ok(())

View File

@ -18,6 +18,7 @@ use crate::types::AstNode;
use crate::types::BabelCall;
use crate::types::Bold;
use crate::types::CenterBlock;
use crate::types::CharOffsetInLine;
use crate::types::CheckboxType;
use crate::types::Citation;
use crate::types::CitationReference;
@ -52,6 +53,7 @@ use crate::types::Keyword;
use crate::types::LatexEnvironment;
use crate::types::LatexFragment;
use crate::types::LineBreak;
use crate::types::LineNumber;
use crate::types::Minute;
use crate::types::MinuteInner;
use crate::types::Month;
@ -75,6 +77,7 @@ use crate::types::RadioTarget;
use crate::types::RegularLink;
use crate::types::RepeaterType;
use crate::types::RepeaterWarningDelayValueType;
use crate::types::RetainLabels;
use crate::types::Section;
use crate::types::SpecialBlock;
use crate::types::SrcBlock;
@ -83,6 +86,7 @@ use crate::types::StatisticsCookie;
use crate::types::StrikeThrough;
use crate::types::Subscript;
use crate::types::Superscript;
use crate::types::SwitchNumberLines;
use crate::types::Table;
use crate::types::TableCell;
use crate::types::TableRow;
@ -231,7 +235,7 @@ impl<'b, 's> DiffResult<'b, 's> {
.any(|child| child.is_immediately_bad() || child.has_bad_children())
}
fn foreground_color(red: u8, green: u8, blue: u8) -> String {
pub(crate) fn foreground_color(red: u8, green: u8, blue: u8) -> String {
if DiffResult::should_use_color() {
format!(
"\x1b[38;2;{red};{green};{blue}m",
@ -245,7 +249,7 @@ impl<'b, 's> DiffResult<'b, 's> {
}
#[allow(dead_code)]
fn background_color(red: u8, green: u8, blue: u8) -> String {
pub(crate) fn background_color(red: u8, green: u8, blue: u8) -> String {
if DiffResult::should_use_color() {
format!(
"\x1b[48;2;{red};{green};{blue}m",
@ -258,7 +262,7 @@ impl<'b, 's> DiffResult<'b, 's> {
}
}
fn reset_color() -> &'static str {
pub(crate) fn reset_color() -> &'static str {
if DiffResult::should_use_color() {
"\x1b[0m"
} else {
@ -837,8 +841,19 @@ fn compare_paragraph<'b, 's>(
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let mut child_status = Vec::new();
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
@ -865,6 +880,17 @@ fn compare_plain_list<'b, 's>(
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
// Compare type
// :type is an unquoted atom of either descriptive, ordered, or unordered
let list_type = get_property_unquoted_atom(emacs, ":type")?;
@ -1023,8 +1049,19 @@ fn compare_center_block<'b, 's>(
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let mut child_status = Vec::new();
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
@ -1048,8 +1085,19 @@ fn compare_quote_block<'b, 's>(
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let mut child_status = Vec::new();
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
@ -1076,14 +1124,25 @@ fn compare_special_block<'b, 's>(
let mut this_status = DiffStatus::Good;
let mut message = None;
// Compare type
let special_block_type =
get_property_quoted_string(emacs, ":type")?.ok_or("Special blocks should have a name.")?;
if special_block_type != rust.name {
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
special_block_type, rust.name
name, rust.name
));
}
// Compare type
let special_block_type =
get_property_quoted_string(emacs, ":type")?.ok_or("Special blocks should have a name.")?;
if special_block_type != rust.block_type {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
special_block_type, rust.block_type
));
}
@ -1126,14 +1185,25 @@ fn compare_dynamic_block<'b, 's>(
let mut this_status = DiffStatus::Good;
let mut message = None;
// Compare block-name
let block_name = get_property_quoted_string(emacs, ":block-name")?
.ok_or("Dynamic blocks should have a name.")?;
if block_name != rust.name {
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
block_name, rust.name
name, rust.name
));
}
// Compare block-name
let block_name = get_property_quoted_string(emacs, ":block-name")?
.ok_or("Dynamic blocks should have a name.")?;
if block_name != rust.block_name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
block_name, rust.block_name
));
}
@ -1177,6 +1247,17 @@ fn compare_footnote_definition<'b, 's>(
let mut message = None;
// TODO: Compare :pre-blank
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
// Compare label
let label = get_property_quoted_string(emacs, ":label")?
.ok_or("Footnote definitions should have a name.")?;
@ -1245,10 +1326,10 @@ fn compare_drawer<'b, 's>(
let mut this_status = DiffStatus::Good;
let mut message = None;
// Compare drawer-name
let name =
get_property_quoted_string(emacs, ":drawer-name")?.ok_or("Drawers should have a name.")?;
if name != rust.name {
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
@ -1256,6 +1337,17 @@ fn compare_drawer<'b, 's>(
));
}
// Compare drawer-name
let drawer_name =
get_property_quoted_string(emacs, ":drawer-name")?.ok_or("Drawers should have a name.")?;
if drawer_name != rust.drawer_name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Drawer name mismatch (emacs != rust) {:?} != {:?}",
drawer_name, rust.drawer_name
));
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
}
@ -1308,11 +1400,11 @@ fn compare_node_property<'b, 's>(
// Compare key
let key =
get_property_quoted_string(emacs, ":key")?.ok_or("Node properties should have a key.")?;
if key != rust.name {
if key != rust.property_name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Key mismatch (emacs != rust) {:?} != {:?}",
key, rust.name
key, rust.property_name
));
}
@ -1351,6 +1443,17 @@ fn compare_table<'b, 's>(
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
// Compare formulas
//
// :tblfm is either nil or a list () filled with quoted strings containing the value for any tblfm keywords at the end of the table.
@ -1492,8 +1595,19 @@ fn compare_verse_block<'b, 's>(
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let child_status = Vec::new();
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
for (_emacs_child, _rust_child) in children.iter().skip(2).zip(rust.children.iter()) {}
@ -1513,10 +1627,29 @@ fn compare_comment_block<'b, 's>(
emacs: &'b Token<'s>,
rust: &'b CommentBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :value
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
// Compare value
let contents = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new());
if contents != rust.contents {
this_status = DiffStatus::Bad;
message = Some(format!(
"Value mismatch (emacs != rust) {:?} != {:?}",
contents, rust.contents
));
}
Ok(DiffResult {
status: this_status,
@ -1533,11 +1666,165 @@ fn compare_example_block<'b, 's>(
_source: &'s str,
emacs: &'b Token<'s>,
rust: &'b ExampleBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let this_status = DiffStatus::Good;
let message = None;
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error + 's>> {
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :value :switches :number-lines :preserve-indent :retain-labels :use-labels :label-fmt
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
// Compare value
let contents = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new());
if contents != rust.contents {
this_status = DiffStatus::Bad;
message = Some(format!(
"Value mismatch (emacs != rust) {:?} != {:?}",
contents, rust.contents
));
}
// Compare switches
let switches = get_property_quoted_string(emacs, ":switches")?;
match (switches.as_ref().map(String::as_str), rust.switches) {
(None, None) => {}
(Some(""), None) => {}
(None, Some("")) => {
unreachable!("The organic parser would return a None instead of an empty string.");
}
(Some(e), Some(r)) if e == r => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Switches mismatch (emacs != rust) {:?} != {:?}",
switches, rust.switches
));
}
}
// Compare number-lines
let number_lines = get_property(emacs, ":number-lines")?;
match (number_lines, &rust.number_lines) {
(None, None) => {}
(Some(number_lines), Some(rust_number_lines)) => {
let token_list = number_lines.as_list()?;
let number_type = token_list
.get(0)
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a type.")?;
let number_value = token_list
.get(2)
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(|val| val.parse::<LineNumber>())
.map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a value.")?;
match (number_type, number_value, rust_number_lines) {
("new", emacs_val, SwitchNumberLines::New(rust_val)) if emacs_val == *rust_val => {}
("continued", emacs_val, SwitchNumberLines::Continued(rust_val))
if emacs_val == *rust_val => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Number lines mismatch (emacs != rust) {:?} != {:?}",
number_lines, rust.number_lines
));
}
}
}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Number lines mismatch (emacs != rust) {:?} != {:?}",
number_lines, rust.number_lines
));
}
};
// Compare preserve-indent
let preserve_indent: Option<CharOffsetInLine> =
get_property_numeric(emacs, ":preserve-indent")?;
if preserve_indent != rust.preserve_indent {
this_status = DiffStatus::Bad;
message = Some(format!(
"Prserve indent mismatch (emacs != rust) {:?} != {:?}",
preserve_indent, rust.preserve_indent
));
}
// Compare retain-labels
// retain-labels is t by default, nil if -r is set, or a number if -k and -r is set.
let retain_labels = get_property_unquoted_atom(emacs, ":retain-labels")?;
if let Some(retain_labels) = retain_labels {
if retain_labels == "t" {
match rust.retain_labels {
RetainLabels::Yes => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Retain labels mismatch (emacs != rust) {:?} != {:?}",
retain_labels, rust.retain_labels
));
}
}
} else {
let retain_labels: CharOffsetInLine = get_property_numeric(emacs, ":retain-labels")?.expect("Cannot be None or else the earlier get_property_unquoted_atom would have been None.");
match (retain_labels, &rust.retain_labels) {
(e, RetainLabels::Keep(r)) if e == *r => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Retain labels mismatch (emacs != rust) {:?} != {:?}",
retain_labels, rust.retain_labels
));
}
}
}
} else {
match rust.retain_labels {
RetainLabels::No => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Retain labels mismatch (emacs != rust) {:?} != {:?}",
retain_labels, rust.retain_labels
));
}
}
}
// Compare use-labels
let use_labels = get_property_boolean(emacs, ":use-labels")?;
if use_labels != rust.use_labels {
this_status = DiffStatus::Bad;
message = Some(format!(
"Use labels mismatch (emacs != rust) {:?} != {:?}",
use_labels, rust.use_labels
));
}
// Compare label-fmt
let label_format = get_property_quoted_string(emacs, ":label-fmt")?;
match (label_format.as_ref(), rust.label_format) {
(None, None) => {}
(Some(emacs_label_format), Some(rust_label_format))
if emacs_label_format == rust_label_format => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Label format mismatch (emacs != rust) {:?} != {:?}",
label_format, rust.label_format
));
}
}
Ok(DiffResult {
status: this_status,
@ -1555,10 +1842,41 @@ fn compare_export_block<'b, 's>(
emacs: &'b Token<'s>,
rust: &'b ExportBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :type :value
// TODO: Compare :caption
// Compare type
let export_type = get_property_quoted_string(emacs, ":type")?;
let rust_export_type = rust.get_export_type();
if export_type != rust_export_type {
this_status = DiffStatus::Bad;
message = Some(format!(
"Export type mismatch (emacs != rust) {:?} != {:?}",
export_type, rust.export_type
));
}
// Compare value
let contents = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new());
if contents != rust.contents {
this_status = DiffStatus::Bad;
message = Some(format!(
"Value mismatch (emacs != rust) {:?} != {:?}",
contents, rust.contents
));
}
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
Ok(DiffResult {
status: this_status,
@ -1575,11 +1893,193 @@ fn compare_src_block<'b, 's>(
_source: &'s str,
emacs: &'b Token<'s>,
rust: &'b SrcBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let this_status = DiffStatus::Good;
let message = None;
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error + 's>> {
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :language :switches :parameters :number-lines :preserve-indent :retain-labels :use-labels :label-fmt :value
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
// Compare language
let language = get_property_quoted_string(emacs, ":language")?;
if language.as_ref().map(String::as_str) != rust.language {
this_status = DiffStatus::Bad;
message = Some(format!(
"Language mismatch (emacs != rust) {:?} != {:?}",
language, rust.language
));
}
// Compare value
let contents = get_property_quoted_string(emacs, ":value")?.unwrap_or(String::new());
if contents != rust.contents {
this_status = DiffStatus::Bad;
message = Some(format!(
"Value mismatch (emacs != rust) {:?} != {:?}",
contents, rust.contents
));
}
// Compare switches
let switches = get_property_quoted_string(emacs, ":switches")?;
match (switches.as_ref().map(String::as_str), rust.switches) {
(None, None) => {}
(Some(""), None) => {}
(None, Some("")) => {
unreachable!("The organic parser would return a None instead of an empty string.");
}
(Some(e), Some(r)) if e == r => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Switches mismatch (emacs != rust) {:?} != {:?}",
switches, rust.switches
));
}
}
// Compare parameters
let parameters = get_property_quoted_string(emacs, ":parameters")?;
match (parameters.as_ref().map(String::as_str), rust.parameters) {
(None, None) => {}
(Some(""), None) => {}
(None, Some("")) => {
unreachable!("The organic parser would return a None instead of an empty string.");
}
(Some(e), Some(r)) if e == r => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Parameters mismatch (emacs != rust) {:?} != {:?}",
parameters, rust.parameters
));
}
}
// Compare number-lines
let number_lines = get_property(emacs, ":number-lines")?;
match (number_lines, &rust.number_lines) {
(None, None) => {}
(Some(number_lines), Some(rust_number_lines)) => {
let token_list = number_lines.as_list()?;
let number_type = token_list
.get(0)
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a type.")?;
let number_value = token_list
.get(2)
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.map(|val| val.parse::<LineNumber>())
.map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a value.")?;
match (number_type, number_value, rust_number_lines) {
("new", emacs_val, SwitchNumberLines::New(rust_val)) if emacs_val == *rust_val => {}
("continued", emacs_val, SwitchNumberLines::Continued(rust_val))
if emacs_val == *rust_val => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Number lines mismatch (emacs != rust) {:?} != {:?}",
number_lines, rust.number_lines
));
}
}
}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Number lines mismatch (emacs != rust) {:?} != {:?}",
number_lines, rust.number_lines
));
}
};
// Compare preserve-indent
let preserve_indent: Option<CharOffsetInLine> =
get_property_numeric(emacs, ":preserve-indent")?;
if preserve_indent != rust.preserve_indent {
this_status = DiffStatus::Bad;
message = Some(format!(
"Prserve indent mismatch (emacs != rust) {:?} != {:?}",
preserve_indent, rust.preserve_indent
));
}
// Compare retain-labels
// retain-labels is t by default, nil if -r is set, or a number if -k and -r is set.
let retain_labels = get_property_unquoted_atom(emacs, ":retain-labels")?;
if let Some(retain_labels) = retain_labels {
if retain_labels == "t" {
match rust.retain_labels {
RetainLabels::Yes => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Retain labels mismatch (emacs != rust) {:?} != {:?}",
retain_labels, rust.retain_labels
));
}
}
} else {
let retain_labels: CharOffsetInLine = get_property_numeric(emacs, ":retain-labels")?.expect("Cannot be None or else the earlier get_property_unquoted_atom would have been None.");
match (retain_labels, &rust.retain_labels) {
(e, RetainLabels::Keep(r)) if e == *r => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Retain labels mismatch (emacs != rust) {:?} != {:?}",
retain_labels, rust.retain_labels
));
}
}
}
} else {
match rust.retain_labels {
RetainLabels::No => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Retain labels mismatch (emacs != rust) {:?} != {:?}",
retain_labels, rust.retain_labels
));
}
}
}
// Compare use-labels
let use_labels = get_property_boolean(emacs, ":use-labels")?;
if use_labels != rust.use_labels {
this_status = DiffStatus::Bad;
message = Some(format!(
"Use labels mismatch (emacs != rust) {:?} != {:?}",
use_labels, rust.use_labels
));
}
// Compare label-fmt
let label_format = get_property_quoted_string(emacs, ":label-fmt")?;
match (label_format.as_ref(), rust.label_format) {
(None, None) => {}
(Some(emacs_label_format), Some(rust_label_format))
if emacs_label_format == rust_label_format => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Label format mismatch (emacs != rust) {:?} != {:?}",
label_format, rust.label_format
));
}
}
Ok(DiffResult {
status: this_status,
@ -1618,11 +2118,22 @@ fn compare_diary_sexp<'b, 's>(
emacs: &'b Token<'s>,
rust: &'b DiarySexp<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :value
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
@ -1711,11 +2222,22 @@ fn compare_fixed_width_area<'b, 's>(
rust: &'b FixedWidthArea<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :value
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
@ -1733,8 +2255,19 @@ fn compare_horizontal_rule<'b, 's>(
rust: &'b HorizontalRule<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
Ok(DiffResult {
status: this_status,
@ -1756,6 +2289,17 @@ fn compare_keyword<'b, 's>(
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
let key = unquote(
get_property(emacs, ":key")?
.ok_or("Emacs keywords should have a :key")?
@ -1802,6 +2346,18 @@ fn compare_babel_call<'b, 's>(
let mut message = None;
// TODO: Compare :call :inside-header :arguments :end-header
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
let value = unquote(
get_property(emacs, ":value")?
.ok_or("Emacs keywords should have a :value")?
@ -1832,11 +2388,22 @@ fn compare_latex_environment<'b, 's>(
rust: &'b LatexEnvironment<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let this_status = DiffStatus::Good;
let message = None;
let mut this_status = DiffStatus::Good;
let mut message = None;
// TODO: Compare :value
// TODO: Compare :caption
// Compare name
let name = get_property_quoted_string(emacs, ":name")?;
if name.as_ref().map(String::as_str) != rust.name {
this_status = DiffStatus::Bad;
message = Some(format!(
"Name mismatch (emacs != rust) {:?} != {:?}",
name, rust.name
));
}
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),

View File

@ -12,6 +12,7 @@ use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::OrgSource;
use crate::types::Keyword;
#[derive(Debug)]
pub(crate) enum ContextElement<'r, 's> {
@ -27,11 +28,22 @@ pub(crate) enum ContextElement<'r, 's> {
/// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool),
/// Indicate that we are parsing a paragraph that already has affiliated keywords.
///
/// The value stored is the start of the element after the affiliated keywords. In this way, we can ensure that we do not exit an element immediately after the affiliated keyword had been consumed.
HasAffiliatedKeyword(HasAffiliatedKeywordInner<'r, 's>),
/// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement.
#[allow(dead_code)]
Placeholder(PhantomData<&'s str>),
}
#[derive(Debug, Clone)]
pub(crate) struct HasAffiliatedKeywordInner<'r, 's> {
pub(crate) start_after_affiliated_keywords: OrgSource<'s>,
pub(crate) keywords: &'r Vec<Keyword<'s>>,
}
pub(crate) struct ExitMatcherNode<'r> {
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
pub(crate) exit_matcher: &'r DynContextMatcher<'r>,

View File

@ -32,6 +32,11 @@ pub struct GlobalSettings<'g, 's> {
///
/// Corresponds to org-footnote-section elisp variable.
pub footnote_section: &'g str,
/// The label format for references inside src/example blocks.
///
/// Corresponds to org-coderef-label-format elisp variable.
pub coderef_label_format: &'g str,
}
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@ -49,6 +54,7 @@ impl<'g, 's> GlobalSettings<'g, 's> {
tab_width: DEFAULT_TAB_WIDTH,
odd_levels_only: HeadlineLevelFilter::default(),
footnote_section: "Footnotes",
coderef_label_format: "(ref:%s)",
}
}
}

View File

@ -21,6 +21,7 @@ type DynMatcher<'c> = dyn Matcher + 'c;
pub(crate) use context::Context;
pub(crate) use context::ContextElement;
pub(crate) use context::ExitMatcherNode;
pub(crate) use context::HasAffiliatedKeywordInner;
pub(crate) use exiting::ExitClass;
pub use file_access_interface::FileAccessInterface;
pub use file_access_interface::LocalFileAccessInterface;

View File

@ -3,9 +3,12 @@ use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::combinator::eof;
use nom::multi::many0;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::get_consumed;
@ -17,6 +20,7 @@ pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
let (remaining, _clock) = tag("%%(")(input)?;
let (remaining, _contents) = is_not("\r\n")(remaining)?;
@ -27,12 +31,14 @@ pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
remaining,
DiarySexp {
source: source.into(),
name: get_name(&affiliated_keywords),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, tag("%%(")))(input)?;
Ok((input, ()))
}

View File

@ -7,10 +7,13 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@ -41,6 +44,7 @@ pub(crate) fn drawer<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(),
))));
}
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
@ -90,7 +94,8 @@ pub(crate) fn drawer<'b, 'g, 'r, 's>(
remaining,
Drawer {
source: source.into(),
name: drawer_name.into(),
name: get_name(&affiliated_keywords),
drawer_name: drawer_name.into(),
children,
},
))

View File

@ -17,7 +17,9 @@ use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@ -47,6 +49,8 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(),
))));
}
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_, name, parameters, _, _)) = tuple((
@ -97,7 +101,8 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
remaining,
DynamicBlock {
source: source.into(),
name: name.into(),
name: get_name(&affiliated_keywords),
block_name: name.into(),
parameters: parameters.map(|val| val.into()),
children,
},

View File

@ -1,6 +1,10 @@
use nom::branch::alt;
use nom::combinator::map;
use nom::multi::many0;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::sequence::tuple;
#[cfg(feature = "tracing")]
use tracing::span;
use super::clock::clock;
use super::comment::comment;
@ -72,52 +76,95 @@ fn _element<'b, 'g, 'r, 's>(
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context);
// TODO: Affiliated keywords cannot be on comments, clocks, headings, inlinetasks, items, node properties, planning, property drawers, sections, and table rows
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
let (remaining, mut element) = match alt((
map(plain_list_matcher, Element::PlainList),
greater_block_matcher,
map(dynamic_block_matcher, Element::DynamicBlock),
map(footnote_definition_matcher, Element::FootnoteDefinition),
map(comment_matcher, Element::Comment),
map(drawer_matcher, Element::Drawer),
map(table_matcher, Element::Table),
map(verse_block_matcher, Element::VerseBlock),
map(comment_block_matcher, Element::CommentBlock),
map(example_block_matcher, Element::ExampleBlock),
map(export_block_matcher, Element::ExportBlock),
map(src_block_matcher, Element::SrcBlock),
map(clock_matcher, Element::Clock),
map(diary_sexp_matcher, Element::DiarySexp),
map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword),
))(remaining)
{
the_ok @ Ok(_) => the_ok,
Err(_) => {
if can_be_paragraph {
match map(paragraph_matcher, Element::Paragraph)(remaining) {
the_ok @ Ok(_) => the_ok,
Err(_) => {
// TODO: Because this function expects a single element, if there are multiple affiliated keywords before an element that cannot have affiliated keywords, we end up re-parsing the affiliated keywords many times.
affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input)
}
}
} else {
affiliated_keywords.clear();
map(affiliated_keyword_matcher, Element::Keyword)(input)
}
let (mut remaining, mut maybe_element) = {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Main element block");
#[cfg(feature = "tracing")]
let _enter = span.enter();
opt(alt((
map(plain_list_matcher, Element::PlainList),
greater_block_matcher,
map(dynamic_block_matcher, Element::DynamicBlock),
map(footnote_definition_matcher, Element::FootnoteDefinition),
map(comment_matcher, Element::Comment),
map(drawer_matcher, Element::Drawer),
map(table_matcher, Element::Table),
map(verse_block_matcher, Element::VerseBlock),
map(comment_block_matcher, Element::CommentBlock),
map(example_block_matcher, Element::ExampleBlock),
map(export_block_matcher, Element::ExportBlock),
map(src_block_matcher, Element::SrcBlock),
map(clock_matcher, Element::Clock),
map(diary_sexp_matcher, Element::DiarySexp),
map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword),
)))(input)?
};
if maybe_element.is_none() && can_be_paragraph {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Paragraph with affiliated keyword.");
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, paragraph_with_affiliated_keyword) = opt(map(
tuple((
peek(affiliated_keyword),
map(paragraph_matcher, Element::Paragraph),
)),
|(_, paragraph)| paragraph,
))(remaining)?;
if paragraph_with_affiliated_keyword.is_some() {
remaining = remain;
maybe_element = paragraph_with_affiliated_keyword;
}
}?;
}
if maybe_element.is_none() {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Affiliated keyword as regular keyword."
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, kw) = opt(map(affiliated_keyword, Element::Keyword))(remaining)?;
if kw.is_some() {
maybe_element = kw;
remaining = remain;
}
}
if maybe_element.is_none() && can_be_paragraph {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Paragraph without affiliated keyword."
);
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, paragraph_without_affiliated_keyword) =
map(paragraph_matcher, Element::Paragraph)(remaining)?;
remaining = remain;
maybe_element = Some(paragraph_without_affiliated_keyword);
}
if maybe_element.is_none() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element.",
))));
}
let mut element = maybe_element.expect("The above if-statement ensures this is Some().");
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;

View File

@ -10,7 +10,9 @@ use nom::multi::many0;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::only_space1;
use super::util::org_line_ending;
use crate::context::parser_with_context;
@ -26,6 +28,7 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, _first_line) = fixed_width_area_line_matcher(input)?;
@ -37,6 +40,7 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
remaining,
FixedWidthArea {
source: source.into(),
name: get_name(&affiliated_keywords),
},
))
}
@ -59,6 +63,7 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((
start_of_line,
space0,

View File

@ -11,7 +11,9 @@ use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::include_input;
use super::util::WORD_CONSTITUENT_CHARACTERS;
use crate::context::parser_with_context;
@ -41,6 +43,7 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(),
))));
}
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
// Cannot be indented.
let (remaining, (_, lbl, _, _, _)) = tuple((
@ -85,6 +88,7 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
remaining,
FootnoteDefinition {
source: source.into(),
name: get_name(&affiliated_keywords),
label: lbl.into(),
children: children.into_iter().map(|(_, item)| item).collect(),
},
@ -119,6 +123,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}

View File

@ -17,7 +17,9 @@ use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::in_section;
use crate::context::parser_with_context;
use crate::context::ContextElement;
@ -35,6 +37,7 @@ use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::CenterBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::QuoteBlock;
use crate::types::SetSource;
@ -45,6 +48,7 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name)) = tuple((
@ -58,9 +62,9 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
))(remaining)?;
let name = Into::<&str>::into(name);
let (remaining, element) = match name.to_lowercase().as_str() {
"center" => center_block(context, remaining, input)?,
"quote" => quote_block(context, remaining, input)?,
_ => special_block(name)(context, remaining, input)?,
"center" => center_block(context, remaining, input, &affiliated_keywords)?,
"quote" => quote_block(context, remaining, input, &affiliated_keywords)?,
_ => special_block(name)(context, remaining, input, &affiliated_keywords)?,
};
Ok((remaining, element))
}
@ -70,12 +74,17 @@ fn center_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
affiliated_keywords: &Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, (source, children)) =
greater_block_body(context, input, original_input, "center", "center block")?;
Ok((
remaining,
Element::CenterBlock(CenterBlock { source, children }),
Element::CenterBlock(CenterBlock {
source,
name: get_name(&affiliated_keywords),
children,
}),
))
}
@ -84,12 +93,17 @@ fn quote_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
affiliated_keywords: &Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, (source, children)) =
greater_block_body(context, input, original_input, "quote", "quote block")?;
Ok((
remaining,
Element::QuoteBlock(QuoteBlock { source, children }),
Element::QuoteBlock(QuoteBlock {
source,
name: get_name(&affiliated_keywords),
children,
}),
))
}
@ -99,11 +113,19 @@ fn special_block<'s>(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
OrgSource<'s>,
&Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>>
+ 's {
let context_name = format!("special block {}", name);
move |context, input, original_input| {
_special_block(context, input, original_input, name, context_name.as_str())
move |context, input, original_input, affiliated_keywords| {
_special_block(
context,
input,
original_input,
name,
context_name.as_str(),
affiliated_keywords,
)
}
}
@ -114,6 +136,7 @@ fn _special_block<'c, 'b, 'g, 'r, 's>(
original_input: OrgSource<'s>,
name: &'s str,
context_name: &'c str,
affiliated_keywords: &Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) =
@ -122,8 +145,9 @@ fn _special_block<'c, 'b, 'g, 'r, 's>(
remaining,
Element::SpecialBlock(SpecialBlock {
source,
name: get_name(&affiliated_keywords),
children,
name,
block_type: name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
}),
))

View File

@ -5,10 +5,13 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1_count;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::start_of_line;
@ -19,6 +22,7 @@ pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
let (remaining, rule) = recognize(tuple((
space0,
@ -30,6 +34,7 @@ pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
remaining,
HorizontalRule {
source: rule.into(),
name: get_name(&affiliated_keywords),
},
))
}

View File

@ -13,11 +13,13 @@ use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::get_name;
use crate::context::Matcher;
use crate::context::RefContext;
use crate::error::CustomError;
@ -61,6 +63,7 @@ fn _filtered_keyword<'s, F: Matcher>(
remaining,
Keyword {
source: consumed_input.into(),
name: None, // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: "".into(),
},
@ -78,6 +81,7 @@ fn _filtered_keyword<'s, F: Matcher>(
remaining,
Keyword {
source: consumed_input.into(),
name: None, // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: parsed_value.into(),
},
@ -89,14 +93,14 @@ pub(crate) fn keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(regular_keyword_key)(input)
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(input)?;
kw.name = get_name(&affiliated_keywords);
Ok((remaining, kw))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(affiliated_key)(input)
}
@ -105,11 +109,13 @@ pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCall<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, kw) = filtered_keyword(babel_call_key)(input)?;
Ok((
remaining,
BabelCall {
source: kw.source,
name: get_name(&affiliated_keywords),
key: kw.key,
value: kw.value,
},

View File

@ -8,11 +8,14 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::get_name;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
@ -29,6 +32,7 @@ pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
@ -54,6 +58,7 @@ pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
remaining,
LatexEnvironment {
source: source.into(),
name: get_name(&affiliated_keywords),
},
))
}

View File

@ -1,24 +1,33 @@
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::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_name;
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;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line;
@ -26,12 +35,16 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::parser::util::text_until_exit;
use crate::types::CharOffsetInLine;
use crate::types::CommentBlock;
use crate::types::ExampleBlock;
use crate::types::ExportBlock;
use crate::types::LineNumber;
use crate::types::Object;
use crate::types::PlainText;
use crate::types::RetainLabels;
use crate::types::SrcBlock;
use crate::types::SwitchNumberLines;
use crate::types::VerseBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@ -39,7 +52,8 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, VerseBlock<'s>> {
let (remaining, name) = lesser_block_begin("verse")(context, input)?;
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("verse")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("verse");
@ -82,7 +96,7 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
remaining,
VerseBlock {
source: source.into(),
name: name.into(),
name: get_name(&affiliated_keywords),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
children,
},
@ -94,8 +108,9 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CommentBlock<'s>> {
let (remaining, name) = lesser_block_begin("comment")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("comment")(context, input)?;
let (remaining, _parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("comment");
let contexts = [
@ -109,10 +124,6 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
@ -122,8 +133,7 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
remaining,
CommentBlock {
source: source.into(),
name: name.into(),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
name: get_name(&affiliated_keywords),
contents: contents.into(),
},
))
@ -134,8 +144,9 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
let (remaining, _name) = lesser_block_begin("example")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("example")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, example_switches)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("example");
let contexts = [
@ -149,22 +160,42 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let parameters = parameters.map(|(_, parameters)| parameters);
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
if let Some(parameters) = parameters {
(
if parameters.source.len() == 0 {
None
} else {
Some(parameters.source)
},
parameters.number_lines,
parameters.preserve_indent,
parameters.retain_labels,
parameters.use_labels,
parameters.label_format,
)
} else {
(None, None, None, RetainLabels::Yes, true, None)
}
};
Ok((
remaining,
ExampleBlock {
source: source.into(),
name: source.into(),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents: contents.into(),
name: get_name(&affiliated_keywords),
switches,
number_lines,
preserve_indent,
retain_labels,
use_labels,
label_format,
contents,
},
))
}
@ -174,9 +205,15 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportBlock<'s>> {
let (remaining, name) = lesser_block_begin("export")(context, input)?;
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("export")(context, input)?;
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, export_type) = opt(map(
tuple((space1, switch_word, peek(tuple((space0, line_ending))))),
|(_, export_type, _)| export_type,
))(remaining)?;
let (remaining, parameters) =
opt(map(tuple((space1, data)), |(_, parameters)| parameters))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("export");
let contexts = [
@ -190,12 +227,8 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let source = get_consumed(input, remaining);
@ -203,9 +236,10 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
remaining,
ExportBlock {
source: source.into(),
name: name.into(),
name: get_name(&affiliated_keywords),
export_type: export_type.map(Into::<&str>::into),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents: contents.into(),
contents,
},
))
}
@ -215,9 +249,15 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> {
let (remaining, name) = lesser_block_begin("src")(context, input)?;
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (remaining, _) = lesser_block_begin("src")(context, input)?;
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, language) =
opt(map(tuple((space1, switch_word)), |(_, language)| language))(remaining)?;
let (remaining, switches) = opt(src_switches)(remaining)?;
let (remaining, parameters) = opt(map(tuple((space1, src_parameters)), |(_, parameters)| {
parameters
}))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("src");
let contexts = [
@ -231,22 +271,42 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, contents) = content(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
if let Some(switches) = switches {
(
if switches.source.len() == 0 {
None
} else {
Some(switches.source)
},
switches.number_lines,
switches.preserve_indent,
switches.retain_labels,
switches.use_labels,
switches.label_format,
)
} else {
(None, None, None, RetainLabels::Yes, true, None)
}
};
Ok((
remaining,
SrcBlock {
source: source.into(),
name: name.into(),
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents: contents.into(),
name: get_name(&affiliated_keywords),
language: language.map(Into::<&str>::into),
switches,
parameters: parameters.map(Into::<&str>::into),
number_lines,
preserve_indent,
retain_labels,
use_labels,
label_format,
contents,
},
))
}
@ -309,3 +369,259 @@ fn _lesser_block_begin<'b, 'g, 'r, 's, 'c>(
))(remaining)?;
Ok((remaining, name))
}
#[derive(Debug)]
struct ExampleSrcSwitches<'s> {
source: &'s str,
number_lines: Option<SwitchNumberLines>,
retain_labels: RetainLabels,
preserve_indent: Option<CharOffsetInLine>,
use_labels: bool,
label_format: Option<&'s str>,
}
#[derive(Debug)]
enum SwitchState {
Normal,
NewLineNumber,
ContinuedLineNumber,
LabelFormat,
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn src_parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(
recognize(many_till(anychar, peek(tuple((space0, line_ending))))),
|parameters: &OrgSource<'_>| parameters.len() > 0,
)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn src_switches<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
let (remaining, leading_spaces) = space1(input)?;
let offset = Into::<&str>::into(leading_spaces).chars().count();
let offset = CharOffsetInLine::try_from(offset)
.expect("Character offset should fit in CharOffsetInLine");
example_src_switches(true, offset)(remaining)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn example_switches<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
let (remaining, switches) = example_src_switches(false, 0)(input)?;
Ok((remaining, switches))
}
fn example_src_switches(
stop_at_parameters: bool,
additional_char_offset: CharOffsetInLine,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
move |input| _example_src_switches(input, stop_at_parameters, additional_char_offset)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _example_src_switches<'s>(
input: OrgSource<'s>,
stop_at_parameters: bool,
additional_char_offset: CharOffsetInLine,
) -> Res<OrgSource<'s>, ExampleSrcSwitches<'s>> {
let mut number_lines = None;
let mut retain_labels = RetainLabels::Yes;
let mut preserve_indent = None;
let mut use_labels = true;
let mut label_format = None;
let mut saw_r = false;
let mut matched_a_word = false;
let mut remaining = input;
let mut last_match_remaining = input;
let mut state = SwitchState::Normal;
'outer: loop {
let (remain, word) = opt(switch_word)(remaining)?;
let word = match word {
Some(word) => word,
None => {
break;
}
};
let normalized_word = Into::<&str>::into(word);
loop {
match (&state, normalized_word) {
(SwitchState::Normal, "-n") => {
state = SwitchState::NewLineNumber;
}
(SwitchState::Normal, "+n") => {
state = SwitchState::ContinuedLineNumber;
}
(SwitchState::Normal, "-r") => {
saw_r = true;
use_labels = false;
match retain_labels {
RetainLabels::Yes => {
retain_labels = RetainLabels::No;
}
_ => {}
}
}
(SwitchState::Normal, "-l") => {
state = SwitchState::LabelFormat;
}
(SwitchState::Normal, "-k") => {
use_labels = false;
let text_until_flag = input.get_until(word);
let character_offset = Into::<&str>::into(text_until_flag).chars().count();
let character_offset = CharOffsetInLine::try_from(character_offset)
.expect("Character offset should fit in CharOffsetInLine");
retain_labels = RetainLabels::Keep(character_offset + additional_char_offset);
}
(SwitchState::Normal, "-i") => {
let text_until_flag = input.get_until(word);
let character_offset = Into::<&str>::into(text_until_flag).chars().count();
let character_offset = CharOffsetInLine::try_from(character_offset)
.expect("Character offset should fit in CharOffsetInLine");
preserve_indent = Some(character_offset + additional_char_offset);
}
(SwitchState::NewLineNumber, _) => {
let val = normalized_word.parse::<LineNumber>();
if let Ok(val) = val {
if val < 0 {
number_lines = Some(SwitchNumberLines::New(0));
} else {
// Note that this can result in a negative 1 if the val is originally 0.
number_lines = Some(SwitchNumberLines::New(val - 1));
}
state = SwitchState::Normal;
} else {
number_lines = Some(SwitchNumberLines::New(0));
state = SwitchState::Normal;
continue; // Re-processes the word
}
}
(SwitchState::ContinuedLineNumber, _) => {
let val = normalized_word.parse::<LineNumber>();
if let Ok(val) = val {
if val < 0 {
number_lines = Some(SwitchNumberLines::Continued(0));
} else {
// Note that this can result in a negative 1 if the val is originally 0.
number_lines = Some(SwitchNumberLines::Continued(val - 1));
}
state = SwitchState::Normal;
} else {
number_lines = Some(SwitchNumberLines::Continued(0));
state = SwitchState::Normal;
continue; // Re-processes the word
}
}
(SwitchState::LabelFormat, _) => {
label_format = Some(normalized_word);
state = SwitchState::Normal;
}
(SwitchState::Normal, _) if stop_at_parameters => {
break 'outer;
}
(SwitchState::Normal, _) => {}
};
matched_a_word = true;
remaining = remain;
last_match_remaining = remain;
let (remain, divider) = opt(space1)(remaining)?;
if divider.is_none() {
break 'outer;
}
remaining = remain;
break;
}
}
if !matched_a_word {
return Err(nom::Err::Error(CustomError::MyError(MyError("No words."))));
}
let remaining = last_match_remaining;
let (remaining, _post_spaces) = opt(tuple((space0, peek(line_ending))))(remaining)?;
let source = input.get_until(remaining);
// Handle state that didn't get processed because we ran out of words.
match state {
SwitchState::Normal => {}
SwitchState::NewLineNumber => {
number_lines = Some(SwitchNumberLines::New(0));
}
SwitchState::ContinuedLineNumber => {
number_lines = Some(SwitchNumberLines::Continued(0));
}
SwitchState::LabelFormat => {}
}
let retain_labels = match retain_labels {
RetainLabels::Keep(_) if !saw_r => RetainLabels::Yes,
_ => retain_labels,
};
Ok((
remaining,
ExampleSrcSwitches {
source: Into::<&str>::into(source),
number_lines,
retain_labels,
preserve_indent,
use_labels,
label_format,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
map(
tuple((tag(r#"""#), is_not("\"\r\n"), tag(r#"""#))),
|(_, contents, _)| contents,
),
is_not(" \t\r\n"),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn content<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, String> {
let mut ret = String::new();
let mut remaining = input;
let exit_matcher_parser = parser_with_context!(exit_matcher_parser)(context);
loop {
if exit_matcher_parser(remaining).is_ok() {
break;
}
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
pre_escape_whitespace.map(|val| ret.push_str(Into::<&str>::into(val)));
ret.push_str(line.into());
remaining = remain;
}
Ok((remaining, ret))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn content_line<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (Option<OrgSource<'s>>, OrgSource<'s>)> {
let (remaining, pre_escape_whitespace) = opt(map(
tuple((
recognize(tuple((
space0,
many_till(
tag(","),
peek(tuple((tag(","), alt((tag("#+"), tag("*")))))),
),
))),
tag(","),
)),
|(pre_comma, _)| pre_comma,
))(input)?;
let (remaining, line_post_escape) = recognize(many_till(anychar, line_ending))(remaining)?;
Ok((remaining, (pre_escape_whitespace, line_post_escape)))
}

View File

@ -14,7 +14,7 @@ use crate::error::MyError;
pub(crate) type BracketDepth = i16;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq)]
pub(crate) struct OrgSource<'s> {
full_source: &'s str,
start: usize,

View File

@ -2,19 +2,26 @@ use nom::branch::alt;
use nom::combinator::eof;
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;
use super::element_parser::detect_element;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::blank_line;
use super::util::get_consumed;
use super::util::get_has_affiliated_keyword;
use super::util::get_name;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::HasAffiliatedKeywordInner;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::exit_matcher_parser;
@ -26,11 +33,19 @@ pub(crate) fn paragraph<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &paragraph_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let contexts = [
ContextElement::HasAffiliatedKeyword(HasAffiliatedKeywordInner {
start_after_affiliated_keywords: input,
keywords: &affiliated_keywords,
}),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &paragraph_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@ -47,6 +62,7 @@ pub(crate) fn paragraph<'b, 'g, 'r, 's>(
remaining,
Paragraph {
source: source.into(),
name: get_name(&affiliated_keywords),
children,
},
))
@ -57,10 +73,21 @@ fn paragraph_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let non_paragraph_element_matcher = parser_with_context!(detect_element(false))(context);
let regular_end = recognize(tuple((start_of_line, many1(blank_line))))(input);
if regular_end.is_ok() {
return regular_end;
}
match get_has_affiliated_keyword(context) {
Some(start_post_affiliated_keywords) if input == start_post_affiliated_keywords => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No exit due to affiliated keywords.",
))));
}
_ => {}
}
// Check to see if input is the start of a HasAffiliatedKeyword
alt((
recognize(tuple((start_of_line, many1(blank_line)))),
recognize(non_paragraph_element_matcher),
recognize(parser_with_context!(detect_element(false))(context)),
eof,
))(input)
}

View File

@ -19,8 +19,10 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::element_parser::element;
use super::keyword::affiliated_keyword;
use super::object_parser::standard_set_object;
use super::org_source::OrgSource;
use super::util::get_name;
use super::util::include_input;
use super::util::indentation_level;
use super::util::non_whitespace_character;
@ -53,6 +55,7 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
if verify(
tuple((
start_of_line,
@ -78,6 +81,8 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainList<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let contexts = [
ContextElement::Context("plain list"),
ContextElement::ConsumeTrailingWhitespace(true),
@ -150,6 +155,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
remaining,
PlainList {
source: source.into(),
name: get_name(&affiliated_keywords),
list_type: first_item_list_type.expect("Plain lists require at least one element."),
children: children.into_iter().map(|(_start, item)| item).collect(),
},

View File

@ -120,7 +120,7 @@ fn node_property<'b, 'g, 'r, 's>(
remaining,
NodeProperty {
source: source.into(),
name: Into::<&str>::into(name),
property_name: Into::<&str>::into(name),
value: None,
},
))
@ -133,7 +133,7 @@ fn node_property<'b, 'g, 'r, 's>(
remaining,
NodeProperty {
source: source.into(),
name: Into::<&str>::into(name),
property_name: Into::<&str>::into(name),
value: Some(value.into()),
},
))

View File

@ -13,10 +13,12 @@ use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::keyword::affiliated_keyword;
use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource;
use super::util::exit_matcher_parser;
use super::util::get_name;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
@ -38,6 +40,7 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Table<'s>> {
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(input)?;
peek(tuple((space0, tag("|"))))(input)?;
@ -68,6 +71,7 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
remaining,
Table {
source: source.into(),
name: get_name(&affiliated_keywords),
formulas,
children,
},
@ -76,6 +80,7 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (input, _) = many0(affiliated_keyword)(input)?;
tuple((start_of_line, space0, tag("|")))(input)?;
Ok((input, ()))
}

View File

@ -22,6 +22,7 @@ use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::types::IndentationLevel;
use crate::types::Keyword;
pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@ -275,3 +276,36 @@ pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
.sum();
Ok((remaining, (indentation_level, leading_whitespace)))
}
pub(crate) fn get_name<'s>(affiliated_keywords: &Vec<Keyword<'s>>) -> Option<&'s str> {
let name_keyword = affiliated_keywords
.iter()
.filter(|kw| {
kw.key.eq_ignore_ascii_case("name")
|| kw.key.eq_ignore_ascii_case("source")
|| kw.key.eq_ignore_ascii_case("tblname")
|| kw.key.eq_ignore_ascii_case("resname")
|| kw.key.eq_ignore_ascii_case("srcname")
|| kw.key.eq_ignore_ascii_case("label")
})
.last();
name_keyword.map(|kw| kw.value)
}
pub(crate) fn get_has_affiliated_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
) -> Option<OrgSource<'s>> {
for context in context.iter() {
match context {
ContextElement::HasAffiliatedKeyword(inner) => {
if !inner.keywords.is_empty() {
return Some(inner.start_after_affiliated_keywords);
} else {
return None;
}
}
_ => {}
}
}
None
}

View File

@ -7,6 +7,7 @@ use super::StandardProperties;
#[derive(Debug)]
pub struct PlainList<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub list_type: PlainListType,
pub children: Vec<PlainListItem<'s>>,
}
@ -46,19 +47,22 @@ pub enum CheckboxType {
#[derive(Debug)]
pub struct CenterBlock<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub struct QuoteBlock<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub struct SpecialBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub block_type: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
@ -66,7 +70,8 @@ pub struct SpecialBlock<'s> {
#[derive(Debug)]
pub struct DynamicBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub block_name: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
@ -74,6 +79,7 @@ pub struct DynamicBlock<'s> {
#[derive(Debug)]
pub struct FootnoteDefinition<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub label: &'s str,
pub children: Vec<Element<'s>>,
}
@ -81,7 +87,8 @@ pub struct FootnoteDefinition<'s> {
#[derive(Debug)]
pub struct Drawer<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub drawer_name: &'s str,
pub children: Vec<Element<'s>>,
}
@ -94,13 +101,14 @@ pub struct PropertyDrawer<'s> {
#[derive(Debug)]
pub struct NodeProperty<'s> {
pub source: &'s str,
pub name: &'s str,
pub property_name: &'s str,
pub value: Option<&'s str>,
}
#[derive(Debug)]
pub struct Table<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub formulas: Vec<Keyword<'s>>,
pub children: Vec<TableRow<'s>>,
}

View File

@ -6,6 +6,7 @@ use super::Timestamp;
#[derive(Debug)]
pub struct Paragraph<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
@ -24,7 +25,7 @@ pub struct TableCell<'s> {
#[derive(Debug)]
pub struct VerseBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub data: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
@ -32,33 +33,55 @@ pub struct VerseBlock<'s> {
#[derive(Debug)]
pub struct CommentBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub data: Option<&'s str>,
pub name: Option<&'s str>,
pub contents: &'s str,
}
pub type CharOffsetInLine = u16;
#[derive(Debug)]
pub enum RetainLabels {
No,
Yes,
/// When adding -k to the switches on an example or src block, the labels are kept in the source code and links will use line numbers.
Keep(CharOffsetInLine),
}
#[derive(Debug)]
pub struct ExampleBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub data: Option<&'s str>,
pub contents: &'s str,
pub name: Option<&'s str>,
pub switches: Option<&'s str>,
pub number_lines: Option<SwitchNumberLines>,
pub preserve_indent: Option<CharOffsetInLine>,
pub retain_labels: RetainLabels,
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub contents: String,
}
#[derive(Debug)]
pub struct ExportBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub name: Option<&'s str>,
pub export_type: Option<&'s str>,
pub data: Option<&'s str>,
pub contents: &'s str,
pub contents: String,
}
#[derive(Debug)]
pub struct SrcBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub data: Option<&'s str>,
pub contents: &'s str,
pub name: Option<&'s str>,
pub language: Option<&'s str>,
pub switches: Option<&'s str>,
pub parameters: Option<&'s str>,
pub number_lines: Option<SwitchNumberLines>,
pub preserve_indent: Option<CharOffsetInLine>,
pub retain_labels: RetainLabels,
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub contents: String,
}
#[derive(Debug)]
@ -69,6 +92,7 @@ pub struct Clock<'s> {
#[derive(Debug)]
pub struct DiarySexp<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
}
#[derive(Debug)]
@ -82,16 +106,19 @@ pub struct Planning<'s> {
#[derive(Debug)]
pub struct FixedWidthArea<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
}
#[derive(Debug)]
pub struct HorizontalRule<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
}
#[derive(Debug)]
pub struct Keyword<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub key: &'s str,
pub value: &'s str,
}
@ -99,6 +126,7 @@ pub struct Keyword<'s> {
#[derive(Debug)]
pub struct BabelCall<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
pub key: &'s str,
pub value: &'s str,
}
@ -106,14 +134,30 @@ pub struct BabelCall<'s> {
#[derive(Debug)]
pub struct LatexEnvironment<'s> {
pub source: &'s str,
pub name: Option<&'s str>,
}
/// A line number used in switches to lesser blocks.
///
/// This must be signed because emacs subtracts 1 from the actual value in the org-mode text, which makes a 0 turn into a -1.
pub type LineNumber = isize;
#[derive(Debug)]
pub enum SwitchNumberLines {
New(LineNumber),
Continued(LineNumber),
}
impl<'s> Paragraph<'s> {
/// Generate a paragraph of the passed in text with no additional properties.
///
/// This is used for elements that support an "empty" content like greater blocks.
pub(crate) fn of_text(input: &'s str) -> Self {
let mut objects = Vec::with_capacity(1);
objects.push(Object::PlainText(PlainText { source: input }));
Paragraph {
source: input,
name: None,
children: objects,
}
}
@ -223,3 +267,12 @@ impl<'s> Comment<'s> {
ret
}
}
impl<'s> ExportBlock<'s> {
/// Gets the export type capitalized.
///
/// Upstream Emacs Org-mode capitalizes the export type.
pub fn get_export_type(&self) -> Option<String> {
self.export_type.map(|s| s.to_uppercase())
}
}

View File

@ -37,6 +37,7 @@ pub use greater_element::Table;
pub use greater_element::TableRow;
pub use greater_element::TableRowType;
pub use lesser_element::BabelCall;
pub use lesser_element::CharOffsetInLine;
pub use lesser_element::Clock;
pub use lesser_element::Comment;
pub use lesser_element::CommentBlock;
@ -47,9 +48,12 @@ pub use lesser_element::FixedWidthArea;
pub use lesser_element::HorizontalRule;
pub use lesser_element::Keyword;
pub use lesser_element::LatexEnvironment;
pub use lesser_element::LineNumber;
pub use lesser_element::Paragraph;
pub use lesser_element::Planning;
pub use lesser_element::RetainLabels;
pub use lesser_element::SrcBlock;
pub use lesser_element::SwitchNumberLines;
pub use lesser_element::TableCell;
pub use lesser_element::VerseBlock;
pub use object::AngleLink;