44 Commits

Author SHA1 Message Date
Tom Alexander
b943f90766 Implement the new fields for regular link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:19:49 -05:00
Tom Alexander
0108f5b0b1 Implement the new fields for subscript and superscript.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:04:59 -05:00
Tom Alexander
50145c6cf2 Implement the new fields for line break.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-08 16:20:58 -05:00
Tom Alexander
4a8607726c Implement the new fields for comment. 2023-12-08 16:20:58 -05:00
Tom Alexander
9bcba4020d Implement the new fields for verbatim and code. 2023-12-08 16:20:58 -05:00
Tom Alexander
8fd9ff3848 Implement the new fields for bold, italic, underline, and strike-through. 2023-12-08 15:51:38 -05:00
Tom Alexander
3fb7cb82cd Implement get_contents for document.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-11-01 01:22:43 -04:00
Tom Alexander
e0ec5c115f Need a constant value for generic numbers. 2023-11-01 00:49:22 -04:00
Tom Alexander
f0868ba3ed Add a post blank implementation to document. 2023-10-31 23:56:40 -04:00
Tom Alexander
425bc12353 Add implementations to calculate the new fields for heading. 2023-10-31 23:46:53 -04:00
Tom Alexander
03754be71e Implement the new fields for section. 2023-10-31 23:16:57 -04:00
Tom Alexander
70002800c2 Implement the new fields for footnote definitions. 2023-10-31 23:12:04 -04:00
Tom Alexander
281c35677b Implement the new fields for paragraph. 2023-10-31 23:06:43 -04:00
Tom Alexander
92d15c3d91 Fix clippy. 2023-10-31 22:58:17 -04:00
Tom Alexander
b1773ac90e Get post blank for footnote references. 2023-10-31 22:58:17 -04:00
Tom Alexander
645d9abf9c Support nil contents. 2023-10-31 22:58:17 -04:00
Tom Alexander
d2f2bdf88d Implement get_contents for footnote references. 2023-10-31 22:58:17 -04:00
Tom Alexander
90ba17b68c Switch to a numeric post-blank.
Turns out post-blank has different meanings to different object types so we need to return a number to properly do the compare.
2023-10-31 22:32:01 -04:00
Tom Alexander
31406fd520 Fix clippy.
Some checks failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-10-31 22:19:39 -04:00
Tom Alexander
49bc51ba89 Compare post-blank. 2023-10-31 22:18:28 -04:00
Tom Alexander
92592104a4 Compare contents begin/end. 2023-10-31 22:11:38 -04:00
Tom Alexander
33f4614d28 Make get_rust_byte_offsets more generic so it can be used for contents.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-31 21:59:58 -04:00
Tom Alexander
6c197c376a Add todo implementations of the new standard property functions. 2023-10-31 21:49:33 -04:00
Tom Alexander
bcf1b49db2 Remove the GetStandardProperties trait.
This was using dynamic dispatch to deal with enums to avoid the repetitive typing.
2023-10-31 21:26:00 -04:00
Tom Alexander
49f6e70a19 Use RPIT to get static dispatch GetStandardProperties. 2023-10-31 21:20:46 -04:00
Tom Alexander
31fb815681 Add a function for getting the post blank. 2023-10-31 21:20:46 -04:00
Tom Alexander
7dfe24ff98 Merge branch 'lazy_parse_lesser_block_contents'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 20:54:01 -04:00
Tom Alexander
a5627d0cee Do not parse the lesser block contents during parsing, but rather only if the contents are requested.
Some checks failed
rust-test Build rust-test has failed
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
This seemed like an unnecessary allocation during parsing, especially considering we throw away some parses based on whether or not we found radio targets in the source.
2023-10-31 20:43:08 -04:00
Tom Alexander
93cfa71df2 Merge branch 'foreign_document_literate_build_emacs'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 19:49:15 -04:00
Tom Alexander
78320d3265 Fix clippy errors.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-31 18:32:01 -04:00
Tom Alexander
9e908935f8 Add special case to delete invalid org-mode file.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-31 17:26:13 -04:00
Tom Alexander
b18a703529 Handle nil values for compare_property_object_tree. 2023-10-31 17:20:35 -04:00
Tom Alexander
ea52dc60be Add a literate tutorial for building emacs to the foreign documents test. 2023-10-31 16:33:11 -04:00
Tom Alexander
f5699ce830 Remove PartialEq from Object.
Some checks failed
rustfmt Build rustfmt has succeeded
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-10-31 16:33:10 -04:00
Tom Alexander
10aa0956ee Merge branch 'lesser_block_memory_optimization'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-27 22:13:25 -04:00
Tom Alexander
816c164996 Only allocate memory if removing text for lesser blocks.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-27 21:50:08 -04:00
Tom Alexander
ee201e1336 Merge branch 'explicit_all_node_iter'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-23 18:39:16 -04:00
Tom Alexander
4897952330 Make creating AllAstNodeIter explicit.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
This is to remove the ambiguity between calling iter on the specific structs like Document and calling iter on an AstNode by having an explicitly-named function to create the iterator.
2023-10-23 18:25:59 -04:00
Tom Alexander
e1d85c6dc2 Merge branch 'remove_set_source'
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-10-23 18:06:56 -04:00
Tom Alexander
c420ccd029 Fix clippy errors.
All checks were successful
clippy Build clippy has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-10-23 17:43:43 -04:00
Tom Alexander
a880629831 Make clippy not write to the host git repo. 2023-10-23 17:43:32 -04:00
Tom Alexander
5e2dea1f28 Remove the SetSource trait.
It was only being used for creating paragraphs of specific text, so I just adjusted the of_text function to handle it.
2023-10-23 17:43:32 -04:00
Tom Alexander
f47d688be4 Remove owned String from CustomError.
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
clippy Build clippy has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has succeeded
This is a 15% performance improvement.
2023-10-21 14:29:37 -04:00
Tom Alexander
acfc5e5e68 Only allocate memory when unquoting sexp string that contains escapes.
All checks were successful
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
If the quoted string contains no escape sequences, then unquoting the string can be done by simply shaving off the leading and trailing quotation marks which can be a slice operation. By returning Cow, we can return either a borrowed slice or an owned String.
2023-10-20 12:53:27 -04:00
44 changed files with 1758 additions and 593 deletions

View File

@@ -45,10 +45,6 @@ dockerclippy:
clippy: clippy:
> cargo clippy --no-deps --all-targets --all-features -- -D warnings > cargo clippy --no-deps --all-targets --all-features -- -D warnings
.PHONY: clippyfix
clippyfix:
> cargo clippy --fix --lib -p organic --all-features
.PHONY: test .PHONY: test
test: test:
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)

View File

@@ -25,13 +25,13 @@ ifdef REMOTE_REPO
else else
@echo "REMOTE_REPO not defined, not removing from remote repo." @echo "REMOTE_REPO not defined, not removing from remote repo."
endif endif
docker volume rm cargo-cache docker volume rm rust-cache cargo-cache
# NOTE: This target will write to folders underneath the git-root # NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME) docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)

View File

@@ -93,6 +93,12 @@ ARG WORG_PATH=/foreign_documents/worg
ARG WORG_REPO=https://git.sr.ht/~bzg/worg ARG WORG_REPO=https://git.sr.ht/~bzg/worg
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
ARG LITERATE_BUILD_EMACS_VERSION=e3ac1afe1e40af601be7af12c1d13d96308ab209
ARG LITERATE_BUILD_EMACS_PATH=/foreign_documents/literate_build_emacs
ARG LITERATE_BUILD_EMACS_REPO=https://gitlab.com/spudlyo/orgdemo2.git
RUN mkdir -p $LITERATE_BUILD_EMACS_PATH && git -C $LITERATE_BUILD_EMACS_PATH init --initial-branch=main && git -C $LITERATE_BUILD_EMACS_PATH remote add origin $LITERATE_BUILD_EMACS_REPO && git -C $LITERATE_BUILD_EMACS_PATH fetch origin $LITERATE_BUILD_EMACS_VERSION && git -C $LITERATE_BUILD_EMACS_PATH checkout FETCH_HEAD
# unused/aws.org contains invalid paths for setupfile which causes both upstream org-mode and Organic to error out.
RUN rm $LITERATE_BUILD_EMACS_PATH/unused/aws.org
FROM tester as foreign-document-test FROM tester as foreign-document-test
RUN apk add --no-cache bash coreutils RUN apk add --no-cache bash coreutils
@@ -100,6 +106,7 @@ RUN mkdir /foreign_documents
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
COPY --from=foreign-document-gather /foreign_documents/literate_build_emacs /foreign_documents/literate_build_emacs
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs COPY --from=build-emacs /root/emacs /foreign_documents/emacs
ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"] ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]

View File

@@ -0,0 +1,5 @@
#+caption:
#+caption: *foo*
#+caption[bar]:
#+begin_src bash
#+end_src

View File

@@ -1,3 +0,0 @@
foo <<bar>> baz
lorem << ipsum >> dolar

View File

@@ -53,6 +53,9 @@ async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
let layer = layer.chain(compare_group("doomemacs", || { let layer = layer.chain(compare_group("doomemacs", || {
compare_all_org_document("/foreign_documents/doomemacs") compare_all_org_document("/foreign_documents/doomemacs")
})); }));
let layer = layer.chain(compare_group("literate_build_emacs", || {
compare_all_org_document("/foreign_documents/literate_build_emacs")
}));
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect(); let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
let mut any_failed = false; let mut any_failed = false;

View File

