98 Commits

Author SHA1 Message Date
Tom Alexander
59222c58b1 Publish version 0.1.13.
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-12-15 21:10:21 -05:00
Tom Alexander
4d95a7f244 Merge branch 'post_blank'
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-12-15 20:11:20 -05:00
Tom Alexander
5a8159eed7 Fix clippy.
All checks were successful
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-12-15 19:57:35 -05:00
Tom Alexander
e24fcb9ded Add dummy values for new fields for plaintext. 2023-12-15 19:54:03 -05:00
Tom Alexander
4b94dc60d2 Fix handling of documents containing only whitespace. 2023-12-15 19:49:12 -05:00
Tom Alexander
2046603d01 Fix handling post blank for org documents. 2023-12-15 19:42:43 -05:00
Tom Alexander
30412361e1 Fix handling fixed width area post-blank inside a list. 2023-12-15 19:37:33 -05:00
Tom Alexander
e846c85188 Fix handling fixed width areas with empty lines in the middle. 2023-12-15 19:17:16 -05:00
Tom Alexander
99b74095e6 Fix heading post-blank. 2023-12-15 19:10:14 -05:00
Tom Alexander
6b802d36bf Implement the new fields for target. 2023-12-15 18:57:19 -05:00
Tom Alexander
33ca43ca40 Remove the old Paragraph::of_text function.
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 succeeded
2023-12-15 18:06:48 -05:00
Tom Alexander
f5280a3090 Implement the new fields for bullshitium broken dynamic block. 2023-12-15 18:04:42 -05:00
Tom Alexander
c28d8ccea4 Fix post-blank for headlines containing only whitespace. 2023-12-15 17:59:47 -05:00
Tom Alexander
9690545901 Fix setting contents for broken end bullshitium when there is a paragraph present.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 17:40:08 -05:00
Tom Alexander
eba4fb94cf Implement the new fields for dynamic block. 2023-12-15 17:26:01 -05:00
Tom Alexander
565978225a Implement the new fields for table.
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-15 17:16:49 -05:00
Tom Alexander
cce9ca87fa Fix handling of leading blank lines in greater blocks.
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-15 16:55:47 -05:00
Tom Alexander
683c523ece Implement the new fields for greater block. 2023-12-15 16:15:22 -05:00
Tom Alexander
7a4dc20dc9 Implement the new fields for plain list.
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-15 15:59:00 -05:00
Tom Alexander
022dda06eb Implement the new fields for plain list item. 2023-12-15 15:52:53 -05:00
Tom Alexander
7b88a2d248 Implement the new fields for broken end bullshitium. 2023-12-15 15:40:17 -05:00
Tom Alexander
fce5b92091 Remove leading blank lines from document contents. 2023-12-15 15:30:46 -05:00
Tom Alexander
45a506334c Remove leading blank lines from heading contents. 2023-12-15 15:20:31 -05:00
Tom Alexander
e47901a67f Implement the new fields for node property.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 15:05:03 -05:00
Tom Alexander
7430daa768 Fix handling of property drawers containing only whitespace. 2023-12-15 15:05:03 -05:00
Tom Alexander
6ce25c8a3b Update property drawer empty test to include a variety of whitespace. 2023-12-15 14:25:01 -05:00
Tom Alexander
7b8fa1eb4a Fix get_contents for sections. 2023-12-15 13:21:58 -05:00
Tom Alexander
ffa5349f25 Fix get_contents for headlines. 2023-12-15 13:14:49 -05:00
Tom Alexander
bb472b63cc Implement the new fields for property drawer. 2023-12-15 13:03:42 -05:00
Tom Alexander
57f566a7a1 Implement the new fields for planning. 2023-12-15 12:55:05 -05:00
Tom Alexander
2181993246 Implement the new fields for horizontal rule. 2023-12-15 12:50:01 -05:00
Tom Alexander
60d1ecfa75 Fix fixed width area to not consume trailing line break so it can be part of the post-blank.
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-15 12:45:51 -05:00
Tom Alexander
3962db12a8 Implement the new fields for fixed width area. 2023-12-15 12:29:46 -05:00
Tom Alexander
f192507cd9 Implement the new fields for diary sexp.
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-15 12:10:34 -05:00
Tom Alexander
252be3e001 Fix post blank for timestamp date ranges. 2023-12-15 11:38:52 -05:00
Tom Alexander
28f12a04f7 Implement the new fields for drawer.
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-15 11:22:27 -05:00
Tom Alexander
d6232dc49c Implement the new fields for clock. 2023-12-15 10:49:04 -05:00
Tom Alexander
68a220aa1c Implement the new fields for babel call.
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-15 10:45:16 -05:00
Tom Alexander
2e7db0f8bd Implement the new fields for lesser block.
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 15:59:56 -05:00
Tom Alexander
175ff1e6c4 Implement the new fields for LaTeX environment.
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 14:49:18 -05:00
Tom Alexander
0b42139393 Implement the new fields for inline babel call and inline source block. 2023-12-11 14:47:22 -05:00
Tom Alexander
67a9103b07 Implement the new fields for export snippet. 2023-12-11 14:41:49 -05:00
Tom Alexander
f141a4e186 Implement the new fields for citation.
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 14:38:14 -05:00
Tom Alexander
aba29df34c Implement the new fields for org macro. 2023-12-11 14:22:56 -05:00
Tom Alexander
87ce7d7432 Implement the new fields for timestamp. 2023-12-11 14:18:04 -05:00
Tom Alexander
68dccd54b1 Implement the new fields for radio link. 2023-12-11 14:10:27 -05:00
Tom Alexander
4753f4c7c6 Implement the new fields for plain 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 13:08:53 -05:00
Tom Alexander
13c62bf29f Implement the new fields for angle 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 13:00:43 -05:00
Tom Alexander
670209e9fc Fix post blank for comment. 2023-12-11 12:58:05 -05:00
Tom Alexander
4af0d3141f Implement the new fields for statistics cookie. 2023-12-11 12:51:07 -05:00
Tom Alexander
ab281de3c6 Implement the new fields for latex fragment.
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:46:20 -05:00
Tom Alexander
d556d28f49 Implement the new fields for entity. 2023-12-11 12:44:42 -05:00
Tom Alexander
9cfb2fa052 Implement the new fields for keywords.
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:38:13 -05:00
Tom Alexander
30c03b5529 Implement the new fields for radio target. 2023-12-11 12:27:35 -05:00
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
76 changed files with 2457 additions and 811 deletions

View File

@@ -2,7 +2,7 @@
[package]
name = "organic"
version = "0.1.12"
version = "0.1.13"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021"

View File

@@ -45,10 +45,6 @@ dockerclippy:
clippy:
> cargo clippy --no-deps --all-targets --all-features -- -D warnings
.PHONY: clippyfix
clippyfix:
> cargo clippy --fix --lib -p organic --all-features
.PHONY: test
test:
> 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
@echo "REMOTE_REPO not defined, not removing from remote repo."
endif
docker volume rm cargo-cache
docker volume rm rust-cache cargo-cache
# NOTE: This target will write to folders underneath the git-root
.PHONY: run
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
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
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
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/doomemacs /foreign_documents/doomemacs
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-emacs /root/emacs /foreign_documents/emacs
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

View File

@@ -0,0 +1,4 @@

View File

@@ -0,0 +1,5 @@
* foo

View File

@@ -1,3 +1,32 @@
* Empty
:PROPERTIES:
:END:
* Single new line
:PROPERTIES:
:END:
* Single line with spaces
:PROPERTIES:
:END:
* Many lines, first line without spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces, later line with spaces
:PROPERTIES:
:END:

View File

@@ -5,3 +5,5 @@
#+call: dolar cat(dog)
#+call: (bat)
#+call:

View File

@@ -0,0 +1,3 @@
: foo
:
: bar

View File

@@ -0,0 +1,6 @@
1. foo
#+begin_src text
#+end_src
2. baz

View File

@@ -1,3 +1,3 @@
foo <<bar>> baz
<<FOO>> bar
lorem << ipsum >> dolar
[[FOO][baz]]

View File

@@ -0,0 +1,5 @@
* foo
** bar
* baz

View File

