62 Commits

Author SHA1 Message Date
Tom Alexander
fcd63b1231 fixup
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 21:58:41 -04:00
Tom Alexander
743b4c9982 fixup
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 21:55:20 -04:00
Tom Alexander
7eccf8fccd Remove dependency on run-docker-image task.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
2023-08-31 21:48:06 -04:00
Tom Alexander
a40a504f94 Switch to using read-only root in docker containers.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 21:23:51 -04:00
Tom Alexander
80d77ff5d6 Fix handling of unicode in compare tests.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 20:20:00 -04:00
Tom Alexander
ee92049e5d Merge branch 'special_block'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 19:19:29 -04:00
Tom Alexander
510985e97c Fix greater blocks by preventing nesting even if they are not the immediate parent. 2023-08-31 19:17:46 -04:00
Tom Alexander
949d0989f4 Add a test showing the current parser is broken with deeply nested greater blocks. 2023-08-31 19:15:28 -04:00
Tom Alexander
2a4d22bdd4 Add test for nesting of greater blocks.
This shows that greater blocks of different types can be directly nested even if they are both special blocks (as long as they are different special blocks).
2023-08-31 19:09:38 -04:00
Tom Alexander
7a903acedc Support special blocks in compare. 2023-08-31 19:04:44 -04:00
Tom Alexander
5171326d63 Create a test for special blocks. 2023-08-31 19:02:18 -04:00
Tom Alexander
67f79aeb51 Remove context from detect plain list.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 18:53:28 -04:00
Tom Alexander
b2383d9f93 Fix footnote definition performance.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
We were re-parsing each footnote definition in the footnote definition exit matcher which causes their contents to get re-parsed. This compounds with long lists of footnote definitions.
2023-08-31 18:47:23 -04:00
Tom Alexander
9e2a323f6f Merge branch 'trailing_whitespace_at_end_of_file'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 17:29:52 -04:00
Tom Alexander
0fcb3f73f9 Move handling of contentless item to handle contentless description item. 2023-08-31 17:25:42 -04:00
Tom Alexander
bfc9e7f58d When re-parsing last item in list, only disable white space consumption for last element in item. 2023-08-31 17:08:21 -04:00
Tom Alexander
b5f0521b56 Only consume trailing whitespace when not exiting for all objects. 2023-08-31 15:45:31 -04:00
Tom Alexander
2048d8f0b6 Support comments at the end of the line in diary sexp.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 15:31:54 -04:00
Tom Alexander
466716881e Fix macros consuming whitespace even when the exit matcher is calling for an exit.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 15:11:29 -04:00
Tom Alexander
eb9c582fa5 '>' is allowed as a post character in text markup.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:57:15 -04:00
Tom Alexander
214e895d85 Support backslash at the end of a macro. 2023-08-29 23:42:16 -04:00
Tom Alexander
db3086743c Publish version 0.1.4.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:28:17 -04:00
Tom Alexander
207a0546b0 Run full test suite despite default feature selection.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:23:31 -04:00
Tom Alexander
e9480fd156 Disable the compare tests when the compare feature is disabled.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 23:17:46 -04:00
Tom Alexander
28aca041f7 Publish version 0.1.3.
Some checks failed
rustfmt Build rustfmt has failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
2023-08-29 23:05:36 -04:00
Tom Alexander
d82def2a70 Merge branch 'compare_improvements'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 22:58:42 -04:00
Tom Alexander
d471f7178b Wrap the components of headlines in diff layers. 2023-08-29 22:57:08 -04:00
Tom Alexander
2c5c26c55f Allow diff layers that are not associated with tokens. 2023-08-29 22:47:40 -04:00
Tom Alexander
7944659802 Compare headline level. 2023-08-29 22:11:56 -04:00
Tom Alexander
58aca53144 Move get_property into util. 2023-08-29 22:07:23 -04:00
Tom Alexander
6f2d90162b Do not use the plain text parser for property drawers.
This additional exit condition was causing property drawers to parse incorrectly.
2023-08-29 22:03:20 -04:00
Tom Alexander
f170a557ed Use character offsets in diff. 2023-08-29 21:49:16 -04:00
Tom Alexander
eaa38ce772 Include an error message for failed bounds checking.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 21:27:54 -04:00
Tom Alexander
a6d742a536 Fix handling line breaks after divider in description lists. 2023-08-29 21:27:54 -04:00
Tom Alexander
45be9e7bde Merge branch 'description_list'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 19:36:45 -04:00
Tom Alexander
f6c895319f Add support for diffing description lists. 2023-08-29 19:35:54 -04:00
Tom Alexander
2682779534 Add support for parsing description lists. 2023-08-29 18:13:55 -04:00
Tom Alexander
b48d472546 Fix build.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 18:07:35 -04:00
Tom Alexander
ea6faf728c Add test for description list. 2023-08-29 18:07:35 -04:00
Tom Alexander
f4ea1b7303 Merge branch 'accuracy_fixes_from_feeding_large_documents'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has failed
2023-08-29 17:49:19 -04:00
Tom Alexander
80b55fdd45 Consume trailing whitespace for macro. 2023-08-29 17:42:58 -04:00
Tom Alexander
f426e32798 Do not include trailing punctuation or whitespace in plain links. 2023-08-29 17:35:04 -04:00
Tom Alexander
66037356c5 Add test showing plain links ending in punctuation currently do not parse correctly. 2023-08-29 17:24:44 -04:00
Tom Alexander
1bcd1895c0 Allow affiliating keywords with regular keywords. 2023-08-29 17:19:13 -04:00
Tom Alexander
e3d38cfbe2 Add a test for floating affiliated keywords. 2023-08-29 17:05:19 -04:00
Tom Alexander
2ba0dc49be Support export affiliated keywords. 2023-08-29 17:01:35 -04:00
Tom Alexander
9df40fb13f Only allow specific keywords for affiliated keywords. 2023-08-29 16:56:07 -04:00
Tom Alexander
cc671925db Support empty sections under headings. 2023-08-29 16:07:43 -04:00
Tom Alexander
950baa9d5d Only allow a single section under a heading. 2023-08-29 16:03:13 -04:00
Tom Alexander
56865c68fc Do not allow plain links without a path. 2023-08-29 15:44:04 -04:00
Tom Alexander
f592b73ae7 Merge branch 'reduce_context_usage_in_exit_matchers'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 15:17:16 -04:00
Tom Alexander
3206027b96 Add all entities. 2023-08-29 15:16:22 -04:00
Tom Alexander
3e6df7ba78 Print character offset from rust's parse perspective during compare. 2023-08-29 14:40:58 -04:00
Tom Alexander
ac313d093e Improve error handling in compare. 2023-08-29 14:20:53 -04:00
Tom Alexander
f376f1cf8e Add a test for empty sections. 2023-08-29 14:10:26 -04:00
Tom Alexander
f21385a901 Add a helper function for logging during debugging.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-29 14:03:14 -04:00
Tom Alexander
1d06d95bb1 Add a minimum stars figure to heading parser to reduce re-parses. 2023-08-29 14:01:28 -04:00
Tom Alexander
bfc88c1d1b Use a detect_headline function instead of the full headline parse for the section_end exit matcher.
This shaved 2 seconds off the first 800 lines of org-mode/doc/org-guide.org.
2023-08-29 11:35:54 -04:00
Tom Alexander
f29720e5b9 Switch to using a type for bracket depth.
This is to make changing the type easier in the future.
2023-08-29 11:18:15 -04:00
Tom Alexander
27a9b5aeb1 Switch to i16 for backet depth count.
This is having a measurable performance increase. 32k bracket depth should be enough for any non-malicious document.
2023-08-29 11:14:50 -04:00
Tom Alexander
8051c3d2b7 Remove line number tracking.
The documentation was incorrect, none of the org-mode elements have a line number restriction for their contents.
2023-08-29 11:09:28 -04:00
Tom Alexander
bd97d2f69d Switch to i32 for tracking bracket depth. 2023-08-29 11:07:00 -04:00
65 changed files with 2169 additions and 637 deletions

View File