@@ -1,3 +1,5 @@
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt::Debug; use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
@@ -262,11 +264,11 @@ pub(crate) fn compare_property_set_of_quoted_string<
.iter() .iter()
.map(|e| e.as_atom()) .map(|e| e.as_atom())
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let value: Vec<String> = value let value: Vec<Cow<'_, str>> = value
.into_iter() .into_iter()
.map(unquote) .map(unquote)
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let value: BTreeSet<&str> = value.iter().map(|e| e.as_str()).collect(); let value: BTreeSet<&str> = value.iter().map(|e| e.borrow()).collect();
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect(); let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
if !mismatched.is_empty() { if !mismatched.is_empty() {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
@@ -546,6 +548,21 @@ where
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len()); let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) { for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) {
match (kw_e.as_atom(), kw_r) {
(Ok("nil"), (None, mandatory_value)) if mandatory_value.is_empty() => {
// If its an empty keyword then it becomes nil in the elisp.
continue;
}
(Ok("nil"), _) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
_ => {}
}
let kw_e = kw_e.as_list()?; let kw_e = kw_e.as_list()?;
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0); let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length); let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
@@ -554,6 +571,17 @@ where
let mut kw_e = kw_e.iter(); let mut kw_e = kw_e.iter();
// First element is a list representing the mandatory value. // First element is a list representing the mandatory value.
if let Some(val_e) = kw_e.next() { if let Some(val_e) = kw_e.next() {
match (val_e.as_atom(), kw_r) {
(Ok("nil"), (_, mandatory_value)) if mandatory_value.is_empty() => {}
(Ok("nil"), _) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, kw_e, kw_r
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
_ => {
let el = val_e.as_list()?; let el = val_e.as_list()?;
if el.len() != kw_r.1.len() { if el.len() != kw_r.1.len() {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
@@ -566,6 +594,8 @@ where
for (e, r) in el.iter().zip(kw_r.1.iter()) { for (e, r) in el.iter().zip(kw_r.1.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?); child_status.push(compare_ast_node(source, e, r.into())?);
} }
}
};
} else { } else {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
@@ -653,7 +683,7 @@ pub(crate) fn compare_property_number_lines<
(Some(number_lines), Some(rust_number_lines)) => { (Some(number_lines), Some(rust_number_lines)) => {
let token_list = number_lines.as_list()?; let token_list = number_lines.as_list()?;
let number_type = token_list let number_type = token_list
.get(0) .first()
.map(Token::as_atom) .map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))? .map_or(Ok(None), |r| r.map(Some))?
.ok_or(":number-lines should have a type.")?; .ok_or(":number-lines should have a type.")?;

View File

@@ -57,7 +57,6 @@ use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition; use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference; use crate::types::FootnoteReference;
use crate::types::FootnoteReferenceType; use crate::types::FootnoteReferenceType;
use crate::types::GetStandardProperties;
use crate::types::Heading; use crate::types::Heading;
use crate::types::HorizontalRule; use crate::types::HorizontalRule;
use crate::types::Hour; use crate::types::Hour;
@@ -128,7 +127,7 @@ pub struct DiffResult<'b, 's> {
emacs_token: &'b Token<'s>, emacs_token: &'b Token<'s>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub(crate) enum DiffStatus { pub(crate) enum DiffStatus {
Good, Good,
Bad, Bad,
@@ -164,7 +163,7 @@ impl<'b, 's> DiffEntry<'b, 's> {
fn is_immediately_bad(&self) -> bool { fn is_immediately_bad(&self) -> bool {
match self { match self {
DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad, DiffEntry::DiffResult(diff) => matches!(diff.status, DiffStatus::Bad),
DiffEntry::DiffLayer(_) => false, DiffEntry::DiffLayer(_) => false,
} }
} }
@@ -413,7 +412,7 @@ pub(crate) fn compare_ast_node<'b, 's>(
name: rust.get_elisp_fact().get_elisp_name(), name: rust.get_elisp_fact().get_elisp_name(),
message: Some(e.to_string()), message: Some(e.to_string()),
children: Vec::new(), children: Vec::new(),
rust_source: rust.get_standard_properties().get_source(), rust_source: rust.get_source(),
emacs_token: emacs, emacs_token: emacs,
} }
.into() .into()
@@ -1576,7 +1575,7 @@ fn compare_example_block<'b, 's>(
[], [],
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents.as_str()), |r| Some(r.get_value()),
compare_property_quoted_string compare_property_quoted_string
), ),
( (
@@ -1654,7 +1653,7 @@ fn compare_export_block<'b, 's>(
), ),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents.as_str()), |r| Some(r.get_value()),
compare_property_quoted_string compare_property_quoted_string
) )
) { ) {
@@ -1702,7 +1701,7 @@ fn compare_src_block<'b, 's>(
), ),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents.as_str()), |r| Some(r.get_value()),
compare_property_quoted_string compare_property_quoted_string
), ),
( (
@@ -2153,7 +2152,7 @@ fn compare_plain_text<'b, 's>(
let text = emacs.as_text()?; let text = emacs.as_text()?;
let start_ind: usize = text let start_ind: usize = text
.properties .properties
.get(0) .first()
.expect("Should have start index.") .expect("Should have start index.")
.as_atom()? .as_atom()?
.parse()?; .parse()?;

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
use nom::branch::alt; use nom::branch::alt;
@@ -36,12 +37,6 @@ pub struct TextWithProperties<'s> {
pub(crate) properties: Vec<Token<'s>>, pub(crate) properties: Vec<Token<'s>>,
} }
enum ParseState {
Normal,
Escape,
Octal(Vec<u8>),
}
impl<'s> Token<'s> { impl<'s> Token<'s> {
pub(crate) fn as_vector<'p>( pub(crate) fn as_vector<'p>(
&'p self, &'p self,
@@ -117,8 +112,27 @@ fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
&input[..offset] &input[..offset]
} }
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> { #[derive(Debug)]
let mut out: Vec<u8> = Vec::with_capacity(text.len()); enum UnquoteState {
Normal,
Escape,
HasEscape {
out: Vec<u8>,
},
HasEscapeEscape {
out: Vec<u8>,
},
Octal {
octal_begin_offset: usize,
octal: Vec<u8>,
},
HasEscapeOctal {
out: Vec<u8>,
octal: Vec<u8>,
},
}
pub(crate) fn unquote(text: &str) -> Result<Cow<'_, str>, Box<dyn std::error::Error>> {
if !text.starts_with('"') { if !text.starts_with('"') {
return Err("Quoted text does not start with quote.".into()); return Err("Quoted text does not start with quote.".into());
} }
@@ -126,54 +140,143 @@ pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>>
return Err("Quoted text does not end with quote.".into()); return Err("Quoted text does not end with quote.".into());
} }
let interior_text = &text[1..(text.len() - 1)]; let interior_text = &text[1..(text.len() - 1)];
let mut state = ParseState::Normal; let mut state = UnquoteState::Normal;
for current_char in interior_text.bytes() { for (offset, current_char) in interior_text.bytes().enumerate() {
// Check to see if octal finished // Check to see if octal finished
state = match (state, current_char) { state = match (state, current_char) {
(ParseState::Octal(octal), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => { (
ParseState::Octal(octal) UnquoteState::Octal {
octal_begin_offset,
octal,
},
b'0'..=b'7',
) if octal.len() < MAX_OCTAL_LENGTH => UnquoteState::Octal {
octal_begin_offset,
octal,
},
(
UnquoteState::Octal {
octal_begin_offset,
octal,
},
_,
) => {
let octal_number_string = String::from_utf8(octal)?;
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
out.extend_from_slice(&interior_text.as_bytes()[..octal_begin_offset]);
out.push(decoded_byte);
UnquoteState::HasEscape { out }
} }
(ParseState::Octal(octal), _) => { (UnquoteState::HasEscapeOctal { out, octal }, b'0'..=b'7')
if octal.len() < MAX_OCTAL_LENGTH =>
{
UnquoteState::HasEscapeOctal { out, octal }
}
(UnquoteState::HasEscapeOctal { mut out, octal }, _) => {
let octal_number_string = String::from_utf8(octal)?; let octal_number_string = String::from_utf8(octal)?;
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?; let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
out.push(decoded_byte); out.push(decoded_byte);
ParseState::Normal UnquoteState::HasEscape { out }
} }
(state, _) => state, (state, _) => state,
}; };
state = match (state, current_char) { state = match (state, current_char) {
(ParseState::Normal, b'\\') => ParseState::Escape, (UnquoteState::Normal, b'\\') => UnquoteState::Escape,
(ParseState::Normal, _) => { (UnquoteState::Normal, _) => UnquoteState::Normal,
(UnquoteState::HasEscape { out }, b'\\') => UnquoteState::HasEscapeEscape { out },
(UnquoteState::HasEscape { mut out }, _) => {
out.push(current_char); out.push(current_char);
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'n') => { (UnquoteState::Escape, b'n') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'\n'); out.push(b'\n');
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'\\') => { (UnquoteState::HasEscapeEscape { mut out }, b'n') => {
out.push(b'\n');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'\\') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'\\'); out.push(b'\\');
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'"') => { (UnquoteState::HasEscapeEscape { mut out }, b'\\') => {
out.push(b'\\');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'"') => {
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
// Subtract 1 from offset to account for backslash.
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
out.push(b'"'); out.push(b'"');
ParseState::Normal UnquoteState::HasEscape { out }
} }
(ParseState::Escape, b'0'..=b'7') => { (UnquoteState::HasEscapeEscape { mut out }, b'"') => {
out.push(b'"');
UnquoteState::HasEscape { out }
}
(UnquoteState::Escape, b'0'..=b'7') => {
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH); let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
octal.push(current_char); octal.push(current_char);
ParseState::Octal(octal) // Substract 1 from offset to account for backslash
UnquoteState::Octal {
octal_begin_offset: offset - 1,
octal,
} }
(ParseState::Octal(mut octal), b'0'..=b'7') => { }
(UnquoteState::HasEscapeEscape { out }, b'0'..=b'7') => {
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
octal.push(current_char); octal.push(current_char);
ParseState::Octal(octal) // Substract 1 from offset to account for backslash
UnquoteState::HasEscapeOctal { out, octal }
} }
_ => panic!("Invalid state unquoting string."), (
UnquoteState::Octal {
octal_begin_offset,
mut octal,
},
b'0'..=b'7',
) => {
octal.push(current_char);
UnquoteState::Octal {
octal_begin_offset,
octal,
}
}
(UnquoteState::HasEscapeOctal { out, mut octal }, b'0'..=b'7') => {
octal.push(current_char);
UnquoteState::HasEscapeOctal { out, octal }
}
(state, _) => panic!(
"Invalid state unquoting string: {:?} | {} | {:?}",
state, offset, interior_text
),
}; };
} }
Ok(String::from_utf8(out)?) match state {
UnquoteState::Normal | UnquoteState::Escape | UnquoteState::Octal { .. } => {
Ok(Cow::Borrowed(interior_text))
}
UnquoteState::HasEscape { out } => Ok(Cow::Owned(String::from_utf8(out)?)),
UnquoteState::HasEscapeEscape { mut out } => {
out.push(b'\\');
Ok(Cow::Owned(String::from_utf8(out)?))
}
UnquoteState::HasEscapeOctal { mut out, octal } => {
out.push(b'\\');
out.extend(octal);
Ok(Cow::Owned(String::from_utf8(out)?))
}
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::str::FromStr; use std::str::FromStr;
use super::compare_field::compare_property_list_of_quoted_string; use super::compare_field::compare_property_list_of_quoted_string;
@@ -14,7 +15,6 @@ use crate::compare::sexp::unquote;
use crate::types::AffiliatedKeywordValue; use crate::types::AffiliatedKeywordValue;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::GetAffiliatedKeywords; use crate::types::GetAffiliatedKeywords;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties; use crate::types::StandardProperties;
/// Check if the child string slice is a slice of the parent string slice. /// Check if the child string slice is a slice of the parent string slice.
@@ -29,32 +29,29 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get the byte offset into source that the rust object exists at. /// Get the byte offset into source that the rust object exists at.
/// ///
/// These offsets are zero-based unlike the elisp ones. /// These offsets are zero-based unlike the elisp ones.
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>( fn get_rust_byte_offsets(original_document: &str, subset: &str) -> (usize, usize) {
original_document: &'s str, debug_assert!(is_slice_of(original_document, subset));
rust_ast_node: &'b S, let offset = subset.as_ptr() as usize - original_document.as_ptr() as usize;
) -> (usize, usize) { let end = offset + subset.len();
let rust_object_source = rust_ast_node.get_source();
debug_assert!(is_slice_of(original_document, rust_object_source));
let offset = rust_object_source.as_ptr() as usize - original_document.as_ptr() as usize;
let end = offset + rust_object_source.len();
(offset, end) (offset, end)
} }
pub(crate) fn compare_standard_properties< pub(crate) fn compare_standard_properties<
'b, 'b,
's, 's,
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized, S: StandardProperties<'s> + GetElispFact<'s> + ?Sized,
>( >(
original_document: &'s str, original_document: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust: &'b S, rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?; assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
assert_bounds(original_document, emacs, rust.get_standard_properties())?; assert_bounds(original_document, emacs, rust)?;
assert_post_blank(emacs, rust)?;
Ok(()) Ok(())
} }
pub(crate) fn assert_name<S: AsRef<str>>( fn assert_name<S: AsRef<str>>(
emacs: &Token<'_>, emacs: &Token<'_>,
name: S, name: S,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
@@ -77,25 +74,73 @@ pub(crate) fn assert_name<S: AsRef<str>>(
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties. /// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
/// ///
/// This does **not** handle plain text because plain text is a special case. /// This does **not** handle plain text because plain text is a special case.
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>( fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
original_document: &'s str, original_document: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust: &'b S, rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
// Check begin/end
{
let (begin, end) = ( let (begin, end) = (
standard_properties standard_properties
.begin .begin
.ok_or("Token should have a begin.")?, .ok_or("Token should have a begin.")?,
standard_properties.end.ok_or("Token should have an end.")?, standard_properties.end.ok_or("Token should have an end.")?,
); );
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust); // 0-based let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust.get_source()); // 0-based
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
let rust_end_char_offset = let rust_end_char_offset =
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
if rust_begin_char_offset != begin || rust_end_char_offset != end { if rust_begin_char_offset != begin || rust_end_char_offset != end {
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?; Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
} }
}
// Check contents-begin/contents-end
{
if let Some(rust_contents) = rust.get_contents() {
let (begin, end) = (
standard_properties
.contents_begin
.ok_or("Token should have a contents-begin.")?,
standard_properties
.contents_end
.ok_or("Token should have an contents-end.")?,
);
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust_contents); // 0-based
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
let rust_end_char_offset =
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
if rust_begin_char_offset != begin || rust_end_char_offset != end {
Err(format!("Rust contents bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs contents bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
}
} else if standard_properties.contents_begin.is_some()
|| standard_properties.contents_end.is_some()
{
Err(format!("Rust contents is None but emacs contents bounds are ({emacs_begin:?}, {emacs_end:?})", emacs_begin=standard_properties.contents_begin, emacs_end=standard_properties.contents_end))?;
}
}
Ok(())
}
/// Assert that the post blank matches between emacs and organic.
///
/// This does **not** handle plain text because plain text is a special case.
fn assert_post_blank<'b, 's, S: StandardProperties<'s> + ?Sized>(
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
let rust_post_blank = rust.get_post_blank();
let emacs_post_blank = standard_properties
.post_blank
.ok_or("Token should have a post-blank.")?;
if rust_post_blank as usize != emacs_post_blank {
Err(format!("Rust post-blank (in chars) {rust_post_blank} does not match emacs post-blank ({emacs_post_blank})", rust_post_blank = rust_post_blank, emacs_post_blank = emacs_post_blank))?;
}
Ok(()) Ok(())
} }
@@ -206,10 +251,10 @@ pub(crate) fn get_property_unquoted_atom<'s>(
/// Get a named property containing an quoted string from the emacs token. /// Get a named property containing an quoted string from the emacs token.
/// ///
/// Returns None if key is not found. /// Returns None if key is not found.
pub(crate) fn get_property_quoted_string( pub(crate) fn get_property_quoted_string<'s>(
emacs: &Token<'_>, emacs: &Token<'s>,
key: &str, key: &str,
) -> Result<Option<String>, Box<dyn std::error::Error>> { ) -> Result<Option<Cow<'s, str>>, Box<dyn std::error::Error>> {
get_property(emacs, key)? get_property(emacs, key)?
.map(Token::as_atom) .map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))? .map_or(Ok(None), |r| r.map(Some))?

View File

@@ -6,8 +6,6 @@ pub(crate) type Res<T, U> = IResult<T, U, CustomError>;
#[derive(Debug)] #[derive(Debug)]
pub enum CustomError { pub enum CustomError {
#[allow(dead_code)]
Text(String),
Static(&'static str), Static(&'static str),
IO(std::io::Error), IO(std::io::Error),
Parser(ErrorKind), Parser(ErrorKind),
@@ -35,9 +33,3 @@ impl From<&'static str> for CustomError {
CustomError::Static(value) CustomError::Static(value)
} }
} }
impl From<String> for CustomError {
fn from(value: String) -> Self {
CustomError::Text(value)
}
}

View File

@@ -90,12 +90,11 @@ impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
} }
} }
impl<'r, 's> IntoIterator for AstNode<'r, 's> { impl<'r, 's> AstNode<'r, 's> {
type Item = AstNode<'r, 's>; /// Iterate all AST nodes.
///
type IntoIter = AllAstNodeIter<'r, 's>; /// This is different from the iter/into_iter functions which iterate a single level of the children. This iterates the entire tree including returning the root node itself.
pub fn iter_all_ast_nodes(self) -> AllAstNodeIter<'r, 's> {
fn into_iter(self) -> Self::IntoIter {
AllAstNodeIter { AllAstNodeIter {
root: Some(self), root: Some(self),
queue: VecDeque::new(), queue: VecDeque::new(),

View File

@@ -225,7 +225,7 @@ fn impl_balanced_bracket<
let contents_end = remaining; let contents_end = remaining;
let (remaining, _) = end_parser(remaining)?; let (remaining, _) = end_parser(remaining)?;
let contents = if contents_start != contents_end { let contents = if Into::<&str>::into(contents_start) != Into::<&str>::into(contents_end) {
Some(contents_start.get_until(contents_end)) Some(contents_start.get_until(contents_end))
} else { } else {
None None
@@ -244,7 +244,7 @@ mod tests {
let input = OrgSource::new("()"); let input = OrgSource::new("()");
let (remaining, call) = opt(babel_call_call)(input)?; let (remaining, call) = opt(babel_call_call)(input)?;
assert_eq!(Into::<&str>::into(remaining), "()"); assert_eq!(Into::<&str>::into(remaining), "()");
assert_eq!(call, None); assert!(call.is_none());
Ok(()) Ok(())
} }
} }

View File

@@ -15,10 +15,8 @@ use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
use crate::parser::macros::element; use crate::parser::macros::element;
use crate::types::AffiliatedKeywords;
use crate::types::Object; use crate::types::Object;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::PlainText;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
@@ -80,13 +78,10 @@ pub(crate) fn broken_end<'b, 'g, 'r, 's>(
Ok(( Ok((
remaining, remaining,
Paragraph { Paragraph::of_text(
source: input.get_until(remaining).into(), input.get_until(remaining).into(),
affiliated_keywords: AffiliatedKeywords::default(), input.get_until(lead_in_remaining).into(),
children: vec![Object::PlainText(PlainText { ),
source: input.get_until(lead_in_remaining).into(),
})],
},
)) ))
} }
} }
@@ -139,13 +134,10 @@ pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
Ok(( Ok((
remaining, remaining,
Paragraph { Paragraph::of_text(
source: input.get_until(remaining).into(), input.get_until(remaining).into(),
affiliated_keywords: AffiliatedKeywords::default(), input.get_until(lead_in_remaining).into(),
children: vec![Object::PlainText(PlainText { ),
source: input.get_until(lead_in_remaining).into(),
})],
},
)) ))
} }
} }