@@ -53,6 +53,9 @@ async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
let layer = layer.chain(compare_group("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 mut any_failed = false;

View File

@@ -1,3 +1,5 @@
use std::borrow::Borrow;
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::fmt::Debug;
use std::str::FromStr;
@@ -262,11 +264,11 @@ pub(crate) fn compare_property_set_of_quoted_string<
.iter()
.map(|e| e.as_atom())
.collect::<Result<Vec<_>, _>>()?;
let value: Vec<String> = value
let value: Vec<Cow<'_, str>> = value
.into_iter()
.map(unquote)
.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();
if !mismatched.is_empty() {
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());
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 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);
@@ -554,18 +571,31 @@ where
let mut kw_e = kw_e.iter();
// First element is a list representing the mandatory value.
if let Some(val_e) = kw_e.next() {
let el = val_e.as_list()?;
if el.len() != kw_r.1.len() {
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));
}
for (e, r) in el.iter().zip(kw_r.1.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
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()?;
if el.len() != kw_r.1.len() {
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));
}
for (e, r) in el.iter().zip(kw_r.1.iter()) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
}
};
} else {
let this_status = DiffStatus::Bad;
let message = Some(format!(
@@ -653,7 +683,7 @@ pub(crate) fn compare_property_number_lines<
(Some(number_lines), Some(rust_number_lines)) => {
let token_list = number_lines.as_list()?;
let number_type = token_list
.get(0)
.first()
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.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::FootnoteReference;
use crate::types::FootnoteReferenceType;
use crate::types::GetStandardProperties;
use crate::types::Heading;
use crate::types::HorizontalRule;
use crate::types::Hour;
@@ -128,7 +127,7 @@ pub struct DiffResult<'b, 's> {
emacs_token: &'b Token<'s>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub(crate) enum DiffStatus {
Good,
Bad,
@@ -164,7 +163,7 @@ impl<'b, 's> DiffEntry<'b, 's> {
fn is_immediately_bad(&self) -> bool {
match self {
DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad,
DiffEntry::DiffResult(diff) => matches!(diff.status, DiffStatus::Bad),
DiffEntry::DiffLayer(_) => false,
}
}
@@ -413,7 +412,7 @@ pub(crate) fn compare_ast_node<'b, 's>(
name: rust.get_elisp_fact().get_elisp_name(),
message: Some(e.to_string()),
children: Vec::new(),
rust_source: rust.get_standard_properties().get_source(),
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into()
@@ -1576,7 +1575,7 @@ fn compare_example_block<'b, 's>(
[],
(
EmacsField::Required(":value"),
|r| Some(r.contents.as_str()),
|r| Some(r.get_value()),
compare_property_quoted_string
),
(
@@ -1654,7 +1653,7 @@ fn compare_export_block<'b, 's>(
),
(
EmacsField::Required(":value"),
|r| Some(r.contents.as_str()),
|r| Some(r.get_value()),
compare_property_quoted_string
)
) {
@@ -1702,7 +1701,7 @@ fn compare_src_block<'b, 's>(
),
(
EmacsField::Required(":value"),
|r| Some(r.contents.as_str()),
|r| Some(r.get_value()),
compare_property_quoted_string
),
(
@@ -2153,7 +2152,7 @@ fn compare_plain_text<'b, 's>(
let text = emacs.as_text()?;
let start_ind: usize = text
.properties
.get(0)
.first()
.expect("Should have start index.")
.as_atom()?
.parse()?;

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::HashMap;
use nom::branch::alt;
@@ -36,12 +37,6 @@ pub struct TextWithProperties<'s> {
pub(crate) properties: Vec<Token<'s>>,
}
enum ParseState {
Normal,
Escape,
Octal(Vec<u8>),
}
impl<'s> Token<'s> {
pub(crate) fn as_vector<'p>(
&'p self,
@@ -117,8 +112,27 @@ fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
&input[..offset]
}
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut out: Vec<u8> = Vec::with_capacity(text.len());
#[derive(Debug)]
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('"') {
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());
}
let interior_text = &text[1..(text.len() - 1)];
let mut state = ParseState::Normal;
for current_char in interior_text.bytes() {
let mut state = UnquoteState::Normal;
for (offset, current_char) in interior_text.bytes().enumerate() {
// Check to see if octal finished
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 decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
out.push(decoded_byte);
ParseState::Normal
UnquoteState::HasEscape { out }
}
(state, _) => state,
};
state = match (state, current_char) {
(ParseState::Normal, b'\\') => ParseState::Escape,
(ParseState::Normal, _) => {
(UnquoteState::Normal, b'\\') => UnquoteState::Escape,
(UnquoteState::Normal, _) => UnquoteState::Normal,
(UnquoteState::HasEscape { out }, b'\\') => UnquoteState::HasEscapeEscape { out },
(UnquoteState::HasEscape { mut out }, _) => {
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');
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'\\');
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'"');
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);
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);
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"))]

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::str::FromStr;
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::AstNode;
use crate::types::GetAffiliatedKeywords;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
/// 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.
///
/// These offsets are zero-based unlike the elisp ones.
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>(
original_document: &'s str,
rust_ast_node: &'b S,
) -> (usize, usize) {
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();
fn get_rust_byte_offsets(original_document: &str, subset: &str) -> (usize, usize) {
debug_assert!(is_slice_of(original_document, subset));
let offset = subset.as_ptr() as usize - original_document.as_ptr() as usize;
let end = offset + subset.len();
(offset, end)
}
pub(crate) fn compare_standard_properties<
'b,
's,
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized,
S: StandardProperties<'s> + GetElispFact<'s> + ?Sized,
>(
original_document: &'s str,
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
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(())
}
pub(crate) fn assert_name<S: AsRef<str>>(
fn assert_name<S: AsRef<str>>(
emacs: &Token<'_>,
name: S,
) -> Result<(), Box<dyn std::error::Error>> {
@@ -77,24 +74,72 @@ 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.
///
/// 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,
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
let (begin, end) = (
standard_properties
.begin
.ok_or("Token should have a begin.")?,
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_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 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 begin/end
{
let (begin, end) = (
standard_properties
.begin
.ok_or("Token should have a begin.")?,
standard_properties.end.ok_or("Token should have an end.")?,
);
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_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 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 {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(())
@@ -206,10 +251,10 @@ pub(crate) fn get_property_unquoted_atom<'s>(
/// Get a named property containing an quoted string from the emacs token.
///
/// Returns None if key is not found.
pub(crate) fn get_property_quoted_string(
emacs: &Token<'_>,
pub(crate) fn get_property_quoted_string<'s>(
emacs: &Token<'s>,
key: &str,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
) -> Result<Option<Cow<'s, str>>, Box<dyn std::error::Error>> {
get_property(emacs, key)?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
@@ -240,7 +285,7 @@ where
pub(crate) fn compare_children<'b, 's, 'x, RC>(
source: &'s str,
emacs: &'b Token<'s>,
rust_children: &'x Vec<RC>,
rust_children: &'x [RC],
child_status: &mut Vec<DiffEntry<'b, 's>>,
this_status: &mut DiffStatus,
message: &mut Option<String>,

View File

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

View File

@@ -27,7 +27,7 @@ pub(crate) fn record_event(event_type: EventType, input: OrgSource<'_>) {
pub fn report(original_document: &str) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let mut results: Vec<_> = db.iter().map(|(k, v)| (k, v)).collect();
let mut results: Vec<_> = db.iter().collect();
results.sort_by_key(|(_k, v)| *v);
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));

View File

@@ -90,12 +90,11 @@ impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
}
}
impl<'r, 's> IntoIterator for AstNode<'r, 's> {
type Item = AstNode<'r, 's>;
type IntoIter = AllAstNodeIter<'r, 's>;
fn into_iter(self) -> Self::IntoIter {
impl<'r, 's> AstNode<'r, 's> {
/// Iterate all AST nodes.
///
/// 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> {
AllAstNodeIter {
root: Some(self),
queue: VecDeque::new(),

View File

@@ -47,7 +47,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
parser_with_context!(parse_angle_link)(context),
))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -59,6 +59,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
raw_link: raw_link.into(),
search_option: parsed_link.search_option,
application: parsed_link.application,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -4,7 +4,6 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
@@ -43,32 +42,10 @@ where
start_of_line(remaining)?;
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
BabelCall {
source: Into::<&str>::into(source),
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(line_break.take(0)),
call: None,
inside_header: None,
arguments: None,
end_header: None,
},
));
}
let (remaining, _ws) = space0(remaining)?;
let (remaining, (value, babel_call_value)) = consumed(babel_call_value)(remaining)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, babel_call_value) = babel_call_value(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -80,17 +57,22 @@ where
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(value).trim_end(),
value: Into::<&str>::into(babel_call_value.value),
call: babel_call_value.call.map(Into::<&str>::into),
inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
arguments: babel_call_value.arguments.map(Into::<&str>::into),
end_header: babel_call_value.end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[derive(Debug)]
struct BabelCallValue<'s> {
/// The entire string to the right of "#+call: " without the trailing line break.
value: OrgSource<'s>,
/// The function name which may contain a line break if there are no headers/arguments.
call: Option<OrgSource<'s>>,
inside_header: Option<OrgSource<'s>>,
arguments: Option<OrgSource<'s>>,
@@ -99,13 +81,45 @@ struct BabelCallValue<'s> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, call) = opt(babel_call_call)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
alt((
babel_call_value_without_headers,
babel_call_value_with_headers,
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value_without_headers<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, value) = babel_call_call_with_headers(input)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let call = get_consumed(input, remaining);
Ok((
remaining,
BabelCallValue {
value,
call: Some(call),
inside_header: None,
arguments: None,
end_header: None,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value_with_headers<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, call) = opt(babel_call_call_with_headers)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
let value = get_consumed(input, remaining);
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
Ok((
remaining,
BabelCallValue {
value,
call,
inside_header,
arguments: arguments.flatten(),
@@ -115,14 +129,15 @@ fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallVal
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
fn babel_call_call_with_headers<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// When babel call contains no arguments or headers (for example: "#+call: lorem ipsum\n") then the trailing line break is part of the call. Otherwise, it is not.
verify(
recognize(many_till(
anychar,
alt((
peek(recognize(one_of("[("))),
peek(alt((
recognize(one_of("[(")),
recognize(tuple((space0, org_line_ending))),
)),
))),
)),
|s| s.len() > 0,
)(input)
@@ -225,26 +240,10 @@ fn impl_balanced_bracket<
let contents_end = 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))
} else {
None
};
Ok((remaining, contents))
}
#[cfg(test)]
mod tests {
use nom::combinator::opt;
use super::*;
#[test]
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("()");
let (remaining, call) = opt(babel_call_call)(input)?;
assert_eq!(Into::<&str>::into(remaining), "()");
assert_eq!(call, None);
Ok(())
}
}

View File

@@ -15,10 +15,8 @@ use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::macros::element;
use crate::types::AffiliatedKeywords;
use crate::types::Object;
use crate::types::Paragraph;
use crate::types::PlainText;
#[cfg_attr(
feature = "tracing",
@@ -65,6 +63,7 @@ pub(crate) fn broken_end<'b, 'g, 'r, 's>(
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
@@ -75,18 +74,19 @@ pub(crate) fn broken_end<'b, 'g, 'r, 's>(
};
Ok((remaining, paragraph))
} else {
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph {
source: input.get_until(remaining).into(),
affiliated_keywords: AffiliatedKeywords::default(),
children: vec![Object::PlainText(PlainText {
source: input.get_until(lead_in_remaining).into(),
})],
},
Paragraph::of_text(
input.get_until(remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}
}
@@ -124,6 +124,7 @@ pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
@@ -134,18 +135,19 @@ pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
};
Ok((remaining, paragraph))
} else {
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph {
source: input.get_until(remaining).into(),
affiliated_keywords: AffiliatedKeywords::default(),
children: vec![Object::PlainText(PlainText {
source: input.get_until(lead_in_remaining).into(),
})],
},
Paragraph::of_text(
input.get_until(remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}
}

View File

@@ -46,16 +46,22 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
let (remaining, prefix) =
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let contents_begin = remaining;
let (remaining, references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let contents_end = {
let (rem, _) = opt(tag(";"))(remaining)?;
rem
};
let (remaining, suffix) = must_balance_bracket(opt(map(
tuple((tag(";"), parser_with_context!(global_suffix)(context))),
|(_, suffix)| suffix,
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let contents = contents_begin.get_until(contents_end);
Ok((
remaining,
Citation {
@@ -64,6 +70,8 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
prefix: prefix.unwrap_or(Vec::new()),
suffix: suffix.unwrap_or(Vec::new()),
children: references,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -210,12 +218,11 @@ mod tests {
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::element_parser::element;
use crate::types::CitationReference;
use crate::types::Element;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
#[test]
fn citation_simple() {
fn citation_simple() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("[cite:@foo]");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
@@ -227,28 +234,33 @@ mod tests {
_ => panic!("Should be a paragraph!"),
};
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"[cite:@foo]"
);
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
assert_eq!(first_paragraph.children.len(), 1);
assert_eq!(
first_paragraph
.children
.get(0)
.expect("Len already asserted to be 1"),
&Object::Citation(Citation {
source: "[cite:@foo]",
style: None,
prefix: vec![],
suffix: vec![],
children: vec![CitationReference {
source: "@foo",
key: "foo",
prefix: vec![],
suffix: vec![]
}]
})
);
match first_paragraph
.children
.first()
.expect("Len already asserted to be 1.")
{
Object::Citation(inner) => {
assert_eq!(inner.get_source(), "[cite:@foo]");
assert_eq!(inner.children.len(), 1);
assert!(inner.prefix.is_empty());
assert!(inner.suffix.is_empty());
assert!(inner.style.is_none());
let citation_reference = inner
.children
.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

@@ -40,7 +40,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?;
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -54,6 +54,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
} else {
ClockStatus::Running
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -81,7 +82,7 @@ fn clock_timestamp<'b, 'g, 'r, 's>(
|(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)),
),
map(
parser_with_context!(inactive_timestamp)(context),
parser_with_context!(inactive_timestamp(true))(context),
|timestamp| (timestamp, None),
),
))(input)

View File

@@ -46,7 +46,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
@@ -67,6 +67,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
Comment {
source: source.into(),
value,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -31,7 +31,7 @@ where
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
let (remaining, _eol) = org_line_ending(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -43,6 +43,7 @@ where
affiliated_keywords,
),
value: Into::<&str>::into(value),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use nom::combinator::all_consuming;
use nom::combinator::opt;
use nom::multi::many0;
use nom::InputTake;
use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings;
@@ -143,7 +144,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.
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
.into_iter()
.iter_all_ast_nodes()
.filter_map(|ast_node| {
if let AstNode::RadioTarget(ast_node) = ast_node {
Some(ast_node)
@@ -181,8 +182,10 @@ fn _document<'b, 'g, 'r, 's>(
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading(0))(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?;
let contents_begin = remaining;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?;
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -192,6 +195,11 @@ fn _document<'b, 'g, 'r, 's>(
path: None,
zeroth_section,
children,
contents: if contents.len() > 0 {
Into::<&str>::into(contents)
} else {
Into::<&str>::into(remaining.take(0))
},
},
))
}

View File

@@ -4,6 +4,7 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
@@ -12,7 +13,9 @@ use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -21,7 +24,6 @@ use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -30,8 +32,6 @@ use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::Drawer;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(
feature = "tracing",
@@ -71,30 +71,12 @@ where
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let (remaining, (contents, children)) =
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, children) = match tuple((
not(exit_matcher),
blank_line,
many_till(blank_line, exit_matcher),
))(remaining)
{
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);
element.set_source(source.into());
(remain, vec![element])
}
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, _end) = drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -108,10 +90,34 @@ where
),
drawer_name: drawer_name.into(),
children,
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Element<'s>>> {
let element_matcher = parser_with_context!(element(true))(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
if let Ok((remaining, (_not_exit, empty_para))) =
tuple((not(exit_matcher), bind_context!(empty_paragraph, context)))(input)
{
return Ok((remaining, vec![Element::Paragraph(empty_para)]));
}
let (remaining, (children, _exit_contents)) = many_till(element_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)

View File

@@ -6,20 +6,20 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::greater_block::leading_blank_lines_end;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -28,7 +28,6 @@ use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -36,8 +35,6 @@ use crate::parser::util::start_of_line;
use crate::types::DynamicBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(
feature = "tracing",
@@ -82,25 +79,25 @@ where
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
not(exit_matcher)(remaining)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple((
blank_line,
many0(preceded(not(exit_matcher), blank_line)),
))))(remaining)?;
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
element.set_source(source.into());
element
});
let contents_begin = remaining;
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &leading_blank_lines_end,
});
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
children.insert(0, Element::Paragraph(lines));
}
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -114,6 +111,12 @@ where
block_name: name.into(),
parameters: parameters.map(|val| val.into()),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -28,7 +28,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
let (remaining, _) = tag("\\")(input)?;
let (remaining, (entity_definition, entity_name, use_brackets)) = name(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -43,6 +43,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
ascii: entity_definition.ascii,
utf8: entity_definition.utf8,
use_brackets,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -39,7 +39,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
parser_with_context!(contents)(&parser_context),
)))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
source: source.into(),
backend: backend_name.into(),
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -2,12 +2,15 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
@@ -35,28 +38,25 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
let (remaining, first_line) = fixed_width_area_line(remaining)?;
let (remaining, remaining_lines) = many0(preceded(
not(tuple((org_line_ending, exit_matcher))),
map(
tuple((org_line_ending, fixed_width_area_line)),
|(_line_ending, line_contents)| line_contents,
),
))(remaining)?;
let (remaining, _trailing_ws) =
let post_blank_begin = remaining;
let (remaining, _first_line_break) = org_line_ending(remaining)?;
let (remaining, _additional_post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
let last_line = remaining_lines.pop();
if let Some(last_line) = last_line {
value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
let last_line = Into::<&str>::into(last_line);
// Trim the line ending from the final line.
value.push(&last_line[..(last_line.len() - 1)])
} else {
// Trim the line ending from the only line.
let only_line = Into::<&str>::into(first_line);
value.push(&only_line[..(only_line.len() - 1)])
}
value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
Ok((
remaining,
FixedWidthArea {
@@ -66,25 +66,24 @@ where
affiliated_keywords,
),
value,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
fn fixed_width_area_line<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _) = tuple((space0, tag(":")))(input)?;
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
return Ok((remaining, line_break));
if let Ok((_remain, _line_break)) = org_line_ending(remaining) {
return Ok((remaining, remaining.take(0)));
}
let (remaining, _) = tag(" ")(remaining)?;
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
let (remaining, value) = recognize(many_till(anychar, peek(org_line_ending)))(remaining)?;
Ok((remaining, value))
}

View File

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

View File

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

View File

@@ -5,22 +5,21 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::in_section;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
@@ -37,9 +36,7 @@ use crate::parser::util::start_of_line;
use crate::types::CenterBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::QuoteBlock;
use crate::types::SetSource;
use crate::types::SpecialBlock;
#[cfg_attr(
@@ -103,7 +100,7 @@ fn center_block<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
@@ -113,12 +110,14 @@ where
Ok((
remaining,
Element::CenterBlock(CenterBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
@@ -136,7 +135,7 @@ fn quote_block<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
@@ -146,12 +145,14 @@ where
Ok((
remaining,
Element::QuoteBlock(QuoteBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
@@ -197,7 +198,7 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
remaining,
pre_affiliated_keywords_input,
@@ -207,18 +208,28 @@ where
Ok((
remaining,
Element::SpecialBlock(SpecialBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
block_type: name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
#[derive(Debug)]
struct GreaterBlockBody<'s> {
source: &'s str,
children: Vec<Element<'s>>,
contents: Option<&'s str>,
post_blank: Option<&'s str>,
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
@@ -229,7 +240,7 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
pre_affiliated_keywords_input: OrgSource<'s>,
name: &'c str,
context_name: &'c str,
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
) -> Res<OrgSource<'s>, GreaterBlockBody<'s>> {
if in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
@@ -251,30 +262,43 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
not(exit_matcher)(remaining)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple((
blank_line,
many0(preceded(not(exit_matcher), blank_line)),
))))(remaining)?;
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
element.set_source(source.into());
element
});
let contents_begin = remaining;
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &leading_blank_lines_end,
});
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
children.insert(0, Element::Paragraph(lines));
}
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(pre_affiliated_keywords_input, remaining);
Ok((remaining, (Into::<&str>::into(source), children)))
Ok((
remaining,
GreaterBlockBody {
source: Into::<&str>::into(source),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -310,3 +334,14 @@ fn _greater_block_end<'b, 'g, 'r, 's, 'c>(
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn leading_blank_lines_end<'b, 'g, 'r, 's, 'c>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(not(blank_line))(input)
}

View File

@@ -66,6 +66,8 @@ fn _heading<'b, 'g, 'r, 's>(
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = bind_context!(section, context);
let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
let (contents_begin, _) = opt(many0(blank_line))(remaining)?;
let maybe_post_blank = get_consumed(remaining, contents_begin);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
@@ -82,7 +84,8 @@ fn _heading<'b, 'g, 'r, 's>(
}
children.insert(0, section);
}
let remaining = if children.is_empty() {
let has_children = !children.is_empty();
let remaining = if !has_children {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
@@ -91,6 +94,7 @@ fn _heading<'b, 'g, 'r, 's>(
};
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -112,6 +116,16 @@ fn _heading<'b, 'g, 'r, 's>(
scheduled,
deadline,
closed,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: if has_children {
None
} else {
Some(Into::<&str>::into(maybe_post_blank))
},
},
))
}

View File

@@ -38,7 +38,7 @@ where
space0,
alt((line_ending, eof)),
)))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -49,6 +49,7 @@ where
context.get_global_settings(),
affiliated_keywords,
),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -102,7 +102,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
}) {
let (_, (in_progress_words, complete_words)) =
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::Failure(e) => e,
})?;
@@ -123,7 +123,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
kw.value,
)
.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::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"))
{
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::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"))]
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
document.category = Into::<AstNode>::into(&*document)
.into_iter()
.iter_all_ast_nodes()
.filter_map(|ast_node| {
if let AstNode::Keyword(ast_node) = ast_node {
if ast_node.key.eq_ignore_ascii_case("category") {

View File

@@ -38,7 +38,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
let (remaining, arguments) = argument(context, remaining)?;
let (remaining, end_header) = opt(parser_with_context!(header)(context))(remaining)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -54,6 +54,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
None
},
end_header: end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -38,7 +38,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
let (remaining, language) = lang(context, remaining)?;
let (remaining, parameters) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, value) = body(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
language: language.into(),
parameters: parameters.map(Into::<&str>::into),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -58,6 +58,7 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: "",
post_blank: None,
},
));
}
@@ -71,6 +72,7 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: parsed_value.into(),
post_blank: None,
},
))
}
@@ -89,12 +91,13 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
kw.affiliated_keywords =
parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords);
kw.source = Into::<&str>::into(source);
kw.post_blank = post_blank.map(Into::<&str>::into);
Ok((remaining, kw))
}

View File

@@ -57,7 +57,7 @@ where
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let value_end = remaining;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let value = get_consumed(value_start, value_end);
@@ -70,6 +70,7 @@ where
affiliated_keywords,
),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -39,7 +39,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
parser_with_context!(bordered_dollar_fragment)(context),
))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -47,6 +47,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
LatexFragment {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -80,22 +80,28 @@ where
let object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
// Check for a completely empty block
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining,
vec![Object::PlainText(PlainText {
source: whitespace.into(),
})],
),
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(object_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, contents, children) =
match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining,
whitespace,
if whitespace.len() > 0 {
vec![Object::PlainText(PlainText {
source: whitespace.into(),
})]
} else {
Vec::new()
},
),
Err(_) => {
let (remaining, (contents, (children, _exit_contents))) =
consumed(many_till(object_matcher, exit_matcher))(remaining)?;
(remaining, contents, children)
}
};
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -108,6 +114,8 @@ where
),
data: parameters.map(Into::<&str>::into),
children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -144,7 +152,7 @@ where
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -156,6 +164,7 @@ where
affiliated_keywords,
),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -202,10 +211,10 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]);
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, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
@@ -236,7 +245,8 @@ where
retain_labels,
use_labels,
label_format,
contents,
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -276,10 +286,10 @@ where
let parser_context = parser_context.with_additional_node(&contexts[1]);
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, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -292,7 +302,8 @@ where
),
export_type: export_type.map(Into::<&str>::into),
data: parameters.map(Into::<&str>::into),
contents,
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -331,10 +342,10 @@ where
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let (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, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
@@ -371,7 +382,8 @@ where
retain_labels,
use_labels,
label_format,
contents,
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -650,51 +662,3 @@ fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not(" \t\r\n"),
))(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

@@ -32,7 +32,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
let (remaining, _) = tag("}}}")(remaining)?;
let macro_value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -47,6 +47,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
.map(|arg| arg.into())
.collect(),
value: Into::<&str>::into(macro_value),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

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

View File

@@ -1,5 +1,8 @@
use nom::branch::alt;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many1;
@@ -12,6 +15,7 @@ use super::org_source::OrgSource;
use super::util::blank_line;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -45,14 +49,14 @@ where
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 (remaining, (children, _exit_contents)) = verify(
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(standard_set_object_matcher, exit_matcher),
|(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.
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -60,6 +64,8 @@ where
remaining,
Paragraph {
source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
@@ -69,6 +75,57 @@ where
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn empty_paragraph<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
// If it is just a single newline then source, contents, and post-blank are "\n".
// If it has multiple newlines then contents is the first "\n" and post-blank is all the new lines.
// If there are any spaces on the first line then post-blank excludes the first line.
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line_with_spaces) =
opt(recognize(tuple((space1, org_line_ending))))(input)?;
let post_blank_begin = remaining;
if let Some(first_line_with_spaces) = first_line_with_spaces {
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line_with_spaces),
Some(Into::<&str>::into(first_line_with_spaces)),
Some(Into::<&str>::into(post_blank)),
),
))
} else {
let (remaining, first_line) = blank_line(remaining)?;
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line),
Some(Into::<&str>::into(first_line)),
Some(Into::<&str>::into(post_blank)),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
@@ -96,7 +153,8 @@ mod tests {
use crate::context::List;
use crate::parser::element_parser::element;
use crate::parser::org_source::OrgSource;
use crate::types::GetStandardProperties;
use crate::parser::paragraph::empty_paragraph;
use crate::types::StandardProperties;
#[test]
fn two_paragraphs() {
@@ -109,13 +167,20 @@ mod tests {
let (remaining, second_paragraph) =
paragraph_matcher(remaining).expect("Parse second paragraph.");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"foo bar baz\n\n"
);
assert_eq!(
second_paragraph.get_standard_properties().get_source(),
"lorem ipsum"
);
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
}
#[test]
fn paragraph_whitespace() {
let input = OrgSource::new("\n");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = bind_context!(empty_paragraph, &initial_context);
let (remaining, paragraph) = paragraph_matcher(input).expect("Parse paragraph");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(paragraph.get_source(), "\n");
assert_eq!(paragraph.get_contents(), Some("\n"));
}
}