@@ -99,20 +99,41 @@ spec:
runAfter: runAfter:
- fetch-repository - fetch-repository
- name: run-image - name: run-image
taskRef: taskSpec:
name: run-docker-image metadata: {}
stepTemplate:
name: ""
workingDir: "$(workspaces.source.path)"
workspaces:
- name: source
mountPath: /source
- name: cargo-cache
mountPath: /usr/local/cargo/registry
optional: true
steps:
- name: run
image: "$(params.IMAGE)"
command: []
args:
[
--no-default-features,
--features,
compare,
--no-fail-fast,
--lib,
--test,
test_loader,
]
workspaces: workspaces:
- name: source - name: source
workspace: git-source workspace: git-source
- name: cargo-cache - name: cargo-cache
workspace: cargo-cache workspace: cargo-cache
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
runAfter: runAfter:
- build-image - build-image
params:
- name: args
value: [--no-fail-fast, --lib, --test, test_loader]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally: finally:
- name: report-success - name: report-success
when: when:

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.2" version = "0.1.4"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser." description = "An org-mode parser."
edition = "2021" edition = "2021"
@@ -13,8 +13,7 @@ resolver = "2"
include = [ include = [
"LICENSE", "LICENSE",
"**/*.rs", "**/*.rs",
"Cargo.toml", "Cargo.toml"
"tests/*"
] ]
[lib] [lib]
@@ -40,7 +39,7 @@ tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-fil
walkdir = "2.3.3" walkdir = "2.3.3"
[features] [features]
default = ["compare"] default = []
compare = [] compare = []
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"] tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]

View File

@@ -35,12 +35,12 @@ clean:
.PHONY: test .PHONY: test
test: test:
> cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: dockertest .PHONY: dockertest
dockertest: dockertest:
> $(MAKE) -C docker/organic_test > $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t -v "$$(readlink -f ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS) > docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: dockerclean .PHONY: dockerclean
dockerclean: dockerclean:
@@ -49,18 +49,18 @@ dockerclean:
.PHONY: integrationtest .PHONY: integrationtest
integrationtest: integrationtest:
> cargo test --no-fail-fast --test test_loader -- --test-threads $(TESTJOBS) > cargo test --no-default-features --features compare --no-fail-fast --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: unittest .PHONY: unittest
unittest: unittest:
> cargo test --lib -- --test-threads $(TESTJOBS) > cargo test --no-default-features --lib -- --test-threads $(TESTJOBS)
.PHONY: jaeger .PHONY: jaeger
jaeger: jaeger:
# 4317 for OTLP gRPC, 4318 for OTLP HTTP. We currently use gRPC but I forward both ports regardless. # 4317 for OTLP gRPC, 4318 for OTLP HTTP. We currently use gRPC but I forward both ports regardless.
# #
# These flags didn't help even though they seem like they would: --collector.queue-size=20000 --collector.num-workers=100 # These flags didn't help even though they seem like they would: --collector.queue-size=20000 --collector.num-workers=100
> docker run -d --rm --name organicdocker -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=20000000 --collector.otlp.grpc.max-message-size=20000000 > docker run -d --rm --name organicdocker --read-only -p 4317:4317 -p 4318:4318 -p 16686:16686 -e COLLECTOR_OTLP_ENABLED=true jaegertracing/all-in-one:1.47 --collector.grpc-server.max-message-size=20000000 --collector.otlp.grpc.max-message-size=20000000
.PHONY: jaegerweb .PHONY: jaegerweb
jaegerweb: jaegerweb:

View File

@@ -1,10 +1,16 @@
#[cfg(feature = "compare")]
use std::env; use std::env;
#[cfg(feature = "compare")]
use std::fs::File; use std::fs::File;
#[cfg(feature = "compare")]
use std::io::Write; use std::io::Write;
#[cfg(feature = "compare")]
use std::path::Path; use std::path::Path;
#[cfg(feature = "compare")]
use walkdir::WalkDir; use walkdir::WalkDir;
#[cfg(feature = "compare")]
fn main() { fn main() {
let out_dir = env::var("OUT_DIR").unwrap(); let out_dir = env::var("OUT_DIR").unwrap();
let destination = Path::new(&out_dir).join("tests.rs"); let destination = Path::new(&out_dir).join("tests.rs");
@@ -31,6 +37,10 @@ fn main() {
} }
} }
#[cfg(not(feature = "compare"))]
fn main() {}
#[cfg(feature = "compare")]
fn write_test(test_file: &mut File, test: &walkdir::DirEntry) { fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
let test_name = test let test_name = test
.path() .path()
@@ -55,6 +65,7 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
.unwrap(); .unwrap();
} }
#[cfg(feature = "compare")]
fn write_header(test_file: &mut File) { fn write_header(test_file: &mut File) {
write!( write!(
test_file, test_file,
@@ -70,6 +81,7 @@ use organic::parser::sexp::sexp_with_padding;
.unwrap(); .unwrap();
} }
#[cfg(feature = "compare")]
fn is_expect_fail(name: &str) -> Option<&str> { fn is_expect_fail(name: &str) -> Option<&str> {
match name { match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."), "autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),

View File

@@ -29,8 +29,8 @@ endif
# NOTE: This target will write to folders underneath the git-root # NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME) docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME) docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)

View File

@@ -30,8 +30,8 @@ endif
# NOTE: This target will write to folders underneath the git-root # NOTE: This target will write to folders underneath the git-root
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init -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" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh -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" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)

View File

@@ -26,6 +26,7 @@ RUN make DESTDIR="/root/dist" install
FROM rustlang/rust:nightly-alpine3.17 FROM rustlang/rust:nightly-alpine3.17
ENV LANG=en_US.UTF-8
RUN apk add --no-cache musl-dev ncurses gnutls RUN apk add --no-cache musl-dev ncurses gnutls
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
COPY --from=build-emacs /root/dist/ / COPY --from=build-emacs /root/dist/ /

View File

@@ -29,8 +29,8 @@ endif
.PHONY: run .PHONY: run
run: build run: build
docker run --rm --init -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) --no-fail-fast --lib --test test_loader 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) --no-default-features --features compare --no-fail-fast --lib --test test_loader
.PHONY: shell .PHONY: shell
shell: build shell: build
docker run --rm -i -t --entrypoint /bin/sh -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) 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)

1
elisp_snippets/README.md Normal file
View File

@@ -0,0 +1 @@
This folder is for snippets of elisp that are useful for development.

View File

@@ -0,0 +1,3 @@
(dolist (var org-element-affiliated-keywords)
(message "\"%s\"," (downcase var))
)

View File

@@ -0,0 +1,5 @@
(dolist (var org-entities)
(when (listp var)
(message "\"%s\"," (nth 0 var))
)
)

View File

@@ -0,0 +1,18 @@
#+begin_defun
foo
#+begin_lorem
,#+begin_center
bar
,#+end_center
ipsum
#+end_lorem
baz
#+end_defun
#+begin_center
#+begin_quote
#+begin_center
lorem
#+end_center
#+end_quote
#+end_center

View File

@@ -0,0 +1,12 @@
#+begin_defun
foo
#+begin_lorem
ipsum
#+end_lorem
bar
#+begin_center
#+begin_quote
baz
#+end_quote
#+end_center
#+end_defun

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
- foo :: bar
- cat ::
dog
- lorem
:: ipsum
-
lorem :: ipsum
- dolar *bold* foo :: ipsum
- big gap ::
stuff

View File

@@ -0,0 +1 @@
- {{{foo(bar)}}} :: baz

View File

@@ -0,0 +1,2 @@
- foo

View File

@@ -0,0 +1,7 @@
** foo
:PROPERTIES:
:DESCRIPTION: lorem
:ALT_TITLE: ipsum
:END:
bar

View File

@@ -0,0 +1 @@
%%(foo bar) ; baz

View File

@@ -0,0 +1,15 @@
#+name: foo
#+caption: bar
#+caption: baz
[[file:lorem/ipsum.png]]
#+name: cat
#+foo: dog
[[file:lorem/ipsum.png]]
#+name: cat
#+foo: dog
foo

View File

@@ -0,0 +1,52 @@
non-link text
eww://
rmail://
mhe://
irc://
info://
gnus://
docview://
bibtex://
bbdb://
w3m://
doi://
file+sys://
file+emacs://
shell://
news://
mailto://
https://
http://
ftp://
help://
file://
elisp://
randomfakeprotocl://
non-link text
non-link text
eww:
rmail:
mhe:
irc:
info:
gnus:
docview:
bibtex:
bbdb:
w3m:
doi:
file+sys:
file+emacs:
shell:
news:
mailto:
https:
http:
ftp:
help:
file:
elisp:
randomfakeprotocl:
non-link text

View File

@@ -0,0 +1,3 @@
mailto:foo@bar.baz.
mailto:foo@bar.baz....

View File

@@ -0,0 +1 @@
foo ==>bar=.

View File

@@ -0,0 +1,9 @@
* Foo
* Bar
* Baz

View File

@@ -0,0 +1 @@
🧡💛💚💙💜

View File

@@ -41,6 +41,7 @@ function launch_container {
if [ "$SHELL" != "YES" ]; then if [ "$SHELL" != "YES" ]; then
local features_joined=$(IFS=","; echo "${features[*]}") local features_joined=$(IFS=","; echo "${features[*]}")
additional_args+=(cargo run --no-default-features --features "$features_joined") additional_args+=(cargo run --no-default-features --features "$features_joined")
additional_flags+=(--read-only)
else else
additional_args+=(/bin/sh) additional_args+=(/bin/sh)
additional_flags+=(-t) additional_flags+=(-t)
@@ -50,7 +51,7 @@ function launch_container {
additional_flags+=(--env RUST_BACKTRACE=full) additional_flags+=(--env RUST_BACKTRACE=full)
fi fi
docker run "${additional_flags[@]}" --init --rm -i -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
} }
main "${@}" main "${@}"