View File

@@ -210,12 +210,11 @@ mod tests {
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::types::CitationReference;
use crate::types::Element; use crate::types::Element;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn citation_simple() { fn citation_simple() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("[cite:@foo]"); let input = OrgSource::new("[cite:@foo]");
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
@@ -227,28 +226,33 @@ mod tests {
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
first_paragraph.get_standard_properties().get_source(),
"[cite:@foo]"
);
assert_eq!(first_paragraph.children.len(), 1); assert_eq!(first_paragraph.children.len(), 1);
assert_eq!(
first_paragraph match first_paragraph
.children .children
.get(0) .first()
.expect("Len already asserted to be 1"), .expect("Len already asserted to be 1.")
&Object::Citation(Citation { {
source: "[cite:@foo]", Object::Citation(inner) => {
style: None, assert_eq!(inner.get_source(), "[cite:@foo]");
prefix: vec![], assert_eq!(inner.children.len(), 1);
suffix: vec![], assert!(inner.prefix.is_empty());
children: vec![CitationReference { assert!(inner.suffix.is_empty());
source: "@foo", assert!(inner.style.is_none());
key: "foo", let citation_reference = inner
prefix: vec![], .children
suffix: vec![] .first()
}] .expect("Len already asserted to be 1.");
}) assert_eq!(citation_reference.get_source(), "@foo");
); assert_eq!(citation_reference.key, "foo");
assert!(citation_reference.prefix.is_empty());
assert!(citation_reference.suffix.is_empty());
}
_ => {
return Err("Child should be a citation.".into());
}
};
Ok(())
} }
} }

View File

@@ -143,7 +143,7 @@ fn document_org_source<'b, 'g, 'r, 's>(
{ {
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets. // If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document) let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
.into_iter() .iter_all_ast_nodes()
.filter_map(|ast_node| { .filter_map(|ast_node| {
if let AstNode::RadioTarget(ast_node) = ast_node { if let AstNode::RadioTarget(ast_node) = ast_node {
Some(ast_node) Some(ast_node)

View File

@@ -31,7 +31,6 @@ use crate::types::Drawer;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
@@ -81,9 +80,8 @@ where
))(remaining) ))(remaining)
{ {
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain); let source = get_consumed(remaining, remain);
element.set_source(source.into()); let element = Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()));
(remain, vec![element]) (remain, vec![element])
} }
Err(_) => { Err(_) => {

View File

@@ -37,7 +37,6 @@ use crate::types::DynamicBlock;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
@@ -88,9 +87,7 @@ where
))))(remaining)?; ))))(remaining)?;
let leading_blank_lines = let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| { leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into())); Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
element.set_source(source.into());
element
}); });
let (remaining, (mut children, _exit_contents)) = let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;

View File

@@ -75,6 +75,7 @@ where
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let before_contents = remaining;
let (mut remaining, (mut children, _exit_contents)) = let (mut remaining, (mut children, _exit_contents)) =
many_till(include_input(element_matcher), exit_matcher)(remaining)?; many_till(include_input(element_matcher), exit_matcher)(remaining)?;
@@ -90,13 +91,16 @@ where
} }
} }
let (remaining, _trailing_ws) = let contents = get_consumed(before_contents, remaining);
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
FootnoteDefinition { FootnoteDefinition {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
affiliated_keywords: parse_affiliated_keywords( affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
@@ -160,7 +164,7 @@ mod tests {
use crate::context::Context; use crate::context::Context;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
@@ -181,17 +185,13 @@ line footnote.",
footnote_definition_matcher(remaining).expect("Parse second footnote_definition."); footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(
first_footnote_definition first_footnote_definition.get_source(),
.get_standard_properties()
.get_source(),
"[fn:1] A footnote. "[fn:1] A footnote.
" "
); );
assert_eq!( assert_eq!(
second_footnote_definition second_footnote_definition.get_source(),
.get_standard_properties()
.get_source(),
"[fn:2] A multi- "[fn:2] A multi-
line footnote." line footnote."
@@ -216,9 +216,7 @@ not in the footnote.",
footnote_definition_matcher(input).expect("Parse first footnote_definition"); footnote_definition_matcher(input).expect("Parse first footnote_definition");
assert_eq!(Into::<&str>::into(remaining), "not in the footnote."); assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
assert_eq!( assert_eq!(
first_footnote_definition first_footnote_definition.get_source(),
.get_standard_properties()
.get_source(),
"[fn:2] A multi- "[fn:2] A multi-
line footnote. line footnote.

View File

@@ -2,6 +2,7 @@ use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::consumed;
use nom::combinator::map_parser; use nom::combinator::map_parser;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many1; use nom::multi::many1;
@@ -59,7 +60,7 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context)); let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser( let (remaining, (contents, children)) = consumed(map_parser(
verify( verify(
parser_with_context!(text_until_exit)(&parser_context), parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0, |text| text.len() > 0,
@@ -69,17 +70,19 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
&initial_context, &initial_context,
)))(i) )))(i)
}), }),
)(remaining)?; ))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
FootnoteReference { FootnoteReference {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
label: None, label: None,
definition: children, definition: children,
}, },
@@ -106,7 +109,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context)); let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser( let (remaining, (contents, children)) = consumed(map_parser(
verify( verify(
parser_with_context!(text_until_exit)(&parser_context), parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0, |text| text.len() > 0,
@@ -116,17 +119,19 @@ fn inline_footnote<'b, 'g, 'r, 's>(
&initial_context, &initial_context,
)))(i) )))(i)
}), }),
)(remaining)?; ))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
FootnoteReference { FootnoteReference {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
label: Some(label_contents.into()), label: Some(label_contents.into()),
definition: children, definition: children,
}, },
@@ -144,13 +149,15 @@ fn footnote_reference_only<'b, 'g, 'r, 's>(
let (remaining, _) = tag_no_case("[fn:")(input)?; let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?; let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
FootnoteReference { FootnoteReference {
source: source.into(), source: source.into(),
contents: None,
post_blank: post_blank.map(Into::<&str>::into),
label: Some(label_contents.into()), label: Some(label_contents.into()),
definition: Vec::with_capacity(0), definition: Vec::with_capacity(0),
}, },

View File

@@ -39,7 +39,6 @@ use crate::types::Element;
use crate::types::Keyword; use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::QuoteBlock; use crate::types::QuoteBlock;
use crate::types::SetSource;
use crate::types::SpecialBlock; use crate::types::SpecialBlock;
#[cfg_attr( #[cfg_attr(
@@ -257,9 +256,7 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
))))(remaining)?; ))))(remaining)?;
let leading_blank_lines = let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| { leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into())); Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
element.set_source(source.into());
element
}); });
let (remaining, (mut children, _exit_contents)) = let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;

View File

@@ -102,7 +102,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
}) { }) {
let (_, (in_progress_words, complete_words)) = let (_, (in_progress_words, complete_words)) =
todo_keywords(kw.value).map_err(|err| match err { todo_keywords(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()), nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e, nom::Err::Error(e) => e,
nom::Err::Failure(e) => e, nom::Err::Failure(e) => e,
})?; })?;
@@ -123,7 +123,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
kw.value, kw.value,
) )
.map_err(|err: nom::Err<_>| match err { .map_err(|err: nom::Err<_>| match err {
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()), nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e, nom::Err::Error(e) => e,
nom::Err::Failure(e) => e, nom::Err::Failure(e) => e,
})?; })?;
@@ -141,7 +141,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
.filter(|kw| kw.key.eq_ignore_ascii_case("link")) .filter(|kw| kw.key.eq_ignore_ascii_case("link"))
{ {
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err { let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err {
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()), nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e, nom::Err::Error(e) => e,
nom::Err::Failure(e) => e, nom::Err::Failure(e) => e,
})?; })?;
@@ -157,7 +157,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) { pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
document.category = Into::<AstNode>::into(&*document) document.category = Into::<AstNode>::into(&*document)
.into_iter() .iter_all_ast_nodes()
.filter_map(|ast_node| { .filter_map(|ast_node| {
if let AstNode::Keyword(ast_node) = ast_node { if let AstNode::Keyword(ast_node) = ast_node {
if ast_node.key.eq_ignore_ascii_case("category") { if ast_node.key.eq_ignore_ascii_case("category") {

View File

@@ -202,7 +202,7 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]); let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?; let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
@@ -236,7 +236,7 @@ where
retain_labels, retain_labels,
use_labels, use_labels,
label_format, label_format,
contents, value: Into::<&str>::into(contents),
}, },
)) ))
} }
@@ -276,7 +276,7 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]); let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?; let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
@@ -292,7 +292,7 @@ where
), ),
export_type: export_type.map(Into::<&str>::into), export_type: export_type.map(Into::<&str>::into),
data: parameters.map(Into::<&str>::into), data: parameters.map(Into::<&str>::into),
contents, value: Into::<&str>::into(contents),
}, },
)) ))
} }
@@ -331,7 +331,7 @@ where
let parser_context = context.with_additional_node(&contexts[0]); 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[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, contents) = content(&parser_context, remaining)?; let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?; let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
@@ -371,7 +371,7 @@ where
retain_labels, retain_labels,
use_labels, use_labels,
label_format, label_format,
contents, value: Into::<&str>::into(contents),
}, },
)) ))
} }
@@ -650,51 +650,3 @@ fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n"), is_not(" \t\r\n"),
))(input) ))(input)
} }
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
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)?;
if let Some(val) = pre_escape_whitespace {
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

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

View File

@@ -1,4 +1,5 @@
use nom::branch::alt; use nom::branch::alt;
use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -45,14 +46,14 @@ where
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(standard_set_object_matcher, exit_matcher), many_till(standard_set_object_matcher, exit_matcher),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
)(remaining)?; ))(remaining)?;
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph. // Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -60,6 +61,8 @@ where
remaining, remaining,
Paragraph { Paragraph {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
affiliated_keywords: parse_affiliated_keywords( affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(), context.get_global_settings(),
affiliated_keywords, affiliated_keywords,
@@ -96,7 +99,7 @@ mod tests {
use crate::context::List; use crate::context::List;
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::parser::org_source::OrgSource; use crate::parser::org_source::OrgSource;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn two_paragraphs() { fn two_paragraphs() {
@@ -109,13 +112,7 @@ mod tests {
let (remaining, second_paragraph) = let (remaining, second_paragraph) =
paragraph_matcher(remaining).expect("Parse second paragraph."); paragraph_matcher(remaining).expect("Parse second paragraph.");
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
first_paragraph.get_standard_properties().get_source(), assert_eq!(second_paragraph.get_source(), "lorem ipsum");
"foo bar baz\n\n"
);
assert_eq!(
second_paragraph.get_standard_properties().get_source(),
"lorem ipsum"
);
} }
} }

View File

@@ -629,7 +629,7 @@ mod tests {
use crate::context::Context; use crate::context::Context;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn plain_list_item_empty() { fn plain_list_item_empty() {
@@ -640,7 +640,7 @@ mod tests {
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context); let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap(); let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1."); assert_eq!(result.get_source(), "1.");
} }
#[test] #[test]
@@ -652,7 +652,7 @@ mod tests {
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context); let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap(); let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo"); assert_eq!(result.get_source(), "1. foo");
} }
#[test] #[test]
@@ -664,7 +664,7 @@ mod tests {
let (remaining, result) = let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap(); plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1."); assert_eq!(result.get_source(), "1.");
} }
#[test] #[test]
@@ -676,7 +676,7 @@ mod tests {
let (remaining, result) = let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap(); plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo"); assert_eq!(result.get_source(), "1. foo");
} }
#[test] #[test]
@@ -721,7 +721,7 @@ mod tests {
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), " ipsum\n"); assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
assert_eq!( assert_eq!(
result.get_standard_properties().get_source(), result.get_source(),
r#"1. foo r#"1. foo
2. bar 2. bar
baz baz
@@ -749,7 +749,7 @@ baz"#,
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "baz"); assert_eq!(Into::<&str>::into(remaining), "baz");
assert_eq!( assert_eq!(
result.get_standard_properties().get_source(), result.get_source(),
r#"1. foo r#"1. foo
1. bar 1. bar
@@ -782,7 +782,7 @@ dolar"#,
plain_list_matcher(input).expect("Should parse the plain list successfully."); plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "dolar"); assert_eq!(Into::<&str>::into(remaining), "dolar");
assert_eq!( assert_eq!(
result.get_standard_properties().get_source(), result.get_source(),
r#"1. foo r#"1. foo
bar bar

View File

@@ -143,7 +143,7 @@ mod tests {
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text; use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
use crate::types::GetStandardProperties; use crate::types::StandardProperties;
#[test] #[test]
fn plain_text_simple() { fn plain_text_simple() {
@@ -160,9 +160,6 @@ mod tests {
)(input) )(input)
.unwrap(); .unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(result.get_source(), Into::<&str>::into(input));
result.get_standard_properties().get_source(),
Into::<&str>::into(input)
);
} }
} }

View File

@@ -175,11 +175,11 @@ mod tests {
use crate::parser::element_parser::element; use crate::parser::element_parser::element;
use crate::types::Bold; use crate::types::Bold;
use crate::types::Element; use crate::types::Element;
use crate::types::GetStandardProperties;
use crate::types::PlainText; use crate::types::PlainText;
use crate::types::StandardProperties;
#[test] #[test]
fn plain_text_radio_target() { fn plain_text_radio_target() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("foo bar baz"); let input = OrgSource::new("foo bar baz");
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })]; let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
let global_settings = GlobalSettings { let global_settings = GlobalSettings {
@@ -195,29 +195,38 @@ mod tests {
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "foo bar baz");
first_paragraph.get_standard_properties().get_source(),
"foo bar baz"
);
assert_eq!(first_paragraph.children.len(), 3); assert_eq!(first_paragraph.children.len(), 3);
assert_eq!( match first_paragraph
first_paragraph
.children .children
.get(1) .get(1)
.expect("Len already asserted to be 3"), .expect("Len already asserted to be 3.")
&Object::RadioLink(RadioLink { {
source: "bar ", Object::RadioLink(inner) => {
children: vec![Object::PlainText(PlainText { source: "bar" })], assert_eq!(inner.get_source(), "bar ");
path: "bar" assert_eq!(inner.path, "bar");
}) assert_eq!(inner.children.len(), 1);
); let child = inner
.children
.first()
.expect("Length already asserted to be 1.");
assert!(matches!(child, Object::PlainText(_)));
assert_eq!(child.get_source(), "bar");
}
_ => {
return Err("Child should be a radio link.".into());
}
};
Ok(())
} }
#[test] #[test]
fn bold_radio_target() { fn bold_radio_target() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("foo *bar* baz"); let input = OrgSource::new("foo *bar* baz");
let radio_target_match = vec![Object::Bold(Bold { let radio_target_match = vec![Object::Bold(Bold {
source: "*bar*", source: "*bar*",
contents: "bar",
post_blank: Some(" "),
children: vec![Object::PlainText(PlainText { source: "bar" })], children: vec![Object::PlainText(PlainText { source: "bar" })],
})]; })];
let global_settings = GlobalSettings { let global_settings = GlobalSettings {
@@ -234,24 +243,28 @@ mod tests {
_ => panic!("Should be a paragraph!"), _ => panic!("Should be a paragraph!"),
}; };
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!( assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
first_paragraph.get_standard_properties().get_source(),
"foo *bar* baz"
);
assert_eq!(first_paragraph.children.len(), 3); assert_eq!(first_paragraph.children.len(), 3);
assert_eq!( match first_paragraph
first_paragraph
.children .children
.get(1) .get(1)
.expect("Len already asserted to be 3"), .expect("Len already asserted to be 3.")
&Object::RadioLink(RadioLink { {
source: "*bar* ", Object::RadioLink(inner) => {
children: vec![Object::Bold(Bold { assert_eq!(inner.get_source(), "*bar* ");
source: "*bar* ", assert_eq!(inner.path, "*bar* ");
children: vec![Object::PlainText(PlainText { source: "bar" })] assert_eq!(inner.children.len(), 1);
})], let child = inner
path: "*bar* " .children
}) .first()
); .expect("Length already asserted to be 1.");
assert!(matches!(child, Object::Bold(_)));
assert_eq!(child.get_source(), "*bar* ");
}
_ => {
return Err("Child should be a radio link.".into());
}
};
Ok(())
} }
} }