View File

@@ -54,7 +54,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
let (remaining, _) = pre(context, input)?;
let (remaining, path_plain) = parse_path_plain(context, remaining)?;
peek(parser_with_context!(post)(context))(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -66,6 +66,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
raw_link: path_plain.raw_link,
search_option: path_plain.search_option,
application: path_plain.application,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -7,6 +7,7 @@ use nom::character::complete::multispace1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
@@ -152,6 +153,7 @@ where
let mut children = Vec::new();
let mut first_item_indentation: Option<IndentationLevel> = None;
let mut first_item_list_type: Option<PlainListType> = None;
let contents_begin = remaining;
let mut remaining = remaining;
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
@@ -195,7 +197,8 @@ where
)));
}
let (remaining, _trailing_ws) =
let contents = get_consumed(contents_begin, remaining);
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -208,6 +211,8 @@ where
),
list_type: first_item_list_type.expect("Plain lists require at least one element."),
children: children.into_iter().map(|(_start, item)| item).collect(),
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -265,7 +270,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
let maybe_contentless_item: Res<OrgSource<'_>, ()> =
detect_contentless_item_contents(&parser_context, remaining);
if let Ok((_rem, _ws)) = maybe_contentless_item {
let (remaining, _trailing_ws) = if tuple((
let (remaining, post_blank) = if tuple((
blank_line,
bind_context!(final_item_whitespace_cutoff, context),
))(remaining)
@@ -291,6 +296,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
.unwrap_or(Vec::new()),
pre_blank: 0,
children: Vec::new(),
contents: None,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
),
));
@@ -301,13 +312,13 @@ fn plain_list_item<'b, 'g, 'r, 's>(
.filter(|b| *b == b'\n')
.count();
let (remaining, (children, _exit_contents)) = many_till(
let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
include_input(bind_context!(element(true), &parser_context)),
bind_context!(exit_matcher_parser, &parser_context),
)(remaining)?;
))(remaining)?;
// We have to use the parser_context here to include the whitespace cut-off
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
let source = get_consumed(input, remaining);
@@ -329,6 +340,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
pre_blank: PlainListItemPreBlank::try_from(pre_blank)
.expect("pre-blank cannot be larger than 2."),
children: children.into_iter().map(|(_start, item)| item).collect(),
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
),
));
@@ -629,7 +646,7 @@ mod tests {
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::types::GetStandardProperties;
use crate::types::StandardProperties;
#[test]
fn plain_list_item_empty() {
@@ -640,7 +657,7 @@ mod tests {
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1.");
assert_eq!(result.get_source(), "1.");
}
#[test]
@@ -652,7 +669,7 @@ mod tests {
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
assert_eq!(result.get_source(), "1. foo");
}
#[test]
@@ -664,7 +681,7 @@ mod tests {
let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1.");
assert_eq!(result.get_source(), "1.");
}
#[test]
@@ -676,7 +693,7 @@ mod tests {
let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
assert_eq!(result.get_source(), "1. foo");
}
#[test]
@@ -721,7 +738,7 @@ mod tests {
plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
assert_eq!(
result.get_standard_properties().get_source(),
result.get_source(),
r#"1. foo
2. bar
baz
@@ -749,7 +766,7 @@ baz"#,
plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "baz");
assert_eq!(
result.get_standard_properties().get_source(),
result.get_source(),
r#"1. foo
1. bar
@@ -782,7 +799,7 @@ dolar"#,
plain_list_matcher(input).expect("Should parse the plain list successfully.");
assert_eq!(Into::<&str>::into(remaining), "dolar");
assert_eq!(
result.get_standard_properties().get_source(),
result.get_source(),
r#"1. foo
bar

View File

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

View File

@@ -33,7 +33,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -62,6 +62,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
scheduled,
deadline,
closed,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -6,6 +6,7 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
@@ -64,14 +65,11 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let node_property_matcher = parser_with_context!(node_property)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(remaining)?;
let (remaining, (contents, children)) =
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let (remaining, _end) = property_drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -80,10 +78,31 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
PropertyDrawer {
source: source.into(),
children,
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<NodeProperty<'s>>> {
let node_property_matcher = parser_with_context!(node_property)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))

View File

@@ -39,7 +39,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
let rematched_target = rematch_target(context, radio_target, input);
if let Ok((remaining, rematched_target)) = rematched_target {
let path = get_consumed(input, remaining);
let (remaining, _) = space0(remaining)?;
let (remaining, post_blank) = space0(remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
@@ -47,6 +47,11 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
source: source.into(),
children: rematched_target,
path: path.into(),
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
));
}
@@ -134,7 +139,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
))(remaining)?;
let (remaining, _closing) = tag(">>>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -142,6 +147,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
RadioTarget {
source: source.into(),
value: raw_value.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -175,11 +181,11 @@ mod tests {
use crate::parser::element_parser::element;
use crate::types::Bold;
use crate::types::Element;
use crate::types::GetStandardProperties;
use crate::types::PlainText;
use crate::types::StandardProperties;
#[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 radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
let global_settings = GlobalSettings {
@@ -195,29 +201,38 @@ mod tests {
_ => panic!("Should be a paragraph!"),
};
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"foo bar baz"
);
assert_eq!(first_paragraph.get_source(), "foo bar baz");
assert_eq!(first_paragraph.children.len(), 3);
assert_eq!(
first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3"),
&Object::RadioLink(RadioLink {
source: "bar ",
children: vec![Object::PlainText(PlainText { source: "bar" })],
path: "bar"
})
);
match first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3.")
{
Object::RadioLink(inner) => {
assert_eq!(inner.get_source(), "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]
fn bold_radio_target() {
fn bold_radio_target() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("foo *bar* baz");
let radio_target_match = vec![Object::Bold(Bold {
source: "*bar*",
contents: "bar",
post_blank: Some(" "),
children: vec![Object::PlainText(PlainText { source: "bar" })],
})];
let global_settings = GlobalSettings {
@@ -234,24 +249,28 @@ mod tests {
_ => panic!("Should be a paragraph!"),
};
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(
first_paragraph.get_standard_properties().get_source(),
"foo *bar* baz"
);
assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
assert_eq!(first_paragraph.children.len(), 3);
assert_eq!(
first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3"),
&Object::RadioLink(RadioLink {
source: "*bar* ",
children: vec![Object::Bold(Bold {
source: "*bar* ",
children: vec![Object::PlainText(PlainText { source: "bar" })]
})],
path: "*bar* "
})
);
match first_paragraph
.children
.get(1)
.expect("Len already asserted to be 3.")
{
Object::RadioLink(inner) => {
assert_eq!(inner.get_source(), "*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::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, path) = pathreg(context, 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)?;
let source = get_consumed(input, remaining);
Ok((
@@ -84,6 +84,8 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
path: path.path,
raw_link: path.raw_link,
search_option: path.search_option,
contents: None,
post_blank: post_blank.map(Into::<&str>::into),
children: Vec::new(),
application: path.application,
},
@@ -101,9 +103,10 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("][")(remaining)?;
let (remaining, description) = description(context, remaining)?;
let (remaining, (contents, description)) =
consumed(parser_with_context!(description)(context))(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)?;
let source = get_consumed(input, remaining);
Ok((
@@ -114,6 +117,8 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
path: path.path,
raw_link: path.raw_link,
search_option: path.search_option,
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
children: description,
application: path.application,
},

View File

@@ -72,7 +72,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)?;
let source = get_consumed(input, remaining);
@@ -80,6 +80,7 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
remaining,
Section {
source: source.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -128,7 +129,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
children.insert(0, ele)
}
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -136,6 +137,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
remaining,
Section {
source: source.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))

View File

@@ -40,7 +40,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
tag("%]"),
)))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -68,7 +69,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
tag("]"),
)))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -76,6 +77,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::take_while;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::combinator::consumed;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
@@ -54,17 +55,20 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'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)?;
pre(input)?;
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)?;
let source = get_consumed(input, remaining);
let (use_brackets, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]),
ScriptBody::WithBraces(body) => (true, body),
let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (
false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
};
Ok((
@@ -72,6 +76,8 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
Subscript {
source: source.into(),
use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body,
},
))
@@ -89,13 +95,17 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
let (remaining, _) = tag("^")(input)?;
pre(input)?;
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)?;
let source = get_consumed(input, remaining);
let (use_brackets, body) = match body {
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]),
ScriptBody::WithBraces(body) => (true, body),
let (use_brackets, contents, body) = match body {
ScriptBody::Braceless(text) => (
false,
text,
vec![Object::PlainText(PlainText { source: text })],
),
ScriptBody::WithBraces(contents, body) => (true, contents, body),
};
Ok((
@@ -103,6 +113,8 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
Superscript {
source: source.into(),
use_brackets,
contents,
post_blank: post_blank.map(Into::<&str>::into),
children: body,
},
))
@@ -117,7 +129,7 @@ fn pre<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
#[derive(Debug)]
enum ScriptBody<'s> {
Braceless(&'s str),
WithBraces(Vec<Object<'s>>),
WithBraces(&'s str, Vec<Object<'s>>),
}
#[cfg_attr(
@@ -135,9 +147,10 @@ fn script_body<'b, 'g, 'r, 's>(
map(parser_with_context!(script_alphanum)(context), |body| {
ScriptBody::Braceless(body.into())
}),
map(parser_with_context!(script_with_braces)(context), |body| {
ScriptBody::WithBraces(body)
}),
map(
parser_with_context!(script_with_braces)(context),
|(contents, body)| ScriptBody::WithBraces(Into::<&str>::into(contents), body),
),
map(
parser_with_context!(script_with_parenthesis)(context),
|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>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>)> {
let (remaining, _) = tag("{")(input)?;
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
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 (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!(exit_matcher_parser)(&parser_context),
)(remaining)?;
))(remaining)?;
let (remaining, _) = tag("}")(remaining)?;
Ok((remaining, children))
Ok((remaining, (contents, children)))
}
fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher {

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
@@ -67,13 +68,13 @@ where
let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(org_mode_table_row_matcher, exit_matcher)(remaining)?;
let (remaining, (contents, (children, _exit_contents))) =
consumed(many_till(org_mode_table_row_matcher, exit_matcher))(remaining)?;
let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -87,6 +88,8 @@ where
),
formulas,
children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -150,6 +153,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
TableRow {
source: source.into(),
children: Vec::new(),
contents: None,
},
))
}
@@ -164,8 +168,8 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, TableRow<'s>> {
start_of_line(input)?;
let (remaining, _) = tuple((space0, tag("|")))(input)?;
let (remaining, children) =
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
let (remaining, (contents, children)) =
consumed(many1(parser_with_context!(org_mode_table_cell)(context)))(remaining)?;
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -173,6 +177,11 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
TableRow {
source: source.into(),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
},
))
}
@@ -194,12 +203,12 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
parser_with_context!(table_cell_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, _) = space0(input)?;
let (remaining, (children, _exit_contents)) = verify(
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(table_cell_set_object_matcher, exit_matcher),
|(children, exit_contents)| {
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
},
)(remaining)?;
))(remaining)?;
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
@@ -210,6 +219,7 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
TableCell {
source: source.into(),
children,
contents: Into::<&str>::into(contents),
},
))
}