View File

@@ -33,7 +33,7 @@ function get_test_names {
local test_file_full_path=$($REALPATH "$test_file") local test_file_full_path=$($REALPATH "$test_file")
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path") local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
local without_extension="${relative_to_samples%.org}" local without_extension="${relative_to_samples%.org}"
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]' echo "autogen_${without_extension//\//_}" | tr '[:upper:]' '[:lower:]'
else else
echo "$test_file" | tr '[:upper:]' '[:lower:]' echo "$test_file" | tr '[:upper:]' '[:lower:]'
fi fi
@@ -52,11 +52,11 @@ function launch_container {
set -euo pipefail set -euo pipefail
IFS=\$'\n\t' IFS=\$'\n\t'
cargo test --no-fail-fast --lib --test test_loader "$test" -- --show-output cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader "$test" -- --show-output
EOF EOF
) )
docker run "${additional_flags[@]}" --init --rm -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script" docker run "${additional_flags[@]}" --init --rm --read-only --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
} }

View File

@@ -12,7 +12,7 @@ function main {
local test local test
while read test; do while read test; do
cargo test --no-fail-fast --test test_loader "$test" -- --show-output cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output
done<<<"$test_names" done<<<"$test_names"
} }
@@ -25,7 +25,7 @@ function get_test_names {
local test_file_full_path=$($REALPATH "$test_file") local test_file_full_path=$($REALPATH "$test_file")
local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path") local relative_to_samples=$($REALPATH --relative-to "$samples_dir" "$test_file_full_path")
local without_extension="${relative_to_samples%.org}" local without_extension="${relative_to_samples%.org}"
echo "${without_extension/\//_}" | tr '[:upper:]' '[:lower:]' echo "${without_extension//\//_}" | tr '[:upper:]' '[:lower:]'
else else
echo "$test_file" | tr '[:upper:]' '[:lower:]' echo "$test_file" | tr '[:upper:]' '[:lower:]'
fi fi

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get the offset into source that the rust object exists at. /// Get the offset into source that the rust object exists at.
/// ///
/// These offsets are zero-based unlike the elisp ones. /// These offsets are zero-based unlike the elisp ones.
pub fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) { fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) {
let rust_object_source = rust_object.get_source(); let rust_object_source = rust_object.get_source();
assert!(is_slice_of(source, rust_object_source)); assert!(is_slice_of(source, rust_object_source));
let offset = rust_object_source.as_ptr() as usize - source.as_ptr() as usize; let offset = rust_object_source.as_ptr() as usize - source.as_ptr() as usize;
@@ -50,8 +50,11 @@ pub fn assert_bounds<'s, S: Source<'s>>(
standard_properties.end.ok_or("Token should have an end.")?, standard_properties.end.ok_or("Token should have an end.")?,
); );
let (rust_begin, rust_end) = get_offsets(source, rust); let (rust_begin, rust_end) = get_offsets(source, rust);
if (rust_begin + 1) != begin || (rust_end + 1) != end { let rust_begin_char_offset = (&source[..rust_begin]).chars().count();
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?; let rust_end_char_offset =
rust_begin_char_offset + (&source[rust_begin..rust_end]).chars().count();
if (rust_begin_char_offset + 1) != begin || (rust_end_char_offset + 1) != 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 + 1, rust_end = rust_end_char_offset + 1, emacs_begin=begin, emacs_end=end))?;
} }
Ok(()) Ok(())
@@ -137,3 +140,23 @@ fn maybe_token_to_usize(
.flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil .flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil
.map_or(Ok(None), |r| r.map(Some))?) .map_or(Ok(None), |r| r.map(Some))?)
} }
pub fn get_property<'s, 'x>(
emacs: &'s Token<'s>,
key: &'x str,
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let prop = attributes_map
.get(key)
.ok_or(format!("Missing {} attribute.", key))?;
match prop.as_atom() {
Ok("nil") => return Ok(None),
_ => {}
};
Ok(Some(*prop))
}

View File