View File

@@ -73,7 +73,7 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, path) = pathreg(context, remaining)?; let (remaining, path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -84,6 +84,8 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
path: path.path, path: path.path,
raw_link: path.raw_link, raw_link: path.raw_link,
search_option: path.search_option, search_option: path.search_option,
contents: None,
post_blank: post_blank.map(Into::<&str>::into),
children: Vec::new(), children: Vec::new(),
application: path.application, application: path.application,
}, },
@@ -101,9 +103,10 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, path) = pathreg(context, remaining)?; let (remaining, path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("][")(remaining)?; let (remaining, _closing_bracket) = tag("][")(remaining)?;
let (remaining, description) = description(context, remaining)?; let (remaining, (contents, description)) =
consumed(parser_with_context!(description)(context))(remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -114,6 +117,8 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
path: path.path, path: path.path,
raw_link: path.raw_link, raw_link: path.raw_link,
search_option: path.search_option, search_option: path.search_option,
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
children: description, children: description,
application: path.application, application: path.application,
}, },

View File

@@ -1,3 +1,4 @@
use nom::combinator::consumed;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -58,12 +59,12 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
many0(blank_line), many0(blank_line),
)))(input)?; )))(input)?;
let (remaining, (mut children, _exit_contents)) = verify( let (remaining, (contents, (mut children, _exit_contents))) = consumed(verify(
many_till(element_matcher, exit_matcher), many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| { |(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some() !children.is_empty() || comment_and_property_drawer_element.is_some()
}, },
)(remaining)?; ))(remaining)?;
if let Some((comment, property_drawer, _ws)) = comment_and_property_drawer_element { if let Some((comment, property_drawer, _ws)) = comment_and_property_drawer_element {
children.insert(0, Element::PropertyDrawer(property_drawer)); children.insert(0, Element::PropertyDrawer(property_drawer));
@@ -72,7 +73,7 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
} }
} }
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -80,6 +81,8 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
remaining, remaining,
Section { Section {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -115,12 +118,12 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
remaining = remain; remaining = remain;
input = remain; input = remain;
} }
let (remaining, (mut children, _exit_contents)) = verify( let (remaining, (contents, (mut children, _exit_contents))) = consumed(verify(
many_till(element_matcher, exit_matcher), many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| { |(children, _exit_contents)| {
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some() !children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
}, },
)(remaining)?; ))(remaining)?;
if let Some(ele) = property_drawer_element.map(Element::PropertyDrawer) { if let Some(ele) = property_drawer_element.map(Element::PropertyDrawer) {
children.insert(0, ele); children.insert(0, ele);
} }
@@ -128,7 +131,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
children.insert(0, ele) children.insert(0, ele)
} }
let (remaining, _trailing_ws) = let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -136,6 +139,8 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
remaining, remaining,
Section { Section {
source: source.into(), source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::take_while; use nom::bytes::complete::take_while;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::combinator::consumed;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
@@ -54,17 +55,20 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Subscript<'s>> { ) -> Res<OrgSource<'s>, Subscript<'s>> {
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("_")(input)?; let (remaining, _) = tag("_")(input)?;
pre(input)?; pre(input)?;
let (remaining, body) = script_body(context, remaining)?; let (remaining, body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let (use_brackets, body) = match body { let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]), ScriptBody::Braceless(text) => (
ScriptBody::WithBraces(body) => (true, body), false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
}; };
Ok(( Ok((
@@ -72,6 +76,8 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
Subscript { Subscript {
source: source.into(), source: source.into(),
use_brackets, use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body, children: body,
}, },
)) ))
@@ -89,13 +95,17 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
let (remaining, _) = tag("^")(input)?; let (remaining, _) = tag("^")(input)?;
pre(input)?; pre(input)?;
let (remaining, body) = script_body(context, remaining)?; let (remaining, body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
let (use_brackets, body) = match body { let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]), ScriptBody::Braceless(text) => (
ScriptBody::WithBraces(body) => (true, body), false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
}; };
Ok(( Ok((
@@ -103,6 +113,8 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
Superscript { Superscript {
source: source.into(), source: source.into(),
use_brackets, use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body, children: body,
}, },
)) ))
@@ -117,7 +129,7 @@ fn pre<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
#[derive(Debug)] #[derive(Debug)]
enum ScriptBody<'s> { enum ScriptBody<'s> {
Braceless(&'s str), Braceless(&'s str),
WithBraces(Vec<Object<'s>>), WithBraces(&'s str, Vec<Object<'s>>),
} }
#[cfg_attr( #[cfg_attr(
@@ -135,9 +147,10 @@ fn script_body<'b, 'g, 'r, 's>(
map(parser_with_context!(script_alphanum)(context), |body| { map(parser_with_context!(script_alphanum)(context), |body| {
ScriptBody::Braceless(body.into()) ScriptBody::Braceless(body.into())
}), }),
map(parser_with_context!(script_with_braces)(context), |body| { map(
ScriptBody::WithBraces(body) parser_with_context!(script_with_braces)(context),
}), |(contents, body)| ScriptBody::WithBraces(Into::<&str>::into(contents), body),
),
map( map(
parser_with_context!(script_with_parenthesis)(context), parser_with_context!(script_with_parenthesis)(context),
|body| ScriptBody::Braceless(body.into()), |body| ScriptBody::Braceless(body.into()),
@@ -195,7 +208,7 @@ fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>,
fn script_with_braces<'b, 'g, 'r, 's>( fn script_with_braces<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>)> {
let (remaining, _) = tag("{")(input)?; let (remaining, _) = tag("{")(input)?;
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth()); let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
@@ -204,13 +217,13 @@ fn script_with_braces<'b, 'g, 'r, 's>(
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = many_till( let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
parser_with_context!(standard_set_object)(&parser_context), parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
)(remaining)?; ))(remaining)?;
let (remaining, _) = tag("}")(remaining)?; let (remaining, _) = tag("}")(remaining)?;
Ok((remaining, children)) Ok((remaining, (contents, children)))
} }
fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher { fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher {

View File

@@ -3,11 +3,13 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::multispace1; use nom::character::complete::multispace1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space1;
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::consumed;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::map_parser; use nom::combinator::map_parser;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -76,12 +78,14 @@ fn bold<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Bold<'s>> { ) -> Res<OrgSource<'s>, Bold<'s>> {
let (remaining, children) = text_markup_object("*")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("*")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Bold { Bold {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -95,12 +99,14 @@ fn italic<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Italic<'s>> { ) -> Res<OrgSource<'s>, Italic<'s>> {
let (remaining, children) = text_markup_object("/")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("/")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Italic { Italic {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -114,12 +120,14 @@ fn underline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Underline<'s>> { ) -> Res<OrgSource<'s>, Underline<'s>> {
let (remaining, children) = text_markup_object("_")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("_")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Underline { Underline {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -133,12 +141,14 @@ fn strike_through<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StrikeThrough<'s>> { ) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
let (remaining, children) = text_markup_object("+")(context, input)?; let (remaining, (contents, children, post_blank)) = text_markup_object("+")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
StrikeThrough { StrikeThrough {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}, },
)) ))
@@ -152,13 +162,14 @@ fn verbatim<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Verbatim<'s>> { ) -> Res<OrgSource<'s>, Verbatim<'s>> {
let (remaining, contents) = text_markup_string("=")(context, input)?; let (remaining, (contents, post_blank)) = text_markup_string("=")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Verbatim { Verbatim {
source: source.into(), source: source.into(),
contents: contents.into(), contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -171,13 +182,14 @@ fn code<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Code<'s>> { ) -> Res<OrgSource<'s>, Code<'s>> {
let (remaining, contents) = text_markup_string("~")(context, input)?; let (remaining, (contents, post_blank)) = text_markup_string("~")(context, input)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Code { Code {
source: source.into(), source: source.into(),
contents: contents.into(), contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
}, },
)) ))
} }
@@ -187,8 +199,10 @@ fn text_markup_object(
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>, OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> ) -> Res<
+ '_ { OrgSource<'s>,
(OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>),
> + '_ {
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol) move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
} }
@@ -200,7 +214,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = let (remaining, _peek_not_whitespace) =
@@ -215,7 +229,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context)); let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser( let (remaining, (contents, children)) = consumed(map_parser(
verify( verify(
parser_with_context!(text_until_exit)(&parser_context), parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0, |text| text.len() > 0,
@@ -225,7 +239,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
&initial_context, &initial_context,
)))(i) )))(i)
}), }),
)(remaining)?; ))(remaining)?;
{ {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@@ -240,9 +254,9 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
} }
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, children)) Ok((remaining, (contents, children, post_blank)))
} }
fn text_markup_string( fn text_markup_string(
@@ -250,7 +264,7 @@ fn text_markup_string(
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>, OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> ) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)>
+ '_ { + '_ {
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol) move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
} }
@@ -263,7 +277,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = let (remaining, _peek_not_whitespace) =
@@ -296,9 +310,9 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
} }
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, contents)) Ok((remaining, (contents, post_blank)))
} }
#[cfg_attr( #[cfg_attr(
@@ -382,13 +396,15 @@ impl<'x> RematchObject<'x> for Bold<'x> {
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "*", &self.children)?; _rematch_text_markup_object(_context, input, "*", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::Bold(Bold { Object::Bold(Bold {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -405,13 +421,15 @@ impl<'x> RematchObject<'x> for Italic<'x> {
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "/", &self.children)?; _rematch_text_markup_object(_context, input, "/", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::Italic(Italic { Object::Italic(Italic {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -428,13 +446,15 @@ impl<'x> RematchObject<'x> for Underline<'x> {
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "_", &self.children)?; _rematch_text_markup_object(_context, input, "_", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::Underline(Underline { Object::Underline(Underline {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -451,13 +471,15 @@ impl<'x> RematchObject<'x> for StrikeThrough<'x> {
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) = let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "+", &self.children)?; _rematch_text_markup_object(_context, input, "+", &self.children)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Object::StrikeThrough(StrikeThrough { Object::StrikeThrough(StrikeThrough {
source: source.into(), source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children, children,
}), }),
)) ))
@@ -473,7 +495,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'static str, marker_symbol: &'static str,
original_match_children: &'x Vec<Object<'x>>, original_match_children: &'x Vec<Object<'x>>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?; let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?; let (remaining, open) = tag(marker_symbol)(remaining)?;
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?; let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
@@ -484,6 +506,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let contents_begin = remaining;
let (remaining, children) = let (remaining, children) =
// TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code. // TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code.
rematch_target(&parser_context, original_match_children, remaining)?; rematch_target(&parser_context, original_match_children, remaining)?;
@@ -499,8 +522,10 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
))); )));
} }
} }
let contents_end = remaining;
let contents = contents_begin.get_until(contents_end);
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, post_blank) = opt(space1)(remaining)?;
Ok((remaining, children)) Ok((remaining, (contents, children, post_blank)))
} }

View File

@@ -81,14 +81,21 @@ pub(crate) fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
// We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::"). // We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::").
let (remaining, _) = many_till( let (remaining, post_blank) = recognize(many_till(
one_of(" \t"), one_of(" \t"),
alt(( alt((
peek(recognize(none_of(" \t"))), peek(recognize(none_of(" \t"))),
parser_with_context!(exit_matcher_parser)(context), parser_with_context!(exit_matcher_parser)(context),
)), )),
)(input)?; ))(input)?;
Ok((remaining, None)) Ok((
remaining,
if post_blank.len() == 0 {
None
} else {
Some(post_blank)
},
))
} }
#[cfg_attr( #[cfg_attr(

View File