View File

@@ -46,7 +46,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
)));
}
let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -55,6 +55,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
Target {
source: source.into(),
value: body.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -3,11 +3,13 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::multispace1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::all_consuming;
use nom::combinator::consumed;
use nom::combinator::map;
use nom::combinator::map_parser;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -76,12 +78,14 @@ fn bold<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'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);
Ok((
remaining,
Bold {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -95,12 +99,14 @@ fn italic<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'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);
Ok((
remaining,
Italic {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -114,12 +120,14 @@ fn underline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'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);
Ok((
remaining,
Underline {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -133,12 +141,14 @@ fn strike_through<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'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);
Ok((
remaining,
StrikeThrough {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))
@@ -152,13 +162,14 @@ fn verbatim<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'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);
Ok((
remaining,
Verbatim {
source: source.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>,
input: OrgSource<'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);
Ok((
remaining,
Code {
source: source.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(
RefContext<'b, 'g, 'r, '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)
}
@@ -200,7 +214,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
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, open) = tag(marker_symbol)(remaining)?;
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 = Context::new(context.get_global_settings(), List::new(&initial_context));
let (remaining, children) = map_parser(
let (remaining, (contents, children)) = consumed(map_parser(
verify(
parser_with_context!(text_until_exit)(&parser_context),
|text| text.len() > 0,
@@ -225,7 +239,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
&initial_context,
)))(i)
}),
)(remaining)?;
))(remaining)?;
{
#[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, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, children))
Ok((remaining, (contents, children, post_blank)))
}
fn text_markup_string(
@@ -250,7 +264,7 @@ fn text_markup_string(
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, '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)
}
@@ -263,7 +277,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)> {
let (remaining, _) = pre(context, input)?;
let (remaining, open) = tag(marker_symbol)(remaining)?;
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, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, contents))
Ok((remaining, (contents, post_blank)))
}
#[cfg_attr(
@@ -382,13 +396,15 @@ impl<'x> RematchObject<'x> for Bold<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "*", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Bold(Bold {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -405,13 +421,15 @@ impl<'x> RematchObject<'x> for Italic<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "/", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Italic(Italic {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -428,13 +446,15 @@ impl<'x> RematchObject<'x> for Underline<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "_", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::Underline(Underline {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -451,13 +471,15 @@ impl<'x> RematchObject<'x> for StrikeThrough<'x> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, children) =
let (remaining, (contents, children, post_blank)) =
_rematch_text_markup_object(_context, input, "+", &self.children)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::StrikeThrough(StrikeThrough {
source: source.into(),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
}),
))
@@ -473,7 +495,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
input: OrgSource<'s>,
marker_symbol: &'static str,
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, open) = tag(marker_symbol)(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 contents_begin = remaining;
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.
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, _trailing_whitespace) = space0(remaining)?;
Ok((remaining, children))
let (remaining, post_blank) = opt(space1)(remaining)?;
Ok((remaining, (contents, children, post_blank)))
}

View File

@@ -53,8 +53,8 @@ pub(crate) fn timestamp<'b, 'g, 'r, 's>(
parser_with_context!(inactive_time_range_timestamp)(context),
parser_with_context!(active_date_range_timestamp)(context),
parser_with_context!(inactive_date_range_timestamp)(context),
parser_with_context!(active_timestamp)(context),
parser_with_context!(inactive_timestamp)(context),
parser_with_context!(active_timestamp(true))(context),
parser_with_context!(inactive_timestamp(true))(context),
))(input)
}
@@ -69,7 +69,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
let (remaining, _) = tag("<%%(")(input)?;
let (remaining, _body) = sexp(context, remaining)?;
let (remaining, _) = tag(")>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -85,6 +85,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
end_time: None,
repeater: None,
warning_delay: None,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -125,13 +126,23 @@ fn sexp_end<'b, 'g, 'r, 's>(
alt((tag(")>"), recognize(one_of(">\n"))))(input)
}
const fn active_timestamp(
allow_post_blank: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
move |context, input| impl_active_timestamp(context, input, allow_post_blank)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn active_timestamp<'b, 'g, 'r, 's>(
fn impl_active_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
allow_post_blank: bool,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?;
let (remaining, start) = date(context, remaining)?;
@@ -159,8 +170,11 @@ fn active_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, post_blank) = if allow_post_blank {
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
} else {
(remaining, None)
};
let source = get_consumed(input, remaining);
Ok((
@@ -175,17 +189,28 @@ fn active_timestamp<'b, 'g, 'r, 's>(
end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
pub(crate) const fn inactive_timestamp(
allow_post_blank: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
move |context, input| impl_inactive_timestamp(context, input, allow_post_blank)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
fn impl_inactive_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
allow_post_blank: bool,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?;
let (remaining, start) = date(context, remaining)?;
@@ -213,8 +238,11 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, post_blank) = if allow_post_blank {
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
} else {
(remaining, None)
};
let source = get_consumed(input, remaining);
Ok((
@@ -229,6 +257,7 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -241,12 +270,12 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, first_timestamp) = active_timestamp(context, input)?;
let (remaining, first_timestamp) = impl_active_timestamp(context, input, false)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, second_timestamp) = active_timestamp(context, remaining)?;
let (remaining, second_timestamp) = impl_active_timestamp(context, remaining, false)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -264,6 +293,7 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
warning_delay: first_timestamp
.warning_delay
.or(second_timestamp.warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -302,7 +332,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -318,6 +348,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -330,12 +361,12 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, first_timestamp) = inactive_timestamp(context, input)?;
let (remaining, first_timestamp) = impl_inactive_timestamp(context, input, false)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?;
let (remaining, second_timestamp) = impl_inactive_timestamp(context, remaining, false)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -354,6 +385,7 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
warning_delay: first_timestamp
.warning_delay
.or(second_timestamp.warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -392,7 +424,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -408,6 +440,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -81,14 +81,21 @@ pub(crate) fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r
input: 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 (" ::").
let (remaining, _) = many_till(
let (remaining, post_blank) = recognize(many_till(
one_of(" \t"),
alt((
peek(recognize(none_of(" \t"))),
parser_with_context!(exit_matcher_parser)(context),
)),
)(input)?;
Ok((remaining, None))
))(input)?;
Ok((
remaining,
if post_blank.len() == 0 {
None
} else {
Some(post_blank)
},
))
}
#[cfg_attr(

View File

@@ -1,7 +1,9 @@
use super::macros::to_ast_node;
use super::CenterBlock;
use super::PostBlank;
use super::QuoteBlock;
use super::SpecialBlock;
use super::StandardProperties;
use crate::types::AngleLink;
use crate::types::BabelCall;
use crate::types::Bold;
@@ -24,7 +26,6 @@ use crate::types::ExportSnippet;
use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::GetStandardProperties;
use crate::types::Heading;
use crate::types::HorizontalRule;
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 Timestamp<'s>, AstNode::Timestamp);
impl<'r, 's> GetStandardProperties<'s> for AstNode<'r, 's> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn crate::types::StandardProperties<'s> {
impl<'r, 's> StandardProperties<'s> for AstNode<'r, 's> {
fn get_source<'b>(&'b self) -> &'s str {
match self {
AstNode::Document(inner) => *inner,
AstNode::Heading(inner) => *inner,
AstNode::Section(inner) => *inner,
AstNode::Paragraph(inner) => *inner,
AstNode::PlainList(inner) => *inner,
AstNode::PlainListItem(inner) => *inner,
AstNode::CenterBlock(inner) => *inner,
AstNode::QuoteBlock(inner) => *inner,
AstNode::SpecialBlock(inner) => *inner,
AstNode::DynamicBlock(inner) => *inner,
AstNode::FootnoteDefinition(inner) => *inner,
AstNode::Comment(inner) => *inner,
AstNode::Drawer(inner) => *inner,
AstNode::PropertyDrawer(inner) => *inner,
AstNode::NodeProperty(inner) => *inner,
AstNode::Table(inner) => *inner,
AstNode::TableRow(inner) => *inner,
AstNode::VerseBlock(inner) => *inner,
AstNode::CommentBlock(inner) => *inner,
AstNode::ExampleBlock(inner) => *inner,
AstNode::ExportBlock(inner) => *inner,
AstNode::SrcBlock(inner) => *inner,
AstNode::Clock(inner) => *inner,
AstNode::DiarySexp(inner) => *inner,
AstNode::Planning(inner) => *inner,
AstNode::FixedWidthArea(inner) => *inner,
AstNode::HorizontalRule(inner) => *inner,
AstNode::Keyword(inner) => *inner,
AstNode::BabelCall(inner) => *inner,
AstNode::LatexEnvironment(inner) => *inner,
AstNode::Bold(inner) => *inner,
AstNode::Italic(inner) => *inner,
AstNode::Underline(inner) => *inner,
AstNode::StrikeThrough(inner) => *inner,
AstNode::Code(inner) => *inner,
AstNode::Verbatim(inner) => *inner,
AstNode::PlainText(inner) => *inner,
AstNode::RegularLink(inner) => *inner,
AstNode::RadioLink(inner) => *inner,
AstNode::RadioTarget(inner) => *inner,
AstNode::PlainLink(inner) => *inner,
AstNode::AngleLink(inner) => *inner,
AstNode::OrgMacro(inner) => *inner,
AstNode::Entity(inner) => *inner,
AstNode::LatexFragment(inner) => *inner,
AstNode::ExportSnippet(inner) => *inner,
AstNode::FootnoteReference(inner) => *inner,
AstNode::Citation(inner) => *inner,
AstNode::CitationReference(inner) => *inner,
AstNode::InlineBabelCall(inner) => *inner,
AstNode::InlineSourceBlock(inner) => *inner,
AstNode::LineBreak(inner) => *inner,
AstNode::Target(inner) => *inner,
AstNode::StatisticsCookie(inner) => *inner,
AstNode::Subscript(inner) => *inner,
AstNode::Superscript(inner) => *inner,
AstNode::TableCell(inner) => *inner,
AstNode::Timestamp(inner) => *inner,
AstNode::Document(inner) => inner.get_source(),
AstNode::Heading(inner) => inner.get_source(),
AstNode::Section(inner) => inner.get_source(),
AstNode::Paragraph(inner) => inner.get_source(),
AstNode::PlainList(inner) => inner.get_source(),
AstNode::PlainListItem(inner) => inner.get_source(),
AstNode::CenterBlock(inner) => inner.get_source(),
AstNode::QuoteBlock(inner) => inner.get_source(),
AstNode::SpecialBlock(inner) => inner.get_source(),
AstNode::DynamicBlock(inner) => inner.get_source(),
AstNode::FootnoteDefinition(inner) => inner.get_source(),
AstNode::Comment(inner) => inner.get_source(),
AstNode::Drawer(inner) => inner.get_source(),
AstNode::PropertyDrawer(inner) => inner.get_source(),
AstNode::NodeProperty(inner) => inner.get_source(),
AstNode::Table(inner) => inner.get_source(),
AstNode::TableRow(inner) => inner.get_source(),
AstNode::VerseBlock(inner) => inner.get_source(),
AstNode::CommentBlock(inner) => inner.get_source(),
AstNode::ExampleBlock(inner) => inner.get_source(),
AstNode::ExportBlock(inner) => inner.get_source(),
AstNode::SrcBlock(inner) => inner.get_source(),
AstNode::Clock(inner) => inner.get_source(),
AstNode::DiarySexp(inner) => inner.get_source(),
AstNode::Planning(inner) => inner.get_source(),
AstNode::FixedWidthArea(inner) => inner.get_source(),
AstNode::HorizontalRule(inner) => inner.get_source(),
AstNode::Keyword(inner) => inner.get_source(),
AstNode::BabelCall(inner) => inner.get_source(),
AstNode::LatexEnvironment(inner) => inner.get_source(),
AstNode::Bold(inner) => inner.get_source(),
AstNode::Italic(inner) => inner.get_source(),
AstNode::Underline(inner) => inner.get_source(),
AstNode::StrikeThrough(inner) => inner.get_source(),
AstNode::Code(inner) => inner.get_source(),
AstNode::Verbatim(inner) => inner.get_source(),
AstNode::PlainText(inner) => inner.get_source(),
AstNode::RegularLink(inner) => inner.get_source(),
AstNode::RadioLink(inner) => inner.get_source(),
AstNode::RadioTarget(inner) => inner.get_source(),
AstNode::PlainLink(inner) => inner.get_source(),
AstNode::AngleLink(inner) => inner.get_source(),
AstNode::OrgMacro(inner) => inner.get_source(),
AstNode::Entity(inner) => inner.get_source(),
AstNode::LatexFragment(inner) => inner.get_source(),
AstNode::ExportSnippet(inner) => inner.get_source(),
AstNode::FootnoteReference(inner) => inner.get_source(),
AstNode::Citation(inner) => inner.get_source(),
AstNode::CitationReference(inner) => inner.get_source(),
AstNode::InlineBabelCall(inner) => inner.get_source(),
AstNode::InlineSourceBlock(inner) => inner.get_source(),
AstNode::LineBreak(inner) => inner.get_source(),
AstNode::Target(inner) => inner.get_source(),
AstNode::StatisticsCookie(inner) => inner.get_source(),
AstNode::Subscript(inner) => inner.get_source(),
AstNode::Superscript(inner) => inner.get_source(),
AstNode::TableCell(inner) => inner.get_source(),
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,9 @@
use std::path::PathBuf;
use super::Element;
use super::GetStandardProperties;
use super::NodeProperty;
use super::Object;
use super::PostBlank;
use super::StandardProperties;
use super::Timestamp;
@@ -17,6 +17,7 @@ pub struct Document<'s> {
pub path: Option<PathBuf>,
pub zeroth_section: Option<Section<'s>>,
pub children: Vec<Heading<'s>>,
pub contents: &'s str,
}
#[derive(Debug)]
@@ -34,11 +35,14 @@ pub struct Heading<'s> {
pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct Section<'s> {
pub source: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
@@ -54,41 +58,60 @@ pub enum TodoKeywordType {
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> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
0
}
}
impl<'s> StandardProperties<'s> for Section<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.source)
}
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> Heading<'s> {
pub fn get_raw_value(&self) -> String {
// TODO: I think this could just return a string slice instead of an owned string.
let title_source: String = self
.title
.iter()
.map(|obj| obj.get_standard_properties().get_source())
.collect();
let title_source: String = self.title.iter().map(|obj| obj.get_source()).collect();
title_source
}
@@ -132,3 +155,26 @@ impl<'s> Document<'s> {
.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::CenterBlock;
use super::Drawer;
use super::GetStandardProperties;
use super::PostBlank;
use super::QuoteBlock;
use super::SetSource;
use super::SpecialBlock;
use super::StandardProperties;
@@ -55,65 +54,91 @@ pub enum Element<'s> {
LatexEnvironment(LatexEnvironment<'s>),
}
impl<'s> SetSource<'s> for Element<'s> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn set_source(&mut self, source: &'s str) {
impl<'s> StandardProperties<'s> for Element<'s> {
fn get_source<'b>(&'b self) -> &'s str {
match self {
Element::Paragraph(obj) => obj.source = source,
Element::PlainList(obj) => obj.source = source,
Element::CenterBlock(obj) => obj.source = source,
Element::QuoteBlock(obj) => obj.source = source,
Element::SpecialBlock(obj) => obj.source = source,
Element::DynamicBlock(obj) => obj.source = source,
Element::FootnoteDefinition(obj) => obj.source = source,
Element::Comment(obj) => obj.source = source,
Element::Drawer(obj) => obj.source = source,
Element::PropertyDrawer(obj) => obj.source = source,
Element::Table(obj) => obj.source = source,
Element::VerseBlock(obj) => obj.source = source,
Element::CommentBlock(obj) => obj.source = source,
Element::ExampleBlock(obj) => obj.source = source,
Element::ExportBlock(obj) => obj.source = source,
Element::SrcBlock(obj) => obj.source = source,
Element::Clock(obj) => obj.source = source,
Element::DiarySexp(obj) => obj.source = source,
Element::Planning(obj) => obj.source = source,
Element::FixedWidthArea(obj) => obj.source = source,
Element::HorizontalRule(obj) => obj.source = source,
Element::Keyword(obj) => obj.source = source,
Element::BabelCall(obj) => obj.source = source,
Element::LatexEnvironment(obj) => obj.source = source,
Element::Paragraph(inner) => inner.get_source(),
Element::PlainList(inner) => inner.get_source(),
Element::CenterBlock(inner) => inner.get_source(),
Element::QuoteBlock(inner) => inner.get_source(),
Element::SpecialBlock(inner) => inner.get_source(),
Element::DynamicBlock(inner) => inner.get_source(),
Element::FootnoteDefinition(inner) => inner.get_source(),
Element::Comment(inner) => inner.get_source(),
Element::Drawer(inner) => inner.get_source(),
Element::PropertyDrawer(inner) => inner.get_source(),
Element::Table(inner) => inner.get_source(),
Element::VerseBlock(inner) => inner.get_source(),
Element::CommentBlock(inner) => inner.get_source(),
Element::ExampleBlock(inner) => inner.get_source(),
Element::ExportBlock(inner) => inner.get_source(),
Element::SrcBlock(inner) => inner.get_source(),
Element::Clock(inner) => inner.get_source(),
Element::DiarySexp(inner) => inner.get_source(),
Element::Planning(inner) => inner.get_source(),
Element::FixedWidthArea(inner) => inner.get_source(),
Element::HorizontalRule(inner) => inner.get_source(),
Element::Keyword(inner) => inner.get_source(),
Element::BabelCall(inner) => inner.get_source(),
Element::LatexEnvironment(inner) => inner.get_source(),
}
}
}
impl<'s> GetStandardProperties<'s> for Element<'s> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
fn get_contents<'b>(&'b self) -> Option<&'s str> {
match self {
Element::Paragraph(inner) => inner,
Element::PlainList(inner) => inner,
Element::CenterBlock(inner) => inner,
Element::QuoteBlock(inner) => inner,
Element::SpecialBlock(inner) => inner,
Element::DynamicBlock(inner) => inner,
Element::FootnoteDefinition(inner) => inner,
Element::Comment(inner) => inner,
Element::Drawer(inner) => inner,
Element::PropertyDrawer(inner) => inner,
Element::Table(inner) => inner,
Element::VerseBlock(inner) => inner,
Element::CommentBlock(inner) => inner,
Element::ExampleBlock(inner) => inner,
Element::ExportBlock(inner) => inner,
Element::SrcBlock(inner) => inner,
Element::Clock(inner) => inner,
Element::DiarySexp(inner) => inner,
Element::Planning(inner) => inner,
Element::FixedWidthArea(inner) => inner,
Element::HorizontalRule(inner) => inner,
Element::Keyword(inner) => inner,
Element::BabelCall(inner) => inner,
Element::LatexEnvironment(inner) => inner,
Element::Paragraph(inner) => inner.get_contents(),
Element::PlainList(inner) => inner.get_contents(),
Element::CenterBlock(inner) => inner.get_contents(),
Element::QuoteBlock(inner) => inner.get_contents(),
Element::SpecialBlock(inner) => inner.get_contents(),
Element::DynamicBlock(inner) => inner.get_contents(),
Element::FootnoteDefinition(inner) => inner.get_contents(),
Element::Comment(inner) => inner.get_contents(),
Element::Drawer(inner) => inner.get_contents(),
Element::PropertyDrawer(inner) => inner.get_contents(),
Element::Table(inner) => inner.get_contents(),
Element::VerseBlock(inner) => inner.get_contents(),
Element::CommentBlock(inner) => inner.get_contents(),
Element::ExampleBlock(inner) => inner.get_contents(),
Element::ExportBlock(inner) => inner.get_contents(),
Element::SrcBlock(inner) => inner.get_contents(),
Element::Clock(inner) => inner.get_contents(),
Element::DiarySexp(inner) => inner.get_contents(),
Element::Planning(inner) => inner.get_contents(),
Element::FixedWidthArea(inner) => inner.get_contents(),
Element::HorizontalRule(inner) => inner.get_contents(),
Element::Keyword(inner) => inner.get_contents(),
Element::BabelCall(inner) => inner.get_contents(),
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::Keyword;
use super::Object;
use super::PostBlank;
use super::StandardProperties;
#[derive(Debug)]
@@ -12,6 +13,8 @@ pub struct PlainList<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub list_type: PlainListType,
pub children: Vec<PlainListItem<'s>>,
pub contents: Option<&'s str>, // TODO: Can contents ever be None?
pub post_blank: Option<&'s str>,
}
#[derive(Debug, Copy, Clone)]
@@ -34,6 +37,8 @@ pub struct PlainListItem<'s> {
pub tag: Vec<Object<'s>>,
pub pre_blank: PlainListItemPreBlank,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
pub type PlainListItemCounter = u16;
@@ -51,6 +56,8 @@ pub struct CenterBlock<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -58,6 +65,8 @@ pub struct QuoteBlock<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -67,6 +76,8 @@ pub struct SpecialBlock<'s> {
pub block_type: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -76,11 +87,15 @@ pub struct DynamicBlock<'s> {
pub block_name: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct FootnoteDefinition<'s> {
pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub label: &'s str,
pub children: Vec<Element<'s>>,
@@ -92,12 +107,16 @@ pub struct Drawer<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub drawer_name: &'s str,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct PropertyDrawer<'s> {
pub source: &'s str,
pub children: Vec<NodeProperty<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -113,12 +132,15 @@ pub struct Table<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub formulas: Vec<Keyword<'s>>,
pub children: Vec<TableRow<'s>>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct TableRow<'s> {
pub source: &'s str,
pub children: Vec<TableCell<'s>>,
pub contents: Option<&'s str>,
}
#[derive(Debug)]
@@ -131,72 +153,208 @@ impl<'s> StandardProperties<'s> for PlainList<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 PlainListItem<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 CenterBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 QuoteBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 SpecialBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 DynamicBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 FootnoteDefinition<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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 PropertyDrawer<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 NodeProperty<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
fn get_post_blank(&self) -> PostBlank {
0
}
}
impl<'s> StandardProperties<'s> for Table<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> StandardProperties<'s> for TableRow<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
}
fn get_post_blank(&self) -> PostBlank {
0
}
}
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::AffiliatedKeywords;
use super::GetAffiliatedKeywords;
use super::PlainText;
use super::PostBlank;
use super::StandardProperties;
use super::Timestamp;
use crate::error::CustomError;
use crate::error::Res;
#[derive(Debug)]
pub struct Paragraph<'s> {
pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub children: Vec<Object<'s>>,
}
@@ -16,12 +35,14 @@ pub struct Paragraph<'s> {
pub struct Comment<'s> {
pub source: &'s str,
pub value: Vec<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct TableCell<'s> {
pub source: &'s str,
pub children: Vec<Object<'s>>,
pub contents: &'s str,
}
#[derive(Debug)]
@@ -30,6 +51,8 @@ pub struct VerseBlock<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub data: Option<&'s str>,
pub children: Vec<Object<'s>>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -37,6 +60,7 @@ pub struct CommentBlock<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
pub type CharOffsetInLine = u16;
@@ -59,7 +83,8 @@ pub struct ExampleBlock<'s> {
pub retain_labels: RetainLabels,
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub contents: String,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -68,7 +93,8 @@ pub struct ExportBlock<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub export_type: Option<&'s str>,
pub data: Option<&'s str>,
pub contents: String,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -83,7 +109,8 @@ pub struct SrcBlock<'s> {
pub retain_labels: RetainLabels,
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub contents: String,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -98,6 +125,7 @@ pub struct Clock<'s> {
pub timestamp: Timestamp<'s>,
pub duration: Option<&'s str>,
pub status: ClockStatus,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -105,6 +133,7 @@ pub struct DiarySexp<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -113,6 +142,7 @@ pub struct Planning<'s> {
pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -120,12 +150,14 @@ pub struct FixedWidthArea<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub value: Vec<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct HorizontalRule<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -134,6 +166,7 @@ pub struct Keyword<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub key: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -145,6 +178,7 @@ pub struct BabelCall<'s> {
pub inside_header: Option<&'s str>,
pub arguments: Option<&'s str>,
pub end_header: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -152,6 +186,7 @@ pub struct LatexEnvironment<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
/// A line number used in switches to lesser blocks.
@@ -169,11 +204,18 @@ impl<'s> Paragraph<'s> {
/// Generate a paragraph of the passed in text with no additional properties.
///
/// This is used for elements that support an "empty" content like greater blocks.
pub(crate) fn of_text(input: &'s str) -> Self {
pub(crate) fn of_text(
source: &'s str,
body: &'s str,
contents: Option<&'s str>,
post_blank: Option<&'s str>,
) -> Self {
Paragraph {
source: input,
source,
contents,
post_blank,
affiliated_keywords: AffiliatedKeywords::default(),
children: vec![Object::PlainText(PlainText { source: input })],
children: vec![Object::PlainText(PlainText { source: body })],
}
}
}
@@ -182,92 +224,280 @@ impl<'s> StandardProperties<'s> for Paragraph<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
0
}
}
impl<'s> StandardProperties<'s> for Comment<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 VerseBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 ExampleBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 ExportBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 SrcBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 Clock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 DiarySexp<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 Planning<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 FixedWidthArea<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 HorizontalRule<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 Keyword<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 BabelCall<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 LatexEnvironment<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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> Comment<'s> {
@@ -284,13 +514,14 @@ impl<'s> Comment<'s> {
impl<'s> FixedWidthArea<'s> {
pub fn get_value(&self) -> String {
let final_size = self.value.iter().map(|line| line.len()).sum();
let mut ret = String::with_capacity(final_size);
for line in &self.value {
ret.push_str(line);
}
self.value.join("\n")
}
}
ret
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.")
}
}
@@ -301,6 +532,18 @@ impl<'s> ExportBlock<'s> {
pub fn get_export_type(&self) -> Option<String> {
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> {
@@ -374,3 +617,82 @@ impl<'s> GetAffiliatedKeywords<'s> for VerseBlock<'s> {
&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 document;
mod element;
mod get_standard_properties;
mod greater_element;
mod lesser_element;
mod macros;
mod object;
mod source;
mod remove_trailing;
mod standard_properties;
mod util;
pub use affiliated_keyword::AffiliatedKeyword;
pub use affiliated_keyword::AffiliatedKeywordValue;
pub use affiliated_keyword::AffiliatedKeywords;
pub use affiliated_keyword::GetAffiliatedKeywords;
pub(crate) use ast_node::AstNode;
pub use ast_node::AstNode;
pub use document::Document;
pub use document::DocumentElement;
pub use document::Heading;
@@ -23,7 +22,6 @@ pub use document::PriorityCookie;
pub use document::Section;
pub use document::TodoKeywordType;
pub use element::Element;
pub use get_standard_properties::GetStandardProperties;
pub use greater_element::CenterBlock;
pub use greater_element::CheckboxType;
pub use greater_element::Drawer;
@@ -113,5 +111,5 @@ pub use object::WarningDelay;
pub use object::WarningDelayType;
pub use object::Year;
pub use object::YearInner;
pub(crate) use source::SetSource;
pub use standard_properties::PostBlank;
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_whitespace_if_line_break;
use super::util::to_lowercase;
use super::GetStandardProperties;
use super::PostBlank;
use super::StandardProperties;
// TODO: Why did we make Object implement PartialEq again? Was it just for tests?
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub enum Object<'s> {
Bold(Bold<'s>),
Italic(Italic<'s>),
@@ -41,48 +40,58 @@ pub enum Object<'s> {
Timestamp(Timestamp<'s>),
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Bold<'s> {
pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Italic<'s> {
pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Underline<'s> {
pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct StrikeThrough<'s> {
pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Code<'s> {
pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Verbatim<'s> {
pub source: &'s str,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct PlainText<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct RegularLink<'s> {
pub source: &'s str,
pub link_type: LinkType<'s>,
@@ -101,25 +110,29 @@ 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.
pub search_option: Option<Cow<'s, str>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
pub application: Option<Cow<'s, str>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct RadioTarget<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct RadioLink<'s> {
pub source: &'s str,
pub path: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct PlainLink<'s> {
pub source: &'s str,
pub link_type: LinkType<'s>,
@@ -127,9 +140,10 @@ pub struct PlainLink<'s> {
pub raw_link: &'s str,
pub search_option: Option<&'s str>,
pub application: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct AngleLink<'s> {
pub source: &'s str,
pub link_type: LinkType<'s>,
@@ -145,9 +159,10 @@ pub struct AngleLink<'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.
pub search_option: Option<&'s str>,
pub application: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct OrgMacro<'s> {
pub source: &'s str,
@@ -162,9 +177,10 @@ pub struct OrgMacro<'s> {
pub args: Vec<&'s str>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Entity<'s> {
pub source: &'s str,
pub name: &'s str,
@@ -175,38 +191,45 @@ pub struct Entity<'s> {
// Skipping latin1 because it is detrimental to the future. If anyone out there is using latin1, take a long look in the mirror and change your ways.
pub utf8: &'s str,
pub use_brackets: bool,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct LatexFragment<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct ExportSnippet<'s> {
pub source: &'s str,
pub backend: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct FootnoteReference<'s> {
pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub label: Option<&'s str>,
pub definition: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Citation<'s> {
pub source: &'s str,
pub style: Option<&'s str>,
pub prefix: Vec<Object<'s>>,
pub suffix: Vec<Object<'s>>,
pub children: Vec<CitationReference<'s>>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct CitationReference<'s> {
pub source: &'s str,
pub key: &'s str,
@@ -214,7 +237,7 @@ pub struct CitationReference<'s> {
pub suffix: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct InlineBabelCall<'s> {
pub source: &'s str,
pub value: &'s str,
@@ -222,49 +245,57 @@ pub struct InlineBabelCall<'s> {
pub inside_header: Option<&'s str>,
pub arguments: Option<&'s str>,
pub end_header: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct InlineSourceBlock<'s> {
pub source: &'s str,
pub language: &'s str,
pub parameters: Option<&'s str>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct LineBreak<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Target<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct StatisticsCookie<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Subscript<'s> {
pub source: &'s str,
pub use_brackets: bool,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub struct Superscript<'s> {
pub source: &'s str,
pub use_brackets: bool,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
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)
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Timestamp<'s> {
pub source: &'s str,
pub timestamp_type: TimestampType,
@@ -275,9 +306,10 @@ pub struct Timestamp<'s> {
pub end_time: Option<Time<'s>>,
pub repeater: Option<Repeater>,
pub warning_delay: Option<WarningDelay>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum TimestampType {
Diary,
Active,
@@ -286,7 +318,7 @@ pub enum TimestampType {
InactiveRange,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum TimestampRangeType {
None,
DateRange,
@@ -299,19 +331,19 @@ pub type DayOfMonthInner = u8;
pub type HourInner = u8;
pub type MinuteInner = u8;
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Year(YearInner);
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Month(MonthInner);
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct DayOfMonth(DayOfMonthInner);
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Hour(HourInner);
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Minute(MinuteInner);
impl Year {
@@ -386,7 +418,7 @@ impl Minute {
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Date<'s> {
year: Year,
month: Month,
@@ -444,7 +476,7 @@ impl<'s> Date<'s> {
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Time<'s> {
hour: Hour,
minute: Minute,
@@ -478,20 +510,20 @@ impl<'s> Time<'s> {
}
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum RepeaterType {
Cumulative,
CatchUp,
Restart,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum WarningDelayType {
All,
First,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub enum TimeUnit {
Hour,
Day,
@@ -502,214 +534,494 @@ pub enum TimeUnit {
pub type RepeaterWarningDelayValueType = u16;
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct Repeater {
pub repeater_type: RepeaterType,
pub value: RepeaterWarningDelayValueType,
pub unit: TimeUnit,
}
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, Clone)]
pub struct WarningDelay {
pub warning_delay_type: WarningDelayType,
pub value: RepeaterWarningDelayValueType,
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.path)
}
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 RadioTarget<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
Some(self.value)
}
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 PlainLink<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 AngleLink<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 OrgMacro<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 Entity<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 LatexFragment<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 ExportSnippet<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 FootnoteReference<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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 CitationReference<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
fn get_post_blank(&self) -> PostBlank {
0
}
}
impl<'s> StandardProperties<'s> for InlineBabelCall<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 InlineSourceBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 LineBreak<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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 StatisticsCookie<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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 Subscript<'s> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
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> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
None
}
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 PlainText<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
// This field does not actually exist in emacs for plaintext
Some(self.source)
}
fn get_post_blank(&self) -> PostBlank {
// This field does not actually exist in emacs for plaintext
0
}
}
impl<'s> Timestamp<'s> {
@@ -718,7 +1030,7 @@ impl<'s> Timestamp<'s> {
}
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub enum LinkType<'s> {
File,
Protocol(Cow<'s, str>),
@@ -787,7 +1099,7 @@ impl<'s> OrgMacro<'s> {
}
}
#[derive(Debug, PartialEq)]
#[derive(Debug)]
pub enum FootnoteReferenceType {
Standard,
Inline,
@@ -802,3 +1114,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.
fn get_source<'b>(&'b self) -> &'s str;
// Get the slice of the AST node's contents.
//
// This corresponds to :contents-begin to :contents-end
// fn get_contents(&'s self) -> &'s str;
/// Get the slice of the AST node's contents.
///
/// This corresponds to :contents-begin to :contents-end
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.
@@ -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.
// 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;