@@ -55,20 +55,21 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> { fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let emacs_version = get_emacs_version()?; let emacs_version = get_emacs_version()?;
let org_mode_version = get_org_mode_version()?; let org_mode_version = get_org_mode_version()?;
let org_contents = org_contents.as_ref();
eprintln!("Using emacs version: {}", emacs_version.trim()); eprintln!("Using emacs version: {}", emacs_version.trim());
eprintln!("Using org-mode version: {}", org_mode_version.trim()); eprintln!("Using org-mode version: {}", org_mode_version.trim());
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?; let (remaining, rust_parsed) = document(org_contents).map_err(|e| e.to_string())?;
let org_sexp = emacs_parse_org_document(org_contents.as_ref())?; let org_sexp = emacs_parse_org_document(org_contents)?;
let (_remaining, parsed_sexp) = let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?; sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents.as_ref()); println!("{}\n\n\n", org_contents);
println!("{}", org_sexp); println!("{}", org_sexp);
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics // We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print()?; diff_result.print(org_contents)?;
if diff_result.is_bad() { if diff_result.is_bad() {
Err("Diff results do not match.")?; Err("Diff results do not match.")?;
@@ -85,7 +86,7 @@ fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error:
eprintln!( eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing." "This program was built with compare disabled. Only parsing with organic, not comparing."
); );
let (remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?; let (_remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
Ok(()) Ok(())
} }

View File

@@ -5,6 +5,7 @@ use nom::combinator::recognize;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
@@ -26,6 +27,8 @@ pub fn angle_link<'r, 's>(
let (remaining, _separator) = tag(":")(remaining)?; let (remaining, _separator) = tag(":")(remaining)?;
let (remaining, path) = path_angle(context, remaining)?; let (remaining, path) = path_angle(context, remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,

View File

@@ -2,7 +2,6 @@ use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -12,7 +11,9 @@ use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::citation_reference::must_balance_bracket; use super::citation_reference::must_balance_bracket;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
@@ -47,7 +48,8 @@ pub fn citation<'r, 's>(
parser_with_context!(global_suffix)(context), parser_with_context!(global_suffix)(context),
))))(remaining)?; ))))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -102,7 +104,7 @@ fn global_prefix<'r, 's>(
} }
fn global_prefix_end( fn global_prefix_end(
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_global_prefix_end(context, input, starting_bracket_depth) _global_prefix_end(context, input, starting_bracket_depth)
@@ -113,7 +115,7 @@ fn global_prefix_end(
fn _global_prefix_end<'r, 's>( fn _global_prefix_end<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 { if current_depth < 0 {
@@ -154,7 +156,7 @@ fn global_suffix<'r, 's>(
} }
fn global_suffix_end( fn global_suffix_end(
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_global_suffix_end(context, input, starting_bracket_depth) _global_suffix_end(context, input, starting_bracket_depth)
@@ -165,7 +167,7 @@ fn global_suffix_end(
fn _global_suffix_end<'r, 's>( fn _global_suffix_end<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 { if current_depth < 0 {

View File

@@ -10,6 +10,7 @@ use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
@@ -109,7 +110,7 @@ fn key_suffix<'r, 's>(
} }
fn key_prefix_end( fn key_prefix_end(
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_key_prefix_end(context, input, starting_bracket_depth) _key_prefix_end(context, input, starting_bracket_depth)
@@ -120,7 +121,7 @@ fn key_prefix_end(
fn _key_prefix_end<'r, 's>( fn _key_prefix_end<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 { if current_depth < 0 {
@@ -140,7 +141,7 @@ fn _key_prefix_end<'r, 's>(
} }
fn key_suffix_end( fn key_suffix_end(
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_key_suffix_end(context, input, starting_bracket_depth) _key_suffix_end(context, input, starting_bracket_depth)
@@ -151,7 +152,7 @@ fn key_suffix_end(
fn _key_suffix_end<'r, 's>( fn _key_suffix_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 { if current_depth < 0 {

View File

@@ -1,9 +1,12 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@@ -24,6 +27,11 @@ pub fn diary_sexp<'r, 's>(
let (remaining, _clock) = tag("%%")(remaining)?; let (remaining, _clock) = tag("%%")(remaining)?;
let (remaining, _gap_whitespace) = space0(remaining)?; let (remaining, _gap_whitespace) = space0(remaining)?;
let (remaining, _sexp) = recognize(sexp)(remaining)?; let (remaining, _sexp) = recognize(sexp)(remaining)?;
let (remaining, _trailing_comment) = opt(tuple((
space0,
tag(";"),
many_till(anychar, alt((line_ending, eof))),
)))(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?; recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;

View File

@@ -139,7 +139,7 @@ fn _document<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> { ) -> Res<OrgSource<'s>, Document<'s>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context); let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading)(context); let heading_matcher = parser_with_context!(heading(0))(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?; let (remaining, _blank_lines) = many0(blank_line)(input)?;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?; let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?; let (remaining, children) = many0(heading_matcher)(remaining)?;
@@ -260,30 +260,44 @@ fn section<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'r, 's>( fn section_end<'r, 's>(
context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let headline_matcher = parser_with_context!(headline)(context); recognize(detect_headline)(input)
recognize(headline_matcher)(input) }
const fn heading(
parent_stars: usize,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Heading<'s>> {
move |context: Context, input: OrgSource<'_>| _heading(context, input, parent_stars)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading<'r, 's>( fn _heading<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> { ) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?; not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) = let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
headline(context, input)?; headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context); let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading)(context); let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, children) = many0(alt(( let (remaining, maybe_section) =
map( opt(map(section_matcher, DocumentElement::Section))(remaining)?;
verify(heading_matcher, |h| h.stars > star_count), let (remaining, mut children) =
DocumentElement::Heading, many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
), if let Some(section) = maybe_section {
map(section_matcher, DocumentElement::Section), children.insert(0, section);
)))(remaining)?; }
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -299,10 +313,17 @@ fn heading<'r, 's>(
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>( fn headline<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
parent_stars: usize,
) -> Res< ) -> Res<
OrgSource<'s>, OrgSource<'s>,
( (
@@ -325,7 +346,9 @@ fn headline<'r, 's>(
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending), (_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple(( ) = tuple((
start_of_line, start_of_line,
many1_count(tag("*")), verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1, space1,
opt(tuple((heading_keyword, space1))), opt(tuple((heading_keyword, space1))),
many1(standard_set_object_matcher), many1(standard_set_object_matcher),

View File

@@ -12,6 +12,7 @@ use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::footnote_definition; use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block; use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule; use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::keyword; use super::keyword::keyword;
use super::latex_environment::latex_environment; use super::latex_environment::latex_environment;
use super::lesser_block::comment_block; use super::lesser_block::comment_block;
@@ -62,10 +63,12 @@ fn _element<'r, 's>(
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context); let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context); let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context); let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context); let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context); let latex_environment_matcher = parser_with_context!(latex_environment)(context);
let (remaining, mut affiliated_keywords) = many0(keyword_matcher)(input)?; // TODO: Affiliated keywords cannot be on comments, clocks, headings, inlinetasks, items, node properties, planning, property drawers, sections, and table rows
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
let (remaining, mut element) = match alt(( let (remaining, mut element) = match alt((
map(plain_list_matcher, Element::PlainList), map(plain_list_matcher, Element::PlainList),
map(greater_block_matcher, Element::GreaterBlock), map(greater_block_matcher, Element::GreaterBlock),
@@ -84,6 +87,7 @@ fn _element<'r, 's>(
map(fixed_width_area_matcher, Element::FixedWidthArea), map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule), map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment), map(latex_environment_matcher, Element::LatexEnvironment),
map(keyword_matcher, Element::Keyword),
))(remaining) ))(remaining)
{ {
the_ok @ Ok(_) => the_ok, the_ok @ Ok(_) => the_ok,
@@ -93,12 +97,12 @@ fn _element<'r, 's>(
the_ok @ Ok(_) => the_ok, the_ok @ Ok(_) => the_ok,
Err(_) => { Err(_) => {
affiliated_keywords.clear(); affiliated_keywords.clear();
map(keyword_matcher, Element::Keyword)(input) map(affiliated_keyword_matcher, Element::Keyword)(input)
} }
} }
} else { } else {
affiliated_keywords.clear(); affiliated_keywords.clear();
map(keyword_matcher, Element::Keyword)(input) map(affiliated_keyword_matcher, Element::Keyword)(input)
} }
} }
}?; }?;
@@ -124,7 +128,7 @@ fn _detect_element<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(context, input).is_ok() { if detect_plain_list(input).is_ok() {
return Ok((input, ())); return Ok((input, ()));
} }
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {

View File

@@ -2,18 +2,436 @@ use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::satisfy; use nom::character::complete::satisfy;
use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object::Entity; use crate::parser::object::Entity;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
// TODO: Make this a user-provided variable corresponding to elisp's org-entities
const ORG_ENTITIES: [&'static str; 413] = [
"Agrave",
"agrave",
"Aacute",
"aacute",
"Acirc",
"acirc",
"Amacr",
"amacr",
"Atilde",
"atilde",
"Auml",
"auml",
"Aring",
"AA",
"aring",
"AElig",
"aelig",
"Ccedil",
"ccedil",
"Egrave",
"egrave",
"Eacute",
"eacute",
"Ecirc",
"ecirc",
"Euml",
"euml",
"Igrave",
"igrave",
"Iacute",
"iacute",
"Idot",
"inodot",
"Icirc",
"icirc",
"Iuml",
"iuml",
"Ntilde",
"ntilde",
"Ograve",
"ograve",
"Oacute",
"oacute",
"Ocirc",
"ocirc",
"Otilde",
"otilde",
"Ouml",
"ouml",
"Oslash",
"oslash",
"OElig",
"oelig",
"Scaron",
"scaron",
"szlig",
"Ugrave",
"ugrave",
"Uacute",
"uacute",
"Ucirc",
"ucirc",
"Uuml",
"uuml",
"Yacute",
"yacute",
"Yuml",
"yuml",
"fnof",
"real",
"image",
"weierp",
"ell",
"imath",
"jmath",
"Alpha",
"alpha",
"Beta",
"beta",
"Gamma",
"gamma",
"Delta",
"delta",
"Epsilon",
"epsilon",
"varepsilon",
"Zeta",
"zeta",
"Eta",
"eta",
"Theta",
"theta",
"thetasym",
"vartheta",
"Iota",
"iota",
"Kappa",
"kappa",
"Lambda",
"lambda",
"Mu",
"mu",
"nu",
"Nu",
"Xi",
"xi",
"Omicron",
"omicron",
"Pi",
"pi",
"Rho",
"rho",
"Sigma",
"sigma",
"sigmaf",
"varsigma",
"Tau",
"Upsilon",
"upsih",
"upsilon",
"Phi",
"phi",
"varphi",
"Chi",
"chi",
"acutex",
"Psi",
"psi",
"tau",
"Omega",
"omega",
"piv",
"varpi",
"partial",
"alefsym",
"aleph",
"gimel",
"beth",
"dalet",
"ETH",
"eth",
"THORN",
"thorn",
"dots",
"cdots",
"hellip",
"middot",
"iexcl",
"iquest",
"shy",
"ndash",
"mdash",
"quot",
"acute",
"ldquo",
"rdquo",
"bdquo",
"lsquo",
"rsquo",
"sbquo",
"laquo",
"raquo",
"lsaquo",
"rsaquo",
"circ",
"vert",
"vbar",
"brvbar",
"S",
"sect",
"amp",
"lt",
"gt",
"tilde",
"slash",
"plus",
"under",
"equal",
"asciicirc",
"dagger",
"dag",
"Dagger",
"ddag",
"nbsp",
"ensp",
"emsp",
"thinsp",
"curren",
"cent",
"pound",
"yen",
"euro",
"EUR",
"dollar",
"USD",
"copy",
"reg",
"trade",
"minus",
"pm",
"plusmn",
"times",
"frasl",
"colon",
"div",
"frac12",
"frac14",
"frac34",
"permil",
"sup1",
"sup2",
"sup3",
"radic",
"sum",
"prod",
"micro",
"macr",
"deg",
"prime",
"Prime",
"infin",
"infty",
"prop",
"propto",
"not",
"neg",
"land",
"wedge",
"lor",
"vee",
"cap",
"cup",
"smile",
"frown",
"int",
"therefore",
"there4",
"because",
"sim",
"cong",
"simeq",
"asymp",
"approx",
"ne",
"neq",
"equiv",
"triangleq",
"le",
"leq",
"ge",
"geq",
"lessgtr",
"lesseqgtr",
"ll",
"Ll",
"lll",
"gg",
"Gg",
"ggg",
"prec",
"preceq",
"preccurlyeq",
"succ",
"succeq",
"succcurlyeq",
"sub",
"subset",
"sup",
"supset",
"nsub",
"sube",
"nsup",
"supe",
"setminus",
"forall",
"exist",
"exists",
"nexist",
"nexists",
"empty",
"emptyset",
"isin",
"in",
"notin",
"ni",
"nabla",
"ang",
"angle",
"perp",
"parallel",
"sdot",
"cdot",
"lceil",
"rceil",
"lfloor",
"rfloor",
"lang",
"rang",
"langle",
"rangle",
"hbar",
"mho",
"larr",
"leftarrow",
"gets",
"lArr",
"Leftarrow",
"uarr",
"uparrow",
"uArr",
"Uparrow",
"rarr",
"to",
"rightarrow",
"rArr",
"Rightarrow",
"darr",
"downarrow",
"dArr",
"Downarrow",
"harr",
"leftrightarrow",
"hArr",
"Leftrightarrow",
"crarr",
"hookleftarrow",
"arccos",
"arcsin",
"arctan",
"arg",
"cos",
"cosh",
"cot",
"coth",
"csc",
"deg",
"det",
"dim",
"exp",
"gcd",
"hom",
"inf",
"ker",
"lg",
"lim",
"liminf",
"limsup",
"ln",
"log",
"max",
"min",
"Pr",
"sec",
"sin",
"sinh",
"sup",
"tan",
"tanh",
"bull",
"bullet",
"star",
"lowast",
"ast",
"odot",
"oplus",
"otimes",
"check",
"checkmark",
"para",
"ordf",
"ordm",
"cedil",
"oline",
"uml",
"zwnj",
"zwj",
"lrm",
"rlm",
"smiley",
"blacksmile",
"sad",
"frowny",
"clubs",
"clubsuit",
"spades",
"spadesuit",
"hearts",
"heartsuit",
"diams",
"diamondsuit",
"diamond",
"Diamond",
"loz",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
"_ ",
];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn entity<'r, 's>( pub fn entity<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
@@ -21,11 +439,9 @@ pub fn entity<'r, 's>(
) -> Res<OrgSource<'s>, Entity<'s>> { ) -> Res<OrgSource<'s>, Entity<'s>> {
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
let (remaining, entity_name) = name(context, remaining)?; let (remaining, entity_name) = name(context, remaining)?;
let (remaining, _) = alt(( let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?;
tag("{}"), let (remaining, _trailing_whitespace) =
peek(recognize(parser_with_context!(entity_end)(context))), maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
))(remaining)?;
let (remaining, _) = space0(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -43,20 +459,23 @@ fn name<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-entities and optionally org-entities-user // TODO: This should be defined by org-entities and optionally org-entities-user
for entity in ORG_ENTITIES {
let result = tag_no_case::<_, _, CustomError<_>>(entity)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
}
}
// TODO: Add the rest of the entities, this is a very incomplete list Err(nom::Err::Error(CustomError::MyError(MyError(
let (remaining, proto) = alt((alt(( "NoEntity".into(),
tag_no_case("delta"), ))))
tag_no_case("pi"),
tag_no_case("ast"),
tag_no_case("lt"),
tag_no_case("gt"),
)),))(input)?;
Ok((remaining, proto))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn entity_end<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { fn entity_end<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = alt((eof, recognize(satisfy(|c| !c.is_alphabetic()))))(input)?; let (remaining, _) = alt((eof, recognize(satisfy(|c| !c.is_alphabetic()))))(input)?;
Ok((remaining, ())) Ok((remaining, ()))

View File

@@ -8,6 +8,7 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
@@ -35,6 +36,8 @@ pub fn export_snippet<'r, 's>(
parser_with_context!(contents)(&parser_context), parser_with_context!(contents)(&parser_context),
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?; let (remaining, _) = tag("@@")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,

View File

@@ -80,21 +80,10 @@ fn footnote_definition_end<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let allow_nesting_context =
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
if immediate_in_section(context, "footnote definition") {
&allow_nesting_context
} else {
context
},
);
let maybe_consume_trailing_whitespace_matcher =
parser_with_context!(maybe_consume_trailing_whitespace)(context);
let (remaining, source) = alt(( let (remaining, source) = alt((
recognize(tuple(( recognize(tuple((
maybe_consume_trailing_whitespace_matcher, parser_with_context!(maybe_consume_trailing_whitespace)(context),
footnote_definition_matcher, detect_footnote_definition,
))), ))),
recognize(tuple(( recognize(tuple((
start_of_line, start_of_line,
@@ -107,6 +96,12 @@ fn footnote_definition_end<'r, 's>(
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -1,12 +1,13 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::space0;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_context::ContextElement; use super::parser_context::ContextElement;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -53,7 +54,8 @@ fn anonymous_footnote<'r, 's>(
)(remaining)?; )(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -88,7 +90,8 @@ fn inline_footnote<'r, 's>(
)(remaining)?; )(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -102,13 +105,14 @@ fn inline_footnote<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_reference_only<'r, 's>( fn footnote_reference_only<'r, 's>(
_context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?; let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?; let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -121,7 +125,7 @@ fn footnote_reference_only<'r, 's>(
} }
fn footnote_definition_end( fn footnote_definition_end(
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_footnote_definition_end(context, input, starting_bracket_depth) _footnote_definition_end(context, input, starting_bracket_depth)
@@ -132,7 +136,7 @@ fn footnote_definition_end(
fn _footnote_definition_end<'r, 's>( fn _footnote_definition_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {

View File

@@ -12,6 +12,7 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::in_section;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -26,7 +27,6 @@ use crate::parser::source::SetSource;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Element; use crate::parser::Element;
use crate::parser::Paragraph; use crate::parser::Paragraph;
@@ -49,11 +49,11 @@ pub fn greater_block<'r, 's>(
}), }),
))(remaining)?; ))(remaining)?;
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() { let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
"center" => "center block", "center" => "center block".to_owned(),
"quote" => "quote block", "quote" => "quote block".to_owned(),
_ => "greater block", name @ _ => format!("special block {}", name),
}; };
if immediate_in_section(context, context_name) { if in_section(context, context_name.as_str()) {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
@@ -63,7 +63,7 @@ pub fn greater_block<'r, 's>(
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = line_ending(remaining)?;
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context(context_name)) .with_additional_node(ContextElement::Context(context_name.as_str()))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha, class: ExitClass::Alpha,
exit_matcher: &exit_with_name, exit_matcher: &exit_with_name,

View File

@@ -1,6 +1,7 @@
use super::element::Element; use super::element::Element;
use super::lesser_element::TableCell; use super::lesser_element::TableCell;
use super::source::Source; use super::source::Source;
use super::Object;
#[derive(Debug)] #[derive(Debug)]
pub struct PlainList<'s> { pub struct PlainList<'s> {
@@ -13,6 +14,7 @@ pub struct PlainListItem<'s> {
pub source: &'s str, pub source: &'s str,
pub indentation: usize, pub indentation: usize,
pub bullet: &'s str, pub bullet: &'s str,
pub tag: Vec<Object<'s>>,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
} }

View File

@@ -4,13 +4,14 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -33,7 +34,8 @@ pub fn inline_babel_call<'r, 's>(
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _argument) = argument(context, remaining)?; let (remaining, _argument) = argument(context, remaining)?;
let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header2) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -91,7 +93,7 @@ fn header<'r, 's>(
} }
fn header_end( fn header_end(
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_header_end(context, input, starting_bracket_depth) _header_end(context, input, starting_bracket_depth)
@@ -102,7 +104,7 @@ fn header_end(
fn _header_end<'r, 's>( fn _header_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
@@ -141,7 +143,7 @@ fn argument<'r, 's>(
} }
fn argument_end( fn argument_end(
starting_parenthesis_depth: isize, starting_parenthesis_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_argument_end(context, input, starting_parenthesis_depth) _argument_end(context, input, starting_parenthesis_depth)
@@ -152,7 +154,7 @@ fn argument_end(
fn _argument_end<'r, 's>( fn _argument_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_parenthesis_depth: isize, starting_parenthesis_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth; let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
if current_depth > 0 { if current_depth > 0 {

View File

@@ -4,7 +4,6 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -12,7 +11,9 @@ use nom::multi::many_till;
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use tracing::span; use tracing::span;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -34,7 +35,8 @@ pub fn inline_source_block<'r, 's>(
let (remaining, _) = lang(context, remaining)?; let (remaining, _) = lang(context, remaining)?;
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?; let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, _body) = body(context, remaining)?; let (remaining, _body) = body(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -92,7 +94,7 @@ fn header<'r, 's>(
} }
fn header_end( fn header_end(
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_header_end(context, input, starting_bracket_depth) _header_end(context, input, starting_bracket_depth)
@@ -103,7 +105,7 @@ fn header_end(
fn _header_end<'r, 's>( fn _header_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_bracket_depth: isize, starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth; let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth > 0 { if current_depth > 0 {
@@ -152,7 +154,7 @@ fn body<'r, 's>(
} }
fn body_end( fn body_end(
starting_brace_depth: isize, starting_brace_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth) move |context: Context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth)
} }
@@ -161,7 +163,7 @@ fn body_end(
fn _body_end<'r, 's>( fn _body_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_brace_depth: isize, starting_brace_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_brace_depth() - starting_brace_depth; let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 { if current_depth > 0 {

View File

@@ -2,6 +2,8 @@ use nom::branch::alt;
use nom::bytes::complete::is_not; use nom::bytes::complete::is_not;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while1;
use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
@@ -9,14 +11,24 @@ use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::Context; use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::parser::Keyword; use crate::parser::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
"caption", "data", "header", "headers", "label", "name", "plot", "resname", "result",
"results", "source", "srcname", "tblname",
];
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn keyword<'r, 's>( pub fn keyword<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
@@ -41,3 +53,111 @@ pub fn keyword<'r, 's>(
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn affiliated_keyword<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
start_of_line(input)?;
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
let (remaining, rule) = recognize(tuple((
space0,
tag("#+"),
affiliated_key,
tag(":"),
alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
alt((line_ending, eof)),
)))(input)?;
Ok((
remaining,
Keyword {
source: rule.into(),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
recognize(tuple((dual_affiliated_key, tag("["), optval, tag("]")))),
plain_affiliated_key,
export_keyword,
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoKeywordKey".into(),
))))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoKeywordKey".into(),
))))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn optval<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many_till(
anychar,
peek(optval_end(input.get_bracket_depth())),
))(input)
}
const fn optval_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |input: OrgSource<'_>| _optval_end(input, starting_bracket_depth)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _optval_end<'s>(
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
if current_depth < 0 {
// This shouldn't be possible because if depth is 0 then a closing bracket should end the opval.
unreachable!("Exceeded optval bracket depth.")
}
if current_depth == 0 {
let close_bracket = tag::<_, _, CustomError<_>>("]")(input);
if close_bracket.is_ok() {
return close_bracket;
}
}
tag("\n")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn export_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
tag_no_case("attr_"),
take_while1(|c: char| c.is_alphanumeric() || "-_".contains(c)),
)))(input)
}

View File

@@ -5,7 +5,6 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::none_of; use nom::character::complete::none_of;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
@@ -14,6 +13,7 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -36,7 +36,8 @@ pub fn latex_fragment<'r, 's>(
parser_with_context!(dollar_char_fragment)(context), parser_with_context!(dollar_char_fragment)(context),
parser_with_context!(bordered_dollar_fragment)(context), parser_with_context!(bordered_dollar_fragment)(context),
))(input)?; ))(input)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,

View File

@@ -374,3 +374,9 @@ impl<'s> Source<'s> for Timestamp<'s> {
self.source self.source
} }
} }
impl<'s> Source<'s> for PlainText<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}

View File

@@ -33,7 +33,7 @@ pub fn standard_set_object<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
alt(( let (remaining, object) = alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript), map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
@@ -82,7 +82,8 @@ pub fn standard_set_object<'r, 's>(
map(parser_with_context!(angle_link)(context), Object::AngleLink), map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
map(parser_with_context!(plain_text)(context), Object::PlainText), map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input) ))(input)?;
Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -90,7 +91,7 @@ pub fn minimal_set_object<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
alt(( let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript), map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(superscript)(context),
@@ -103,7 +104,8 @@ pub fn minimal_set_object<'r, 's>(
), ),
parser_with_context!(text_markup)(context), parser_with_context!(text_markup)(context),
map(parser_with_context!(plain_text)(context), Object::PlainText), map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input) ))(input)?;
Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -111,7 +113,7 @@ pub fn any_object_except_plain_text<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
alt(( let (remaining, object) = alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
map(parser_with_context!(subscript)(context), Object::Subscript), map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
@@ -159,7 +161,8 @@ pub fn any_object_except_plain_text<'r, 's>(
map(parser_with_context!(plain_link)(context), Object::PlainLink), map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink), map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
))(input) ))(input)?;
Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -168,7 +171,7 @@ pub fn regular_link_description_object_set<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]] // TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
alt(( let (remaining, object) = alt((
map( map(
parser_with_context!(export_snippet)(context), parser_with_context!(export_snippet)(context),
Object::ExportSnippet, Object::ExportSnippet,
@@ -187,7 +190,8 @@ pub fn regular_link_description_object_set<'r, 's>(
), ),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object)(context),
))(input) ))(input)?;
Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -195,7 +199,7 @@ pub fn table_cell_set_object<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
alt(( let (remaining, object) = alt((
map(parser_with_context!(citation)(context), Object::Citation), map(parser_with_context!(citation)(context), Object::Citation),
map( map(
parser_with_context!(export_snippet)(context), parser_with_context!(export_snippet)(context),
@@ -220,5 +224,6 @@ pub fn table_cell_set_object<'r, 's>(
map(parser_with_context!(target)(context), Object::Target), map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object)(context),
))(input) ))(input)?;
Ok((remaining, object))
} }

View File

@@ -8,7 +8,9 @@ use nom::multi::many0;
use nom::multi::separated_list0; use nom::multi::separated_list0;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError;
use crate::error::Res; use crate::error::Res;
use crate::parser::object::OrgMacro; use crate::parser::object::OrgMacro;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -24,6 +26,8 @@ pub fn org_macro<'r, 's>(
let (remaining, macro_name) = org_macro_name(context, remaining)?; let (remaining, macro_name) = org_macro_name(context, remaining)?;
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?; let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
let (remaining, _) = tag("}}}")(remaining)?; let (remaining, _) = tag("}}}")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -84,6 +88,11 @@ fn org_macro_arg<'r, 's>(
} }
if next_char == '\\' { if next_char == '\\' {
escaping = true; escaping = true;
if peek(tag::<_, _, CustomError<_>>(")"))(new_remaining).is_ok() {
// Special case for backslash at the end of a macro
remaining = new_remaining;
break;
}
} }
if next_char == ',' || next_char == ')' { if next_char == ',' || next_char == ')' {
break; break;

View File

@@ -11,17 +11,18 @@ use nom::Slice;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
pub type BracketDepth = i16;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct OrgSource<'s> { pub struct OrgSource<'s> {
full_source: &'s str, full_source: &'s str,
start: usize, start: usize,
end: usize, // exclusive end: usize, // exclusive
start_of_line: usize, start_of_line: usize,
bracket_depth: BracketDepth, // []
brace_depth: BracketDepth, // {}
parenthesis_depth: BracketDepth, // ()
preceding_character: Option<char>, preceding_character: Option<char>,
line_number: usize,
bracket_depth: isize, // []
brace_depth: isize, // {}
parenthesis_depth: isize, // ()
} }
impl<'s> std::fmt::Debug for OrgSource<'s> { impl<'s> std::fmt::Debug for OrgSource<'s> {
@@ -43,7 +44,6 @@ impl<'s> OrgSource<'s> {
end: input.len(), end: input.len(),
start_of_line: 0, start_of_line: 0,
preceding_character: None, preceding_character: None,
line_number: 1,
bracket_depth: 0, bracket_depth: 0,
brace_depth: 0, brace_depth: 0,
parenthesis_depth: 0, parenthesis_depth: 0,
@@ -73,19 +73,15 @@ impl<'s> OrgSource<'s> {
self.slice(..(other.start - self.start)) self.slice(..(other.start - self.start))
} }
pub fn get_line_number(&self) -> usize { pub fn get_bracket_depth(&self) -> BracketDepth {
self.line_number
}
pub fn get_bracket_depth(&self) -> isize {
self.bracket_depth self.bracket_depth
} }
pub fn get_brace_depth(&self) -> isize { pub fn get_brace_depth(&self) -> BracketDepth {
self.brace_depth self.brace_depth
} }
pub fn get_parenthesis_depth(&self) -> isize { pub fn get_parenthesis_depth(&self) -> BracketDepth {
self.parenthesis_depth self.parenthesis_depth
} }
} }
@@ -152,7 +148,6 @@ where
let skipped_text = &self.full_source[self.start..new_start]; let skipped_text = &self.full_source[self.start..new_start];
let mut start_of_line = self.start_of_line; let mut start_of_line = self.start_of_line;
let mut line_number = self.line_number;
let mut bracket_depth = self.bracket_depth; let mut bracket_depth = self.bracket_depth;
let mut brace_depth = self.brace_depth; let mut brace_depth = self.brace_depth;
let mut parenthesis_depth = self.parenthesis_depth; let mut parenthesis_depth = self.parenthesis_depth;
@@ -160,7 +155,6 @@ where
match byte { match byte {
b'\n' => { b'\n' => {
start_of_line = self.start + offset + 1; start_of_line = self.start + offset + 1;
line_number += 1;
} }
b'[' => { b'[' => {
bracket_depth += 1; bracket_depth += 1;
@@ -190,7 +184,6 @@ where
end: new_end, end: new_end,
start_of_line, start_of_line,
preceding_character: skipped_text.chars().last(), preceding_character: skipped_text.chars().last(),
line_number,
bracket_depth, bracket_depth,
brace_depth, brace_depth,
parenthesis_depth, parenthesis_depth,
@@ -436,15 +429,6 @@ mod tests {
assert_eq!(input.slice(8..).get_preceding_character(), Some('💛')); assert_eq!(input.slice(8..).get_preceding_character(), Some('💛'));
} }
#[test]
fn line_number() {
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
assert_eq!(input.get_line_number(), 1);
assert_eq!(input.slice(5..).get_line_number(), 1);
assert_eq!(input.slice(6..).get_line_number(), 2);
assert_eq!(input.slice(6..).slice(10..).get_line_number(), 4);
}
#[test] #[test]
fn depth() { fn depth() {
let input = OrgSource::new("[][()][({)]}}}}"); let input = OrgSource::new("[][()][({)]}}}}");

View File

@@ -7,6 +7,7 @@ use nom::character::complete::one_of;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@@ -23,6 +24,33 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
// TODO: Make this a user-provided variable corresponding to elisp's org-link-parameters
const ORG_LINK_PARAMETERS: [&'static str; 23] = [
"id",
"eww",
"rmail",
"mhe",
"irc",
"info",
"gnus",
"docview",
"bibtex",
"bbdb",
"w3m",
"doi",
"file+sys",
"file+emacs",
"shell",
"news",
"mailto",
"https",
"http",
"ftp",
"help",
"file",
"elisp",
];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_link<'r, 's>( pub fn plain_link<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
@@ -73,36 +101,19 @@ pub fn protocol<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-link-parameters // TODO: This should be defined by org-link-parameters
let (remaining, proto) = alt(( for link_parameter in ORG_LINK_PARAMETERS {
alt(( let result = tag_no_case::<_, _, CustomError<_>>(link_parameter)(input);
tag_no_case("id"), match result {
tag_no_case("eww"), Ok((remaining, ent)) => {
tag_no_case("rmail"), return Ok((remaining, ent));
tag_no_case("mhe"), }
tag_no_case("irc"), Err(_) => {}
tag_no_case("info"), }
tag_no_case("gnus"), }
tag_no_case("docview"),
tag_no_case("bibtex"), Err(nom::Err::Error(CustomError::MyError(MyError(
tag_no_case("bbdb"), "NoLinkProtocol".into(),
tag_no_case("w3m"), ))))
)),
alt((
tag_no_case("doi"),
tag_no_case("file+sys"),
tag_no_case("file+emacs"),
tag_no_case("shell"),
tag_no_case("news"),
tag_no_case("mailto"),
tag_no_case("https"),
tag_no_case("http"),
tag_no_case("ftp"),
tag_no_case("help"),
tag_no_case("file"),
tag_no_case("elisp"),
)),
))(input)?;
Ok((remaining, proto))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -119,7 +130,11 @@ fn path_plain<'r, 's>(
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, path) = recognize(many_till(anychar, peek(exit_matcher)))(input)?; let (remaining, path) = recognize(verify(
many_till(anychar, peek(exit_matcher)),
|(children, _exit_contents)| !children.is_empty(),
))(input)?;
Ok((remaining, path)) Ok((remaining, path))
} }
@@ -128,5 +143,10 @@ fn path_plain_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(one_of(" \t\r\n()[]<>"))(input) recognize(many_till(
verify(anychar, |c| {
*c != '/' && (c.is_ascii_punctuation() || c.is_whitespace())
}),
one_of(" \t\r\n()[]<>"),
))(input)
} }

View File

@@ -6,19 +6,24 @@ use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1; use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::greater_element::PlainList; use super::greater_element::PlainList;
use super::greater_element::PlainListItem; use super::greater_element::PlainListItem;
use super::object_parser::standard_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::parser_with_context::parser_with_context;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
use super::Context; use super::Context;
use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
@@ -33,10 +38,7 @@ use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_plain_list<'r, 's>( pub fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if verify( if verify(
tuple(( tuple((
start_of_line, start_of_line,
@@ -64,6 +66,7 @@ pub fn plain_list<'r, 's>(
) -> Res<OrgSource<'s>, PlainList<'s>> { ) -> Res<OrgSource<'s>, PlainList<'s>> {
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::Context("plain list")) .with_additional_node(ContextElement::Context("plain list"))
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Beta,
exit_matcher: &plain_list_end, exit_matcher: &plain_list_end,
@@ -138,25 +141,33 @@ pub fn plain_list_item<'r, 's>(
Into::<&str>::into(bull) != "*" || indent_level > 0 Into::<&str>::into(bull) != "*" || indent_level > 0
})(remaining)?; })(remaining)?;
// TODO: This isn't taking into account items that immediately line break and then have contents let (remaining, maybe_tag) = opt(tuple((
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> = eof(remaining); space1,
parser_with_context!(item_tag)(context),
tag(" ::"),
)))(remaining)?;
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> =
peek(recognize(tuple((many0(blank_line), eof))))(remaining);
match maybe_contentless_item { match maybe_contentless_item {
Ok((rem, _ws)) => { Ok((_rem, _ws)) => {
let source = get_consumed(input, rem); let (remaining, _trailing_ws) = opt(blank_line)(remaining)?;
let source = get_consumed(input, remaining);
return Ok(( return Ok((
rem, remaining,
PlainListItem { PlainListItem {
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag)
.unwrap_or(Vec::new()),
children: Vec::new(), children: Vec::new(),
}, },
)); ));
} }
Err(_) => {} Err(_) => {}
}; };
let (remaining, _ws) = item_tag_post_gap(context, remaining)?;
let (remaining, _ws) = alt((space1, line_ending))(remaining)?;
let exit_matcher = plain_list_item_end(indent_level); let exit_matcher = plain_list_item_end(indent_level);
let parser_context = context let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
@@ -165,11 +176,24 @@ pub fn plain_list_item<'r, 's>(
exit_matcher: &exit_matcher, exit_matcher: &exit_matcher,
})); }));
let (remaining, (children, _exit_contents)) = many_till( let (mut remaining, (mut children, _exit_contents)) = many_till(
parser_with_context!(element(true))(&parser_context), include_input(parser_with_context!(element(true))(&parser_context)),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
)(remaining)?; )(remaining)?;
if !children.is_empty() && !context.should_consume_trailing_whitespace() {
let final_item_context =
parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false));
let (final_child_start, _original_final_child) = children
.pop()
.expect("if-statement already checked that children was non-empty.");
let (remain, reparsed_final_element) = include_input(parser_with_context!(element(true))(
&final_item_context,
))(final_child_start)?;
remaining = remain;
children.push(reparsed_final_element);
}
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -180,11 +204,26 @@ pub fn plain_list_item<'r, 's>(
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
children, tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag)
.unwrap_or(Vec::new()),
children: children.into_iter().map(|(_start, item)| item).collect(),
}, },
)); ));
} }
fn include_input<'s, F, O>(
mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
where
F: FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>,
{
move |input: OrgSource<'_>| {
let (remaining, output) = inner(input)?;
Ok((remaining, (input, output)))
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
@@ -262,6 +301,60 @@ fn _line_indented_lte<'r, 's>(
Ok(matched) Ok(matched)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &item_tag_end,
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
// TODO: Should this be using a different set like the minimal set?
parser_with_context!(standard_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(alt((
line_ending,
tag(" :: "),
recognize(tuple((tag(" ::"), alt((line_ending, eof))))),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_post_gap<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
verify(
recognize(tuple((
alt((blank_line, space0)),
many_till(
blank_line,
alt((
peek(recognize(not(blank_line))),
peek(recognize(tuple((many0(blank_line), eof)))),
parser_with_context!(exit_matcher_parser)(context),
)),
),
))),
|gap| gap.len() > 0,
)(input)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -426,24 +519,21 @@ dolar"#,
r#"+ r#"+
"#, "#,
); );
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let result = detect_plain_list(input);
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test] #[test]
fn detect_eof() { fn detect_eof() {
let input = OrgSource::new(r#"+"#); let input = OrgSource::new(r#"+"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let result = detect_plain_list(input);
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test] #[test]
fn detect_no_gap() { fn detect_no_gap() {
let input = OrgSource::new(r#"+foo"#); let input = OrgSource::new(r#"+foo"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let result = detect_plain_list(input);
let result = detect_plain_list(&initial_context, input);
// Since there is no whitespace after the '+' this is a paragraph, not a plain list. // Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -451,8 +541,7 @@ dolar"#,
#[test] #[test]
fn detect_with_gap() { fn detect_with_gap() {
let input = OrgSource::new(r#"+ foo"#); let input = OrgSource::new(r#"+ foo"#);
let initial_context: ContextTree<'_, '_> = ContextTree::new(); let result = detect_plain_list(input);
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
} }

View File

@@ -2,13 +2,14 @@ use nom::branch::alt;
use nom::bytes::complete::is_not; use nom::bytes::complete::is_not;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
@@ -23,7 +24,6 @@ use crate::parser::greater_element::PropertyDrawer;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode; use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
use crate::parser::plain_text::plain_text;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section; use crate::parser::util::immediate_in_section;
@@ -147,11 +147,16 @@ fn node_property_name<'r, 's>(
})); }));
let (remaining, name) = recognize(tuple(( let (remaining, name) = recognize(tuple((
map(parser_with_context!(plain_text)(&parser_context), |pt| { verify(
pt.source many_till(
}), anychar,
parser_with_context!(exit_matcher_parser)(&parser_context),
),
|(children, _exit_contents)| !children.is_empty(),
),
opt(tag("+")), opt(tag("+")),
)))(input)?; )))(input)?;
Ok((remaining, name)) Ok((remaining, name))
} }

View File

@@ -6,6 +6,7 @@ use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use super::Object; use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
@@ -104,7 +105,8 @@ pub fn radio_target<'r, 's>(
)(remaining)?; )(remaining)?;
let (remaining, _closing) = tag(">>>")(remaining)?; let (remaining, _closing) = tag(">>>")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,

View File

@@ -3,13 +3,13 @@ use nom::bytes::complete::escaped;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::take_till1; use nom::bytes::complete::take_till1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::parser_with_context::parser_with_context; use super::parser_with_context::parser_with_context;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use super::Object; use super::Object;
use super::RegularLink; use super::RegularLink;
@@ -39,7 +39,8 @@ pub fn regular_link_without_description<'r, 's>(
let (remaining, _opening_bracket) = tag("[[")(input)?; let (remaining, _opening_bracket) = tag("[[")(input)?;
let (remaining, _path) = pathreg(context, remaining)?; let (remaining, _path) = pathreg(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -59,7 +60,8 @@ pub fn regular_link_with_description<'r, 's>(
let (remaining, _closing_bracket) = tag("][")(remaining)?; let (remaining, _closing_bracket) = tag("][")(remaining)?;
let (remaining, _description) = description(context, remaining)?; let (remaining, _description) = description(context, remaining)?;
let (remaining, _closing_bracket) = tag("]]")(remaining)?; let (remaining, _closing_bracket) = tag("]]")(remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,

View File

@@ -1,10 +1,10 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::space0;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context; use crate::parser::parser_with_context::parser_with_context;
@@ -23,12 +23,13 @@ pub fn statistics_cookie<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn percent_statistics_cookie<'r, 's>( pub fn percent_statistics_cookie<'r, 's>(
_context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = let (remaining, source) =
recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?; recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok(( Ok((
remaining, remaining,
StatisticsCookie { StatisticsCookie {
@@ -39,7 +40,7 @@ pub fn percent_statistics_cookie<'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fraction_statistics_cookie<'r, 's>( pub fn fraction_statistics_cookie<'r, 's>(
_context: Context<'r, 's>, context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = recognize(tuple(( let (remaining, source) = recognize(tuple((
@@ -49,7 +50,8 @@ pub fn fraction_statistics_cookie<'r, 's>(
nom::character::complete::u64, nom::character::complete::u64,
tag("]"), tag("]"),
)))(input)?; )))(input)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok(( Ok((
remaining, remaining,
StatisticsCookie { StatisticsCookie {

View File

@@ -2,7 +2,6 @@ use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
@@ -11,7 +10,9 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use super::Object; use super::Object;
use crate::error::CustomError; use crate::error::CustomError;
@@ -36,7 +37,8 @@ pub fn subscript<'r, 's>(
let (remaining, _) = tag("_")(input)?; let (remaining, _) = tag("_")(input)?;
pre(context, input)?; pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -55,7 +57,8 @@ pub fn superscript<'r, 's>(
let (remaining, _) = tag("^")(input)?; let (remaining, _) = tag("^")(input)?;
pre(context, input)?; pre(context, input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
@@ -170,7 +173,7 @@ fn script_with_braces<'r, 's>(
} }
fn script_with_braces_end( fn script_with_braces_end(
starting_brace_depth: isize, starting_brace_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| { move |context: Context, input: OrgSource<'_>| {
_script_with_braces_end(context, input, starting_brace_depth) _script_with_braces_end(context, input, starting_brace_depth)
@@ -181,7 +184,7 @@ fn script_with_braces_end(
fn _script_with_braces_end<'r, 's>( fn _script_with_braces_end<'r, 's>(
_context: Context<'r, 's>, _context: Context<'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
starting_brace_depth: isize, starting_brace_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let current_depth = input.get_brace_depth() - starting_brace_depth; let current_depth = input.get_brace_depth() - starting_brace_depth;
if current_depth > 0 { if current_depth > 0 {

View File

@@ -1,13 +1,13 @@
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -49,7 +49,8 @@ pub fn target<'r, 's>(
)))); ))));
} }
let (remaining, _) = tag(">>")(remaining)?; let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((

View File

@@ -17,6 +17,7 @@ use tracing::span;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
@@ -204,7 +205,8 @@ fn _text_markup_object<'r, 's, 'x>(
} }
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, children)) Ok((remaining, children))
} }
@@ -254,7 +256,8 @@ fn _text_markup_string<'r, 's, 'x>(
} }
let (remaining, _close) = text_markup_end_specialized(context, remaining)?; let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
let (remaining, _trailing_whitespace) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
Ok((remaining, contents)) Ok((remaining, contents))
} }
@@ -277,7 +280,7 @@ pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSo
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\">")), line_ending))(input)?; let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\"")), line_ending))(input)?;
Ok((remaining, ())) Ok((remaining, ()))
} }

View File

@@ -3,7 +3,6 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
@@ -12,6 +11,7 @@ use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::Context; use super::Context;
use crate::error::Res; use crate::error::Res;
use crate::parser::exiting::ExitClass; use crate::parser::exiting::ExitClass;
@@ -49,7 +49,8 @@ fn diary_timestamp<'r, 's>(
let (remaining, _) = tag("<%%(")(input)?; let (remaining, _) = tag("<%%(")(input)?;
let (remaining, _body) = sexp(context, remaining)?; let (remaining, _body) = sexp(context, remaining)?;
let (remaining, _) = tag(")>")(remaining)?; let (remaining, _) = tag(")>")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -112,7 +113,8 @@ fn active_timestamp<'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -145,7 +147,8 @@ fn inactive_timestamp<'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -166,7 +169,8 @@ fn active_date_range_timestamp<'r, 's>(
let (remaining, _separator) = tag("--")(remaining)?; let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = active_timestamp(context, remaining)?; let (remaining, _second_timestamp) = active_timestamp(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -206,7 +210,8 @@ fn active_time_range_timestamp<'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag(">")(remaining)?; let (remaining, _) = tag(">")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -227,7 +232,8 @@ fn inactive_date_range_timestamp<'r, 's>(
let (remaining, _separator) = tag("--")(remaining)?; let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?; let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -267,7 +273,8 @@ fn inactive_time_range_timestamp<'r, 's>(
)))(remaining)?; )))(remaining)?;
let (remaining, _) = tag("]")(remaining)?; let (remaining, _) = tag("]")(remaining)?;
let (remaining, _) = space0(remaining)?; let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((

View File

@@ -68,6 +68,18 @@ pub fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s
alt((eof, recognize(many0(blank_line))))(input) alt((eof, recognize(many0(blank_line))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_object_trailing_whitespace_if_not_exiting<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
if exit_matcher_parser(context, input).is_err() {
opt(space0)(input)
} else {
Ok((input, None))
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>( pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>(
context: Context<'r, 's>, context: Context<'r, 's>,
@@ -155,3 +167,16 @@ pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
"Not implemented yet.".into(), "Not implemented yet.".into(),
)))); ))));
} }
#[allow(dead_code)]
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
/// Text from the current point until the next line break or end of file
///
/// Useful for debugging.
pub fn text_until_eol<'r, 's>(
input: OrgSource<'s>,
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
.map(|(_remaining, line)| Into::<&str>::into(line))?;
Ok(line.trim())
}

View File

@@ -1 +1,2 @@
#[cfg(feature = "compare")]
include!(concat!(env!("OUT_DIR"), "/tests.rs")); include!(concat!(env!("OUT_DIR"), "/tests.rs"));

View File

@@ -11,7 +11,7 @@ fn {name}() {{
let diff_result = let diff_result =
compare_document(&parsed_sexp, &rust_parsed).expect("Compare parsed documents."); compare_document(&parsed_sexp, &rust_parsed).expect("Compare parsed documents.");
diff_result diff_result
.print() .print(org_contents.as_str())
.expect("Print document parse tree diff."); .expect("Print document parse tree diff.");
assert!(!diff_result.is_bad()); assert!(!diff_result.is_bad());
assert_eq!(remaining, ""); assert_eq!(remaining, "");