@@ -1,7 +1,9 @@
use super::macros::to_ast_node; use super::macros::to_ast_node;
use super::CenterBlock; use super::CenterBlock;
use super::PostBlank;
use super::QuoteBlock; use super::QuoteBlock;
use super::SpecialBlock; use super::SpecialBlock;
use super::StandardProperties;
use crate::types::AngleLink; use crate::types::AngleLink;
use crate::types::BabelCall; use crate::types::BabelCall;
use crate::types::Bold; use crate::types::Bold;
@@ -24,7 +26,6 @@ use crate::types::ExportSnippet;
use crate::types::FixedWidthArea; use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition; use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference; use crate::types::FootnoteReference;
use crate::types::GetStandardProperties;
use crate::types::Heading; use crate::types::Heading;
use crate::types::HorizontalRule; use crate::types::HorizontalRule;
use crate::types::InlineBabelCall; use crate::types::InlineBabelCall;
@@ -259,67 +260,193 @@ to_ast_node!(&'r Superscript<'s>, AstNode::Superscript);
to_ast_node!(&'r TableCell<'s>, AstNode::TableCell); to_ast_node!(&'r TableCell<'s>, AstNode::TableCell);
to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp); to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp);
impl<'r, 's> GetStandardProperties<'s> for AstNode<'r, 's> { impl<'r, 's> StandardProperties<'s> for AstNode<'r, 's> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn crate::types::StandardProperties<'s> { fn get_source<'b>(&'b self) -> &'s str {
match self { match self {
AstNode::Document(inner) => *inner, AstNode::Document(inner) => inner.get_source(),
AstNode::Heading(inner) => *inner, AstNode::Heading(inner) => inner.get_source(),
AstNode::Section(inner) => *inner, AstNode::Section(inner) => inner.get_source(),
AstNode::Paragraph(inner) => *inner, AstNode::Paragraph(inner) => inner.get_source(),
AstNode::PlainList(inner) => *inner, AstNode::PlainList(inner) => inner.get_source(),
AstNode::PlainListItem(inner) => *inner, AstNode::PlainListItem(inner) => inner.get_source(),
AstNode::CenterBlock(inner) => *inner, AstNode::CenterBlock(inner) => inner.get_source(),
AstNode::QuoteBlock(inner) => *inner, AstNode::QuoteBlock(inner) => inner.get_source(),
AstNode::SpecialBlock(inner) => *inner, AstNode::SpecialBlock(inner) => inner.get_source(),
AstNode::DynamicBlock(inner) => *inner, AstNode::DynamicBlock(inner) => inner.get_source(),
AstNode::FootnoteDefinition(inner) => *inner, AstNode::FootnoteDefinition(inner) => inner.get_source(),
AstNode::Comment(inner) => *inner, AstNode::Comment(inner) => inner.get_source(),
AstNode::Drawer(inner) => *inner, AstNode::Drawer(inner) => inner.get_source(),
AstNode::PropertyDrawer(inner) => *inner, AstNode::PropertyDrawer(inner) => inner.get_source(),
AstNode::NodeProperty(inner) => *inner, AstNode::NodeProperty(inner) => inner.get_source(),
AstNode::Table(inner) => *inner, AstNode::Table(inner) => inner.get_source(),
AstNode::TableRow(inner) => *inner, AstNode::TableRow(inner) => inner.get_source(),
AstNode::VerseBlock(inner) => *inner, AstNode::VerseBlock(inner) => inner.get_source(),
AstNode::CommentBlock(inner) => *inner, AstNode::CommentBlock(inner) => inner.get_source(),
AstNode::ExampleBlock(inner) => *inner, AstNode::ExampleBlock(inner) => inner.get_source(),
AstNode::ExportBlock(inner) => *inner, AstNode::ExportBlock(inner) => inner.get_source(),
AstNode::SrcBlock(inner) => *inner, AstNode::SrcBlock(inner) => inner.get_source(),
AstNode::Clock(inner) => *inner, AstNode::Clock(inner) => inner.get_source(),
AstNode::DiarySexp(inner) => *inner, AstNode::DiarySexp(inner) => inner.get_source(),
AstNode::Planning(inner) => *inner, AstNode::Planning(inner) => inner.get_source(),
AstNode::FixedWidthArea(inner) => *inner, AstNode::FixedWidthArea(inner) => inner.get_source(),
AstNode::HorizontalRule(inner) => *inner, AstNode::HorizontalRule(inner) => inner.get_source(),
AstNode::Keyword(inner) => *inner, AstNode::Keyword(inner) => inner.get_source(),
AstNode::BabelCall(inner) => *inner, AstNode::BabelCall(inner) => inner.get_source(),
AstNode::LatexEnvironment(inner) => *inner, AstNode::LatexEnvironment(inner) => inner.get_source(),
AstNode::Bold(inner) => *inner, AstNode::Bold(inner) => inner.get_source(),
AstNode::Italic(inner) => *inner, AstNode::Italic(inner) => inner.get_source(),
AstNode::Underline(inner) => *inner, AstNode::Underline(inner) => inner.get_source(),
AstNode::StrikeThrough(inner) => *inner, AstNode::StrikeThrough(inner) => inner.get_source(),
AstNode::Code(inner) => *inner, AstNode::Code(inner) => inner.get_source(),
AstNode::Verbatim(inner) => *inner, AstNode::Verbatim(inner) => inner.get_source(),
AstNode::PlainText(inner) => *inner, AstNode::PlainText(inner) => inner.get_source(),
AstNode::RegularLink(inner) => *inner, AstNode::RegularLink(inner) => inner.get_source(),
AstNode::RadioLink(inner) => *inner, AstNode::RadioLink(inner) => inner.get_source(),
AstNode::RadioTarget(inner) => *inner, AstNode::RadioTarget(inner) => inner.get_source(),
AstNode::PlainLink(inner) => *inner, AstNode::PlainLink(inner) => inner.get_source(),
AstNode::AngleLink(inner) => *inner, AstNode::AngleLink(inner) => inner.get_source(),
AstNode::OrgMacro(inner) => *inner, AstNode::OrgMacro(inner) => inner.get_source(),
AstNode::Entity(inner) => *inner, AstNode::Entity(inner) => inner.get_source(),
AstNode::LatexFragment(inner) => *inner, AstNode::LatexFragment(inner) => inner.get_source(),
AstNode::ExportSnippet(inner) => *inner, AstNode::ExportSnippet(inner) => inner.get_source(),
AstNode::FootnoteReference(inner) => *inner, AstNode::FootnoteReference(inner) => inner.get_source(),
AstNode::Citation(inner) => *inner, AstNode::Citation(inner) => inner.get_source(),
AstNode::CitationReference(inner) => *inner, AstNode::CitationReference(inner) => inner.get_source(),
AstNode::InlineBabelCall(inner) => *inner, AstNode::InlineBabelCall(inner) => inner.get_source(),
AstNode::InlineSourceBlock(inner) => *inner, AstNode::InlineSourceBlock(inner) => inner.get_source(),
AstNode::LineBreak(inner) => *inner, AstNode::LineBreak(inner) => inner.get_source(),
AstNode::Target(inner) => *inner, AstNode::Target(inner) => inner.get_source(),
AstNode::StatisticsCookie(inner) => *inner, AstNode::StatisticsCookie(inner) => inner.get_source(),
AstNode::Subscript(inner) => *inner, AstNode::Subscript(inner) => inner.get_source(),
AstNode::Superscript(inner) => *inner, AstNode::Superscript(inner) => inner.get_source(),
AstNode::TableCell(inner) => *inner, AstNode::TableCell(inner) => inner.get_source(),
AstNode::Timestamp(inner) => *inner, AstNode::Timestamp(inner) => inner.get_source(),
}
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
match self {
AstNode::Document(inner) => inner.get_contents(),
AstNode::Heading(inner) => inner.get_contents(),
AstNode::Section(inner) => inner.get_contents(),
AstNode::Paragraph(inner) => inner.get_contents(),
AstNode::PlainList(inner) => inner.get_contents(),
AstNode::PlainListItem(inner) => inner.get_contents(),
AstNode::CenterBlock(inner) => inner.get_contents(),
AstNode::QuoteBlock(inner) => inner.get_contents(),
AstNode::SpecialBlock(inner) => inner.get_contents(),
AstNode::DynamicBlock(inner) => inner.get_contents(),
AstNode::FootnoteDefinition(inner) => inner.get_contents(),
AstNode::Comment(inner) => inner.get_contents(),
AstNode::Drawer(inner) => inner.get_contents(),
AstNode::PropertyDrawer(inner) => inner.get_contents(),
AstNode::NodeProperty(inner) => inner.get_contents(),
AstNode::Table(inner) => inner.get_contents(),
AstNode::TableRow(inner) => inner.get_contents(),
AstNode::VerseBlock(inner) => inner.get_contents(),
AstNode::CommentBlock(inner) => inner.get_contents(),
AstNode::ExampleBlock(inner) => inner.get_contents(),
AstNode::ExportBlock(inner) => inner.get_contents(),
AstNode::SrcBlock(inner) => inner.get_contents(),
AstNode::Clock(inner) => inner.get_contents(),
AstNode::DiarySexp(inner) => inner.get_contents(),
AstNode::Planning(inner) => inner.get_contents(),
AstNode::FixedWidthArea(inner) => inner.get_contents(),
AstNode::HorizontalRule(inner) => inner.get_contents(),
AstNode::Keyword(inner) => inner.get_contents(),
AstNode::BabelCall(inner) => inner.get_contents(),
AstNode::LatexEnvironment(inner) => inner.get_contents(),
AstNode::Bold(inner) => inner.get_contents(),
AstNode::Italic(inner) => inner.get_contents(),
AstNode::Underline(inner) => inner.get_contents(),
AstNode::StrikeThrough(inner) => inner.get_contents(),
AstNode::Code(inner) => inner.get_contents(),
AstNode::Verbatim(inner) => inner.get_contents(),
AstNode::PlainText(inner) => inner.get_contents(),
AstNode::RegularLink(inner) => inner.get_contents(),
AstNode::RadioLink(inner) => inner.get_contents(),
AstNode::RadioTarget(inner) => inner.get_contents(),
AstNode::PlainLink(inner) => inner.get_contents(),
AstNode::AngleLink(inner) => inner.get_contents(),
AstNode::OrgMacro(inner) => inner.get_contents(),
AstNode::Entity(inner) => inner.get_contents(),
AstNode::LatexFragment(inner) => inner.get_contents(),
AstNode::ExportSnippet(inner) => inner.get_contents(),
AstNode::FootnoteReference(inner) => inner.get_contents(),
AstNode::Citation(inner) => inner.get_contents(),
AstNode::CitationReference(inner) => inner.get_contents(),
AstNode::InlineBabelCall(inner) => inner.get_contents(),
AstNode::InlineSourceBlock(inner) => inner.get_contents(),
AstNode::LineBreak(inner) => inner.get_contents(),
AstNode::Target(inner) => inner.get_contents(),
AstNode::StatisticsCookie(inner) => inner.get_contents(),
AstNode::Subscript(inner) => inner.get_contents(),
AstNode::Superscript(inner) => inner.get_contents(),
AstNode::TableCell(inner) => inner.get_contents(),
AstNode::Timestamp(inner) => inner.get_contents(),
}
}
fn get_post_blank(&self) -> PostBlank {
match self {
AstNode::Document(inner) => inner.get_post_blank(),
AstNode::Heading(inner) => inner.get_post_blank(),
AstNode::Section(inner) => inner.get_post_blank(),
AstNode::Paragraph(inner) => inner.get_post_blank(),
AstNode::PlainList(inner) => inner.get_post_blank(),
AstNode::PlainListItem(inner) => inner.get_post_blank(),
AstNode::CenterBlock(inner) => inner.get_post_blank(),
AstNode::QuoteBlock(inner) => inner.get_post_blank(),
AstNode::SpecialBlock(inner) => inner.get_post_blank(),
AstNode::DynamicBlock(inner) => inner.get_post_blank(),
AstNode::FootnoteDefinition(inner) => inner.get_post_blank(),
AstNode::Comment(inner) => inner.get_post_blank(),
AstNode::Drawer(inner) => inner.get_post_blank(),
AstNode::PropertyDrawer(inner) => inner.get_post_blank(),
AstNode::NodeProperty(inner) => inner.get_post_blank(),
AstNode::Table(inner) => inner.get_post_blank(),
AstNode::TableRow(inner) => inner.get_post_blank(),
AstNode::VerseBlock(inner) => inner.get_post_blank(),
AstNode::CommentBlock(inner) => inner.get_post_blank(),
AstNode::ExampleBlock(inner) => inner.get_post_blank(),
AstNode::ExportBlock(inner) => inner.get_post_blank(),
AstNode::SrcBlock(inner) => inner.get_post_blank(),
AstNode::Clock(inner) => inner.get_post_blank(),
AstNode::DiarySexp(inner) => inner.get_post_blank(),
AstNode::Planning(inner) => inner.get_post_blank(),
AstNode::FixedWidthArea(inner) => inner.get_post_blank(),
AstNode::HorizontalRule(inner) => inner.get_post_blank(),
AstNode::Keyword(inner) => inner.get_post_blank(),
AstNode::BabelCall(inner) => inner.get_post_blank(),
AstNode::LatexEnvironment(inner) => inner.get_post_blank(),
AstNode::Bold(inner) => inner.get_post_blank(),
AstNode::Italic(inner) => inner.get_post_blank(),
AstNode::Underline(inner) => inner.get_post_blank(),
AstNode::StrikeThrough(inner) => inner.get_post_blank(),
AstNode::Code(inner) => inner.get_post_blank(),
AstNode::Verbatim(inner) => inner.get_post_blank(),
AstNode::PlainText(inner) => inner.get_post_blank(),
AstNode::RegularLink(inner) => inner.get_post_blank(),
AstNode::RadioLink(inner) => inner.get_post_blank(),
AstNode::RadioTarget(inner) => inner.get_post_blank(),
AstNode::PlainLink(inner) => inner.get_post_blank(),
AstNode::AngleLink(inner) => inner.get_post_blank(),
AstNode::OrgMacro(inner) => inner.get_post_blank(),
AstNode::Entity(inner) => inner.get_post_blank(),
AstNode::LatexFragment(inner) => inner.get_post_blank(),
AstNode::ExportSnippet(inner) => inner.get_post_blank(),
AstNode::FootnoteReference(inner) => inner.get_post_blank(),
AstNode::Citation(inner) => inner.get_post_blank(),
AstNode::CitationReference(inner) => inner.get_post_blank(),
AstNode::InlineBabelCall(inner) => inner.get_post_blank(),
AstNode::InlineSourceBlock(inner) => inner.get_post_blank(),
AstNode::LineBreak(inner) => inner.get_post_blank(),
AstNode::Target(inner) => inner.get_post_blank(),
AstNode::StatisticsCookie(inner) => inner.get_post_blank(),
AstNode::Subscript(inner) => inner.get_post_blank(),
AstNode::Superscript(inner) => inner.get_post_blank(),
AstNode::TableCell(inner) => inner.get_post_blank(),
AstNode::Timestamp(inner) => inner.get_post_blank(),
} }
} }
} }

View File

@@ -1,9 +1,10 @@
use std::path::PathBuf; use std::path::PathBuf;
use super::remove_trailing::RemoveTrailing;
use super::Element; use super::Element;
use super::GetStandardProperties;
use super::NodeProperty; use super::NodeProperty;
use super::Object; use super::Object;
use super::PostBlank;
use super::StandardProperties; use super::StandardProperties;
use super::Timestamp; use super::Timestamp;
@@ -39,6 +40,8 @@ pub struct Heading<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct Section<'s> { pub struct Section<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
} }
@@ -54,41 +57,108 @@ pub enum TodoKeywordType {
Done, Done,
} }
impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self {
DocumentElement::Heading(inner) => inner,
DocumentElement::Section(inner) => inner,
}
}
}
impl<'s> StandardProperties<'s> for Document<'s> { impl<'s> StandardProperties<'s> for Document<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
let post_blank = self.get_post_blank();
let mut content_lines = self
.source
.split_inclusive('\n')
.remove_trailing(post_blank);
let first_line = content_lines.next();
let last_line = content_lines.last().or(first_line);
match (first_line, last_line) {
(None, None) => None,
(None, Some(_)) | (Some(_), None) => unreachable!(),
(Some(first_line), Some(last_line)) => {
let first_line_offset = first_line.as_ptr() as usize;
let last_line_offset = last_line.as_ptr() as usize + last_line.len();
let source_offset = self.source.as_ptr() as usize;
debug_assert!(super::lesser_element::is_slice_of(self.source, first_line));
debug_assert!(super::lesser_element::is_slice_of(self.source, last_line));
Some(
&self.source[(first_line_offset - source_offset)
..(last_line_offset - first_line_offset)],
)
}
}
}
fn get_post_blank(&self) -> PostBlank {
self.into_iter()
.last()
.map(|child| child.get_post_blank())
.unwrap_or(0)
}
} }
impl<'s> StandardProperties<'s> for Section<'s> { impl<'s> StandardProperties<'s> for Section<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Heading<'s> { impl<'s> StandardProperties<'s> for Heading<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
let first_child = self.children.first();
let last_child = self.children.last();
match (first_child, last_child) {
(None, None) => None,
(None, Some(_)) | (Some(_), None) => unreachable!(),
(Some(first_child), Some(last_child)) => {
let first_child_offset = first_child.get_source().as_ptr() as usize;
let last_child_offset = {
let last_child_source = last_child.get_source();
last_child_source.as_ptr() as usize + last_child_source.len()
};
let source_offset = self.source.as_ptr() as usize;
debug_assert!(super::lesser_element::is_slice_of(
self.source,
first_child.get_source()
));
debug_assert!(super::lesser_element::is_slice_of(
self.source,
last_child.get_source()
));
Some(
&self.source[(first_child_offset - source_offset)
..(last_child_offset - first_child_offset)],
)
}
}
}
fn get_post_blank(&self) -> PostBlank {
self.children
.last()
.map(|child| child.get_post_blank())
.unwrap_or(0)
}
} }
impl<'s> Heading<'s> { impl<'s> Heading<'s> {
pub fn get_raw_value(&self) -> String { pub fn get_raw_value(&self) -> String {
// TODO: I think this could just return a string slice instead of an owned string. // TODO: I think this could just return a string slice instead of an owned string.
let title_source: String = self let title_source: String = self.title.iter().map(|obj| obj.get_source()).collect();
.title
.iter()
.map(|obj| obj.get_standard_properties().get_source())
.collect();
title_source title_source
} }
@@ -132,3 +202,26 @@ impl<'s> Document<'s> {
.flat_map(|property_drawer| property_drawer.children.iter()) .flat_map(|property_drawer| property_drawer.children.iter())
} }
} }
impl<'s> StandardProperties<'s> for DocumentElement<'s> {
fn get_source<'b>(&'b self) -> &'s str {
match self {
DocumentElement::Heading(inner) => inner.get_source(),
DocumentElement::Section(inner) => inner.get_source(),
}
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
match self {
DocumentElement::Heading(inner) => inner.get_contents(),
DocumentElement::Section(inner) => inner.get_contents(),
}
}
fn get_post_blank(&self) -> PostBlank {
match self {
DocumentElement::Heading(inner) => inner.get_post_blank(),
DocumentElement::Section(inner) => inner.get_post_blank(),
}
}
}

View File

@@ -20,9 +20,8 @@ use super::lesser_element::SrcBlock;
use super::lesser_element::VerseBlock; use super::lesser_element::VerseBlock;
use super::CenterBlock; use super::CenterBlock;
use super::Drawer; use super::Drawer;
use super::GetStandardProperties; use super::PostBlank;
use super::QuoteBlock; use super::QuoteBlock;
use super::SetSource;
use super::SpecialBlock; use super::SpecialBlock;
use super::StandardProperties; use super::StandardProperties;
@@ -55,65 +54,91 @@ pub enum Element<'s> {
LatexEnvironment(LatexEnvironment<'s>), LatexEnvironment(LatexEnvironment<'s>),
} }
impl<'s> SetSource<'s> for Element<'s> { impl<'s> StandardProperties<'s> for Element<'s> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] fn get_source<'b>(&'b self) -> &'s str {
fn set_source(&mut self, source: &'s str) {
match self { match self {
Element::Paragraph(obj) => obj.source = source, Element::Paragraph(inner) => inner.get_source(),
Element::PlainList(obj) => obj.source = source, Element::PlainList(inner) => inner.get_source(),
Element::CenterBlock(obj) => obj.source = source, Element::CenterBlock(inner) => inner.get_source(),
Element::QuoteBlock(obj) => obj.source = source, Element::QuoteBlock(inner) => inner.get_source(),
Element::SpecialBlock(obj) => obj.source = source, Element::SpecialBlock(inner) => inner.get_source(),
Element::DynamicBlock(obj) => obj.source = source, Element::DynamicBlock(inner) => inner.get_source(),
Element::FootnoteDefinition(obj) => obj.source = source, Element::FootnoteDefinition(inner) => inner.get_source(),
Element::Comment(obj) => obj.source = source, Element::Comment(inner) => inner.get_source(),
Element::Drawer(obj) => obj.source = source, Element::Drawer(inner) => inner.get_source(),
Element::PropertyDrawer(obj) => obj.source = source, Element::PropertyDrawer(inner) => inner.get_source(),
Element::Table(obj) => obj.source = source, Element::Table(inner) => inner.get_source(),
Element::VerseBlock(obj) => obj.source = source, Element::VerseBlock(inner) => inner.get_source(),
Element::CommentBlock(obj) => obj.source = source, Element::CommentBlock(inner) => inner.get_source(),
Element::ExampleBlock(obj) => obj.source = source, Element::ExampleBlock(inner) => inner.get_source(),
Element::ExportBlock(obj) => obj.source = source, Element::ExportBlock(inner) => inner.get_source(),
Element::SrcBlock(obj) => obj.source = source, Element::SrcBlock(inner) => inner.get_source(),
Element::Clock(obj) => obj.source = source, Element::Clock(inner) => inner.get_source(),
Element::DiarySexp(obj) => obj.source = source, Element::DiarySexp(inner) => inner.get_source(),
Element::Planning(obj) => obj.source = source, Element::Planning(inner) => inner.get_source(),
Element::FixedWidthArea(obj) => obj.source = source, Element::FixedWidthArea(inner) => inner.get_source(),
Element::HorizontalRule(obj) => obj.source = source, Element::HorizontalRule(inner) => inner.get_source(),
Element::Keyword(obj) => obj.source = source, Element::Keyword(inner) => inner.get_source(),
Element::BabelCall(obj) => obj.source = source, Element::BabelCall(inner) => inner.get_source(),
Element::LatexEnvironment(obj) => obj.source = source, Element::LatexEnvironment(inner) => inner.get_source(),
} }
} }
}
impl<'s> GetStandardProperties<'s> for Element<'s> { fn get_contents<'b>(&'b self) -> Option<&'s str> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self { match self {
Element::Paragraph(inner) => inner, Element::Paragraph(inner) => inner.get_contents(),
Element::PlainList(inner) => inner, Element::PlainList(inner) => inner.get_contents(),
Element::CenterBlock(inner) => inner, Element::CenterBlock(inner) => inner.get_contents(),
Element::QuoteBlock(inner) => inner, Element::QuoteBlock(inner) => inner.get_contents(),
Element::SpecialBlock(inner) => inner, Element::SpecialBlock(inner) => inner.get_contents(),
Element::DynamicBlock(inner) => inner, Element::DynamicBlock(inner) => inner.get_contents(),
Element::FootnoteDefinition(inner) => inner, Element::FootnoteDefinition(inner) => inner.get_contents(),
Element::Comment(inner) => inner, Element::Comment(inner) => inner.get_contents(),
Element::Drawer(inner) => inner, Element::Drawer(inner) => inner.get_contents(),
Element::PropertyDrawer(inner) => inner, Element::PropertyDrawer(inner) => inner.get_contents(),
Element::Table(inner) => inner, Element::Table(inner) => inner.get_contents(),
Element::VerseBlock(inner) => inner, Element::VerseBlock(inner) => inner.get_contents(),
Element::CommentBlock(inner) => inner, Element::CommentBlock(inner) => inner.get_contents(),
Element::ExampleBlock(inner) => inner, Element::ExampleBlock(inner) => inner.get_contents(),
Element::ExportBlock(inner) => inner, Element::ExportBlock(inner) => inner.get_contents(),
Element::SrcBlock(inner) => inner, Element::SrcBlock(inner) => inner.get_contents(),
Element::Clock(inner) => inner, Element::Clock(inner) => inner.get_contents(),
Element::DiarySexp(inner) => inner, Element::DiarySexp(inner) => inner.get_contents(),
Element::Planning(inner) => inner, Element::Planning(inner) => inner.get_contents(),
Element::FixedWidthArea(inner) => inner, Element::FixedWidthArea(inner) => inner.get_contents(),
Element::HorizontalRule(inner) => inner, Element::HorizontalRule(inner) => inner.get_contents(),
Element::Keyword(inner) => inner, Element::Keyword(inner) => inner.get_contents(),
Element::BabelCall(inner) => inner, Element::BabelCall(inner) => inner.get_contents(),
Element::LatexEnvironment(inner) => inner, Element::LatexEnvironment(inner) => inner.get_contents(),
}
}
fn get_post_blank(&self) -> PostBlank {
match self {
Element::Paragraph(inner) => inner.get_post_blank(),
Element::PlainList(inner) => inner.get_post_blank(),
Element::CenterBlock(inner) => inner.get_post_blank(),
Element::QuoteBlock(inner) => inner.get_post_blank(),
Element::SpecialBlock(inner) => inner.get_post_blank(),
Element::DynamicBlock(inner) => inner.get_post_blank(),
Element::FootnoteDefinition(inner) => inner.get_post_blank(),
Element::Comment(inner) => inner.get_post_blank(),
Element::Drawer(inner) => inner.get_post_blank(),
Element::PropertyDrawer(inner) => inner.get_post_blank(),
Element::Table(inner) => inner.get_post_blank(),
Element::VerseBlock(inner) => inner.get_post_blank(),
Element::CommentBlock(inner) => inner.get_post_blank(),
Element::ExampleBlock(inner) => inner.get_post_blank(),
Element::ExportBlock(inner) => inner.get_post_blank(),
Element::SrcBlock(inner) => inner.get_post_blank(),
Element::Clock(inner) => inner.get_post_blank(),
Element::DiarySexp(inner) => inner.get_post_blank(),
Element::Planning(inner) => inner.get_post_blank(),
Element::FixedWidthArea(inner) => inner.get_post_blank(),
Element::HorizontalRule(inner) => inner.get_post_blank(),
Element::Keyword(inner) => inner.get_post_blank(),
Element::BabelCall(inner) => inner.get_post_blank(),
Element::LatexEnvironment(inner) => inner.get_post_blank(),
} }
} }
} }

View File

@@ -1,12 +0,0 @@
use super::StandardProperties;
pub trait GetStandardProperties<'s> {
// TODO: Can I eliminate this dynamic dispatch, perhaps using nominal generic structs? Low prioritiy since this is not used during parsing.
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s>;
}
impl<'s, I: StandardProperties<'s>> GetStandardProperties<'s> for I {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
self
}
}

View File

@@ -4,6 +4,7 @@ use super::lesser_element::TableCell;
use super::AffiliatedKeywords; use super::AffiliatedKeywords;
use super::Keyword; use super::Keyword;
use super::Object; use super::Object;
use super::PostBlank;
use super::StandardProperties; use super::StandardProperties;
#[derive(Debug)] #[derive(Debug)]
@@ -81,6 +82,8 @@ pub struct DynamicBlock<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct FootnoteDefinition<'s> { pub struct FootnoteDefinition<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub affiliated_keywords: AffiliatedKeywords<'s>, pub affiliated_keywords: AffiliatedKeywords<'s>,
pub label: &'s str, pub label: &'s str,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
@@ -131,72 +134,172 @@ impl<'s> StandardProperties<'s> for PlainList<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for PlainListItem<'s> { impl<'s> StandardProperties<'s> for PlainListItem<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for CenterBlock<'s> { impl<'s> StandardProperties<'s> for CenterBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for QuoteBlock<'s> { impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for SpecialBlock<'s> { impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for DynamicBlock<'s> { impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> { impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Drawer<'s> { impl<'s> StandardProperties<'s> for Drawer<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> { impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for NodeProperty<'s> { impl<'s> StandardProperties<'s> for NodeProperty<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for Table<'s> { impl<'s> StandardProperties<'s> for Table<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for TableRow<'s> { impl<'s> StandardProperties<'s> for TableRow<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> PlainListItem<'s> { impl<'s> PlainListItem<'s> {

View File

@@ -1,13 +1,32 @@
use std::borrow::Cow;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::object::Object; use super::object::Object;
use super::AffiliatedKeywords; use super::AffiliatedKeywords;
use super::GetAffiliatedKeywords; use super::GetAffiliatedKeywords;
use super::PlainText; use super::PlainText;
use super::PostBlank;
use super::StandardProperties; use super::StandardProperties;
use super::Timestamp; use super::Timestamp;
use crate::error::CustomError;
use crate::error::Res;
#[derive(Debug)] #[derive(Debug)]
pub struct Paragraph<'s> { pub struct Paragraph<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub affiliated_keywords: AffiliatedKeywords<'s>, pub affiliated_keywords: AffiliatedKeywords<'s>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
@@ -59,7 +78,7 @@ pub struct ExampleBlock<'s> {
pub retain_labels: RetainLabels, pub retain_labels: RetainLabels,
pub use_labels: bool, pub use_labels: bool,
pub label_format: Option<&'s str>, pub label_format: Option<&'s str>,
pub contents: String, pub value: &'s str,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -68,7 +87,7 @@ pub struct ExportBlock<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>, pub affiliated_keywords: AffiliatedKeywords<'s>,
pub export_type: Option<&'s str>, pub export_type: Option<&'s str>,
pub data: Option<&'s str>, pub data: Option<&'s str>,
pub contents: String, pub value: &'s str,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -83,7 +102,7 @@ pub struct SrcBlock<'s> {
pub retain_labels: RetainLabels, pub retain_labels: RetainLabels,
pub use_labels: bool, pub use_labels: bool,
pub label_format: Option<&'s str>, pub label_format: Option<&'s str>,
pub contents: String, pub value: &'s str,
} }
#[derive(Debug)] #[derive(Debug)]
@@ -169,11 +188,13 @@ impl<'s> Paragraph<'s> {
/// Generate a paragraph of the passed in text with no additional properties. /// 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. /// This is used for elements that support an "empty" content like greater blocks.
pub(crate) fn of_text(input: &'s str) -> Self { pub(crate) fn of_text(source: &'s str, body: &'s str) -> Self {
Paragraph { Paragraph {
source: input, source,
contents: None, // TODO
post_blank: None, // TODO
affiliated_keywords: AffiliatedKeywords::default(), affiliated_keywords: AffiliatedKeywords::default(),
children: vec![Object::PlainText(PlainText { source: input })], children: vec![Object::PlainText(PlainText { source: body })],
} }
} }
} }
@@ -182,92 +203,224 @@ impl<'s> StandardProperties<'s> for Paragraph<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for TableCell<'s> { impl<'s> StandardProperties<'s> for TableCell<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for Comment<'s> { impl<'s> StandardProperties<'s> for Comment<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
fn get_post_blank(&self) -> PostBlank {
0
}
} }
impl<'s> StandardProperties<'s> for VerseBlock<'s> { impl<'s> StandardProperties<'s> for VerseBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for CommentBlock<'s> { impl<'s> StandardProperties<'s> for CommentBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for ExampleBlock<'s> { impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for ExportBlock<'s> { impl<'s> StandardProperties<'s> for ExportBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for SrcBlock<'s> { impl<'s> StandardProperties<'s> for SrcBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for Clock<'s> { impl<'s> StandardProperties<'s> for Clock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for DiarySexp<'s> { impl<'s> StandardProperties<'s> for DiarySexp<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for Planning<'s> { impl<'s> StandardProperties<'s> for Planning<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for FixedWidthArea<'s> { impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for HorizontalRule<'s> { impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for Keyword<'s> { impl<'s> StandardProperties<'s> for Keyword<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for BabelCall<'s> { impl<'s> StandardProperties<'s> for BabelCall<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for LatexEnvironment<'s> { impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> Comment<'s> { impl<'s> Comment<'s> {
@@ -294,6 +447,13 @@ impl<'s> FixedWidthArea<'s> {
} }
} }
impl<'s> ExampleBlock<'s> {
/// Gets the contents of the lesser block, handling the escaping of lines with leading commas.
pub fn get_value(&self) -> Cow<'s, str> {
lesser_block_content(self.value).expect("This parser should never fail.")
}
}
impl<'s> ExportBlock<'s> { impl<'s> ExportBlock<'s> {
/// Gets the export type capitalized. /// Gets the export type capitalized.
/// ///
@@ -301,6 +461,18 @@ impl<'s> ExportBlock<'s> {
pub fn get_export_type(&self) -> Option<String> { pub fn get_export_type(&self) -> Option<String> {
self.export_type.map(|s| s.to_uppercase()) self.export_type.map(|s| s.to_uppercase())
} }
/// Gets the contents of the lesser block, handling the escaping of lines with leading commas.
pub fn get_value(&self) -> Cow<'s, str> {
lesser_block_content(self.value).expect("This parser should never fail.")
}
}
impl<'s> SrcBlock<'s> {
/// Gets the contents of the lesser block, handling the escaping of lines with leading commas.
pub fn get_value(&self) -> Cow<'s, str> {
lesser_block_content(self.value).expect("This parser should never fail.")
}
} }
impl<'s> GetAffiliatedKeywords<'s> for Paragraph<'s> { impl<'s> GetAffiliatedKeywords<'s> for Paragraph<'s> {
@@ -374,3 +546,82 @@ impl<'s> GetAffiliatedKeywords<'s> for VerseBlock<'s> {
&self.affiliated_keywords &self.affiliated_keywords
} }
} }
enum ContentState {
Normal,
Modified(String),
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn lesser_block_content<'s>(input: &'s str) -> Result<Cow<'s, str>, CustomError> {
let mut state = ContentState::Normal;
let mut remaining = input;
loop {
if remaining.is_empty() {
break;
}
let (remain, (pre_escape_whitespace, line)) =
content_line(remaining).map_err(|err| match err {
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
nom::Err::Error(e) => e,
nom::Err::Failure(e) => e,
})?;
if let Some(val) = pre_escape_whitespace {
if let ContentState::Modified(ref mut ret) = state {
ret.push_str(val);
} else {
let mut ret = String::new();
ret.push_str(get_str_until(input, remaining));
ret.push_str(val);
state = ContentState::Modified(ret);
}
}
if let ContentState::Modified(ref mut ret) = state {
ret.push_str(line);
}
remaining = remain;
}
match state {
ContentState::Normal => Ok(Cow::Borrowed(get_str_until(input, remaining))),
ContentState::Modified(ret) => Ok(Cow::Owned(ret)),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn content_line<'s>(input: &'s str) -> Res<&'s str, (Option<&'s str>, &'s str)> {
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)))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
/// Check if the child string slice is a slice of the parent string slice.
pub(crate) fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize;
let parent_end = parent_start + parent.len();
let child_start = child.as_ptr() as usize;
let child_end = child_start + child.len();
child_start >= parent_start && child_end <= parent_end
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn get_str_until<'s>(parent: &'s str, child: &'s str) -> &'s str {
debug_assert!(is_slice_of(parent, child));
let parent_start = parent.as_ptr() as usize;
let child_start = child.as_ptr() as usize;
&parent[..(child_start - parent_start)]
}

View File

@@ -2,19 +2,18 @@ mod affiliated_keyword;
mod ast_node; mod ast_node;
mod document; mod document;
mod element; mod element;
mod get_standard_properties;
mod greater_element; mod greater_element;
mod lesser_element; mod lesser_element;
mod macros; mod macros;
mod object; mod object;
mod source; mod remove_trailing;
mod standard_properties; mod standard_properties;
mod util; mod util;
pub use affiliated_keyword::AffiliatedKeyword; pub use affiliated_keyword::AffiliatedKeyword;
pub use affiliated_keyword::AffiliatedKeywordValue; pub use affiliated_keyword::AffiliatedKeywordValue;
pub use affiliated_keyword::AffiliatedKeywords; pub use affiliated_keyword::AffiliatedKeywords;
pub use affiliated_keyword::GetAffiliatedKeywords; pub use affiliated_keyword::GetAffiliatedKeywords;
pub(crate) use ast_node::AstNode; pub use ast_node::AstNode;
pub use document::Document; pub use document::Document;
pub use document::DocumentElement; pub use document::DocumentElement;
pub use document::Heading; pub use document::Heading;
@@ -23,7 +22,6 @@ pub use document::PriorityCookie;
pub use document::Section; pub use document::Section;
pub use document::TodoKeywordType; pub use document::TodoKeywordType;
pub use element::Element; pub use element::Element;
pub use get_standard_properties::GetStandardProperties;
pub use greater_element::CenterBlock; pub use greater_element::CenterBlock;
pub use greater_element::CheckboxType; pub use greater_element::CheckboxType;
pub use greater_element::Drawer; pub use greater_element::Drawer;
@@ -113,5 +111,5 @@ pub use object::WarningDelay;
pub use object::WarningDelayType; pub use object::WarningDelayType;
pub use object::Year; pub use object::Year;
pub use object::YearInner; pub use object::YearInner;
pub(crate) use source::SetSource; pub use standard_properties::PostBlank;
pub use standard_properties::StandardProperties; pub use standard_properties::StandardProperties;

View File

@@ -6,11 +6,10 @@ use super::util::coalesce_whitespace_if_line_break;
use super::util::remove_line_break; use super::util::remove_line_break;
use super::util::remove_whitespace_if_line_break; use super::util::remove_whitespace_if_line_break;
use super::util::to_lowercase; use super::util::to_lowercase;
use super::GetStandardProperties; use super::PostBlank;
use super::StandardProperties; use super::StandardProperties;
// TODO: Why did we make Object implement PartialEq again? Was it just for tests? #[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum Object<'s> { pub enum Object<'s> {
Bold(Bold<'s>), Bold(Bold<'s>),
Italic(Italic<'s>), Italic(Italic<'s>),
@@ -41,48 +40,58 @@ pub enum Object<'s> {
Timestamp(Timestamp<'s>), Timestamp(Timestamp<'s>),
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Bold<'s> { pub struct Bold<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Italic<'s> { pub struct Italic<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Underline<'s> { pub struct Underline<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct StrikeThrough<'s> { pub struct StrikeThrough<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Code<'s> { pub struct Code<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: &'s str, pub contents: &'s str,
pub post_blank: Option<&'s str>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Verbatim<'s> { pub struct Verbatim<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: &'s str, pub contents: &'s str,
pub post_blank: Option<&'s str>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct PlainText<'s> { pub struct PlainText<'s> {
pub source: &'s str, pub source: &'s str,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct RegularLink<'s> { pub struct RegularLink<'s> {
pub source: &'s str, pub source: &'s str,
pub link_type: LinkType<'s>, pub link_type: LinkType<'s>,
@@ -101,25 +110,27 @@ pub struct RegularLink<'s> {
/// This does not take into account the post-processing that you would get from the upstream emacs org-mode AST. Use `get_search_option` for an equivalent value. /// This does not take into account the post-processing that you would get from the upstream emacs org-mode AST. Use `get_search_option` for an equivalent value.
pub search_option: Option<Cow<'s, str>>, pub search_option: Option<Cow<'s, str>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
pub application: Option<Cow<'s, str>>, pub application: Option<Cow<'s, str>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct RadioTarget<'s> { pub struct RadioTarget<'s> {
pub source: &'s str, pub source: &'s str,
pub value: &'s str, pub value: &'s str,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct RadioLink<'s> { pub struct RadioLink<'s> {
pub source: &'s str, pub source: &'s str,
pub path: &'s str, pub path: &'s str,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct PlainLink<'s> { pub struct PlainLink<'s> {
pub source: &'s str, pub source: &'s str,
pub link_type: LinkType<'s>, pub link_type: LinkType<'s>,
@@ -129,7 +140,7 @@ pub struct PlainLink<'s> {
pub application: Option<&'s str>, pub application: Option<&'s str>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct AngleLink<'s> { pub struct AngleLink<'s> {
pub source: &'s str, pub source: &'s str,
pub link_type: LinkType<'s>, pub link_type: LinkType<'s>,
@@ -147,7 +158,7 @@ pub struct AngleLink<'s> {
pub application: Option<&'s str>, pub application: Option<&'s str>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct OrgMacro<'s> { pub struct OrgMacro<'s> {
pub source: &'s str, pub source: &'s str,
@@ -164,7 +175,7 @@ pub struct OrgMacro<'s> {
pub value: &'s str, pub value: &'s str,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Entity<'s> { pub struct Entity<'s> {
pub source: &'s str, pub source: &'s str,
pub name: &'s str, pub name: &'s str,
@@ -177,27 +188,29 @@ pub struct Entity<'s> {
pub use_brackets: bool, pub use_brackets: bool,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct LatexFragment<'s> { pub struct LatexFragment<'s> {
pub source: &'s str, pub source: &'s str,
pub value: &'s str, pub value: &'s str,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct ExportSnippet<'s> { pub struct ExportSnippet<'s> {
pub source: &'s str, pub source: &'s str,
pub backend: &'s str, pub backend: &'s str,
pub contents: Option<&'s str>, pub contents: Option<&'s str>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct FootnoteReference<'s> { pub struct FootnoteReference<'s> {
pub source: &'s str, pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub label: Option<&'s str>, pub label: Option<&'s str>,
pub definition: Vec<Object<'s>>, pub definition: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Citation<'s> { pub struct Citation<'s> {
pub source: &'s str, pub source: &'s str,
pub style: Option<&'s str>, pub style: Option<&'s str>,
@@ -206,7 +219,7 @@ pub struct Citation<'s> {
pub children: Vec<CitationReference<'s>>, pub children: Vec<CitationReference<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct CitationReference<'s> { pub struct CitationReference<'s> {
pub source: &'s str, pub source: &'s str,
pub key: &'s str, pub key: &'s str,
@@ -214,7 +227,7 @@ pub struct CitationReference<'s> {
pub suffix: Vec<Object<'s>>, pub suffix: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct InlineBabelCall<'s> { pub struct InlineBabelCall<'s> {
pub source: &'s str, pub source: &'s str,
pub value: &'s str, pub value: &'s str,
@@ -224,7 +237,7 @@ pub struct InlineBabelCall<'s> {
pub end_header: Option<&'s str>, pub end_header: Option<&'s str>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct InlineSourceBlock<'s> { pub struct InlineSourceBlock<'s> {
pub source: &'s str, pub source: &'s str,
pub language: &'s str, pub language: &'s str,
@@ -232,39 +245,43 @@ pub struct InlineSourceBlock<'s> {
pub value: &'s str, pub value: &'s str,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct LineBreak<'s> { pub struct LineBreak<'s> {
pub source: &'s str, pub source: &'s str,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Target<'s> { pub struct Target<'s> {
pub source: &'s str, pub source: &'s str,
pub value: &'s str, pub value: &'s str,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct StatisticsCookie<'s> { pub struct StatisticsCookie<'s> {
pub source: &'s str, pub source: &'s str,
pub value: &'s str, pub value: &'s str,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Subscript<'s> { pub struct Subscript<'s> {
pub source: &'s str, pub source: &'s str,
pub use_brackets: bool, pub use_brackets: bool,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub struct Superscript<'s> { pub struct Superscript<'s> {
pub source: &'s str, pub source: &'s str,
pub use_brackets: bool, pub use_brackets: bool,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>, pub children: Vec<Object<'s>>,
} }
// TODO: Perhaps there is an optimization of converting to unix time we can do to shrink this struct. (ref: clippy::large_enum_variant on Element) // TODO: Perhaps there is an optimization of converting to unix time we can do to shrink this struct. (ref: clippy::large_enum_variant on Element)
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Timestamp<'s> { pub struct Timestamp<'s> {
pub source: &'s str, pub source: &'s str,
pub timestamp_type: TimestampType, pub timestamp_type: TimestampType,
@@ -277,7 +294,7 @@ pub struct Timestamp<'s> {
pub warning_delay: Option<WarningDelay>, pub warning_delay: Option<WarningDelay>,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum TimestampType { pub enum TimestampType {
Diary, Diary,
Active, Active,
@@ -286,7 +303,7 @@ pub enum TimestampType {
InactiveRange, InactiveRange,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum TimestampRangeType { pub enum TimestampRangeType {
None, None,
DateRange, DateRange,
@@ -299,19 +316,19 @@ pub type DayOfMonthInner = u8;
pub type HourInner = u8; pub type HourInner = u8;
pub type MinuteInner = u8; pub type MinuteInner = u8;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Year(YearInner); pub struct Year(YearInner);
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Month(MonthInner); pub struct Month(MonthInner);
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct DayOfMonth(DayOfMonthInner); pub struct DayOfMonth(DayOfMonthInner);
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Hour(HourInner); pub struct Hour(HourInner);
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Minute(MinuteInner); pub struct Minute(MinuteInner);
impl Year { impl Year {
@@ -386,7 +403,7 @@ impl Minute {
} }
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Date<'s> { pub struct Date<'s> {
year: Year, year: Year,
month: Month, month: Month,
@@ -444,7 +461,7 @@ impl<'s> Date<'s> {
} }
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Time<'s> { pub struct Time<'s> {
hour: Hour, hour: Hour,
minute: Minute, minute: Minute,
@@ -478,20 +495,20 @@ impl<'s> Time<'s> {
} }
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum RepeaterType { pub enum RepeaterType {
Cumulative, Cumulative,
CatchUp, CatchUp,
Restart, Restart,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum WarningDelayType { pub enum WarningDelayType {
All, All,
First, First,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub enum TimeUnit { pub enum TimeUnit {
Hour, Hour,
Day, Day,
@@ -502,214 +519,436 @@ pub enum TimeUnit {
pub type RepeaterWarningDelayValueType = u16; pub type RepeaterWarningDelayValueType = u16;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct Repeater { pub struct Repeater {
pub repeater_type: RepeaterType, pub repeater_type: RepeaterType,
pub value: RepeaterWarningDelayValueType, pub value: RepeaterWarningDelayValueType,
pub unit: TimeUnit, pub unit: TimeUnit,
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, Clone)]
pub struct WarningDelay { pub struct WarningDelay {
pub warning_delay_type: WarningDelayType, pub warning_delay_type: WarningDelayType,
pub value: RepeaterWarningDelayValueType, pub value: RepeaterWarningDelayValueType,
pub unit: TimeUnit, pub unit: TimeUnit,
} }
impl<'s> GetStandardProperties<'s> for Object<'s> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self {
Object::Bold(inner) => inner,
Object::Italic(inner) => inner,
Object::Underline(inner) => inner,
Object::StrikeThrough(inner) => inner,
Object::Code(inner) => inner,
Object::Verbatim(inner) => inner,
Object::PlainText(inner) => inner,
Object::RegularLink(inner) => inner,
Object::RadioLink(inner) => inner,
Object::RadioTarget(inner) => inner,
Object::PlainLink(inner) => inner,
Object::AngleLink(inner) => inner,
Object::OrgMacro(inner) => inner,
Object::Entity(inner) => inner,
Object::LatexFragment(inner) => inner,
Object::ExportSnippet(inner) => inner,
Object::FootnoteReference(inner) => inner,
Object::Citation(inner) => inner,
Object::CitationReference(inner) => inner,
Object::InlineBabelCall(inner) => inner,
Object::InlineSourceBlock(inner) => inner,
Object::LineBreak(inner) => inner,
Object::Target(inner) => inner,
Object::StatisticsCookie(inner) => inner,
Object::Subscript(inner) => inner,
Object::Superscript(inner) => inner,
Object::Timestamp(inner) => inner,
}
}
}
impl<'s> StandardProperties<'s> for Bold<'s> { impl<'s> StandardProperties<'s> for Bold<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Italic<'s> { impl<'s> StandardProperties<'s> for Italic<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Underline<'s> { impl<'s> StandardProperties<'s> for Underline<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for StrikeThrough<'s> { impl<'s> StandardProperties<'s> for StrikeThrough<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Code<'s> { impl<'s> StandardProperties<'s> for Code<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Verbatim<'s> { impl<'s> StandardProperties<'s> for Verbatim<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for RegularLink<'s> { impl<'s> StandardProperties<'s> for RegularLink<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for RadioLink<'s> { impl<'s> StandardProperties<'s> for RadioLink<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for RadioTarget<'s> { impl<'s> StandardProperties<'s> for RadioTarget<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for PlainLink<'s> { impl<'s> StandardProperties<'s> for PlainLink<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for AngleLink<'s> { impl<'s> StandardProperties<'s> for AngleLink<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for OrgMacro<'s> { impl<'s> StandardProperties<'s> for OrgMacro<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for Entity<'s> { impl<'s> StandardProperties<'s> for Entity<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for LatexFragment<'s> { impl<'s> StandardProperties<'s> for LatexFragment<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for ExportSnippet<'s> { impl<'s> StandardProperties<'s> for ExportSnippet<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for FootnoteReference<'s> { impl<'s> StandardProperties<'s> for FootnoteReference<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Citation<'s> { impl<'s> StandardProperties<'s> for Citation<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for CitationReference<'s> { impl<'s> StandardProperties<'s> for CitationReference<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for InlineBabelCall<'s> { impl<'s> StandardProperties<'s> for InlineBabelCall<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for InlineSourceBlock<'s> { impl<'s> StandardProperties<'s> for InlineSourceBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for LineBreak<'s> { impl<'s> StandardProperties<'s> for LineBreak<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
fn get_post_blank(&self) -> PostBlank {
0
}
} }
impl<'s> StandardProperties<'s> for Target<'s> { impl<'s> StandardProperties<'s> for Target<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for StatisticsCookie<'s> { impl<'s> StandardProperties<'s> for StatisticsCookie<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for Subscript<'s> { impl<'s> StandardProperties<'s> for Subscript<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Superscript<'s> { impl<'s> StandardProperties<'s> for Superscript<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
} }
impl<'s> StandardProperties<'s> for Timestamp<'s> { impl<'s> StandardProperties<'s> for Timestamp<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> StandardProperties<'s> for PlainText<'s> { impl<'s> StandardProperties<'s> for PlainText<'s> {
fn get_source<'b>(&'b self) -> &'s str { fn get_source<'b>(&'b self) -> &'s str {
self.source self.source
} }
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
}
fn get_post_blank(&self) -> PostBlank {
todo!()
}
} }
impl<'s> Timestamp<'s> { impl<'s> Timestamp<'s> {
@@ -718,7 +957,7 @@ impl<'s> Timestamp<'s> {
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum LinkType<'s> { pub enum LinkType<'s> {
File, File,
Protocol(Cow<'s, str>), Protocol(Cow<'s, str>),
@@ -787,7 +1026,7 @@ impl<'s> OrgMacro<'s> {
} }
} }
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum FootnoteReferenceType { pub enum FootnoteReferenceType {
Standard, Standard,
Inline, Inline,
@@ -802,3 +1041,101 @@ impl<'s> FootnoteReference<'s> {
} }
} }
} }
impl<'s> StandardProperties<'s> for Object<'s> {
fn get_source<'b>(&'b self) -> &'s str {
match self {
Object::Bold(inner) => inner.get_source(),
Object::Italic(inner) => inner.get_source(),
Object::Underline(inner) => inner.get_source(),
Object::StrikeThrough(inner) => inner.get_source(),
Object::Code(inner) => inner.get_source(),
Object::Verbatim(inner) => inner.get_source(),
Object::PlainText(inner) => inner.get_source(),
Object::RegularLink(inner) => inner.get_source(),
Object::RadioLink(inner) => inner.get_source(),
Object::RadioTarget(inner) => inner.get_source(),
Object::PlainLink(inner) => inner.get_source(),
Object::AngleLink(inner) => inner.get_source(),
Object::OrgMacro(inner) => inner.get_source(),
Object::Entity(inner) => inner.get_source(),
Object::LatexFragment(inner) => inner.get_source(),
Object::ExportSnippet(inner) => inner.get_source(),
Object::FootnoteReference(inner) => inner.get_source(),
Object::Citation(inner) => inner.get_source(),
Object::CitationReference(inner) => inner.get_source(),
Object::InlineBabelCall(inner) => inner.get_source(),
Object::InlineSourceBlock(inner) => inner.get_source(),
Object::LineBreak(inner) => inner.get_source(),
Object::Target(inner) => inner.get_source(),
Object::StatisticsCookie(inner) => inner.get_source(),
Object::Subscript(inner) => inner.get_source(),
Object::Superscript(inner) => inner.get_source(),
Object::Timestamp(inner) => inner.get_source(),
}
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
match self {
Object::Bold(inner) => inner.get_contents(),
Object::Italic(inner) => inner.get_contents(),
Object::Underline(inner) => inner.get_contents(),
Object::StrikeThrough(inner) => inner.get_contents(),
Object::Code(inner) => inner.get_contents(),
Object::Verbatim(inner) => inner.get_contents(),
Object::PlainText(inner) => inner.get_contents(),
Object::RegularLink(inner) => inner.get_contents(),
Object::RadioLink(inner) => inner.get_contents(),
Object::RadioTarget(inner) => inner.get_contents(),
Object::PlainLink(inner) => inner.get_contents(),
Object::AngleLink(inner) => inner.get_contents(),
Object::OrgMacro(inner) => inner.get_contents(),
Object::Entity(inner) => inner.get_contents(),
Object::LatexFragment(inner) => inner.get_contents(),
Object::ExportSnippet(inner) => inner.get_contents(),
Object::FootnoteReference(inner) => inner.get_contents(),
Object::Citation(inner) => inner.get_contents(),
Object::CitationReference(inner) => inner.get_contents(),
Object::InlineBabelCall(inner) => inner.get_contents(),
Object::InlineSourceBlock(inner) => inner.get_contents(),
Object::LineBreak(inner) => inner.get_contents(),
Object::Target(inner) => inner.get_contents(),
Object::StatisticsCookie(inner) => inner.get_contents(),
Object::Subscript(inner) => inner.get_contents(),
Object::Superscript(inner) => inner.get_contents(),
Object::Timestamp(inner) => inner.get_contents(),
}
}
fn get_post_blank(&self) -> PostBlank {
match self {
Object::Bold(inner) => inner.get_post_blank(),
Object::Italic(inner) => inner.get_post_blank(),
Object::Underline(inner) => inner.get_post_blank(),
Object::StrikeThrough(inner) => inner.get_post_blank(),
Object::Code(inner) => inner.get_post_blank(),
Object::Verbatim(inner) => inner.get_post_blank(),
Object::PlainText(inner) => inner.get_post_blank(),
Object::RegularLink(inner) => inner.get_post_blank(),
Object::RadioLink(inner) => inner.get_post_blank(),
Object::RadioTarget(inner) => inner.get_post_blank(),
Object::PlainLink(inner) => inner.get_post_blank(),
Object::AngleLink(inner) => inner.get_post_blank(),
Object::OrgMacro(inner) => inner.get_post_blank(),
Object::Entity(inner) => inner.get_post_blank(),
Object::LatexFragment(inner) => inner.get_post_blank(),
Object::ExportSnippet(inner) => inner.get_post_blank(),
Object::FootnoteReference(inner) => inner.get_post_blank(),
Object::Citation(inner) => inner.get_post_blank(),
Object::CitationReference(inner) => inner.get_post_blank(),
Object::InlineBabelCall(inner) => inner.get_post_blank(),
Object::InlineSourceBlock(inner) => inner.get_post_blank(),
Object::LineBreak(inner) => inner.get_post_blank(),
Object::Target(inner) => inner.get_post_blank(),
Object::StatisticsCookie(inner) => inner.get_post_blank(),
Object::Subscript(inner) => inner.get_post_blank(),
Object::Superscript(inner) => inner.get_post_blank(),
Object::Timestamp(inner) => inner.get_post_blank(),
}
}
}

View File

@@ -0,0 +1,56 @@
pub(crate) trait RemoveTrailing: Iterator + Sized {
fn remove_trailing<R: Into<usize>>(self, amount_to_remove: R) -> RemoveTrailingIter<Self>;
}
impl<I> RemoveTrailing for I
where
I: Iterator,
{
fn remove_trailing<R: Into<usize>>(self, amount_to_remove: R) -> RemoveTrailingIter<Self> {
RemoveTrailingIter {
inner: self,
buffer: Vec::new(),
next_to_pop: 0,
amount_to_remove: amount_to_remove.into(),
}
}
}
pub(crate) struct RemoveTrailingIter<I: Iterator> {
inner: I,
buffer: Vec<I::Item>,
next_to_pop: usize,
amount_to_remove: usize,
}
impl<I: Iterator> Iterator for RemoveTrailingIter<I> {
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
if self.buffer.len() < self.amount_to_remove {
self.buffer.reserve_exact(self.amount_to_remove);
}
while self.buffer.len() < self.amount_to_remove {
if let Some(elem) = self.inner.next() {
self.buffer.push(elem);
} else {
// The inner was smaller than amount_to_remove, so never return anything.
return None;
}
}
let new_value = self.inner.next();
if self.amount_to_remove == 0 {
return new_value;
}
if let Some(new_value) = new_value {
let ret = std::mem::replace(&mut self.buffer[self.next_to_pop], new_value);
self.next_to_pop = (self.next_to_pop + 1) % self.amount_to_remove;
Some(ret)
} else {
// We have exactly the amount in the buffer than we wanted to cut off, so stop returning values.
None
}
}
}

View File

@@ -1,3 +0,0 @@
pub(crate) trait SetSource<'s> {
fn set_source(&mut self, source: &'s str);
}

View File

@@ -5,10 +5,15 @@ pub trait StandardProperties<'s> {
/// This corresponds to :begin to :end in upstream org-mode's standard properties. /// This corresponds to :begin to :end in upstream org-mode's standard properties.
fn get_source<'b>(&'b self) -> &'s str; fn get_source<'b>(&'b self) -> &'s str;
// Get the slice of the AST node's contents. /// Get the slice of the AST node's contents.
// ///
// This corresponds to :contents-begin to :contents-end /// This corresponds to :contents-begin to :contents-end
// fn get_contents(&'s self) -> &'s str; fn get_contents<'b>(&'b self) -> Option<&'s str>;
/// Get the ast node's post-blank.
///
/// For objects this is a count of the characters of whitespace after the object. For elements this is a count of the line breaks following an element.
fn get_post_blank(&self) -> PostBlank;
} }
// TODO: Write some debugging code to alert when any of the unknown fields below are non-nil in our test data so we can see what these fields represent. // TODO: Write some debugging code to alert when any of the unknown fields below are non-nil in our test data so we can see what these fields represent.
@@ -56,3 +61,5 @@ pub trait StandardProperties<'s> {
// X :parent - Some weird numeric reference to the containing object. Since we output a tree structure, I do not see any value in including this, especially considering the back-references would be a nightmare in rust. // X :parent - Some weird numeric reference to the containing object. Since we output a tree structure, I do not see any value in including this, especially considering the back-references would be a nightmare in rust.
// Special case: Plain text. Plain text counts :begin and :end from the start of the text (so :begin is always 0 AFAICT) and instead of including the full set of standard properties, it only includes :begin, :end, and :parent. // Special case: Plain text. Plain text counts :begin and :end from the start of the text (so :begin is always 0 AFAICT) and instead of including the full set of standard properties, it only includes :begin, :end, and :parent.
pub type PostBlank = u8;