Compare commits
114 Commits
21e1ceb8e0
...
v0.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db3086743c | ||
|
|
207a0546b0 | ||
|
|
e9480fd156 | ||
|
|
28aca041f7 | ||
|
|
d82def2a70 | ||
|
|
d471f7178b | ||
|
|
2c5c26c55f | ||
|
|
7944659802 | ||
|
|
58aca53144 | ||
|
|
6f2d90162b | ||
|
|
f170a557ed | ||
|
|
eaa38ce772 | ||
|
|
a6d742a536 | ||
|
|
45be9e7bde | ||
|
|
f6c895319f | ||
|
|
2682779534 | ||
|
|
b48d472546 | ||
|
|
ea6faf728c | ||
|
|
f4ea1b7303 | ||
|
|
80b55fdd45 | ||
|
|
f426e32798 | ||
|
|
66037356c5 | ||
|
|
1bcd1895c0 | ||
|
|
e3d38cfbe2 | ||
|
|
2ba0dc49be | ||
|
|
9df40fb13f | ||
|
|
cc671925db | ||
|
|
950baa9d5d | ||
|
|
56865c68fc | ||
|
|
f592b73ae7 | ||
|
|
3206027b96 | ||
|
|
3e6df7ba78 | ||
|
|
ac313d093e | ||
|
|
f376f1cf8e | ||
|
|
f21385a901 | ||
|
|
1d06d95bb1 | ||
|
|
bfc88c1d1b | ||
|
|
f29720e5b9 | ||
|
|
27a9b5aeb1 | ||
|
|
8051c3d2b7 | ||
|
|
bd97d2f69d | ||
|
|
14b1d0526c | ||
|
|
288350daef | ||
|
|
c683516620 | ||
|
|
e731e8ff6b | ||
|
|
4c2037ec44 | ||
|
|
a46b358549 | ||
|
|
ec813e3b3f | ||
|
|
f11f7bcc73 | ||
|
|
9e0e5f6f0a | ||
|
|
16e788c36c | ||
|
|
b35d785e73 | ||
|
|
1952d175c0 | ||
|
|
20c17c40be | ||
|
|
b6b869df25 | ||
|
|
18a396b7cb | ||
|
|
085490476e | ||
|
|
9c9964c66f | ||
|
|
1a3e26c148 | ||
|
|
e9e6a8ff64 | ||
|
|
b124317f30 | ||
|
|
ad389f0776 | ||
|
|
75dfc7f812 | ||
|
|
c17de8ef5e | ||
|
|
378b6bb391 | ||
|
|
cc86591a6c | ||
|
|
f25dbc1d7c | ||
|
|
daee50c160 | ||
|
|
3e143796f7 | ||
|
|
9cc5e63c1b | ||
|
|
be6197e4c7 | ||
|
|
2d4e54845b | ||
|
|
d5ea650b96 | ||
|
|
60363579b5 | ||
|
|
1b678fe81f | ||
|
|
bfea828e62 | ||
|
|
bc5745a95f | ||
|
|
efa372a9e9 | ||
|
|
2fb57daaec | ||
|
|
3a38f4cd35 | ||
|
|
45e16fea2d | ||
|
|
5134cece7b | ||
|
|
19432d91ab | ||
|
|
16a107eebb | ||
|
|
77348b560c | ||
|
|
fc79507ef3 | ||
|
|
9c1e6ccc97 | ||
|
|
0dbc8f0925 | ||
|
|
02fe10fba3 | ||
|
|
33d7ae03d1 | ||
|
|
03faa7257f | ||
|
|
ae3510abd5 | ||
|
|
ad3f47864a | ||
|
|
533ef2a9a8 | ||
|
|
cf37bc4111 | ||
|
|
e5224cda63 | ||
|
|
64e3481660 | ||
|
|
32071ce74d | ||
|
|
e84e2b5147 | ||
|
|
3348807a05 | ||
|
|
720afa5d32 | ||
|
|
dab598e5e7 | ||
|
|
b7a5dd48ea | ||
|
|
c475dce6da | ||
|
|
6d1675fa00 | ||
|
|
cda49c628c | ||
|
|
65b87bd65d | ||
|
|
5a7f34b63e | ||
|
|
edff1e089d | ||
|
|
bc29f1dfc0 | ||
|
|
e4656cddf6 | ||
|
|
1e3dadd458 | ||
|
|
2ec055af5a | ||
|
|
6823db5c60 |
@@ -14,10 +14,6 @@ spec:
|
|||||||
- name: path-to-dockerfile
|
- name: path-to-dockerfile
|
||||||
description: The path to the Dockerfile
|
description: The path to the Dockerfile
|
||||||
type: string
|
type: string
|
||||||
- name: command
|
|
||||||
type: array
|
|
||||||
description: Command to run.
|
|
||||||
default: []
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: report-pending
|
- name: report-pending
|
||||||
taskRef:
|
taskRef:
|
||||||
@@ -92,8 +88,6 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- build-image
|
- build-image
|
||||||
params:
|
params:
|
||||||
- name: command
|
|
||||||
value: ["$(params.command[*])"]
|
|
||||||
- name: args
|
- name: args
|
||||||
value: ["--no-default-features"]
|
value: ["--no-default-features"]
|
||||||
- name: docker-image
|
- name: docker-image
|
||||||
@@ -109,8 +103,6 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- run-image-none
|
- run-image-none
|
||||||
params:
|
params:
|
||||||
- name: command
|
|
||||||
value: ["$(params.command[*])"]
|
|
||||||
- name: args
|
- name: args
|
||||||
value: ["--no-default-features", "--features", "tracing"]
|
value: ["--no-default-features", "--features", "tracing"]
|
||||||
- name: docker-image
|
- name: docker-image
|
||||||
@@ -126,8 +118,6 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- run-image-tracing
|
- run-image-tracing
|
||||||
params:
|
params:
|
||||||
- name: command
|
|
||||||
value: ["$(params.command[*])"]
|
|
||||||
- name: args
|
- name: args
|
||||||
value: ["--no-default-features", "--features", "compare"]
|
value: ["--no-default-features", "--features", "compare"]
|
||||||
- name: docker-image
|
- name: docker-image
|
||||||
@@ -143,8 +133,6 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- run-image-compare
|
- run-image-compare
|
||||||
params:
|
params:
|
||||||
- name: command
|
|
||||||
value: ["$(params.command[*])"]
|
|
||||||
- name: args
|
- name: args
|
||||||
value: []
|
value: []
|
||||||
- name: docker-image
|
- name: docker-image
|
||||||
@@ -160,8 +148,6 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- run-image-default
|
- run-image-default
|
||||||
params:
|
params:
|
||||||
- name: command
|
|
||||||
value: ["$(params.command[*])"]
|
|
||||||
- name: args
|
- name: args
|
||||||
value: ["--no-default-features", "--features", "tracing,compare"]
|
value: ["--no-default-features", "--features", "tracing,compare"]
|
||||||
- name: docker-image
|
- name: docker-image
|
||||||
@@ -256,5 +242,3 @@ spec:
|
|||||||
value: docker/organic_build/
|
value: docker/organic_build/
|
||||||
- name: path-to-dockerfile
|
- name: path-to-dockerfile
|
||||||
value: docker/organic_build/Dockerfile
|
value: docker/organic_build/Dockerfile
|
||||||
- name: command
|
|
||||||
value: [cargo, build]
|
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ spec:
|
|||||||
- name: path-to-dockerfile
|
- name: path-to-dockerfile
|
||||||
description: The path to the Dockerfile
|
description: The path to the Dockerfile
|
||||||
type: string
|
type: string
|
||||||
- name: command
|
|
||||||
type: array
|
|
||||||
description: Command to run.
|
|
||||||
default: []
|
|
||||||
- name: args
|
|
||||||
type: array
|
|
||||||
description: Arguments passed to command.
|
|
||||||
default: []
|
|
||||||
tasks:
|
tasks:
|
||||||
- name: do-stuff
|
- name: do-stuff
|
||||||
taskSpec:
|
taskSpec:
|
||||||
@@ -117,10 +109,17 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- build-image
|
- build-image
|
||||||
params:
|
params:
|
||||||
- name: command
|
|
||||||
value: ["$(params.command[*])"]
|
|
||||||
- name: args
|
- name: args
|
||||||
value: ["$(params.args[*])"]
|
value:
|
||||||
|
[
|
||||||
|
--no-default-features,
|
||||||
|
--features,
|
||||||
|
compare,
|
||||||
|
--no-fail-fast,
|
||||||
|
--lib,
|
||||||
|
--test,
|
||||||
|
test_loader,
|
||||||
|
]
|
||||||
- name: docker-image
|
- name: docker-image
|
||||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
finally:
|
finally:
|
||||||
@@ -212,7 +211,3 @@ spec:
|
|||||||
value: docker/organic_test/
|
value: docker/organic_test/
|
||||||
- name: path-to-dockerfile
|
- name: path-to-dockerfile
|
||||||
value: docker/organic_test/Dockerfile
|
value: docker/organic_test/Dockerfile
|
||||||
- name: command
|
|
||||||
value: [cargo, test]
|
|
||||||
- name: args
|
|
||||||
value: [--lib, --test, test_loader]
|
|
||||||
|
|||||||
@@ -14,14 +14,6 @@ spec:
|
|||||||
- name: path-to-dockerfile
|
- name: path-to-dockerfile
|
||||||
description: The path to the Dockerfile
|
description: The path to the Dockerfile
|
||||||
type: string
|
type: string
|
||||||
- name: rustfmt-command
|
|
||||||
type: array
|
|
||||||
description: Command to run rustfmt.
|
|
||||||
default: []
|
|
||||||
- name: rustfmt-args
|
|
||||||
type: array
|
|
||||||
description: Arguments passed to rustfmt.
|
|
||||||
default: []
|
|
||||||
- name: GIT_USER_NAME
|
- name: GIT_USER_NAME
|
||||||
description: The username for git
|
description: The username for git
|
||||||
type: string
|
type: string
|
||||||
@@ -119,10 +111,6 @@ spec:
|
|||||||
runAfter:
|
runAfter:
|
||||||
- build-image
|
- build-image
|
||||||
params:
|
params:
|
||||||
- name: command
|
|
||||||
value: ["$(params.rustfmt-command[*])"]
|
|
||||||
- name: args
|
|
||||||
value: ["$(params.rustfmt-args[*])"]
|
|
||||||
- name: docker-image
|
- name: docker-image
|
||||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
- name: cargo-fix
|
- name: cargo-fix
|
||||||
@@ -240,7 +228,3 @@ spec:
|
|||||||
value: docker/cargo_fmt/
|
value: docker/cargo_fmt/
|
||||||
- name: path-to-dockerfile
|
- name: path-to-dockerfile
|
||||||
value: docker/cargo_fmt/Dockerfile
|
value: docker/cargo_fmt/Dockerfile
|
||||||
- name: command
|
|
||||||
value: [cargo, fmt]
|
|
||||||
- name: args
|
|
||||||
value: []
|
|
||||||
|
|||||||
@@ -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,15 +39,17 @@ 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"]
|
||||||
|
|
||||||
|
# Optimized build for any sort of release.
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = true
|
lto = true
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|
||||||
|
# Profile for performance testing with the "perf" tool. Notably keeps debug enabled and does not strip symbols to make reading the perf output easier.
|
||||||
[profile.perf]
|
[profile.perf]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -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 cargo test --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
> 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-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||||
|
|
||||||
.PHONY: dockerclean
|
.PHONY: dockerclean
|
||||||
dockerclean:
|
dockerclean:
|
||||||
@@ -49,11 +49,11 @@ 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:
|
||||||
|
|||||||
16
build.rs
16
build.rs
@@ -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,14 +81,13 @@ 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."),
|
||||||
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
||||||
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
|
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
|
||||||
"autogen_greater_element_plain_list_trailing_whitespace_ownership_test_case_1" => Some("Seeing odd behavior about whitespace ownership."), // https://list.orgmode.org/9372527e-3852-419e-936a-7b4dd38cc847@app.fastmail.com/
|
"autogen_unicode_hearts" => Some("Unicode is coming out of emacs strange."),
|
||||||
"autogen_greater_element_plain_list_trailing_whitespace_ownership_test_case_3" => Some("Seeing odd behavior about whitespace ownership."), // https://list.orgmode.org/9372527e-3852-419e-936a-7b4dd38cc847@app.fastmail.com/
|
|
||||||
"autogen_greater_element_plain_list_trailing_whitespace_ownership_test_case_4" => Some("Seeing odd behavior about whitespace ownership."), // https://list.orgmode.org/9372527e-3852-419e-936a-7b4dd38cc847@app.fastmail.com/
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,11 @@ else
|
|||||||
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# NOTE: This target will write to folders underneath the git-root
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run: build
|
||||||
docker run --rm -i -t $(IMAGE_NAME)
|
docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)
|
||||||
|
|
||||||
.PHONY: shell
|
.PHONY: shell
|
||||||
shell:
|
shell: build
|
||||||
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
|
docker run --rm -i -t --entrypoint /bin/sh -v "$$(readlink -f ../../):/source" --workdir=/source $(IMAGE_NAME)
|
||||||
|
|||||||
@@ -2,3 +2,5 @@ FROM rustlang/rust:nightly-alpine3.17
|
|||||||
|
|
||||||
RUN apk add --no-cache musl-dev
|
RUN apk add --no-cache musl-dev
|
||||||
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
|
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
|
||||||
|
|
||||||
|
ENTRYPOINT ["cargo", "build"]
|
||||||
|
|||||||
@@ -25,11 +25,13 @@ ifdef REMOTE_REPO
|
|||||||
else
|
else
|
||||||
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
||||||
endif
|
endif
|
||||||
|
docker volume rm cargo-cache
|
||||||
|
|
||||||
|
# NOTE: This target will write to folders underneath the git-root
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run: build
|
||||||
docker run --rm -i -t $(IMAGE_NAME)
|
docker run --rm --init -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
|
||||||
|
|
||||||
.PHONY: shell
|
.PHONY: shell
|
||||||
shell:
|
shell: build
|
||||||
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
|
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)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
FROM alpine:3.17 AS build
|
FROM alpine:3.17 AS build
|
||||||
|
|
||||||
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk
|
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk
|
||||||
|
|
||||||
|
|
||||||
FROM build AS build-emacs
|
FROM build AS build-emacs
|
||||||
|
ARG EMACS_VERSION=emacs-29.1
|
||||||
RUN git clone --depth 1 --branch emacs-29.1 https://git.savannah.gnu.org/git/emacs.git /root/emacs
|
RUN git clone --depth 1 --branch $EMACS_VERSION https://git.savannah.gnu.org/git/emacs.git /root/emacs
|
||||||
WORKDIR /root/emacs
|
WORKDIR /root/emacs
|
||||||
RUN mkdir /root/dist
|
RUN mkdir /root/dist
|
||||||
RUN ./autogen.sh
|
RUN ./autogen.sh
|
||||||
@@ -15,9 +14,12 @@ RUN make DESTDIR="/root/dist" install
|
|||||||
|
|
||||||
|
|
||||||
FROM build AS build-org-mode
|
FROM build AS build-org-mode
|
||||||
|
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1
|
||||||
COPY --from=build-emacs /root/dist/ /
|
COPY --from=build-emacs /root/dist/ /
|
||||||
RUN mkdir /root/dist
|
RUN mkdir /root/dist
|
||||||
RUN mkdir /root/org-mode && git -C /root/org-mode init --initial-branch=main && git -C /root/org-mode remote add origin https://git.savannah.gnu.org/git/emacs/org-mode.git && git -C /root/org-mode fetch origin 299193bf091a63474fc8036bd31de51800a2555a && git -C /root/org-mode checkout FETCH_HEAD
|
# Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want.
|
||||||
|
RUN git clone https://git.savannah.gnu.org/git/emacs/org-mode.git /root/org-mode && git -C /root/org-mode checkout $ORG_VERSION
|
||||||
|
# RUN mkdir /root/org-mode && git -C /root/org-mode init --initial-branch=main && git -C /root/org-mode remote add origin https://git.savannah.gnu.org/git/emacs/org-mode.git && git -C /root/org-mode fetch origin $ORG_VERSION && git -C /root/org-mode checkout FETCH_HEAD
|
||||||
WORKDIR /root/org-mode
|
WORKDIR /root/org-mode
|
||||||
RUN make compile
|
RUN make compile
|
||||||
RUN make DESTDIR="/root/dist" install
|
RUN make DESTDIR="/root/dist" install
|
||||||
@@ -28,3 +30,5 @@ 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/ /
|
||||||
COPY --from=build-org-mode /root/dist/ /
|
COPY --from=build-org-mode /root/dist/ /
|
||||||
|
|
||||||
|
ENTRYPOINT ["cargo", "test"]
|
||||||
|
|||||||
@@ -25,11 +25,12 @@ ifdef REMOTE_REPO
|
|||||||
else
|
else
|
||||||
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
||||||
endif
|
endif
|
||||||
|
docker volume rm rust-cache cargo-cache
|
||||||
|
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run: build
|
||||||
docker run --rm -i -t $(IMAGE_NAME)
|
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-default-features --features compare --no-fail-fast --lib --test test_loader
|
||||||
|
|
||||||
.PHONY: shell
|
.PHONY: shell
|
||||||
shell:
|
shell: build
|
||||||
docker run --rm -i -t --entrypoint /bin/bash $(IMAGE_NAME)
|
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)
|
||||||
|
|||||||
1
elisp_snippets/README.md
Normal file
1
elisp_snippets/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This folder is for snippets of elisp that are useful for development.
|
||||||
3
elisp_snippets/dump_org_element_affiliated_keywords.el
Normal file
3
elisp_snippets/dump_org_element_affiliated_keywords.el
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
(dolist (var org-element-affiliated-keywords)
|
||||||
|
(message "\"%s\"," (downcase var))
|
||||||
|
)
|
||||||
5
elisp_snippets/dump_org_entities.el
Normal file
5
elisp_snippets/dump_org_entities.el
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
(dolist (var org-entities)
|
||||||
|
(when (listp var)
|
||||||
|
(message "\"%s\"," (nth 0 var))
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
1. foo
|
1. plain-list
|
||||||
#+begin_center
|
#+begin_center
|
||||||
|
|
||||||
|
|
||||||
#+end_center
|
#+end_center
|
||||||
2. bar
|
|
||||||
|
Is this still in the plain list?
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
This folder is an investigation into whether or not my exit matchers should operate from the top down or bottom up.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
foo *bar baz * lorem* ipsum
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
Looks like 2 blank lines always exits the top-level plain list.
|
|
||||||
|
|
||||||
Plain lists do not seem to go inside paragraphs but rather exist beside them.
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
1. foo
|
|
||||||
|
|
||||||
bar
|
|
||||||
|
|
||||||
1. baz
|
|
||||||
|
|
||||||
lorem
|
|
||||||
|
|
||||||
ipsum
|
|
||||||
|
|
||||||
|
|
||||||
dolar
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Looks like table cells cannot contain lists but can contain bolds
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
ip *su* m
|
|
||||||
|
|
||||||
| foo | bar |
|
|
||||||
|----------+-----|
|
|
||||||
| 1. lo *re* m | |
|
|
||||||
25
org_mode_samples/greater_element/dynamic_block/simple.org
Normal file
25
org_mode_samples/greater_element/dynamic_block/simple.org
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#+BEGIN: clocktable :scope file :maxlevel 2
|
||||||
|
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
|
||||||
|
| Headline | Time |
|
||||||
|
|--------------+--------|
|
||||||
|
| *Total time* | *0:00* |
|
||||||
|
#+END:
|
||||||
|
|
||||||
|
#+BEGIN: columnview :hlines 1 :id global
|
||||||
|
| ITEM | TODO | PRIORITY | TAGS |
|
||||||
|
|-------+------+----------+------------------------------|
|
||||||
|
| Foo | | B | |
|
||||||
|
|-------+------+----------+------------------------------|
|
||||||
|
| Bar | TODO | B | |
|
||||||
|
|-------+------+----------+------------------------------|
|
||||||
|
| Baz | | B | :thisisatag: |
|
||||||
|
| Lorem | | B | :thisshouldinheritfromabove: |
|
||||||
|
| Ipsum | | B | :multiple:tags: |
|
||||||
|
#+END:
|
||||||
|
* Foo
|
||||||
|
* TODO Bar
|
||||||
|
* Baz :thisisatag:
|
||||||
|
** Lorem :thisshouldinheritfromabove:
|
||||||
|
*** Ipsum :multiple:tags:
|
||||||
|
* Dolar ::
|
||||||
|
* cat :dog: bat
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
1. foo
|
||||||
|
2.
|
||||||
|
bar
|
||||||
|
1.
|
||||||
|
#+begin_center
|
||||||
|
Still in the list
|
||||||
|
#+end_center
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
- foo ::
|
||||||
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
- foo :: bar
|
||||||
|
- cat ::
|
||||||
|
dog
|
||||||
|
- lorem
|
||||||
|
:: ipsum
|
||||||
|
-
|
||||||
|
lorem :: ipsum
|
||||||
|
- dolar *bold* foo :: ipsum
|
||||||
|
- big gap ::
|
||||||
|
|
||||||
|
stuff
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
** foo
|
||||||
|
:PROPERTIES:
|
||||||
|
:DESCRIPTION: lorem
|
||||||
|
:ALT_TITLE: ipsum
|
||||||
|
:END:
|
||||||
|
|
||||||
|
bar
|
||||||
@@ -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
|
||||||
22
org_mode_samples/object/citation/balanced_brackets.org
Normal file
22
org_mode_samples/object/citation/balanced_brackets.org
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Extra open
|
||||||
|
[cite/a/b-_/foo:unbalancedglobal[prefix;keyprefix @foo keysuffix;globalsuffix]
|
||||||
|
|
||||||
|
[cite/a/b-_/foo:globalprefix;unbalancedkey[prefix @foo keysuffix;globalsuffix]
|
||||||
|
|
||||||
|
[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey[suffix;globalsuffix]
|
||||||
|
|
||||||
|
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal[suffix]
|
||||||
|
|
||||||
|
|
||||||
|
# Extra close
|
||||||
|
[cite/a/b-_/foo:unbalancedglobal]prefix;keyprefix @foo keysuffix;globalsuffix]
|
||||||
|
|
||||||
|
[cite/a/b-_/foo:globalprefix;unbalancedkey]prefix @foo keysuffix;globalsuffix]
|
||||||
|
|
||||||
|
[cite/a/b-_/foo:globalprefix;keyprefix @foo unbalancedkey]suffix;globalsuffix]
|
||||||
|
|
||||||
|
[cite/a/b-_/foo:globalprefix;keyprefix @foo keysuffix;unbalancedglobal]suffix]
|
||||||
|
|
||||||
|
|
||||||
|
# balanced:
|
||||||
|
[cite/a/b-_/foo:gl[obalpref]ix;ke[ypref]ix @foo ke[ysuff]ix;gl[obalsuff]ix]
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[fn:2:This footnote [ has balanced ] brackets inside it]
|
||||||
|
[fn::This footnote does not have balanced [ brackets inside it]
|
||||||
6
org_mode_samples/object/latex_fragment/three_lines.org
Normal file
6
org_mode_samples/object/latex_fragment/three_lines.org
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
$foo
|
||||||
|
bar
|
||||||
|
baz
|
||||||
|
lorem
|
||||||
|
ipsum
|
||||||
|
dolar$
|
||||||
52
org_mode_samples/object/plain_link/empty_links.org
Normal file
52
org_mode_samples/object/plain_link/empty_links.org
Normal 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
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
mailto:foo@bar.baz.
|
||||||
|
|
||||||
|
mailto:foo@bar.baz....
|
||||||
17
org_mode_samples/object/text_markup/three_lines.org
Normal file
17
org_mode_samples/object/text_markup/three_lines.org
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
foo *bar
|
||||||
|
baz* lorem
|
||||||
|
|
||||||
|
text *markup
|
||||||
|
can
|
||||||
|
span* more
|
||||||
|
|
||||||
|
than *three
|
||||||
|
lines.
|
||||||
|
foo
|
||||||
|
bar* baz
|
||||||
|
|
||||||
|
foo *bar \\
|
||||||
|
baz \\
|
||||||
|
lorem \\
|
||||||
|
ipsum \\
|
||||||
|
dolar* cat
|
||||||
9
org_mode_samples/sections_and_headings/empty_section.org
Normal file
9
org_mode_samples/sections_and_headings/empty_section.org
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
* Foo
|
||||||
|
|
||||||
|
* Bar
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
* Baz
|
||||||
1
org_mode_samples/unicode/hearts.org
Normal file
1
org_mode_samples/unicode/hearts.org
Normal file
@@ -0,0 +1 @@
|
|||||||
|
🧡💛💚💙💜
|
||||||
@@ -8,11 +8,22 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||||||
|
|
||||||
cd "$DIR/../"
|
cd "$DIR/../"
|
||||||
|
|
||||||
cargo build --profile "$PROFILE" --no-default-features
|
function main {
|
||||||
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare
|
local additional_flags=()
|
||||||
# Convert to a format firefox will read
|
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||||
# flags to consider --show-info
|
PROFILE="debug"
|
||||||
perf script -F +pid --input perf.data > perf.firefox
|
else
|
||||||
|
additional_flags+=(--profile "$PROFILE")
|
||||||
|
fi
|
||||||
|
cargo build --no-default-features "${additional_flags[@]}"
|
||||||
|
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare
|
||||||
|
|
||||||
echo "You probably want to go to https://profiler.firefox.com/"
|
# Convert to a format firefox will read
|
||||||
echo "Either that or run hotspot"
|
# flags to consider --show-info
|
||||||
|
perf script -F +pid --input perf.data > perf.firefox
|
||||||
|
|
||||||
|
echo "You probably want to go to https://profiler.firefox.com/"
|
||||||
|
echo "Either that or run hotspot"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "${@}"
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||||||
: ${SHELL:="NO"} # or YES to launch a shell instead of running the test
|
: ${SHELL:="NO"} # or YES to launch a shell instead of running the test
|
||||||
: ${TRACE:="NO"} # or YES to send traces to jaeger
|
: ${TRACE:="NO"} # or YES to send traces to jaeger
|
||||||
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
|
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
|
||||||
|
: ${NO_COLOR:=""} # Set to anything to disable color output
|
||||||
|
|
||||||
|
|
||||||
cd "$DIR/../"
|
cd "$DIR/../"
|
||||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||||
@@ -24,23 +26,31 @@ function build_container {
|
|||||||
function launch_container {
|
function launch_container {
|
||||||
local additional_flags=()
|
local additional_flags=()
|
||||||
local additional_args=()
|
local additional_args=()
|
||||||
|
local features=(compare)
|
||||||
|
|
||||||
if [ "$SHELL" != "YES" ]; then
|
if [ "$NO_COLOR" != "" ]; then
|
||||||
additional_args+=(cargo run)
|
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
|
||||||
else
|
|
||||||
additional_flags+=(-t)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$TRACE" = "YES" ]; then
|
if [ "$TRACE" = "YES" ]; then
|
||||||
# We use the host network so it can talk to jaeger hosted at 127.0.0.1
|
# We use the host network so it can talk to jaeger hosted at 127.0.0.1
|
||||||
additional_flags+=(--network=host --env RUST_LOG=debug)
|
additional_flags+=(--network=host --env RUST_LOG=debug)
|
||||||
|
features+=(tracing)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SHELL" != "YES" ]; then
|
||||||
|
local features_joined=$(IFS=","; echo "${features[*]}")
|
||||||
|
additional_args+=(cargo run --no-default-features --features "$features_joined")
|
||||||
|
else
|
||||||
|
additional_args+=(/bin/sh)
|
||||||
|
additional_flags+=(-t)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$BACKTRACE" = "YES" ]; then
|
if [ "$BACKTRACE" = "YES" ]; then
|
||||||
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 organic-test "${additional_args[@]}"
|
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[@]}"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "${@}"
|
main "${@}"
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ set -euo pipefail
|
|||||||
IFS=$'\n\t'
|
IFS=$'\n\t'
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
: ${NO_COLOR:=""} # Set to anything to disable color output
|
||||||
|
|
||||||
cd "$DIR/../"
|
cd "$DIR/../"
|
||||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||||
MAKE=$(command -v gmake || command -v make)
|
MAKE=$(command -v gmake || command -v make)
|
||||||
@@ -40,17 +42,21 @@ function get_test_names {
|
|||||||
|
|
||||||
function launch_container {
|
function launch_container {
|
||||||
local test="$1"
|
local test="$1"
|
||||||
local additional_args=()
|
local additional_flags=()
|
||||||
|
|
||||||
|
if [ "$NO_COLOR" != "" ]; then
|
||||||
|
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
|
||||||
|
fi
|
||||||
|
|
||||||
local init_script=$(cat <<EOF
|
local init_script=$(cat <<EOF
|
||||||
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 --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 organic-test sh -c "$init_script"
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
23
scripts/time_parse.bash
Executable file
23
scripts/time_parse.bash
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Time running a single parse without invoking a compare with emacs.
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
: ${PROFILE:="release-lto"}
|
||||||
|
|
||||||
|
cd "$DIR/../"
|
||||||
|
|
||||||
|
function main {
|
||||||
|
local additional_flags=()
|
||||||
|
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||||
|
PROFILE="debug"
|
||||||
|
else
|
||||||
|
additional_flags+=(--profile "$PROFILE")
|
||||||
|
fi
|
||||||
|
cargo build --no-default-features "${additional_flags[@]}"
|
||||||
|
time ./target/${PROFILE}/compare
|
||||||
|
}
|
||||||
|
|
||||||
|
main "${@}"
|
||||||
1234
src/compare/diff.rs
1234
src/compare/diff.rs
File diff suppressed because it is too large
Load Diff
@@ -3,3 +3,5 @@ mod parse;
|
|||||||
mod util;
|
mod util;
|
||||||
pub use diff::compare_document;
|
pub use diff::compare_document;
|
||||||
pub use parse::emacs_parse_org_document;
|
pub use parse::emacs_parse_org_document;
|
||||||
|
pub use parse::get_emacs_version;
|
||||||
|
pub use parse::get_org_mode_version;
|
||||||
|
|||||||
@@ -49,3 +49,40 @@ where
|
|||||||
}
|
}
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let elisp_script = r#"(progn
|
||||||
|
(message "%s" (version))
|
||||||
|
)"#;
|
||||||
|
let mut cmd = Command::new("emacs");
|
||||||
|
let proc = cmd
|
||||||
|
.arg("-q")
|
||||||
|
.arg("--no-site-file")
|
||||||
|
.arg("--no-splash")
|
||||||
|
.arg("--batch")
|
||||||
|
.arg("--eval")
|
||||||
|
.arg(elisp_script);
|
||||||
|
|
||||||
|
let out = proc.output()?;
|
||||||
|
out.status.exit_ok()?;
|
||||||
|
Ok(String::from_utf8(out.stderr)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let elisp_script = r#"(progn
|
||||||
|
(org-mode)
|
||||||
|
(message "%s" (org-version nil t nil))
|
||||||
|
)"#;
|
||||||
|
let mut cmd = Command::new("emacs");
|
||||||
|
let proc = cmd
|
||||||
|
.arg("-q")
|
||||||
|
.arg("--no-site-file")
|
||||||
|
.arg("--no-splash")
|
||||||
|
.arg("--batch")
|
||||||
|
.arg("--eval")
|
||||||
|
.arg(elisp_script);
|
||||||
|
|
||||||
|
let out = proc.output()?;
|
||||||
|
out.status.exit_ok()?;
|
||||||
|
Ok(String::from_utf8(out.stderr)?)
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,6 +42,37 @@ pub fn assert_bounds<'s, S: Source<'s>>(
|
|||||||
emacs: &'s Token<'s>,
|
emacs: &'s Token<'s>,
|
||||||
rust: &'s S,
|
rust: &'s S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let standard_properties = get_standard_properties(emacs)?;
|
||||||
|
let (begin, end) = (
|
||||||
|
standard_properties
|
||||||
|
.begin
|
||||||
|
.ok_or("Token should have a begin.")?,
|
||||||
|
standard_properties.end.ok_or("Token should have an end.")?,
|
||||||
|
);
|
||||||
|
let (rust_begin, rust_end) = get_offsets(source, rust);
|
||||||
|
if (rust_begin + 1) != begin || (rust_end + 1) != end {
|
||||||
|
Err(format!("Rust bounds (in bytes) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StandardProperties {
|
||||||
|
begin: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
post_affiliated: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
contents_begin: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
contents_end: Option<usize>,
|
||||||
|
end: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
post_blank: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_standard_properties<'s>(
|
||||||
|
emacs: &'s Token<'s>,
|
||||||
|
) -> Result<StandardProperties, Box<dyn std::error::Error>> {
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let attributes_child = children
|
let attributes_child = children
|
||||||
.iter()
|
.iter()
|
||||||
@@ -49,34 +80,80 @@ pub fn assert_bounds<'s, S: Source<'s>>(
|
|||||||
.ok_or("Should have an attributes child.")?;
|
.ok_or("Should have an attributes child.")?;
|
||||||
let attributes_map = attributes_child.as_map()?;
|
let attributes_map = attributes_child.as_map()?;
|
||||||
let standard_properties = attributes_map.get(":standard-properties");
|
let standard_properties = attributes_map.get(":standard-properties");
|
||||||
let (begin, end) = if standard_properties.is_some() {
|
Ok(if standard_properties.is_some() {
|
||||||
let std_props = standard_properties
|
let mut std_props = standard_properties
|
||||||
.expect("if statement proves its Some")
|
.expect("if statement proves its Some")
|
||||||
.as_vector()?;
|
.as_vector()?
|
||||||
let begin = std_props
|
.into_iter();
|
||||||
.get(0)
|
let begin = maybe_token_to_usize(std_props.next())?;
|
||||||
.ok_or("Missing first element in standard properties")?
|
let post_affiliated = maybe_token_to_usize(std_props.next())?;
|
||||||
.as_atom()?;
|
let contents_begin = maybe_token_to_usize(std_props.next())?;
|
||||||
let end = std_props
|
let contents_end = maybe_token_to_usize(std_props.next())?;
|
||||||
.get(1)
|
let end = maybe_token_to_usize(std_props.next())?;
|
||||||
.ok_or("Missing first element in standard properties")?
|
let post_blank = maybe_token_to_usize(std_props.next())?;
|
||||||
.as_atom()?;
|
StandardProperties {
|
||||||
(begin, end)
|
begin,
|
||||||
} else {
|
post_affiliated,
|
||||||
let begin = attributes_map
|
contents_begin,
|
||||||
.get(":begin")
|
contents_end,
|
||||||
.ok_or("Missing :begin attribute.")?
|
end,
|
||||||
.as_atom()?;
|
post_blank,
|
||||||
let end = attributes_map
|
|
||||||
.get(":end")
|
|
||||||
.ok_or("Missing :end attribute.")?
|
|
||||||
.as_atom()?;
|
|
||||||
(begin, end)
|
|
||||||
};
|
|
||||||
let (rust_begin, rust_end) = get_offsets(source, rust);
|
|
||||||
if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end {
|
|
||||||
Err(format!("Rust bounds ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin + 1, rust_end = rust_end + 1, emacs_begin=begin, emacs_end=end))?;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
Ok(())
|
let begin = maybe_token_to_usize(attributes_map.get(":begin").map(|token| *token))?;
|
||||||
|
let end = maybe_token_to_usize(attributes_map.get(":end").map(|token| *token))?;
|
||||||
|
let contents_begin =
|
||||||
|
maybe_token_to_usize(attributes_map.get(":contents-begin").map(|token| *token))?;
|
||||||
|
let contents_end =
|
||||||
|
maybe_token_to_usize(attributes_map.get(":contents-end").map(|token| *token))?;
|
||||||
|
let post_blank =
|
||||||
|
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
|
||||||
|
let post_affiliated =
|
||||||
|
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
|
||||||
|
StandardProperties {
|
||||||
|
begin,
|
||||||
|
post_affiliated,
|
||||||
|
contents_begin,
|
||||||
|
contents_end,
|
||||||
|
end,
|
||||||
|
post_blank,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_token_to_usize(
|
||||||
|
token: Option<&Token<'_>>,
|
||||||
|
) -> Result<Option<usize>, Box<dyn std::error::Error>> {
|
||||||
|
Ok(token
|
||||||
|
.map(|token| token.as_atom())
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.map(|val| {
|
||||||
|
if val == "nil" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(val.parse::<usize>())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use nom::IResult;
|
|||||||
|
|
||||||
pub type Res<T, U> = IResult<T, U, CustomError<T>>;
|
pub type Res<T, U> = IResult<T, U, CustomError<T>>;
|
||||||
|
|
||||||
|
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum CustomError<I> {
|
pub enum CustomError<I> {
|
||||||
MyError(MyError<I>),
|
MyError(MyError<I>),
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ mod compare;
|
|||||||
pub use compare::compare_document;
|
pub use compare::compare_document;
|
||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
pub use compare::emacs_parse_org_document;
|
pub use compare::emacs_parse_org_document;
|
||||||
|
#[cfg(feature = "compare")]
|
||||||
|
pub use compare::get_emacs_version;
|
||||||
|
#[cfg(feature = "compare")]
|
||||||
|
pub use compare::get_org_mode_version;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
|||||||
25
src/main.rs
25
src/main.rs
@@ -7,6 +7,10 @@ use organic::compare_document;
|
|||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
use organic::emacs_parse_org_document;
|
use organic::emacs_parse_org_document;
|
||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
|
use organic::get_emacs_version;
|
||||||
|
#[cfg(feature = "compare")]
|
||||||
|
use organic::get_org_mode_version;
|
||||||
|
#[cfg(feature = "compare")]
|
||||||
use organic::parser::sexp::sexp_with_padding;
|
use organic::parser::sexp::sexp_with_padding;
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
@@ -49,18 +53,23 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
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 (remaining, rust_parsed) = document(org_contents.as_ref()).expect("Org Parse failure");
|
let emacs_version = get_emacs_version()?;
|
||||||
let org_sexp = emacs_parse_org_document(org_contents.as_ref())?;
|
let org_mode_version = get_org_mode_version()?;
|
||||||
|
let org_contents = org_contents.as_ref();
|
||||||
|
eprintln!("Using emacs version: {}", emacs_version.trim());
|
||||||
|
eprintln!("Using org-mode version: {}", org_mode_version.trim());
|
||||||
|
let (remaining, rust_parsed) = document(org_contents).map_err(|e| e.to_string())?;
|
||||||
|
let org_sexp = emacs_parse_org_document(org_contents)?;
|
||||||
let (_remaining, parsed_sexp) =
|
let (_remaining, parsed_sexp) =
|
||||||
sexp_with_padding(org_sexp.as_str()).expect("Sexp Parse failure");
|
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.")?;
|
||||||
@@ -74,8 +83,10 @@ fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error:
|
|||||||
|
|
||||||
#[cfg(not(feature = "compare"))]
|
#[cfg(not(feature = "compare"))]
|
||||||
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>> {
|
||||||
eprintln!("This program was built with compare disabled. Dumping the AST from rust.");
|
eprintln!(
|
||||||
let (remaining, rust_parsed) = document(org_contents.as_ref()).expect("Org Parse failure");
|
"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())?;
|
||||||
println!("{:#?}", rust_parsed);
|
println!("{:#?}", rust_parsed);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use nom::combinator::peek;
|
|||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
@@ -16,7 +17,10 @@ use crate::parser::util::get_consumed;
|
|||||||
use crate::parser::AngleLink;
|
use crate::parser::AngleLink;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, AngleLink<'s>> {
|
pub fn angle_link<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, AngleLink<'s>> {
|
||||||
let (remaining, _) = tag("<")(input)?;
|
let (remaining, _) = tag("<")(input)?;
|
||||||
let (remaining, proto) = protocol(context, remaining)?;
|
let (remaining, proto) = protocol(context, remaining)?;
|
||||||
let (remaining, _separator) = tag(":")(remaining)?;
|
let (remaining, _separator) = tag(":")(remaining)?;
|
||||||
@@ -26,18 +30,21 @@ pub fn angle_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
AngleLink {
|
AngleLink {
|
||||||
source,
|
source: source.into(),
|
||||||
link_type: proto,
|
link_type: proto.into(),
|
||||||
path,
|
path: path.into(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn path_angle<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &path_angle_end,
|
exit_matcher: &path_angle_end,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -48,6 +55,9 @@ fn path_angle<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn path_angle_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn path_angle_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
tag(">")(input)
|
tag(">")(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,17 @@ use nom::multi::many_till;
|
|||||||
use nom::multi::separated_list1;
|
use nom::multi::separated_list1;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::citation_reference::must_balance_bracket;
|
||||||
|
use super::org_source::BracketDepth;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::citation_reference::citation_reference;
|
use crate::parser::citation_reference::citation_reference;
|
||||||
use crate::parser::citation_reference::citation_reference_key;
|
use crate::parser::citation_reference::citation_reference_key;
|
||||||
use crate::parser::citation_reference::get_bracket_depth;
|
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
use crate::parser::object::Citation;
|
use crate::parser::object::Citation;
|
||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
use crate::parser::parser_context::CitationBracket;
|
|
||||||
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;
|
||||||
@@ -29,26 +30,36 @@ use crate::parser::util::get_consumed;
|
|||||||
use crate::parser::Object;
|
use crate::parser::Object;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn citation<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Citation<'s>> {
|
pub fn citation<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Citation<'s>> {
|
||||||
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
|
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
|
||||||
let (remaining, _) = tag_no_case("[cite")(input)?;
|
let (remaining, _) = tag_no_case("[cite")(input)?;
|
||||||
let (remaining, _) = opt(citestyle)(remaining)?;
|
let (remaining, _) = opt(citestyle)(remaining)?;
|
||||||
let (remaining, _) = tag(":")(remaining)?;
|
let (remaining, _) = tag(":")(remaining)?;
|
||||||
let (remaining, _prefix) = opt(parser_with_context!(global_prefix)(context))(remaining)?;
|
let (remaining, _prefix) =
|
||||||
|
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _references) =
|
let (remaining, _references) =
|
||||||
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
|
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
|
||||||
let (remaining, _suffix) = opt(tuple((
|
let (remaining, _suffix) = must_balance_bracket(opt(tuple((
|
||||||
tag(";"),
|
tag(";"),
|
||||||
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, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Citation { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Citation {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tuple((tag("/"), style))(input)?;
|
let (remaining, _) = tuple((tag("/"), style))(input)?;
|
||||||
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
|
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -56,14 +67,14 @@ fn citestyle<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn style<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn style<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(many1(verify(anychar, |c| {
|
recognize(many1(verify(anychar, |c| {
|
||||||
c.is_alphanumeric() || "_-".contains(*c)
|
c.is_alphanumeric() || "_-".contains(*c)
|
||||||
})))(input)
|
})))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn variant<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(many1(verify(anychar, |c| {
|
recognize(many1(verify(anychar, |c| {
|
||||||
c.is_alphanumeric() || "_-/".contains(*c)
|
c.is_alphanumeric() || "_-/".contains(*c)
|
||||||
})))(input)
|
})))(input)
|
||||||
@@ -72,17 +83,13 @@ fn variant<'r, 's>(input: &'s str) -> Res<&'s str, &'s str> {
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn global_prefix<'r, 's>(
|
fn global_prefix<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
let exit_with_depth = global_prefix_end(input.get_bracket_depth());
|
||||||
let parser_context = context
|
let parser_context =
|
||||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
position: input,
|
class: ExitClass::Gamma,
|
||||||
depth: 0,
|
exit_matcher: &exit_with_depth,
|
||||||
}))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
|
||||||
exit_matcher: &global_prefix_end,
|
|
||||||
}));
|
}));
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(
|
many_till(
|
||||||
@@ -95,28 +102,27 @@ fn global_prefix<'r, 's>(
|
|||||||
Ok((remaining, children))
|
Ok((remaining, children))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn global_prefix_end(
|
||||||
|
starting_bracket_depth: BracketDepth,
|
||||||
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_global_prefix_end(context, input, starting_bracket_depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _global_prefix_end<'r, 's>(
|
||||||
let context_depth = get_bracket_depth(context)
|
context: Context<'r, 's>,
|
||||||
.expect("This function should only be called from inside a citation.");
|
input: OrgSource<'s>,
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
starting_bracket_depth: BracketDepth,
|
||||||
let mut current_depth = context_depth.depth;
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for c in text_since_context_entry.chars() {
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
match c {
|
if current_depth < 0 {
|
||||||
'[' => {
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
|
||||||
current_depth += 1;
|
unreachable!("Exceeded citation global prefix bracket depth.")
|
||||||
}
|
|
||||||
']' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded citation global prefix bracket depth.")
|
|
||||||
}
|
|
||||||
']' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -130,17 +136,13 @@ fn global_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn global_suffix<'r, 's>(
|
fn global_suffix<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
let exit_with_depth = global_suffix_end(input.get_bracket_depth());
|
||||||
let parser_context = context
|
let parser_context =
|
||||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
position: input,
|
class: ExitClass::Gamma,
|
||||||
depth: 0,
|
exit_matcher: &exit_with_depth,
|
||||||
}))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
|
||||||
exit_matcher: &global_suffix_end,
|
|
||||||
}));
|
}));
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(
|
many_till(
|
||||||
@@ -152,28 +154,27 @@ fn global_suffix<'r, 's>(
|
|||||||
Ok((remaining, children))
|
Ok((remaining, children))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn global_suffix_end(
|
||||||
|
starting_bracket_depth: BracketDepth,
|
||||||
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_global_suffix_end(context, input, starting_bracket_depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _global_suffix_end<'r, 's>(
|
||||||
let context_depth = get_bracket_depth(context)
|
context: Context<'r, 's>,
|
||||||
.expect("This function should only be called from inside a citation.");
|
input: OrgSource<'s>,
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
starting_bracket_depth: BracketDepth,
|
||||||
let mut current_depth = context_depth.depth;
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for c in text_since_context_entry.chars() {
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
match c {
|
if current_depth < 0 {
|
||||||
'[' => {
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
|
||||||
current_depth += 1;
|
unreachable!("Exceeded citation global suffix bracket depth.")
|
||||||
}
|
|
||||||
']' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded citation global suffix bracket depth.")
|
|
||||||
}
|
|
||||||
']' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -188,24 +189,21 @@ fn global_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::parser::parser_context::ContextElement;
|
|
||||||
use crate::parser::parser_context::ContextTree;
|
use crate::parser::parser_context::ContextTree;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::source::Source;
|
use crate::parser::source::Source;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn citation_simple() {
|
fn citation_simple() {
|
||||||
let input = "[cite:@foo]";
|
let input = OrgSource::new("[cite:@foo]");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
|
||||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||||
let first_paragraph = match first_paragraph {
|
let first_paragraph = match first_paragraph {
|
||||||
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
|
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
|
||||||
assert_eq!(first_paragraph.children.len(), 1);
|
assert_eq!(first_paragraph.children.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ 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::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
use crate::parser::object::CitationReference;
|
use crate::parser::object::CitationReference;
|
||||||
use crate::parser::object_parser::minimal_set_object;
|
use crate::parser::object_parser::minimal_set_object;
|
||||||
use crate::parser::parser_context::CitationBracket;
|
|
||||||
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;
|
||||||
@@ -28,21 +30,28 @@ use crate::parser::Object;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn citation_reference<'r, 's>(
|
pub fn citation_reference<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, CitationReference<'s>> {
|
) -> Res<OrgSource<'s>, CitationReference<'s>> {
|
||||||
let (remaining, _prefix) = opt(parser_with_context!(key_prefix)(context))(input)?;
|
let (remaining, _prefix) =
|
||||||
|
must_balance_bracket(opt(parser_with_context!(key_prefix)(context)))(input)?;
|
||||||
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
|
let (remaining, _key) = parser_with_context!(citation_reference_key)(context)(remaining)?;
|
||||||
let (remaining, _suffix) = opt(parser_with_context!(key_suffix)(context))(remaining)?;
|
let (remaining, _suffix) =
|
||||||
|
must_balance_bracket(opt(parser_with_context!(key_suffix)(context)))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, CitationReference { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
CitationReference {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn citation_reference_key<'r, 's>(
|
pub fn citation_reference_key<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, source) = recognize(tuple((
|
let (remaining, source) = recognize(tuple((
|
||||||
tag("@"),
|
tag("@"),
|
||||||
many1(verify(
|
many1(verify(
|
||||||
@@ -59,16 +68,15 @@ pub fn citation_reference_key<'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
|
fn key_prefix<'r, 's>(
|
||||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
context: Context<'r, 's>,
|
||||||
let parser_context = context
|
input: OrgSource<'s>,
|
||||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
position: input,
|
let exit_with_depth = key_prefix_end(input.get_bracket_depth());
|
||||||
depth: 0,
|
let parser_context =
|
||||||
}))
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
class: ExitClass::Gamma,
|
||||||
class: ExitClass::Beta,
|
exit_matcher: &exit_with_depth,
|
||||||
exit_matcher: &key_prefix_end,
|
|
||||||
}));
|
}));
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(
|
many_till(
|
||||||
@@ -81,16 +89,15 @@ fn key_prefix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
|
fn key_suffix<'r, 's>(
|
||||||
// TODO: I could insert CitationBracket entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
context: Context<'r, 's>,
|
||||||
let parser_context = context
|
input: OrgSource<'s>,
|
||||||
.with_additional_node(ContextElement::CitationBracket(CitationBracket {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
position: input,
|
let exit_with_depth = key_suffix_end(input.get_bracket_depth());
|
||||||
depth: 0,
|
let parser_context =
|
||||||
}))
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
class: ExitClass::Gamma,
|
||||||
class: ExitClass::Beta,
|
exit_matcher: &exit_with_depth,
|
||||||
exit_matcher: &key_suffix_end,
|
|
||||||
}));
|
}));
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(
|
many_till(
|
||||||
@@ -102,39 +109,27 @@ fn key_suffix<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
Ok((remaining, children))
|
Ok((remaining, children))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
fn key_prefix_end(
|
||||||
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r CitationBracket<'s>> {
|
starting_bracket_depth: BracketDepth,
|
||||||
for node in context.iter() {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
match node.get_data() {
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
ContextElement::CitationBracket(depth) => return Some(depth),
|
_key_prefix_end(context, input, starting_bracket_depth)
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _key_prefix_end<'r, 's>(
|
||||||
let context_depth = get_bracket_depth(context)
|
context: Context<'r, 's>,
|
||||||
.expect("This function should only be called from inside a citation reference.");
|
input: OrgSource<'s>,
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
starting_bracket_depth: BracketDepth,
|
||||||
let mut current_depth = context_depth.depth;
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for c in text_since_context_entry.chars() {
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
match c {
|
if current_depth < 0 {
|
||||||
'[' => {
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
|
||||||
current_depth += 1;
|
unreachable!("Exceeded citation key prefix bracket depth.")
|
||||||
}
|
|
||||||
']' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded citation reference key prefix bracket depth.")
|
|
||||||
}
|
|
||||||
']' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -145,31 +140,48 @@ fn key_prefix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn key_suffix_end(
|
||||||
|
starting_bracket_depth: BracketDepth,
|
||||||
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_key_suffix_end(context, input, starting_bracket_depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn key_suffix_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _key_suffix_end<'r, 's>(
|
||||||
let context_depth = get_bracket_depth(context)
|
_context: Context<'r, 's>,
|
||||||
.expect("This function should only be called from inside a citation reference.");
|
input: OrgSource<'s>,
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
starting_bracket_depth: BracketDepth,
|
||||||
let mut current_depth = context_depth.depth;
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for c in text_since_context_entry.chars() {
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
match c {
|
if current_depth < 0 {
|
||||||
'[' => {
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
|
||||||
current_depth += 1;
|
unreachable!("Exceeded citation key suffix bracket depth.")
|
||||||
}
|
|
||||||
']' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded citation reference key prefix bracket depth.")
|
|
||||||
}
|
|
||||||
']' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tag(";")(input)
|
tag(";")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn must_balance_bracket<'s, F, O>(
|
||||||
|
mut inner: F,
|
||||||
|
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>
|
||||||
|
where
|
||||||
|
F: FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>,
|
||||||
|
{
|
||||||
|
move |input: OrgSource<'_>| {
|
||||||
|
let pre_bracket_depth = input.get_bracket_depth();
|
||||||
|
let (remaining, output) = inner(input)?;
|
||||||
|
if remaining.get_bracket_depth() - pre_bracket_depth != 0 {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"UnbalancedBrackets".into(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
Ok((remaining, output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::combinator::recognize;
|
|||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
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;
|
||||||
@@ -19,8 +20,11 @@ use crate::parser::util::start_of_line;
|
|||||||
use crate::parser::Clock;
|
use crate::parser::Clock;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn clock<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Clock<'s>> {
|
pub fn clock<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Clock<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, _clock) = tag_no_case("clock:")(remaining)?;
|
let (remaining, _clock) = tag_no_case("clock:")(remaining)?;
|
||||||
let (remaining, _gap_whitespace) = space1(remaining)?;
|
let (remaining, _gap_whitespace) = space1(remaining)?;
|
||||||
@@ -31,14 +35,19 @@ pub fn clock<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, C
|
|||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Clock { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Clock {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn inactive_timestamp_range_duration<'r, 's>(
|
fn inactive_timestamp_range_duration<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
tag("["),
|
tag("["),
|
||||||
is_not("\r\n]"),
|
is_not("\r\n]"),
|
||||||
@@ -50,14 +59,17 @@ fn inactive_timestamp_range_duration<'r, 's>(
|
|||||||
space1,
|
space1,
|
||||||
digit1,
|
digit1,
|
||||||
tag(":"),
|
tag(":"),
|
||||||
verify(digit1, |mm: &str| mm.len() == 2),
|
verify(digit1, |mm: &OrgSource<'_>| mm.len() == 2),
|
||||||
space0,
|
space0,
|
||||||
alt((line_ending, eof)),
|
alt((line_ending, eof)),
|
||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn inactive_timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn inactive_timestamp<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
tag("["),
|
tag("["),
|
||||||
is_not("\r\n]"),
|
is_not("\r\n]"),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::multi::many0;
|
|||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -24,10 +25,13 @@ use crate::parser::util::start_of_line;
|
|||||||
use crate::parser::Comment;
|
use crate::parser::Comment;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Comment<'s>> {
|
pub fn comment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Comment<'s>> {
|
||||||
if immediate_in_section(context, "comment") {
|
if immediate_in_section(context, "comment") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element",
|
"Cannot nest objects of the same element".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
let parser_context = context.with_additional_node(ContextElement::Context("comment"));
|
let parser_context = context.with_additional_node(ContextElement::Context("comment"));
|
||||||
@@ -38,12 +42,20 @@ pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
|
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Comment { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Comment {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn comment_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn comment_line<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
let (remaining, _indent) = space0(input)?;
|
let (remaining, _indent) = space0(input)?;
|
||||||
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
|
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
|
||||||
tag("#"),
|
tag("#"),
|
||||||
@@ -57,22 +69,21 @@ fn comment_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parser::parser_context::ContextElement;
|
|
||||||
use crate::parser::parser_context::ContextTree;
|
use crate::parser::parser_context::ContextTree;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn require_space_after_hash() {
|
fn require_space_after_hash() {
|
||||||
let input = "# Comment line
|
let input = OrgSource::new(
|
||||||
|
"# Comment line
|
||||||
#not a comment
|
#not a comment
|
||||||
# Comment again";
|
# Comment again",
|
||||||
|
);
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let comment_matcher = parser_with_context!(comment)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let comment_matcher = parser_with_context!(comment)(&document_context);
|
|
||||||
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
|
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
remaining,
|
Into::<&str>::into(remaining),
|
||||||
r#"#not a comment
|
r#"#not a comment
|
||||||
# Comment again"#
|
# Comment again"#
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use nom::combinator::eof;
|
|||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::sexp::sexp;
|
use super::sexp::sexp;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
@@ -14,8 +15,11 @@ use crate::parser::util::start_of_line;
|
|||||||
use crate::parser::DiarySexp;
|
use crate::parser::DiarySexp;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn diary_sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, DiarySexp<'s>> {
|
pub fn diary_sexp<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, _clock) = tag("%%")(remaining)?;
|
let (remaining, _clock) = tag("%%")(remaining)?;
|
||||||
let (remaining, _gap_whitespace) = space0(remaining)?;
|
let (remaining, _gap_whitespace) = space0(remaining)?;
|
||||||
@@ -24,5 +28,10 @@ pub fn diary_sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
|
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, DiarySexp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
DiarySexp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
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::space1;
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
@@ -12,10 +14,13 @@ use nom::multi::many0;
|
|||||||
use nom::multi::many1;
|
use nom::multi::many1;
|
||||||
use nom::multi::many1_count;
|
use nom::multi::many1_count;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
use nom::multi::separated_list1;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::element::Element;
|
use super::element::Element;
|
||||||
use super::object::Object;
|
use super::object::Object;
|
||||||
|
use super::org_source::convert_error;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::parser_with_context::parser_with_context;
|
use super::parser_with_context::parser_with_context;
|
||||||
use super::source::Source;
|
use super::source::Source;
|
||||||
use super::token::AllTokensIterator;
|
use super::token::AllTokensIterator;
|
||||||
@@ -48,7 +53,10 @@ pub struct Document<'s> {
|
|||||||
pub struct Heading<'s> {
|
pub struct Heading<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub stars: usize,
|
pub stars: usize,
|
||||||
|
pub todo_keyword: Option<&'s str>,
|
||||||
|
// TODO: add todo-type enum
|
||||||
pub title: Vec<Object<'s>>,
|
pub title: Vec<Object<'s>>,
|
||||||
|
pub tags: Vec<&'s str>,
|
||||||
pub children: Vec<DocumentElement<'s>>,
|
pub children: Vec<DocumentElement<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +103,10 @@ impl<'s> Source<'s> for Heading<'s> {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn document(input: &str) -> Res<&str, Document> {
|
pub fn document(input: &str) -> Res<&str, Document> {
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let wrapped_input = OrgSource::new(input);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
let (remaining, document) = _document(&initial_context, wrapped_input)
|
||||||
let (remaining, document) = _document(&document_context, input)?;
|
.map(|(rem, out)| (Into::<&str>::into(rem), out))
|
||||||
|
.map_err(convert_error)?;
|
||||||
{
|
{
|
||||||
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
||||||
let all_radio_targets: Vec<&Vec<Object<'_>>> = document
|
let all_radio_targets: Vec<&Vec<Object<'_>>> = document
|
||||||
@@ -113,19 +122,24 @@ pub fn document(input: &str) -> Res<&str, Document> {
|
|||||||
.map(|rt| &rt.children)
|
.map(|rt| &rt.children)
|
||||||
.collect();
|
.collect();
|
||||||
if !all_radio_targets.is_empty() {
|
if !all_radio_targets.is_empty() {
|
||||||
let document_context = document_context
|
let initial_context = initial_context
|
||||||
.with_additional_node(ContextElement::RadioTarget(all_radio_targets));
|
.with_additional_node(ContextElement::RadioTarget(all_radio_targets));
|
||||||
let (remaining, document) = _document(&document_context, input)?;
|
let (remaining, document) = _document(&initial_context, wrapped_input)
|
||||||
return Ok((remaining, document));
|
.map(|(rem, out)| (Into::<&str>::into(rem), out))
|
||||||
|
.map_err(convert_error)?;
|
||||||
|
return Ok((remaining.into(), document));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((remaining, document))
|
Ok((remaining.into(), document))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
|
fn _document<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'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)?;
|
||||||
@@ -133,7 +147,7 @@ pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Document {
|
Document {
|
||||||
source,
|
source: source.into(),
|
||||||
zeroth_section,
|
zeroth_section,
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
@@ -141,7 +155,10 @@ pub fn _document<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn zeroth_section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Section<'s>> {
|
fn zeroth_section<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Section<'s>> {
|
||||||
// TODO: The zeroth section is specialized so it probably needs its own parser
|
// TODO: The zeroth section is specialized so it probably needs its own parser
|
||||||
let parser_context = context
|
let parser_context = context
|
||||||
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
|
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
|
||||||
@@ -182,11 +199,20 @@ fn zeroth_section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Section { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Section {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn section<'r, 's>(context: Context<'r, 's>, mut input: &'s str) -> Res<&'s str, Section<'s>> {
|
fn section<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
mut input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Section<'s>> {
|
||||||
// TODO: The zeroth section is specialized so it probably needs its own parser
|
// TODO: The zeroth section is specialized so it probably needs its own parser
|
||||||
let parser_context = context
|
let parser_context = context
|
||||||
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
|
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
|
||||||
@@ -222,67 +248,161 @@ fn section<'r, 's>(context: Context<'r, 's>, mut input: &'s str) -> Res<&'s str,
|
|||||||
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)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
|
||||||
Ok((remaining, Section { source, children }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
fn section_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
|
||||||
let headline_matcher = parser_with_context!(headline)(context);
|
|
||||||
recognize(headline_matcher)(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Heading<'s>> {
|
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
|
||||||
let (remaining, (star_count, _ws, title)) = headline(context, input)?;
|
|
||||||
let section_matcher = parser_with_context!(section)(context);
|
|
||||||
let heading_matcher = parser_with_context!(heading)(context);
|
|
||||||
let (remaining, children) = many0(alt((
|
|
||||||
map(
|
|
||||||
verify(heading_matcher, |h| h.stars > star_count),
|
|
||||||
DocumentElement::Heading,
|
|
||||||
),
|
|
||||||
map(section_matcher, DocumentElement::Section),
|
|
||||||
)))(remaining)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Heading {
|
Section {
|
||||||
source,
|
source: source.into(),
|
||||||
stars: star_count,
|
|
||||||
title,
|
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn headline<'r, 's>(
|
fn section_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, (usize, &'s str, Vec<Object<'s>>)> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let parser_context =
|
recognize(detect_headline)(input)
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
}
|
||||||
class: ExitClass::Document,
|
|
||||||
exit_matcher: &headline_end,
|
|
||||||
}));
|
|
||||||
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
|
||||||
let start_of_line_matcher = parser_with_context!(start_of_line)(&parser_context);
|
|
||||||
|
|
||||||
let (remaining, (_sol, star_count, ws, title, _line_ending)) = tuple((
|
const fn heading(
|
||||||
start_of_line_matcher,
|
parent_stars: usize,
|
||||||
many1_count(tag("*")),
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||||
space1,
|
move |context: Context, input: OrgSource<'_>| _heading(context, input, parent_stars)
|
||||||
many1(standard_set_object_matcher),
|
|
||||||
alt((line_ending, eof)),
|
|
||||||
))(input)?;
|
|
||||||
Ok((remaining, (star_count, ws, title)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn headline_end<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _heading<'r, 's>(
|
||||||
line_ending(input)
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
parent_stars: usize,
|
||||||
|
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||||
|
not(|i| context.check_exit_matcher(i))(input)?;
|
||||||
|
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
|
||||||
|
headline(context, input, parent_stars)?;
|
||||||
|
let section_matcher = parser_with_context!(section)(context);
|
||||||
|
let heading_matcher = parser_with_context!(heading(star_count))(context);
|
||||||
|
let (remaining, maybe_section) =
|
||||||
|
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
|
||||||
|
let (remaining, mut children) =
|
||||||
|
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
|
||||||
|
if let Some(section) = maybe_section {
|
||||||
|
children.insert(0, section);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Heading {
|
||||||
|
source: source.into(),
|
||||||
|
stars: star_count,
|
||||||
|
todo_keyword: maybe_todo_keyword
|
||||||
|
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
|
||||||
|
title,
|
||||||
|
tags: heading_tags,
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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"))]
|
||||||
|
fn headline<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
parent_stars: usize,
|
||||||
|
) -> Res<
|
||||||
|
OrgSource<'s>,
|
||||||
|
(
|
||||||
|
usize,
|
||||||
|
OrgSource<'s>,
|
||||||
|
Option<(OrgSource<'s>, OrgSource<'s>)>,
|
||||||
|
Vec<Object<'s>>,
|
||||||
|
Vec<&'s str>,
|
||||||
|
),
|
||||||
|
> {
|
||||||
|
let parser_context =
|
||||||
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Document,
|
||||||
|
exit_matcher: &headline_title_end,
|
||||||
|
}));
|
||||||
|
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||||
|
|
||||||
|
let (
|
||||||
|
remaining,
|
||||||
|
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
|
||||||
|
) = tuple((
|
||||||
|
start_of_line,
|
||||||
|
verify(many1_count(tag("*")), |star_count| {
|
||||||
|
*star_count > parent_stars
|
||||||
|
}),
|
||||||
|
space1,
|
||||||
|
opt(tuple((heading_keyword, space1))),
|
||||||
|
many1(standard_set_object_matcher),
|
||||||
|
opt(tuple((space0, tags))),
|
||||||
|
space0,
|
||||||
|
alt((line_ending, eof)),
|
||||||
|
))(input)?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
(
|
||||||
|
star_count,
|
||||||
|
ws,
|
||||||
|
maybe_todo_keyword,
|
||||||
|
title,
|
||||||
|
maybe_tags
|
||||||
|
.map(|(_ws, tags)| {
|
||||||
|
tags.into_iter()
|
||||||
|
.map(|single_tag| Into::<&str>::into(single_tag))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn headline_title_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
recognize(tuple((
|
||||||
|
opt(tuple((space0, tags, space0))),
|
||||||
|
alt((line_ending, eof)),
|
||||||
|
)))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
|
||||||
|
let (remaining, (_open, tags, _close)) =
|
||||||
|
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
|
||||||
|
Ok((remaining, tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
recognize(many1(verify(anychar, |c| {
|
||||||
|
c.is_alphanumeric() || "_@#%".contains(*c)
|
||||||
|
})))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
|
||||||
|
alt((tag("TODO"), tag("DONE")))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Document<'s> {
|
impl<'s> Document<'s> {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use nom::combinator::recognize;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -31,13 +32,16 @@ use crate::parser::Element;
|
|||||||
use crate::parser::Paragraph;
|
use crate::parser::Paragraph;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Drawer<'s>> {
|
pub fn drawer<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Drawer<'s>> {
|
||||||
if immediate_in_section(context, "drawer") {
|
if immediate_in_section(context, "drawer") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element",
|
"Cannot nest objects of the same element".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
|
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
|
||||||
tag(":"),
|
tag(":"),
|
||||||
@@ -63,9 +67,9 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
))(remaining)
|
))(remaining)
|
||||||
{
|
{
|
||||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line));
|
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||||
let source = get_consumed(remaining, remain);
|
let source = get_consumed(remaining, remain);
|
||||||
element.set_source(source);
|
element.set_source(source.into());
|
||||||
(remain, vec![element])
|
(remain, vec![element])
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -81,21 +85,24 @@ pub fn drawer<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Drawer {
|
Drawer {
|
||||||
source,
|
source: source.into(),
|
||||||
name: drawer_name,
|
name: drawer_name.into(),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
|
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn drawer_end<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
space0,
|
space0,
|
||||||
tag_no_case(":end:"),
|
tag_no_case(":end:"),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::combinator::recognize;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -33,15 +34,15 @@ use crate::parser::Element;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn dynamic_block<'r, 's>(
|
pub fn dynamic_block<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, DynamicBlock<'s>> {
|
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
|
||||||
// TODO: Do I need to differentiate between different dynamic block types.
|
// TODO: Do I need to differentiate between different dynamic block types.
|
||||||
if immediate_in_section(context, "dynamic block") {
|
if immediate_in_section(context, "dynamic block") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element",
|
"Cannot nest objects of the same element".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, name, parameters, _ws)) = tuple((
|
let (remaining, (_begin, name, parameters, _ws)) = tuple((
|
||||||
recognize(tuple((tag_no_case("#+begin:"), space1))),
|
recognize(tuple((tag_no_case("#+begin:"), space1))),
|
||||||
@@ -69,9 +70,9 @@ pub fn dynamic_block<'r, 's>(
|
|||||||
))(remaining)
|
))(remaining)
|
||||||
{
|
{
|
||||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line));
|
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||||
let source = get_consumed(remaining, remain);
|
let source = get_consumed(remaining, remain);
|
||||||
element.set_source(source);
|
element.set_source(source.into());
|
||||||
(remain, vec![element])
|
(remain, vec![element])
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -86,27 +87,30 @@ pub fn dynamic_block<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
DynamicBlock {
|
DynamicBlock {
|
||||||
source,
|
source: source.into(),
|
||||||
name,
|
name: name.into(),
|
||||||
parameters,
|
parameters: parameters.map(|val| val.into()),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not(" \t\r\n")(input)
|
is_not(" \t\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn parameters<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not("\r\n")(input)
|
is_not("\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn dynamic_block_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn dynamic_block_end<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
let (remaining, source) = recognize(tuple((
|
let (remaining, source) = recognize(tuple((
|
||||||
space0,
|
space0,
|
||||||
tag_no_case("#+end:"),
|
tag_no_case("#+end:"),
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -19,28 +20,32 @@ use super::lesser_block::example_block;
|
|||||||
use super::lesser_block::export_block;
|
use super::lesser_block::export_block;
|
||||||
use super::lesser_block::src_block;
|
use super::lesser_block::src_block;
|
||||||
use super::lesser_block::verse_block;
|
use super::lesser_block::verse_block;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::paragraph::paragraph;
|
use super::paragraph::paragraph;
|
||||||
|
use super::plain_list::detect_plain_list;
|
||||||
use super::plain_list::plain_list;
|
use super::plain_list::plain_list;
|
||||||
use super::source::SetSource;
|
use super::source::SetSource;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_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::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::table::org_mode_table;
|
use crate::parser::table::org_mode_table;
|
||||||
|
|
||||||
pub fn element(
|
pub const fn element(
|
||||||
can_be_paragraph: bool,
|
can_be_paragraph: bool,
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, Element<'s>> {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Element<'s>> {
|
||||||
move |context: Context, input: &str| _element(context, input, can_be_paragraph)
|
move |context: Context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _element<'r, 's>(
|
fn _element<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
can_be_paragraph: bool,
|
can_be_paragraph: bool,
|
||||||
) -> Res<&'s str, Element<'s>> {
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||||
let plain_list_matcher = parser_with_context!(plain_list)(context);
|
let plain_list_matcher = parser_with_context!(plain_list)(context);
|
||||||
let greater_block_matcher = parser_with_context!(greater_block)(context);
|
let greater_block_matcher = parser_with_context!(greater_block)(context);
|
||||||
let dynamic_block_matcher = parser_with_context!(dynamic_block)(context);
|
let dynamic_block_matcher = parser_with_context!(dynamic_block)(context);
|
||||||
@@ -58,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),
|
||||||
@@ -80,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,
|
||||||
@@ -89,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}?;
|
}?;
|
||||||
@@ -103,7 +111,30 @@ fn _element<'r, 's>(
|
|||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
element.set_source(source);
|
element.set_source(source.into());
|
||||||
|
|
||||||
Ok((remaining, element))
|
Ok((remaining, element))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn detect_element(
|
||||||
|
can_be_paragraph: bool,
|
||||||
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
|
move |context: Context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn _detect_element<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
can_be_paragraph: bool,
|
||||||
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
|
if detect_plain_list(context, input).is_ok() {
|
||||||
|
return Ok((input, ()));
|
||||||
|
}
|
||||||
|
if _element(context, input, can_be_paragraph).is_ok() {
|
||||||
|
return Ok((input, ()));
|
||||||
|
}
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"No element detected.".into(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,43 +7,474 @@ 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::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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Entity<'s>> {
|
pub fn entity<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'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("{}"),
|
|
||||||
peek(recognize(parser_with_context!(entity_end)(context))),
|
|
||||||
))(remaining)?;
|
|
||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Entity {
|
Entity {
|
||||||
source,
|
source: source.into(),
|
||||||
entity_name,
|
entity_name: entity_name.into(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: 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((tag_no_case("delta"), tag_no_case("pi"))),))(input)?;
|
"NoEntity".into(),
|
||||||
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: &'s str) -> Res<&'s str, ()> {
|
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, ()))
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ pub enum ExitClass {
|
|||||||
|
|
||||||
/// Elements who cede priority to alpha elements when matching.
|
/// Elements who cede priority to alpha elements when matching.
|
||||||
Beta = 300,
|
Beta = 300,
|
||||||
|
|
||||||
|
/// Elements who cede priority to alpha and beta elements when matching.
|
||||||
|
Gamma = 4000,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ExitClass {
|
impl std::fmt::Display for ExitClass {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use nom::multi::many1;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
@@ -20,13 +21,13 @@ use crate::parser::ExportSnippet;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn export_snippet<'r, 's>(
|
pub fn export_snippet<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, ExportSnippet<'s>> {
|
) -> Res<OrgSource<'s>, ExportSnippet<'s>> {
|
||||||
let (remaining, _) = tag("@@")(input)?;
|
let (remaining, _) = tag("@@")(input)?;
|
||||||
let (remaining, backend_name) = backend(context, remaining)?;
|
let (remaining, backend_name) = backend(context, remaining)?;
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &export_snippet_end,
|
exit_matcher: &export_snippet_end,
|
||||||
}));
|
}));
|
||||||
let (remaining, backend_contents) = opt(tuple((
|
let (remaining, backend_contents) = opt(tuple((
|
||||||
@@ -38,15 +39,18 @@ pub fn export_snippet<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
ExportSnippet {
|
ExportSnippet {
|
||||||
source,
|
source: source.into(),
|
||||||
backend: backend_name,
|
backend: backend_name.into(),
|
||||||
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents),
|
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn backend<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn backend<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, backend_name) =
|
let (remaining, backend_name) =
|
||||||
recognize(many1(verify(anychar, |c| c.is_alphanumeric() || *c == '-')))(input)?;
|
recognize(many1(verify(anychar, |c| c.is_alphanumeric() || *c == '-')))(input)?;
|
||||||
|
|
||||||
@@ -54,7 +58,10 @@ fn backend<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn contents<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn contents<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, source) = recognize(verify(
|
let (remaining, source) = recognize(verify(
|
||||||
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|
||||||
|(children, _exit_contents)| !children.is_empty(),
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
@@ -63,6 +70,9 @@ fn contents<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn export_snippet_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn export_snippet_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
tag("@@")(input)
|
tag("@@")(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::multi::many0;
|
|||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
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;
|
||||||
@@ -22,8 +23,8 @@ use crate::parser::FixedWidthArea;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn fixed_width_area<'r, 's>(
|
pub fn fixed_width_area<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, FixedWidthArea<'s>> {
|
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
|
||||||
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
|
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
||||||
let (remaining, _first_line) = fixed_width_area_line_matcher(input)?;
|
let (remaining, _first_line) = fixed_width_area_line_matcher(input)?;
|
||||||
@@ -31,15 +32,20 @@ pub fn fixed_width_area<'r, 's>(
|
|||||||
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
|
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, FixedWidthArea { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
FixedWidthArea {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn fixed_width_area_line<'r, 's>(
|
fn fixed_width_area_line<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _indent) = space0(input)?;
|
let (remaining, _indent) = space0(input)?;
|
||||||
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
|
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
|
||||||
tag(":"),
|
tag(":"),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use nom::multi::many1;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::util::WORD_CONSTITUENT_CHARACTERS;
|
use super::util::WORD_CONSTITUENT_CHARACTERS;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -31,14 +32,14 @@ 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 footnote_definition<'r, 's>(
|
pub fn footnote_definition<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, FootnoteDefinition<'s>> {
|
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
|
||||||
if immediate_in_section(context, "footnote definition") {
|
if immediate_in_section(context, "footnote definition") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element",
|
"Cannot nest objects of the same element".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
// Cannot be indented.
|
// Cannot be indented.
|
||||||
let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
|
let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
|
||||||
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
|
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
|
||||||
@@ -59,15 +60,15 @@ pub fn footnote_definition<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteDefinition {
|
FootnoteDefinition {
|
||||||
source,
|
source: source.into(),
|
||||||
label: lbl,
|
label: lbl.into(),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
alt((
|
||||||
digit1,
|
digit1,
|
||||||
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
|
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
|
||||||
@@ -77,9 +78,8 @@ pub fn label<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn footnote_definition_end<'r, 's>(
|
fn footnote_definition_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let start_of_line_matcher = parser_with_context!(start_of_line)(context);
|
|
||||||
let allow_nesting_context =
|
let allow_nesting_context =
|
||||||
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
|
context.with_additional_node(ContextElement::Context("allow nesting footnotes"));
|
||||||
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
|
let footnote_definition_matcher = parser_with_context!(footnote_definition)(
|
||||||
@@ -97,8 +97,10 @@ fn footnote_definition_end<'r, 's>(
|
|||||||
footnote_definition_matcher,
|
footnote_definition_matcher,
|
||||||
))),
|
))),
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
start_of_line_matcher,
|
start_of_line,
|
||||||
verify(many1(blank_line), |lines: &Vec<&str>| lines.len() >= 2),
|
verify(many1(blank_line), |lines: &Vec<OrgSource<'_>>| {
|
||||||
|
lines.len() >= 2
|
||||||
|
}),
|
||||||
))),
|
))),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
|
|
||||||
@@ -108,27 +110,26 @@ fn footnote_definition_end<'r, 's>(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parser::parser_context::ContextElement;
|
|
||||||
use crate::parser::parser_context::ContextTree;
|
use crate::parser::parser_context::ContextTree;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::Source;
|
use crate::parser::Source;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_paragraphs() {
|
fn two_paragraphs() {
|
||||||
let input = "[fn:1] A footnote.
|
let input = OrgSource::new(
|
||||||
|
"[fn:1] A footnote.
|
||||||
|
|
||||||
[fn:2] A multi-
|
[fn:2] A multi-
|
||||||
|
|
||||||
line footnote.";
|
line footnote.",
|
||||||
|
);
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let footnote_definition_matcher = parser_with_context!(element(true))(&document_context);
|
|
||||||
let (remaining, first_footnote_definition) =
|
let (remaining, first_footnote_definition) =
|
||||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||||
let (remaining, second_footnote_definition) =
|
let (remaining, second_footnote_definition) =
|
||||||
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_footnote_definition.get_source(),
|
first_footnote_definition.get_source(),
|
||||||
"[fn:1] A footnote.
|
"[fn:1] A footnote.
|
||||||
@@ -145,19 +146,19 @@ line footnote."
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiline_break() {
|
fn multiline_break() {
|
||||||
let input = "[fn:2] A multi-
|
let input = OrgSource::new(
|
||||||
|
"[fn:2] A multi-
|
||||||
|
|
||||||
line footnote.
|
line footnote.
|
||||||
|
|
||||||
|
|
||||||
not in the footnote.";
|
not in the footnote.",
|
||||||
|
);
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let footnote_definition_matcher = parser_with_context!(element(true))(&document_context);
|
|
||||||
let (remaining, first_footnote_definition) =
|
let (remaining, first_footnote_definition) =
|
||||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||||
assert_eq!(remaining, "not in the footnote.");
|
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_footnote_definition.get_source(),
|
first_footnote_definition.get_source(),
|
||||||
"[fn:2] A multi-
|
"[fn:2] A multi-
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ 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::parser_context::ContextElement;
|
use super::parser_context::ContextElement;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -14,18 +16,16 @@ use crate::parser::exiting::ExitClass;
|
|||||||
use crate::parser::footnote_definition::label;
|
use crate::parser::footnote_definition::label;
|
||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
use crate::parser::parser_context::ExitMatcherNode;
|
use crate::parser::parser_context::ExitMatcherNode;
|
||||||
use crate::parser::parser_context::FootnoteReferenceDefinition;
|
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
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::FootnoteReference;
|
use crate::parser::FootnoteReference;
|
||||||
use crate::parser::Object;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn footnote_reference<'r, 's>(
|
pub fn footnote_reference<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, FootnoteReference<'s>> {
|
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
|
||||||
alt((
|
alt((
|
||||||
parser_with_context!(anonymous_footnote)(context),
|
parser_with_context!(anonymous_footnote)(context),
|
||||||
parser_with_context!(footnote_reference_only)(context),
|
parser_with_context!(footnote_reference_only)(context),
|
||||||
@@ -36,21 +36,15 @@ pub fn footnote_reference<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn anonymous_footnote<'r, 's>(
|
fn anonymous_footnote<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, FootnoteReference<'s>> {
|
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
|
||||||
let (remaining, _) = tag_no_case("[fn::")(input)?;
|
let (remaining, _) = tag_no_case("[fn::")(input)?;
|
||||||
let parser_context = context
|
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
|
||||||
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
|
let parser_context =
|
||||||
FootnoteReferenceDefinition {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
position: remaining,
|
|
||||||
depth: 0,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
exit_matcher: &footnote_definition_end,
|
exit_matcher: &exit_with_depth,
|
||||||
}));
|
}));
|
||||||
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(
|
many_till(
|
||||||
parser_with_context!(standard_set_object)(&parser_context),
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
@@ -65,7 +59,7 @@ fn anonymous_footnote<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteReference {
|
FootnoteReference {
|
||||||
source,
|
source: source.into(),
|
||||||
label: None,
|
label: None,
|
||||||
definition: children,
|
definition: children,
|
||||||
},
|
},
|
||||||
@@ -75,23 +69,17 @@ fn anonymous_footnote<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn inline_footnote<'r, 's>(
|
fn inline_footnote<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, 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 parser_context = context
|
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
|
||||||
.with_additional_node(ContextElement::FootnoteReferenceDefinition(
|
let parser_context =
|
||||||
FootnoteReferenceDefinition {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
position: remaining,
|
|
||||||
depth: 0,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
exit_matcher: &footnote_definition_end,
|
exit_matcher: &exit_with_depth,
|
||||||
}));
|
}));
|
||||||
// TODO: I could insert FootnoteReferenceDefinition entries in the context after each matched object to reduce the scanning done for counting brackets which should be more efficient.
|
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(
|
many_till(
|
||||||
parser_with_context!(standard_set_object)(&parser_context),
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
@@ -106,8 +94,8 @@ fn inline_footnote<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteReference {
|
FootnoteReference {
|
||||||
source,
|
source: source.into(),
|
||||||
label: Some(label_contents),
|
label: Some(label_contents.into()),
|
||||||
definition: children,
|
definition: children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -115,9 +103,9 @@ 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: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, 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)?;
|
||||||
@@ -126,59 +114,37 @@ fn footnote_reference_only<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteReference {
|
FootnoteReference {
|
||||||
source,
|
source: source.into(),
|
||||||
label: Some(label_contents),
|
label: Some(label_contents.into()),
|
||||||
definition: Vec::with_capacity(0),
|
definition: Vec::with_capacity(0),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
fn footnote_definition_end(
|
||||||
fn definition<'s>(input: &'s str) -> Res<&'s str, Vec<Object<'s>>> {
|
starting_bracket_depth: BracketDepth,
|
||||||
Ok((input, vec![]))
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_footnote_definition_end(context, input, starting_bracket_depth)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn footnote_definition_end<'r, 's>(
|
fn _footnote_definition_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
starting_bracket_depth: BracketDepth,
|
||||||
let context_depth = get_bracket_depth(context)
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
.expect("This function should only be called from inside a footnote definition.");
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
|
||||||
let mut current_depth = context_depth.depth;
|
|
||||||
for c in text_since_context_entry.chars() {
|
|
||||||
match c {
|
|
||||||
'[' => {
|
|
||||||
current_depth += 1;
|
|
||||||
}
|
|
||||||
']' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded footnote reference definition bracket depth.")
|
|
||||||
}
|
|
||||||
']' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current_depth > 0 {
|
if current_depth > 0 {
|
||||||
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
|
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"NoFootnoteReferenceDefinitionEnd",
|
"NoFootnoteReferenceDefinitionEnd".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.
|
||||||
|
unreachable!("Exceeded footnote reference definition bracket depth.")
|
||||||
|
}
|
||||||
tag("]")(input)
|
tag("]")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
fn get_bracket_depth<'r, 's>(
|
|
||||||
context: Context<'r, 's>,
|
|
||||||
) -> Option<&'r FootnoteReferenceDefinition<'s>> {
|
|
||||||
for node in context.iter() {
|
|
||||||
match node.get_data() {
|
|
||||||
ContextElement::FootnoteReferenceDefinition(depth) => return Some(depth),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::combinator::verify;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -33,37 +34,39 @@ use crate::parser::Paragraph;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn greater_block<'r, 's>(
|
pub fn greater_block<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, GreaterBlock<'s>> {
|
) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
|
||||||
// TODO: Do I need to differentiate between different greater block types.
|
// TODO: Do I need to differentiate between different greater block types.
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, name)) = tuple((
|
let (remaining, (_begin, name)) = tuple((
|
||||||
tag_no_case("#+begin_"),
|
tag_no_case("#+begin_"),
|
||||||
verify(name, |name: &str| match name.to_lowercase().as_str() {
|
verify(name, |name: &OrgSource<'_>| {
|
||||||
|
match Into::<&str>::into(name).to_lowercase().as_str() {
|
||||||
"comment" | "example" | "export" | "src" | "verse" => false,
|
"comment" | "example" | "export" | "src" | "verse" => false,
|
||||||
_ => true,
|
_ => true,
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let context_name = match name.to_lowercase().as_str() {
|
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
|
||||||
"center" => "center block",
|
"center" => "center block",
|
||||||
"quote" => "quote block",
|
"quote" => "quote block",
|
||||||
_ => "greater block",
|
_ => "greater block",
|
||||||
};
|
};
|
||||||
if immediate_in_section(context, context_name) {
|
if immediate_in_section(context, context_name) {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element",
|
"Cannot nest objects of the same element".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
let exit_with_name = greater_block_end(name.into());
|
||||||
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
|
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
|
||||||
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))
|
||||||
.with_additional_node(ContextElement::GreaterBlock(name))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Alpha,
|
class: ExitClass::Alpha,
|
||||||
exit_matcher: &greater_block_end,
|
exit_matcher: &exit_with_name,
|
||||||
}));
|
}));
|
||||||
let parameters = match parameters {
|
let parameters = match parameters {
|
||||||
Some((_ws, parameters)) => Some(parameters),
|
Some((_ws, parameters)) => Some(parameters),
|
||||||
@@ -80,9 +83,9 @@ pub fn greater_block<'r, 's>(
|
|||||||
))(remaining)
|
))(remaining)
|
||||||
{
|
{
|
||||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line));
|
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||||
let source = get_consumed(remaining, remain);
|
let source = get_consumed(remaining, remain);
|
||||||
element.set_source(source);
|
element.set_source(source.into());
|
||||||
(remain, vec![element])
|
(remain, vec![element])
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -91,7 +94,7 @@ pub fn greater_block<'r, 's>(
|
|||||||
(remaining, children)
|
(remaining, children)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (remaining, _end) = greater_block_end(&parser_context, remaining)?;
|
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
|
||||||
|
|
||||||
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
|
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
|
||||||
|
|
||||||
@@ -99,46 +102,45 @@ pub fn greater_block<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
GreaterBlock {
|
GreaterBlock {
|
||||||
source,
|
source: source.into(),
|
||||||
name,
|
name: name.into(),
|
||||||
parameters,
|
parameters: parameters.map(|val| Into::<&str>::into(val)),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not(" \t\r\n")(input)
|
is_not(" \t\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn parameters<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not("\r\n")(input)
|
is_not("\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn greater_block_end<'x>(
|
||||||
|
name: &'x str,
|
||||||
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
// TODO: Can this be done without making an owned copy?
|
||||||
|
let name = name.to_owned();
|
||||||
|
move |context: Context, input: OrgSource<'_>| _greater_block_end(context, input, name.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn greater_block_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _greater_block_end<'r, 's, 'x>(
|
||||||
start_of_line(context, input)?;
|
_context: Context<'r, 's>,
|
||||||
let current_name: &str = get_context_greater_block_name(context).ok_or(nom::Err::Error(
|
input: OrgSource<'s>,
|
||||||
CustomError::MyError(MyError("Not inside a greater block")),
|
name: &'x str,
|
||||||
))?;
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, _name, _ws)) = tuple((
|
let (remaining, (_begin, _name, _ws)) = tuple((
|
||||||
tag_no_case("#+end_"),
|
tag_no_case("#+end_"),
|
||||||
tag_no_case(current_name),
|
tag_no_case(name),
|
||||||
alt((eof, line_ending)),
|
alt((eof, line_ending)),
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_context_greater_block_name<'r, 's>(context: Context<'r, 's>) -> Option<&'s str> {
|
|
||||||
for thing in context.iter() {
|
|
||||||
match thing.get_data() {
|
|
||||||
ContextElement::GreaterBlock(name) => return Some(name),
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use nom::combinator::verify;
|
|||||||
use nom::multi::many1_count;
|
use nom::multi::many1_count;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::start_of_line;
|
use crate::parser::util::start_of_line;
|
||||||
@@ -15,15 +16,20 @@ use crate::parser::HorizontalRule;
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn horizontal_rule<'r, 's>(
|
pub fn horizontal_rule<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, HorizontalRule<'s>> {
|
) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, rule) = recognize(tuple((
|
let (remaining, rule) = recognize(tuple((
|
||||||
space0,
|
space0,
|
||||||
verify(many1_count(tag("-")), |dashes| *dashes >= 5),
|
verify(many1_count(tag("-")), |dashes| *dashes >= 5),
|
||||||
space0,
|
space0,
|
||||||
alt((line_ending, eof)),
|
alt((line_ending, eof)),
|
||||||
)))(input)?;
|
)))(input)?;
|
||||||
Ok((remaining, HorizontalRule { source: rule }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
HorizontalRule {
|
||||||
|
source: rule.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ 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::Context;
|
use super::Context;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
use crate::parser::parser_context::BabelHeaderBracket;
|
|
||||||
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;
|
||||||
@@ -24,8 +27,8 @@ use crate::parser::InlineBabelCall;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn inline_babel_call<'r, 's>(
|
pub fn inline_babel_call<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, InlineBabelCall<'s>> {
|
) -> Res<OrgSource<'s>, InlineBabelCall<'s>> {
|
||||||
let (remaining, _) = tag_no_case("call_")(input)?;
|
let (remaining, _) = tag_no_case("call_")(input)?;
|
||||||
let (remaining, _name) = name(context, remaining)?;
|
let (remaining, _name) = name(context, remaining)?;
|
||||||
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
|
let (remaining, _header1) = opt(parser_with_context!(header)(context))(remaining)?;
|
||||||
@@ -33,14 +36,22 @@ pub fn inline_babel_call<'r, 's>(
|
|||||||
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, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, InlineBabelCall { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
InlineBabelCall {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &name_end,
|
exit_matcher: &name_end,
|
||||||
}));
|
}));
|
||||||
let (remaining, name) = recognize(many_till(
|
let (remaining, name) = recognize(many_till(
|
||||||
@@ -51,22 +62,25 @@ fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn name_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(one_of("[("))(input)
|
recognize(one_of("[("))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn header<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
|
|
||||||
let parser_context = context
|
let exit_with_depth = header_end(remaining.get_bracket_depth());
|
||||||
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
|
let parser_context =
|
||||||
position: remaining,
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
depth: 0,
|
class: ExitClass::Gamma,
|
||||||
}))
|
exit_matcher: &exit_with_depth,
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
|
||||||
exit_matcher: &header_end,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let (remaining, name) = recognize(many_till(
|
let (remaining, name) = recognize(many_till(
|
||||||
@@ -77,41 +91,46 @@ fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
|
|||||||
Ok((remaining, name))
|
Ok((remaining, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header_end(
|
||||||
|
starting_bracket_depth: BracketDepth,
|
||||||
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_header_end(context, input, starting_bracket_depth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn header_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _header_end<'r, 's>(
|
||||||
let context_depth = get_bracket_depth(context)
|
_context: Context<'r, 's>,
|
||||||
.expect("This function should only be called from inside an inline babel call header.");
|
input: OrgSource<'s>,
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
starting_bracket_depth: BracketDepth,
|
||||||
let mut current_depth = context_depth.depth;
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for c in text_since_context_entry.chars() {
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
match c {
|
if current_depth > 0 {
|
||||||
'(' => {
|
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||||
current_depth += 1;
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
}
|
"NoHeaderEnd".into(),
|
||||||
')' if current_depth == 0 => {
|
))));
|
||||||
panic!("Exceeded inline babel call header bracket depth.")
|
|
||||||
}
|
|
||||||
')' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
||||||
|
unreachable!("Exceeded header bracket depth.")
|
||||||
}
|
}
|
||||||
alt((tag("]"), line_ending))(input)
|
alt((tag("]"), line_ending))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn argument<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn argument<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tag("(")(input)?;
|
let (remaining, _) = tag("(")(input)?;
|
||||||
|
|
||||||
let parser_context = context
|
let exit_with_depth = argument_end(remaining.get_parenthesis_depth());
|
||||||
.with_additional_node(ContextElement::BabelHeaderBracket(BabelHeaderBracket {
|
let parser_context =
|
||||||
position: remaining,
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
depth: 0,
|
class: ExitClass::Gamma,
|
||||||
}))
|
exit_matcher: &exit_with_depth,
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
|
||||||
exit_matcher: &argument_end,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let (remaining, name) = recognize(many_till(
|
let (remaining, name) = recognize(many_till(
|
||||||
@@ -122,36 +141,30 @@ fn argument<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
|
|||||||
Ok((remaining, name))
|
Ok((remaining, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
fn argument_end(
|
||||||
fn argument_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
starting_parenthesis_depth: BracketDepth,
|
||||||
let context_depth = get_bracket_depth(context)
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
.expect("This function should only be called from inside an inline babel call argument.");
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
_argument_end(context, input, starting_parenthesis_depth)
|
||||||
let mut current_depth = context_depth.depth;
|
|
||||||
for c in text_since_context_entry.chars() {
|
|
||||||
match c {
|
|
||||||
'[' => {
|
|
||||||
current_depth += 1;
|
|
||||||
}
|
}
|
||||||
']' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded inline babel call argument bracket depth.")
|
|
||||||
}
|
|
||||||
']' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alt((tag(")"), line_ending))(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn get_bracket_depth<'r, 's>(context: Context<'r, 's>) -> Option<&'r BabelHeaderBracket<'s>> {
|
fn _argument_end<'r, 's>(
|
||||||
for node in context.iter() {
|
_context: Context<'r, 's>,
|
||||||
match node.get_data() {
|
input: OrgSource<'s>,
|
||||||
ContextElement::BabelHeaderBracket(depth) => return Some(depth),
|
starting_parenthesis_depth: BracketDepth,
|
||||||
_ => {}
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||||
|
if current_depth > 0 {
|
||||||
|
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"NoArgumentEnd".into(),
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.
|
||||||
|
unreachable!("Exceeded argument parenthesis depth.")
|
||||||
}
|
}
|
||||||
None
|
alt((tag(")"), line_ending))(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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;
|
||||||
@@ -11,13 +12,15 @@ 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::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
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_context::InlineSourceBlockBracket;
|
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
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;
|
||||||
@@ -26,19 +29,27 @@ use crate::parser::InlineSourceBlock;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn inline_source_block<'r, 's>(
|
pub fn inline_source_block<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, InlineSourceBlock<'s>> {
|
) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> {
|
||||||
let (remaining, _) = tag_no_case("src_")(input)?;
|
let (remaining, _) = tag_no_case("src_")(input)?;
|
||||||
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, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, InlineSourceBlock { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
InlineSourceBlock {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn lang<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn lang<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
@@ -52,24 +63,25 @@ fn lang<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn lang_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn lang_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(one_of("[{"))(input)
|
recognize(one_of("[{"))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn header<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
|
|
||||||
let parser_context = context
|
let exit_with_depth = header_end(remaining.get_bracket_depth());
|
||||||
.with_additional_node(ContextElement::InlineSourceBlockBracket(
|
let parser_context =
|
||||||
InlineSourceBlockBracket {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
position: remaining,
|
|
||||||
depth: 0,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
exit_matcher: &header_end,
|
exit_matcher: &exit_with_depth,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let (remaining, header_contents) = recognize(many_till(
|
let (remaining, header_contents) = recognize(many_till(
|
||||||
@@ -80,50 +92,46 @@ fn header<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
|
|||||||
Ok((remaining, header_contents))
|
Ok((remaining, header_contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
fn header_end(
|
||||||
fn header_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
starting_bracket_depth: BracketDepth,
|
||||||
let context_depth = get_bracket_depth(context)
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
.expect("This function should only be called from inside an inline source block header.");
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
_header_end(context, input, starting_bracket_depth)
|
||||||
let mut current_depth = context_depth.depth;
|
|
||||||
for c in text_since_context_entry.chars() {
|
|
||||||
match c {
|
|
||||||
'[' => {
|
|
||||||
current_depth += 1;
|
|
||||||
}
|
}
|
||||||
']' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded inline source block header bracket depth.")
|
|
||||||
}
|
|
||||||
']' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current_depth == 0 {
|
|
||||||
let close_bracket = tag::<&str, &str, CustomError<&str>>("]")(input);
|
|
||||||
if close_bracket.is_ok() {
|
|
||||||
return close_bracket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line_ending(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn _header_end<'r, 's>(
|
||||||
|
_context: Context<'r, '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 {
|
||||||
|
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"NoHeaderEnd".into(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
||||||
|
unreachable!("Exceeded header bracket depth.")
|
||||||
|
}
|
||||||
|
alt((tag("]"), line_ending))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn body<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tag("{")(input)?;
|
let (remaining, _) = tag("{")(input)?;
|
||||||
|
|
||||||
let parser_context = context
|
let exit_with_depth = body_end(remaining.get_brace_depth());
|
||||||
.with_additional_node(ContextElement::InlineSourceBlockBracket(
|
let parser_context =
|
||||||
InlineSourceBlockBracket {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
position: remaining,
|
|
||||||
depth: 0,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
exit_matcher: &body_end,
|
exit_matcher: &exit_with_depth,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let (remaining, body_contents) = recognize(many_till(
|
let (remaining, body_contents) = recognize(many_till(
|
||||||
@@ -135,7 +143,7 @@ fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
|
|||||||
let span = span!(
|
let span = span!(
|
||||||
tracing::Level::DEBUG,
|
tracing::Level::DEBUG,
|
||||||
"outside end body",
|
"outside end body",
|
||||||
remaining = remaining
|
remaining = Into::<&str>::into(remaining)
|
||||||
);
|
);
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
@@ -144,57 +152,28 @@ fn body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
|
|||||||
Ok((remaining, body_contents))
|
Ok((remaining, body_contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
fn body_end(
|
||||||
fn body_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
starting_brace_depth: BracketDepth,
|
||||||
let context_depth = get_bracket_depth(context)
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
.expect("This function should only be called from inside an inline source block body.");
|
move |context: Context, input: OrgSource<'_>| _body_end(context, input, starting_brace_depth)
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
|
||||||
let mut current_depth = context_depth.depth;
|
|
||||||
for c in text_since_context_entry.chars() {
|
|
||||||
match c {
|
|
||||||
'{' => {
|
|
||||||
current_depth += 1;
|
|
||||||
}
|
|
||||||
'}' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded inline source block body bracket depth.")
|
|
||||||
}
|
|
||||||
'}' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
let span = span!(
|
|
||||||
tracing::Level::DEBUG,
|
|
||||||
"inside end body",
|
|
||||||
remaining = input,
|
|
||||||
current_depth = current_depth
|
|
||||||
);
|
|
||||||
#[cfg(feature = "tracing")]
|
|
||||||
let _enter = span.enter();
|
|
||||||
|
|
||||||
if current_depth == 0 {
|
|
||||||
let close_bracket = tag::<&str, &str, CustomError<&str>>("}")(input);
|
|
||||||
if close_bracket.is_ok() {
|
|
||||||
return close_bracket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
line_ending(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn get_bracket_depth<'r, 's>(
|
fn _body_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
) -> Option<&'r InlineSourceBlockBracket<'s>> {
|
input: OrgSource<'s>,
|
||||||
for node in context.iter() {
|
starting_brace_depth: BracketDepth,
|
||||||
match node.get_data() {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
ContextElement::InlineSourceBlockBracket(depth) => return Some(depth),
|
let current_depth = input.get_brace_depth() - starting_brace_depth;
|
||||||
_ => {}
|
if current_depth > 0 {
|
||||||
|
// Its impossible for the next character to end the body if we're any amount of brace deep
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"NoBodyEnd".into(),
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.
|
||||||
|
unreachable!("Exceeded body brace depth.")
|
||||||
}
|
}
|
||||||
None
|
alt((tag("}"), line_ending))(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,16 +11,30 @@ 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::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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Keyword<'s>> {
|
pub fn keyword<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
_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.
|
// 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((
|
let (remaining, rule) = recognize(tuple((
|
||||||
space0,
|
space0,
|
||||||
@@ -30,5 +46,118 @@ pub fn keyword<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
|
alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
|
||||||
alt((line_ending, eof)),
|
alt((line_ending, eof)),
|
||||||
)))(input)?;
|
)))(input)?;
|
||||||
Ok((remaining, Keyword { source: rule }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Keyword {
|
||||||
|
source: rule.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::combinator::recognize;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
@@ -25,9 +26,9 @@ use crate::parser::LatexEnvironment;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn latex_environment<'r, 's>(
|
pub fn latex_environment<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, LatexEnvironment<'s>> {
|
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
|
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
|
||||||
tag_no_case(r#"\begin{"#),
|
tag_no_case(r#"\begin{"#),
|
||||||
@@ -37,7 +38,7 @@ pub fn latex_environment<'r, 's>(
|
|||||||
line_ending,
|
line_ending,
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let latex_environment_end_specialized = latex_environment_end(name);
|
let latex_environment_end_specialized = latex_environment_end(name.into());
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
@@ -48,11 +49,16 @@ pub fn latex_environment<'r, 's>(
|
|||||||
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, LatexEnvironment { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
LatexEnvironment {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
|
take_while1(|c: char| c.is_alphanumeric() || c == '*')(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +66,15 @@ fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
|||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(end_matcher))
|
tracing::instrument(ret, level = "debug", skip(end_matcher))
|
||||||
)]
|
)]
|
||||||
pub fn contents<'r, 's, F: Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>>(
|
pub fn contents<
|
||||||
|
'r,
|
||||||
|
's,
|
||||||
|
F: Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>,
|
||||||
|
>(
|
||||||
end_matcher: F,
|
end_matcher: F,
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, source) = recognize(many_till(
|
let (remaining, source) = recognize(many_till(
|
||||||
anychar,
|
anychar,
|
||||||
peek(alt((
|
peek(alt((
|
||||||
@@ -78,20 +88,20 @@ pub fn contents<'r, 's, F: Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>
|
|||||||
|
|
||||||
fn latex_environment_end(
|
fn latex_environment_end(
|
||||||
current_name: &str,
|
current_name: &str,
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let current_name_lower = current_name.to_lowercase();
|
let current_name_lower = current_name.to_lowercase();
|
||||||
move |context: Context, input: &str| {
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
_latex_environment_end(context, input, current_name_lower.as_str())
|
_latex_environment_end(context, input, current_name_lower.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _latex_environment_end<'r, 's, 'x>(
|
fn _latex_environment_end<'r, 's, 'x>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
current_name_lower: &'x str,
|
current_name_lower: &'x str,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, _name, _close_brace, _ws, _line_ending)) = tuple((
|
let (remaining, (_begin, _name, _close_brace, _ws, _line_ending)) = tuple((
|
||||||
tag_no_case(r#"\end{"#),
|
tag_no_case(r#"\end{"#),
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use nom::multi::many0;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -20,14 +21,13 @@ use crate::error::Res;
|
|||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
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::get_one_before;
|
|
||||||
use crate::parser::LatexFragment;
|
use crate::parser::LatexFragment;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn latex_fragment<'r, 's>(
|
pub fn latex_fragment<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, LatexFragment<'s>> {
|
) -> Res<OrgSource<'s>, LatexFragment<'s>> {
|
||||||
let (remaining, _) = alt((
|
let (remaining, _) = alt((
|
||||||
parser_with_context!(raw_latex_fragment)(context),
|
parser_with_context!(raw_latex_fragment)(context),
|
||||||
parser_with_context!(escaped_parenthesis_fragment)(context),
|
parser_with_context!(escaped_parenthesis_fragment)(context),
|
||||||
@@ -38,11 +38,19 @@ pub fn latex_fragment<'r, 's>(
|
|||||||
))(input)?;
|
))(input)?;
|
||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, LatexFragment { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
LatexFragment {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn raw_latex_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn raw_latex_fragment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tag("\\")(input)?;
|
let (remaining, _) = tag("\\")(input)?;
|
||||||
let (remaining, _) = name(context, remaining)?;
|
let (remaining, _) = name(context, remaining)?;
|
||||||
let (remaining, _) = many0(parser_with_context!(brackets)(context))(remaining)?;
|
let (remaining, _) = many0(parser_with_context!(brackets)(context))(remaining)?;
|
||||||
@@ -52,12 +60,18 @@ fn raw_latex_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alpha1(input)
|
alpha1(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn brackets<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn brackets<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, body) = alt((
|
let (remaining, body) = alt((
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
tag("["),
|
tag("["),
|
||||||
@@ -88,8 +102,8 @@ fn brackets<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn escaped_parenthesis_fragment<'r, 's>(
|
fn escaped_parenthesis_fragment<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tag("\\(")(input)?;
|
let (remaining, _) = tag("\\(")(input)?;
|
||||||
let (remaining, _) = recognize(many_till(
|
let (remaining, _) = recognize(many_till(
|
||||||
anychar,
|
anychar,
|
||||||
@@ -107,8 +121,8 @@ fn escaped_parenthesis_fragment<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn escaped_bracket_fragment<'r, 's>(
|
fn escaped_bracket_fragment<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = tag("\\[")(input)?;
|
let (remaining, _) = tag("\\[")(input)?;
|
||||||
let (remaining, _) = recognize(many_till(
|
let (remaining, _) = recognize(many_till(
|
||||||
anychar,
|
anychar,
|
||||||
@@ -126,8 +140,8 @@ fn escaped_bracket_fragment<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn double_dollar_fragment<'r, 's>(
|
fn double_dollar_fragment<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$?
|
// TODO: The documentation on the dollar sign versions is incomplete. Test to figure out what the real requirements are. For example, can this span more than 3 lines and can this contain a single $ since its terminated by $$?
|
||||||
let (remaining, _) = tag("$$")(input)?;
|
let (remaining, _) = tag("$$")(input)?;
|
||||||
let (remaining, _) = recognize(many_till(
|
let (remaining, _) = recognize(many_till(
|
||||||
@@ -144,7 +158,10 @@ fn double_dollar_fragment<'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn dollar_char_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn dollar_char_fragment<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (_, _) = pre(context, input)?;
|
let (_, _) = pre(context, input)?;
|
||||||
let (remaining, _) = tag("$")(input)?;
|
let (remaining, _) = tag("$")(input)?;
|
||||||
let (remaining, _) = verify(none_of(".,?;\""), |c| !c.is_whitespace())(remaining)?;
|
let (remaining, _) = verify(none_of(".,?;\""), |c| !c.is_whitespace())(remaining)?;
|
||||||
@@ -156,21 +173,18 @@ fn dollar_char_fragment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
let document_root = context.get_document_root().unwrap();
|
let preceding_character = input.get_preceding_character();
|
||||||
let preceding_character = get_one_before(document_root, input)
|
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten();
|
|
||||||
if let Some('$') = preceding_character {
|
if let Some('$') = preceding_character {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not a valid pre character for dollar char fragment.",
|
"Not a valid pre character for dollar char fragment.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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: &'s str) -> Res<&'s str, ()> {
|
pub fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
// TODO: What about eof? Test to find out.
|
// TODO: What about eof? Test to find out.
|
||||||
|
|
||||||
// TODO: Figure out which punctuation characters should be included.
|
// TODO: Figure out which punctuation characters should be included.
|
||||||
@@ -181,24 +195,20 @@ pub fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn bordered_dollar_fragment<'r, 's>(
|
fn bordered_dollar_fragment<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (_, _) = pre(context, input)?;
|
let (_, _) = pre(context, input)?;
|
||||||
let (remaining, _) = tag("$")(input)?;
|
let (remaining, _) = tag("$")(input)?;
|
||||||
// TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out.
|
// TODO: I'm assuming I should be peeking at the borders but the documentation is not clear. Test to figure out.
|
||||||
let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?;
|
let (_, _) = peek(parser_with_context!(open_border)(context))(remaining)?;
|
||||||
|
|
||||||
// TODO: As an optimization it would be nice to exit early upon hitting the 3rd line break
|
let (remaining, _) = recognize(many_till(
|
||||||
let (remaining, _) = verify(
|
|
||||||
recognize(many_till(
|
|
||||||
anychar,
|
anychar,
|
||||||
peek(alt((
|
peek(alt((
|
||||||
parser_with_context!(exit_matcher_parser)(context),
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
tag("$"),
|
tag("$"),
|
||||||
))),
|
))),
|
||||||
)),
|
))(remaining)?;
|
||||||
|body: &str| body.lines().take(4).count() <= 3,
|
|
||||||
)(remaining)?;
|
|
||||||
|
|
||||||
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
|
let (_, _) = peek(parser_with_context!(close_border)(context))(remaining)?;
|
||||||
let (remaining, _) = tag("$")(remaining)?;
|
let (remaining, _) = tag("$")(remaining)?;
|
||||||
@@ -209,21 +219,24 @@ fn bordered_dollar_fragment<'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn open_border<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
pub fn open_border<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input)
|
recognize(verify(none_of(".,;$"), |c| !c.is_whitespace()))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn close_border<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
pub fn close_border<'r, 's>(
|
||||||
let document_root = context.get_document_root().unwrap();
|
_context: Context<'r, 's>,
|
||||||
let preceding_character = get_one_before(document_root, input)
|
input: OrgSource<'s>,
|
||||||
.map(|slice| slice.chars().next())
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
.flatten();
|
let preceding_character = input.get_preceding_character();
|
||||||
match preceding_character {
|
match preceding_character {
|
||||||
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
|
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not a valid pre character for dollar char fragment.",
|
"Not a valid pre character for dollar char fragment.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::combinator::verify;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
@@ -34,8 +35,8 @@ use crate::parser::util::text_until_exit;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn verse_block<'r, 's>(
|
pub fn verse_block<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, VerseBlock<'s>> {
|
) -> Res<OrgSource<'s>, VerseBlock<'s>> {
|
||||||
let (remaining, name) = lesser_block_begin("verse")(context, input)?;
|
let (remaining, name) = lesser_block_begin("verse")(context, input)?;
|
||||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||||
let (remaining, _nl) = line_ending(remaining)?;
|
let (remaining, _nl) = line_ending(remaining)?;
|
||||||
@@ -58,7 +59,9 @@ pub fn verse_block<'r, 's>(
|
|||||||
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
|
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
|
||||||
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
|
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
|
||||||
remaining,
|
remaining,
|
||||||
vec![Object::PlainText(PlainText { source: whitespace })],
|
vec![Object::PlainText(PlainText {
|
||||||
|
source: whitespace.into(),
|
||||||
|
})],
|
||||||
),
|
),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let (remaining, (children, _exit_contents)) =
|
let (remaining, (children, _exit_contents)) =
|
||||||
@@ -72,9 +75,9 @@ pub fn verse_block<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
VerseBlock {
|
VerseBlock {
|
||||||
source,
|
source: source.into(),
|
||||||
name,
|
name: name.into(),
|
||||||
data: parameters,
|
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -83,8 +86,8 @@ pub fn verse_block<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn comment_block<'r, 's>(
|
pub fn comment_block<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, CommentBlock<'s>> {
|
) -> Res<OrgSource<'s>, CommentBlock<'s>> {
|
||||||
let (remaining, name) = lesser_block_begin("comment")(context, input)?;
|
let (remaining, name) = lesser_block_begin("comment")(context, input)?;
|
||||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||||
let (remaining, _nl) = line_ending(remaining)?;
|
let (remaining, _nl) = line_ending(remaining)?;
|
||||||
@@ -108,10 +111,10 @@ pub fn comment_block<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
CommentBlock {
|
CommentBlock {
|
||||||
source,
|
source: source.into(),
|
||||||
name,
|
name: name.into(),
|
||||||
data: parameters,
|
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
||||||
contents,
|
contents: contents.into(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -119,9 +122,9 @@ pub fn comment_block<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn example_block<'r, 's>(
|
pub fn example_block<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, ExampleBlock<'s>> {
|
) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
|
||||||
let (remaining, name) = lesser_block_begin("example")(context, input)?;
|
let (remaining, _name) = lesser_block_begin("example")(context, input)?;
|
||||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||||
let (remaining, _nl) = line_ending(remaining)?;
|
let (remaining, _nl) = line_ending(remaining)?;
|
||||||
let lesser_block_end_specialized = lesser_block_end("example");
|
let lesser_block_end_specialized = lesser_block_end("example");
|
||||||
@@ -144,10 +147,10 @@ pub fn example_block<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
ExampleBlock {
|
ExampleBlock {
|
||||||
source,
|
source: source.into(),
|
||||||
name,
|
name: source.into(),
|
||||||
data: parameters,
|
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
||||||
contents,
|
contents: contents.into(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -155,8 +158,8 @@ pub fn example_block<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn export_block<'r, 's>(
|
pub fn export_block<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, ExportBlock<'s>> {
|
) -> Res<OrgSource<'s>, ExportBlock<'s>> {
|
||||||
let (remaining, name) = lesser_block_begin("export")(context, input)?;
|
let (remaining, name) = lesser_block_begin("export")(context, input)?;
|
||||||
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
|
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
|
||||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||||
@@ -181,16 +184,19 @@ pub fn export_block<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
ExportBlock {
|
ExportBlock {
|
||||||
source,
|
source: source.into(),
|
||||||
name,
|
name: name.into(),
|
||||||
data: parameters,
|
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
||||||
contents,
|
contents: contents.into(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn src_block<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, SrcBlock<'s>> {
|
pub fn src_block<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, SrcBlock<'s>> {
|
||||||
let (remaining, name) = lesser_block_begin("src")(context, input)?;
|
let (remaining, name) = lesser_block_begin("src")(context, input)?;
|
||||||
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
|
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
|
||||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||||
@@ -215,40 +221,40 @@ pub fn src_block<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
SrcBlock {
|
SrcBlock {
|
||||||
source,
|
source: source.into(),
|
||||||
name,
|
name: name.into(),
|
||||||
data: parameters,
|
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
||||||
contents,
|
contents: contents.into(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not(" \t\r\n")(input)
|
is_not(" \t\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn data<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not("\r\n")(input)
|
is_not("\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lesser_block_end(
|
fn lesser_block_end(
|
||||||
current_name: &str,
|
current_name: &str,
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let current_name_lower = current_name.to_lowercase();
|
let current_name_lower = current_name.to_lowercase();
|
||||||
move |context: Context, input: &str| {
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
_lesser_block_end(context, input, current_name_lower.as_str())
|
_lesser_block_end(context, input, current_name_lower.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _lesser_block_end<'r, 's, 'x>(
|
fn _lesser_block_end<'r, 's, 'x>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
current_name_lower: &'x str,
|
current_name_lower: &'x str,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, _name, _ws)) = tuple((
|
let (remaining, (_begin, _name, _ws)) = tuple((
|
||||||
tag_no_case("#+end_"),
|
tag_no_case("#+end_"),
|
||||||
@@ -259,27 +265,28 @@ fn _lesser_block_end<'r, 's, 'x>(
|
|||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lesser_block_begin(
|
/// Parser for the beginning of a lesser block
|
||||||
current_name: &str,
|
///
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> {
|
/// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
|
||||||
let current_name_lower = current_name.to_lowercase();
|
const fn lesser_block_begin(
|
||||||
move |context: Context, input: &str| {
|
current_name: &'static str,
|
||||||
_lesser_block_begin(context, input, current_name_lower.as_str())
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
}
|
// TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
|
||||||
|
move |context: Context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _lesser_block_begin<'r, 's, 'x>(
|
fn _lesser_block_begin<'r, 's, 'x>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
current_name_lower: &'x str,
|
current_name_lower: &'x str,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, name)) = tuple((
|
let (remaining, (_begin, name)) = tuple((
|
||||||
tag_no_case("#+begin_"),
|
tag_no_case("#+begin_"),
|
||||||
verify(name, |name: &str| {
|
verify(name, |name: &OrgSource<'_>| {
|
||||||
name.to_lowercase().as_str() == current_name_lower
|
Into::<&str>::into(name).to_lowercase().as_str() == current_name_lower
|
||||||
}),
|
}),
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
Ok((remaining, name))
|
Ok((remaining, name))
|
||||||
|
|||||||
@@ -4,56 +4,52 @@ use nom::character::complete::one_of;
|
|||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::get_current_line_before_position;
|
|
||||||
use crate::parser::util::get_one_before;
|
|
||||||
use crate::parser::LineBreak;
|
use crate::parser::LineBreak;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn line_break<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, LineBreak<'s>> {
|
pub fn line_break<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, LineBreak<'s>> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, _) = tag(r#"\\"#)(remaining)?;
|
let (remaining, _) = tag(r#"\\"#)(remaining)?;
|
||||||
let (remaining, _) = recognize(many0(one_of(" \t")))(remaining)?;
|
let (remaining, _) = recognize(many0(one_of(" \t")))(remaining)?;
|
||||||
let (remaining, _) = line_ending(remaining)?;
|
let (remaining, _) = line_ending(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, LineBreak { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
LineBreak {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
let document_root = context.get_document_root().unwrap();
|
let preceding_character = input.get_preceding_character();
|
||||||
let preceding_character = get_one_before(document_root, input)
|
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten();
|
|
||||||
match preceding_character {
|
match preceding_character {
|
||||||
// If None, we are at the start of the file
|
// If None, we are at the start of the file
|
||||||
None | Some('\\') => {
|
None | Some('\\') => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not a valid pre character for line break.",
|
"Not a valid pre character for line break.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
let current_line = get_current_line_before_position(document_root, input);
|
|
||||||
match current_line {
|
let current_line = input.text_since_line_break();
|
||||||
Some(line) => {
|
let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
|
||||||
let is_non_empty_line = line.chars().any(|c| !c.is_whitespace());
|
|
||||||
if !is_non_empty_line {
|
if !is_non_empty_line {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not a valid pre line for line break.",
|
"Not a valid pre line for line break.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None => {
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"No preceding line for line break.",
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ mod list;
|
|||||||
mod object;
|
mod object;
|
||||||
mod object_parser;
|
mod object_parser;
|
||||||
mod org_macro;
|
mod org_macro;
|
||||||
|
mod org_source;
|
||||||
mod paragraph;
|
mod paragraph;
|
||||||
mod parser_context;
|
mod parser_context;
|
||||||
mod parser_with_context;
|
mod parser_with_context;
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::parser_with_context::parser_with_context;
|
use super::parser_with_context::parser_with_context;
|
||||||
use super::plain_text::plain_text;
|
use super::plain_text::plain_text;
|
||||||
use super::regular_link::regular_link;
|
use super::regular_link::regular_link;
|
||||||
@@ -31,11 +31,9 @@ use crate::parser::timestamp::timestamp;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn standard_set_object<'r, 's>(
|
pub fn standard_set_object<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
let (remaining, object) = alt((
|
||||||
|
|
||||||
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(
|
||||||
@@ -84,17 +82,16 @@ 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"))]
|
||||||
pub fn minimal_set_object<'r, 's>(
|
pub fn minimal_set_object<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
let (remaining, object) = alt((
|
||||||
|
|
||||||
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),
|
||||||
@@ -107,16 +104,16 @@ 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"))]
|
||||||
pub fn any_object_except_plain_text<'r, 's>(
|
pub fn any_object_except_plain_text<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
// Used for exit matchers so this does not check exit matcher condition.
|
let (remaining, object) = alt((
|
||||||
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(
|
||||||
@@ -164,16 +161,17 @@ 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"))]
|
||||||
pub fn regular_link_description_object_set<'r, 's>(
|
pub fn regular_link_description_object_set<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, 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,
|
||||||
@@ -192,5 +190,40 @@ 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"))]
|
||||||
|
pub fn table_cell_set_object<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
|
let (remaining, object) = alt((
|
||||||
|
map(parser_with_context!(citation)(context), Object::Citation),
|
||||||
|
map(
|
||||||
|
parser_with_context!(export_snippet)(context),
|
||||||
|
Object::ExportSnippet,
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
parser_with_context!(footnote_reference)(context),
|
||||||
|
Object::FootnoteReference,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
||||||
|
map(
|
||||||
|
parser_with_context!(regular_link)(context),
|
||||||
|
Object::RegularLink,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(plain_link)(context), Object::PlainLink),
|
||||||
|
map(parser_with_context!(angle_link)(context), Object::AngleLink),
|
||||||
|
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
|
||||||
|
map(
|
||||||
|
parser_with_context!(radio_target)(context),
|
||||||
|
Object::RadioTarget,
|
||||||
|
),
|
||||||
|
map(parser_with_context!(target)(context), Object::Target),
|
||||||
|
map(parser_with_context!(timestamp)(context), Object::Timestamp),
|
||||||
|
parser_with_context!(minimal_set_object)(context),
|
||||||
|
))(input)?;
|
||||||
|
Ok((remaining, object))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::space0;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
@@ -7,6 +8,7 @@ use nom::combinator::verify;
|
|||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::multi::separated_list0;
|
use nom::multi::separated_list0;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::object::OrgMacro;
|
use crate::parser::object::OrgMacro;
|
||||||
@@ -15,25 +17,36 @@ use crate::parser::util::exit_matcher_parser;
|
|||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn org_macro<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, OrgMacro<'s>> {
|
pub fn org_macro<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgMacro<'s>> {
|
||||||
let (remaining, _) = tag("{{{")(input)?;
|
let (remaining, _) = tag("{{{")(input)?;
|
||||||
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) = space0(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
OrgMacro {
|
OrgMacro {
|
||||||
source,
|
source: source.into(),
|
||||||
macro_name,
|
macro_name: macro_name.into(),
|
||||||
macro_args: macro_args.unwrap_or_else(|| Vec::with_capacity(0)),
|
macro_args: macro_args
|
||||||
|
.unwrap_or_else(|| Vec::with_capacity(0))
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| arg.into())
|
||||||
|
.collect(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn org_macro_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn org_macro_name<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = verify(anychar, |c| c.is_alphabetic())(input)?;
|
let (remaining, _) = verify(anychar, |c| c.is_alphabetic())(input)?;
|
||||||
let (remaining, _) = many0(verify(anychar, |c| {
|
let (remaining, _) = many0(verify(anychar, |c| {
|
||||||
c.is_alphanumeric() || *c == '-' || *c == '_'
|
c.is_alphanumeric() || *c == '-' || *c == '_'
|
||||||
@@ -43,7 +56,10 @@ fn org_macro_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn org_macro_args<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Vec<&'s str>> {
|
fn org_macro_args<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
|
||||||
let (remaining, _) = tag("(")(input)?;
|
let (remaining, _) = tag("(")(input)?;
|
||||||
let (remaining, args) =
|
let (remaining, args) =
|
||||||
separated_list0(tag(","), parser_with_context!(org_macro_arg)(context))(remaining)?;
|
separated_list0(tag(","), parser_with_context!(org_macro_arg)(context))(remaining)?;
|
||||||
@@ -53,7 +69,10 @@ fn org_macro_args<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn org_macro_arg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn org_macro_arg<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let mut remaining = input;
|
let mut remaining = input;
|
||||||
let mut escaping: bool = false;
|
let mut escaping: bool = false;
|
||||||
loop {
|
loop {
|
||||||
|
|||||||
446
src/parser/org_source.rs
Normal file
446
src/parser/org_source.rs
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
|
use nom::Compare;
|
||||||
|
use nom::InputIter;
|
||||||
|
use nom::InputLength;
|
||||||
|
use nom::InputTake;
|
||||||
|
use nom::InputTakeAtPosition;
|
||||||
|
use nom::Offset;
|
||||||
|
use nom::Slice;
|
||||||
|
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
|
|
||||||
|
pub type BracketDepth = i16;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct OrgSource<'s> {
|
||||||
|
full_source: &'s str,
|
||||||
|
start: usize,
|
||||||
|
end: usize, // exclusive
|
||||||
|
start_of_line: usize,
|
||||||
|
bracket_depth: BracketDepth, // []
|
||||||
|
brace_depth: BracketDepth, // {}
|
||||||
|
parenthesis_depth: BracketDepth, // ()
|
||||||
|
preceding_character: Option<char>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> std::fmt::Debug for OrgSource<'s> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("OrgSource")
|
||||||
|
.field(&Into::<&str>::into(self))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> OrgSource<'s> {
|
||||||
|
/// Returns a wrapped string that keeps track of values we need for parsing org-mode.
|
||||||
|
///
|
||||||
|
/// Only call this on the full original string. Calling this on a substring can result in invalid values.
|
||||||
|
pub fn new(input: &'s str) -> Self {
|
||||||
|
OrgSource {
|
||||||
|
full_source: input,
|
||||||
|
start: 0,
|
||||||
|
end: input.len(),
|
||||||
|
start_of_line: 0,
|
||||||
|
preceding_character: None,
|
||||||
|
bracket_depth: 0,
|
||||||
|
brace_depth: 0,
|
||||||
|
parenthesis_depth: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the text since the line break preceding the start of this WrappedInput.
|
||||||
|
pub fn text_since_line_break(&self) -> &'s str {
|
||||||
|
&self.full_source[self.start_of_line..self.start]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_preceding_character(&self) -> Option<char> {
|
||||||
|
self.preceding_character
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_at_start_of_line(&self) -> bool {
|
||||||
|
self.start == self.start_of_line
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> {
|
||||||
|
assert!(other.start >= self.start);
|
||||||
|
assert!(other.end <= self.end);
|
||||||
|
self.slice(..(other.start - self.start))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bracket_depth(&self) -> BracketDepth {
|
||||||
|
self.bracket_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_brace_depth(&self) -> BracketDepth {
|
||||||
|
self.brace_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_parenthesis_depth(&self) -> BracketDepth {
|
||||||
|
self.parenthesis_depth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> InputTake for OrgSource<'s> {
|
||||||
|
fn take(&self, count: usize) -> Self {
|
||||||
|
self.slice(..count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn take_split(&self, count: usize) -> (Self, Self) {
|
||||||
|
(self.slice(count..), self.slice(..count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, 'o, O: Into<&'o str>> Compare<O> for OrgSource<'s> {
|
||||||
|
fn compare(&self, t: O) -> nom::CompareResult {
|
||||||
|
(&self.full_source[self.start..self.end]).compare(t.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_no_case(&self, t: O) -> nom::CompareResult {
|
||||||
|
(&self.full_source[self.start..self.end]).compare_no_case(t.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> From<&'s str> for OrgSource<'s> {
|
||||||
|
fn from(value: &'s str) -> Self {
|
||||||
|
OrgSource::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> From<&OrgSource<'s>> for &'s str {
|
||||||
|
fn from(value: &OrgSource<'s>) -> Self {
|
||||||
|
&value.full_source[value.start..value.end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> From<OrgSource<'s>> for &'s str {
|
||||||
|
fn from(value: OrgSource<'s>) -> Self {
|
||||||
|
&value.full_source[value.start..value.end]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, R> Slice<R> for OrgSource<'s>
|
||||||
|
where
|
||||||
|
R: RangeBounds<usize>,
|
||||||
|
{
|
||||||
|
fn slice(&self, range: R) -> Self {
|
||||||
|
let new_start = match range.start_bound() {
|
||||||
|
std::ops::Bound::Included(idx) => self.start + idx,
|
||||||
|
std::ops::Bound::Excluded(idx) => self.start + idx - 1,
|
||||||
|
std::ops::Bound::Unbounded => self.start,
|
||||||
|
};
|
||||||
|
let new_end = match range.end_bound() {
|
||||||
|
std::ops::Bound::Included(idx) => self.start + idx + 1,
|
||||||
|
std::ops::Bound::Excluded(idx) => self.start + idx,
|
||||||
|
std::ops::Bound::Unbounded => self.end,
|
||||||
|
};
|
||||||
|
if new_start < self.start {
|
||||||
|
panic!("Attempted to extend before the start of the WrappedInput.")
|
||||||
|
}
|
||||||
|
if new_end > self.end {
|
||||||
|
panic!("Attempted to extend past the end of the WrappedInput.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let skipped_text = &self.full_source[self.start..new_start];
|
||||||
|
let mut start_of_line = self.start_of_line;
|
||||||
|
let mut bracket_depth = self.bracket_depth;
|
||||||
|
let mut brace_depth = self.brace_depth;
|
||||||
|
let mut parenthesis_depth = self.parenthesis_depth;
|
||||||
|
for (offset, byte) in skipped_text.bytes().enumerate() {
|
||||||
|
match byte {
|
||||||
|
b'\n' => {
|
||||||
|
start_of_line = self.start + offset + 1;
|
||||||
|
}
|
||||||
|
b'[' => {
|
||||||
|
bracket_depth += 1;
|
||||||
|
}
|
||||||
|
b']' => {
|
||||||
|
bracket_depth -= 1;
|
||||||
|
}
|
||||||
|
b'{' => {
|
||||||
|
brace_depth += 1;
|
||||||
|
}
|
||||||
|
b'}' => {
|
||||||
|
brace_depth -= 1;
|
||||||
|
}
|
||||||
|
b'(' => {
|
||||||
|
parenthesis_depth += 1;
|
||||||
|
}
|
||||||
|
b')' => {
|
||||||
|
parenthesis_depth -= 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgSource {
|
||||||
|
full_source: self.full_source,
|
||||||
|
start: new_start,
|
||||||
|
end: new_end,
|
||||||
|
start_of_line,
|
||||||
|
preceding_character: skipped_text.chars().last(),
|
||||||
|
bracket_depth,
|
||||||
|
brace_depth,
|
||||||
|
parenthesis_depth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> std::fmt::Display for OrgSource<'s> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
Into::<&str>::into(self).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> InputLength for OrgSource<'s> {
|
||||||
|
fn input_len(&self) -> usize {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> InputIter for OrgSource<'s> {
|
||||||
|
type Item = <&'s str as InputIter>::Item;
|
||||||
|
|
||||||
|
type Iter = <&'s str as InputIter>::Iter;
|
||||||
|
|
||||||
|
type IterElem = <&'s str as InputIter>::IterElem;
|
||||||
|
|
||||||
|
fn iter_indices(&self) -> Self::Iter {
|
||||||
|
Into::<&str>::into(self).char_indices()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iter_elements(&self) -> Self::IterElem {
|
||||||
|
Into::<&str>::into(self).iter_elements()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn position<P>(&self, predicate: P) -> Option<usize>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
Into::<&str>::into(self).position(predicate)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slice_index(&self, count: usize) -> Result<usize, nom::Needed> {
|
||||||
|
Into::<&str>::into(self).slice_index(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Offset for OrgSource<'s> {
|
||||||
|
fn offset(&self, second: &Self) -> usize {
|
||||||
|
second.start - self.start
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> InputTakeAtPosition for OrgSource<'s> {
|
||||||
|
type Item = <&'s str as InputTakeAtPosition>::Item;
|
||||||
|
|
||||||
|
fn split_at_position<P, E: nom::error::ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
) -> nom::IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
match Into::<&str>::into(self).position(predicate) {
|
||||||
|
Some(idx) => Ok(self.take_split(idx)),
|
||||||
|
None => Err(nom::Err::Incomplete(nom::Needed::new(1))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_at_position1<P, E: nom::error::ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
e: nom::error::ErrorKind,
|
||||||
|
) -> nom::IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
match Into::<&str>::into(self).position(predicate) {
|
||||||
|
Some(0) => Err(nom::Err::Error(E::from_error_kind(self.clone(), e))),
|
||||||
|
Some(idx) => Ok(self.take_split(idx)),
|
||||||
|
None => Err(nom::Err::Incomplete(nom::Needed::new(1))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_at_position_complete<P, E: nom::error::ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
) -> nom::IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
match self.split_at_position(predicate) {
|
||||||
|
Err(nom::Err::Incomplete(_)) => Ok(self.take_split(self.input_len())),
|
||||||
|
res => res,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_at_position1_complete<P, E: nom::error::ParseError<Self>>(
|
||||||
|
&self,
|
||||||
|
predicate: P,
|
||||||
|
e: nom::error::ErrorKind,
|
||||||
|
) -> nom::IResult<Self, Self, E>
|
||||||
|
where
|
||||||
|
P: Fn(Self::Item) -> bool,
|
||||||
|
{
|
||||||
|
let window = Into::<&str>::into(self);
|
||||||
|
match window.position(predicate) {
|
||||||
|
Some(0) => Err(nom::Err::Error(E::from_error_kind(self.clone(), e))),
|
||||||
|
Some(n) => Ok(self.take_split(n)),
|
||||||
|
None => {
|
||||||
|
if window.input_len() == 0 {
|
||||||
|
Err(nom::Err::Error(E::from_error_kind(self.clone(), e)))
|
||||||
|
} else {
|
||||||
|
Ok(self.take_split(self.input_len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_error<'a, I: Into<CustomError<&'a str>>>(
|
||||||
|
err: nom::Err<I>,
|
||||||
|
) -> nom::Err<CustomError<&'a str>> {
|
||||||
|
match err {
|
||||||
|
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
|
||||||
|
nom::Err::Error(err) => nom::Err::Error(err.into()),
|
||||||
|
nom::Err::Failure(err) => nom::Err::Failure(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
|
||||||
|
fn from(value: CustomError<OrgSource<'s>>) -> Self {
|
||||||
|
match value {
|
||||||
|
CustomError::MyError(err) => CustomError::MyError(err.into()),
|
||||||
|
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> From<MyError<OrgSource<'s>>> for MyError<&'s str> {
|
||||||
|
fn from(value: MyError<OrgSource<'s>>) -> Self {
|
||||||
|
MyError(value.0.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range() {
|
||||||
|
let input = OrgSource::new("foo bar baz");
|
||||||
|
let output = input.slice(4..7);
|
||||||
|
assert_eq!(output.to_string(), "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_to() {
|
||||||
|
let input = OrgSource::new("foo bar baz");
|
||||||
|
let output = input.slice(..7);
|
||||||
|
assert_eq!(output.to_string(), "foo bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn range_from() {
|
||||||
|
let input = OrgSource::new("foo bar baz");
|
||||||
|
let output = input.slice(4..);
|
||||||
|
assert_eq!(output.to_string(), "bar baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn full_range() {
|
||||||
|
let input = OrgSource::new("foo bar baz");
|
||||||
|
let output = input.slice(..);
|
||||||
|
assert_eq!(output.to_string(), "foo bar baz");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nested_range() {
|
||||||
|
let input = OrgSource::new("lorem foo bar baz ipsum");
|
||||||
|
let first_cut = input.slice(6..17);
|
||||||
|
let output = first_cut.slice(4..7);
|
||||||
|
assert_eq!(first_cut.to_string(), "foo bar baz");
|
||||||
|
assert_eq!(output.to_string(), "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn out_of_bounds() {
|
||||||
|
let input = OrgSource::new("lorem foo bar baz ipsum");
|
||||||
|
input.slice(6..30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn out_of_nested_bounds() {
|
||||||
|
let input = OrgSource::new("lorem foo bar baz ipsum");
|
||||||
|
let first_cut = input.slice(6..17);
|
||||||
|
first_cut.slice(4..14);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn line_break() {
|
||||||
|
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
|
||||||
|
assert_eq!(input.slice(5..).start_of_line, 0);
|
||||||
|
assert_eq!(input.slice(6..).start_of_line, 6);
|
||||||
|
assert_eq!(input.slice(6..).slice(10..).start_of_line, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn text_since_line_break() {
|
||||||
|
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
|
||||||
|
assert_eq!(input.text_since_line_break(), "");
|
||||||
|
assert_eq!(input.slice(5..).text_since_line_break(), "lorem");
|
||||||
|
assert_eq!(input.slice(6..).text_since_line_break(), "");
|
||||||
|
assert_eq!(input.slice(6..).slice(10..).text_since_line_break(), "ba");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preceding_character() {
|
||||||
|
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
|
||||||
|
assert_eq!(input.get_preceding_character(), None);
|
||||||
|
assert_eq!(input.slice(5..).get_preceding_character(), Some('m'));
|
||||||
|
assert_eq!(input.slice(6..).get_preceding_character(), Some('\n'));
|
||||||
|
assert_eq!(
|
||||||
|
input.slice(6..).slice(10..).get_preceding_character(),
|
||||||
|
Some('a')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_at_start_of_line() {
|
||||||
|
let input = OrgSource::new("lorem\nfoo\nbar\nbaz\nipsum");
|
||||||
|
assert_eq!(input.is_at_start_of_line(), true);
|
||||||
|
assert_eq!(input.slice(5..).is_at_start_of_line(), false);
|
||||||
|
assert_eq!(input.slice(6..).is_at_start_of_line(), true);
|
||||||
|
assert_eq!(input.slice(6..).slice(10..).is_at_start_of_line(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn preceding_character_unicode() {
|
||||||
|
let input = OrgSource::new("🧡💛💚💙💜");
|
||||||
|
assert_eq!(input.get_preceding_character(), None);
|
||||||
|
assert_eq!(input.slice(8..).get_preceding_character(), Some('💛'));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn depth() {
|
||||||
|
let input = OrgSource::new("[][()][({)]}}}}");
|
||||||
|
assert_eq!(input.get_bracket_depth(), 0);
|
||||||
|
assert_eq!(input.get_brace_depth(), 0);
|
||||||
|
assert_eq!(input.get_parenthesis_depth(), 0);
|
||||||
|
assert_eq!(input.slice(4..).get_bracket_depth(), 1);
|
||||||
|
assert_eq!(input.slice(4..).get_brace_depth(), 0);
|
||||||
|
assert_eq!(input.slice(4..).get_parenthesis_depth(), 1);
|
||||||
|
assert_eq!(input.slice(4..).slice(6..).get_bracket_depth(), 1);
|
||||||
|
assert_eq!(input.slice(4..).slice(6..).get_brace_depth(), 1);
|
||||||
|
assert_eq!(input.slice(4..).slice(6..).get_parenthesis_depth(), 0);
|
||||||
|
assert_eq!(input.slice(14..).get_brace_depth(), -2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,13 @@ use nom::multi::many1;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::element_parser::detect_element;
|
||||||
use super::lesser_element::Paragraph;
|
use super::lesser_element::Paragraph;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::util::blank_line;
|
use super::util::blank_line;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::element_parser::element;
|
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
use crate::parser::parser_context::ContextElement;
|
use crate::parser::parser_context::ContextElement;
|
||||||
@@ -21,10 +22,13 @@ use crate::parser::util::exit_matcher_parser;
|
|||||||
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 paragraph<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Paragraph<'s>> {
|
pub fn paragraph<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Paragraph<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: ¶graph_end,
|
exit_matcher: ¶graph_end,
|
||||||
}));
|
}));
|
||||||
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||||
@@ -39,15 +43,23 @@ pub fn paragraph<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
|
|||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Paragraph { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Paragraph {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn paragraph_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn paragraph_end<'r, 's>(
|
||||||
let non_paragraph_element_matcher = parser_with_context!(element(false))(context);
|
context: Context<'r, 's>,
|
||||||
let start_of_line_matcher = parser_with_context!(start_of_line)(&context);
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let non_paragraph_element_matcher = parser_with_context!(detect_element(false))(context);
|
||||||
alt((
|
alt((
|
||||||
recognize(tuple((start_of_line_matcher, many1(blank_line)))),
|
recognize(tuple((start_of_line, many1(blank_line)))),
|
||||||
recognize(non_paragraph_element_matcher),
|
recognize(non_paragraph_element_matcher),
|
||||||
eof,
|
eof,
|
||||||
))(input)
|
))(input)
|
||||||
@@ -56,22 +68,20 @@ fn paragraph_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::parser::parser_context::ContextElement;
|
use crate::parser::org_source::OrgSource;
|
||||||
use crate::parser::parser_context::ContextTree;
|
use crate::parser::parser_context::ContextTree;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::source::Source;
|
use crate::parser::source::Source;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_paragraphs() {
|
fn two_paragraphs() {
|
||||||
let input = "foo bar baz\n\nlorem ipsum";
|
let input = OrgSource::new("foo bar baz\n\nlorem ipsum");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
|
||||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||||
let (remaining, second_paragraph) =
|
let (remaining, second_paragraph) =
|
||||||
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
|
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
|
||||||
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
|
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use nom::IResult;
|
|||||||
|
|
||||||
use super::list::List;
|
use super::list::List;
|
||||||
use super::list::Node;
|
use super::list::Node;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -12,7 +13,8 @@ use crate::error::MyError;
|
|||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
|
|
||||||
type Matcher = dyn for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>;
|
type Matcher =
|
||||||
|
dyn for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ContextTree<'r, 's> {
|
pub struct ContextTree<'r, 's> {
|
||||||
@@ -47,8 +49,8 @@ impl<'r, 's> ContextTree<'r, 's> {
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn check_exit_matcher(
|
pub fn check_exit_matcher(
|
||||||
&'r self,
|
&'r self,
|
||||||
i: &'s str,
|
i: OrgSource<'s>,
|
||||||
) -> IResult<&'s str, &'s str, CustomError<&'s str>> {
|
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
|
||||||
// Special check for EOF. We don't just make this a document-level exit matcher since the IgnoreParent ChainBehavior could cause early exit matchers to not run.
|
// Special check for EOF. We don't just make this a document-level exit matcher since the IgnoreParent ChainBehavior could cause early exit matchers to not run.
|
||||||
let at_end_of_file = eof(i);
|
let at_end_of_file = eof(i);
|
||||||
if at_end_of_file.is_ok() {
|
if at_end_of_file.is_ok() {
|
||||||
@@ -60,7 +62,7 @@ impl<'r, 's> ContextTree<'r, 's> {
|
|||||||
// exit_matcher: ChainBehavior::IgnoreParent(Some(&always_fail)),
|
// exit_matcher: ChainBehavior::IgnoreParent(Some(&always_fail)),
|
||||||
// }));
|
// }));
|
||||||
|
|
||||||
let mut current_class_filter = ExitClass::Beta;
|
let mut current_class_filter = ExitClass::Gamma;
|
||||||
for current_node in self.iter() {
|
for current_node in self.iter() {
|
||||||
let context_element = current_node.get_data();
|
let context_element = current_node.get_data();
|
||||||
match context_element {
|
match context_element {
|
||||||
@@ -78,20 +80,9 @@ impl<'r, 's> ContextTree<'r, 's> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
// TODO: Make this a specific error instead of just a generic MyError
|
// TODO: Make this a specific error instead of just a generic MyError
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError("NoExit"))));
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
}
|
"NoExit".into(),
|
||||||
|
))));
|
||||||
pub fn get_document_root(&self) -> Option<&'s str> {
|
|
||||||
for current_node in self.iter() {
|
|
||||||
let context_element = current_node.get_data();
|
|
||||||
match context_element {
|
|
||||||
ContextElement::DocumentRoot(body) => {
|
|
||||||
return Some(body);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates if elements should consume the whitespace after them.
|
/// Indicates if elements should consume the whitespace after them.
|
||||||
@@ -117,21 +108,12 @@ impl<'r, 's> ContextTree<'r, 's> {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ContextElement<'r, 's> {
|
pub enum ContextElement<'r, 's> {
|
||||||
/// Stores a reference to the entire org-mode document being parsed.
|
|
||||||
///
|
|
||||||
/// This is used for look-behind.
|
|
||||||
DocumentRoot(&'s str),
|
|
||||||
|
|
||||||
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
|
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
|
||||||
ExitMatcherNode(ExitMatcherNode<'r>),
|
ExitMatcherNode(ExitMatcherNode<'r>),
|
||||||
|
|
||||||
|
/// Stores the name of the current element to prevent directly nesting elements of the same type.
|
||||||
Context(&'r str),
|
Context(&'r str),
|
||||||
|
|
||||||
/// Stores the indentation level of the current list item.
|
|
||||||
ListItem(usize),
|
|
||||||
|
|
||||||
/// Stores the name of the greater block.
|
|
||||||
GreaterBlock(&'s str),
|
|
||||||
|
|
||||||
/// Indicates if elements should consume the whitespace after them.
|
/// Indicates if elements should consume the whitespace after them.
|
||||||
ConsumeTrailingWhitespace(bool),
|
ConsumeTrailingWhitespace(bool),
|
||||||
|
|
||||||
@@ -141,71 +123,6 @@ pub enum ContextElement<'r, 's> {
|
|||||||
/// org-mode document since text needs to be re-parsed to look for
|
/// org-mode document since text needs to be re-parsed to look for
|
||||||
/// radio links matching the contents of radio targets.
|
/// radio links matching the contents of radio targets.
|
||||||
RadioTarget(Vec<&'r Vec<Object<'s>>>),
|
RadioTarget(Vec<&'r Vec<Object<'s>>>),
|
||||||
|
|
||||||
/// Stores the current bracket depth inside a footnote reference's definition.
|
|
||||||
///
|
|
||||||
/// The definition inside a footnote reference must have balanced
|
|
||||||
/// brackets [] inside the definition, so this stores the amount
|
|
||||||
/// of opening brackets subtracted by the amount of closing
|
|
||||||
/// brackets within the definition must equal zero.
|
|
||||||
///
|
|
||||||
/// A reference to the position in the string is also included so
|
|
||||||
/// unbalanced brackets can be detected in the middle of an
|
|
||||||
/// object.
|
|
||||||
FootnoteReferenceDefinition(FootnoteReferenceDefinition<'s>),
|
|
||||||
|
|
||||||
/// Stores the current bracket depth inside a citation.
|
|
||||||
///
|
|
||||||
/// The global prefix, global suffix, key prefix, and key suffix
|
|
||||||
/// inside a footnote reference must have balanced brackets []
|
|
||||||
/// inside the definition, so this stores the amount of opening
|
|
||||||
/// brackets subtracted by the amount of closing brackets within
|
|
||||||
/// the definition must equal zero. None of the prefixes or
|
|
||||||
/// suffixes can be nested inside each other so we can use a
|
|
||||||
/// single type for this without conflict.
|
|
||||||
///
|
|
||||||
/// A reference to the position in the string is also included so
|
|
||||||
/// unbalanced brackets can be detected in the middle of an
|
|
||||||
/// object.
|
|
||||||
CitationBracket(CitationBracket<'s>),
|
|
||||||
|
|
||||||
/// Stores the current bracket or parenthesis depth inside an inline babel call.
|
|
||||||
///
|
|
||||||
/// Inside an inline babel call the headers must have balanced
|
|
||||||
/// parentheses () and the arguments must have balanced brackets
|
|
||||||
/// [], so this stores the amount of opening brackets subtracted
|
|
||||||
/// by the amount of closing brackets within the definition must
|
|
||||||
/// equal zero.
|
|
||||||
///
|
|
||||||
/// A reference to the position in the string is also included so
|
|
||||||
/// unbalanced brackets can be detected in the middle of an
|
|
||||||
/// object.
|
|
||||||
BabelHeaderBracket(BabelHeaderBracket<'s>),
|
|
||||||
|
|
||||||
/// Stores the current bracket or parenthesis depth inside an inline babel call.
|
|
||||||
///
|
|
||||||
/// Inside an inline babel call the headers must have balanced
|
|
||||||
/// parentheses () and the arguments must have balanced brackets
|
|
||||||
/// [], so this stores the amount of opening brackets subtracted
|
|
||||||
/// by the amount of closing brackets within the definition must
|
|
||||||
/// equal zero.
|
|
||||||
///
|
|
||||||
/// A reference to the position in the string is also included so
|
|
||||||
/// unbalanced brackets can be detected in the middle of an
|
|
||||||
/// object.
|
|
||||||
InlineSourceBlockBracket(InlineSourceBlockBracket<'s>),
|
|
||||||
|
|
||||||
/// Stores the current bracket or parenthesis depth inside a
|
|
||||||
/// superscript or superscript.
|
|
||||||
///
|
|
||||||
/// Inside the braces of a subscript or superscript there must be
|
|
||||||
/// balanced braces {}, so this stores the amount of opening
|
|
||||||
/// braces subtracted by the amount of closing braces within the
|
|
||||||
/// definition must equal zero.
|
|
||||||
///
|
|
||||||
/// A reference to the position in the string is also included so
|
|
||||||
/// unbalanced braces can be detected in the middle of an object.
|
|
||||||
SubscriptSuperscriptBrace(SubscriptSuperscriptBrace<'s>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExitMatcherNode<'r> {
|
pub struct ExitMatcherNode<'r> {
|
||||||
@@ -213,36 +130,6 @@ pub struct ExitMatcherNode<'r> {
|
|||||||
pub class: ExitClass,
|
pub class: ExitClass,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FootnoteReferenceDefinition<'s> {
|
|
||||||
pub position: &'s str,
|
|
||||||
pub depth: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CitationBracket<'s> {
|
|
||||||
pub position: &'s str,
|
|
||||||
pub depth: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct BabelHeaderBracket<'s> {
|
|
||||||
pub position: &'s str,
|
|
||||||
pub depth: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct InlineSourceBlockBracket<'s> {
|
|
||||||
pub position: &'s str,
|
|
||||||
pub depth: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SubscriptSuperscriptBrace<'s> {
|
|
||||||
pub position: &'s str,
|
|
||||||
pub depth: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let mut formatter = f.debug_struct("ExitMatcherNode");
|
let mut formatter = f.debug_struct("ExitMatcherNode");
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ 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::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -20,11 +22,40 @@ 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::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::get_one_before;
|
|
||||||
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>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainLink<'s>> {
|
pub fn plain_link<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, PlainLink<'s>> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, proto) = protocol(context, remaining)?;
|
let (remaining, proto) = protocol(context, remaining)?;
|
||||||
let (remaining, _separator) = tag(":")(remaining)?;
|
let (remaining, _separator) = tag(":")(remaining)?;
|
||||||
@@ -34,19 +65,16 @@ pub fn plain_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
PlainLink {
|
PlainLink {
|
||||||
source,
|
source: source.into(),
|
||||||
link_type: proto,
|
link_type: proto.into(),
|
||||||
path,
|
path: path.into(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
let document_root = context.get_document_root().unwrap();
|
let preceding_character = input.get_preceding_character();
|
||||||
let preceding_character = get_one_before(document_root, input)
|
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten();
|
|
||||||
match preceding_character {
|
match preceding_character {
|
||||||
// If None, we are at the start of the file which is fine
|
// If None, we are at the start of the file which is fine
|
||||||
None => {}
|
None => {}
|
||||||
@@ -54,7 +82,7 @@ fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
|||||||
Some(_) => {
|
Some(_) => {
|
||||||
// Not at start of line, cannot be a heading
|
// Not at start of line, cannot be a heading
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not a valid pre character for plain link.",
|
"Not a valid pre character for plain link.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -62,62 +90,63 @@ fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn post<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
fn post<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
let (remaining, _) = alt((eof, recognize(none_of(WORD_CONSTITUENT_CHARACTERS))))(input)?;
|
let (remaining, _) = alt((eof, recognize(none_of(WORD_CONSTITUENT_CHARACTERS))))(input)?;
|
||||||
Ok((remaining, ()))
|
Ok((remaining, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn protocol<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
pub fn protocol<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: 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"))]
|
||||||
fn path_plain<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn path_plain<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring"
|
// TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring"
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &path_plain_end,
|
exit_matcher: &path_plain_end,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn path_plain_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn path_plain_end<'r, 's>(
|
||||||
recognize(one_of(" \t\r\n()[]<>"))(input)
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
recognize(many_till(
|
||||||
|
verify(anychar, |c| {
|
||||||
|
*c != '/' && (c.is_ascii_punctuation() || c.is_whitespace())
|
||||||
|
}),
|
||||||
|
one_of(" \t\r\n()[]<>"),
|
||||||
|
))(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +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::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;
|
||||||
@@ -32,7 +38,35 @@ 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 plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainList<'s>> {
|
pub fn detect_plain_list<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
|
if verify(
|
||||||
|
tuple((
|
||||||
|
start_of_line,
|
||||||
|
space0,
|
||||||
|
bullet,
|
||||||
|
alt((space1, line_ending, eof)),
|
||||||
|
)),
|
||||||
|
|(_start, indent, bull, _after_whitespace)| {
|
||||||
|
Into::<&str>::into(bull) != "*" || indent.len() > 0
|
||||||
|
},
|
||||||
|
)(input)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return Ok((input, ()));
|
||||||
|
}
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"No element detected.".into(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
pub fn plain_list<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'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::ExitMatcherNode(ExitMatcherNode {
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
@@ -76,7 +110,7 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
Some(final_child) => final_child,
|
Some(final_child) => final_child,
|
||||||
None => {
|
None => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Plain lists require at least one element.",
|
"Plain lists require at least one element.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -86,14 +120,11 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
|
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
|
||||||
children.push((final_child_start, reparsed_final_item));
|
children.push((final_child_start, reparsed_final_item));
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
PlainList {
|
PlainList {
|
||||||
source,
|
source: source.into(),
|
||||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -102,25 +133,27 @@ pub fn plain_list<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn plain_list_item<'r, 's>(
|
pub fn plain_list_item<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, PlainListItem<'s>> {
|
) -> Res<OrgSource<'s>, PlainListItem<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, leading_whitespace) = space0(input)?;
|
let (remaining, leading_whitespace) = space0(input)?;
|
||||||
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
||||||
let indent_level = leading_whitespace.len();
|
let indent_level = leading_whitespace.len();
|
||||||
let (remaining, bull) =
|
let (remaining, bull) = verify(bullet, |bull: &OrgSource<'_>| {
|
||||||
verify(bullet, |bull: &str| bull != "*" || indent_level > 0)(remaining)?;
|
Into::<&str>::into(bull) != "*" || indent_level > 0
|
||||||
|
})(remaining)?;
|
||||||
|
|
||||||
let maybe_contentless_item: Res<&str, &str> = alt((eof, line_ending))(remaining);
|
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> = eof(remaining);
|
||||||
match maybe_contentless_item {
|
match maybe_contentless_item {
|
||||||
Ok((rem, _ws)) => {
|
Ok((rem, _ws)) => {
|
||||||
let source = get_consumed(input, rem);
|
let source = get_consumed(input, rem);
|
||||||
return Ok((
|
return Ok((
|
||||||
rem,
|
rem,
|
||||||
PlainListItem {
|
PlainListItem {
|
||||||
source,
|
source: source.into(),
|
||||||
indentation: indent_level,
|
indentation: indent_level,
|
||||||
bullet: bull,
|
bullet: bull.into(),
|
||||||
|
tag: Vec::new(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
@@ -128,21 +161,26 @@ pub fn plain_list_item<'r, 's>(
|
|||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (remaining, _ws) = space1(remaining)?;
|
let (remaining, maybe_tag) = opt(tuple((
|
||||||
|
space1,
|
||||||
|
parser_with_context!(item_tag)(context),
|
||||||
|
tag(" ::"),
|
||||||
|
)))(remaining)?;
|
||||||
|
let (remaining, _ws) = item_tag_post_gap(context, remaining)?;
|
||||||
|
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))
|
||||||
.with_additional_node(ContextElement::ListItem(indent_level))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
exit_matcher: &plain_list_item_end,
|
exit_matcher: &exit_matcher,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = many_till(
|
||||||
many_till(
|
|
||||||
parser_with_context!(element(true))(&parser_context),
|
parser_with_context!(element(true))(&parser_context),
|
||||||
|
alt((
|
||||||
|
peek(recognize(tuple((start_of_line, many0(blank_line), eof)))),
|
||||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
),
|
)),
|
||||||
|(children, _exit_contents)| !children.is_empty(),
|
|
||||||
)(remaining)?;
|
)(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
@@ -152,16 +190,19 @@ pub fn plain_list_item<'r, 's>(
|
|||||||
return Ok((
|
return Ok((
|
||||||
remaining,
|
remaining,
|
||||||
PlainListItem {
|
PlainListItem {
|
||||||
source,
|
source: source.into(),
|
||||||
indentation: indent_level,
|
indentation: indent_level,
|
||||||
bullet: bull,
|
bullet: bull.into(),
|
||||||
|
tag: maybe_tag
|
||||||
|
.map(|(_ws, item_tag, _divider)| item_tag)
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn bullet<'s>(i: &'s str) -> Res<&'s str, &'s str> {
|
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
alt((
|
||||||
tag("*"),
|
tag("*"),
|
||||||
tag("-"),
|
tag("-"),
|
||||||
@@ -171,118 +212,179 @@ fn bullet<'s>(i: &'s str) -> Res<&'s str, &'s str> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn counter<'s>(i: &'s str) -> Res<&'s str, &'s str> {
|
fn counter<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i)
|
alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn plain_list_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn plain_list_end<'r, 's>(
|
||||||
let start_of_line_matcher = parser_with_context!(start_of_line)(context);
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
start_of_line_matcher,
|
start_of_line,
|
||||||
verify(many1(blank_line), |lines: &Vec<&str>| lines.len() >= 2),
|
verify(many1(blank_line), |lines: &Vec<OrgSource<'_>>| {
|
||||||
|
lines.len() >= 2
|
||||||
|
}),
|
||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
const fn plain_list_item_end(
|
||||||
fn plain_list_item_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
indent_level: usize,
|
||||||
start_of_line(context, input)?;
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let line_indented_lte_matcher = line_indented_lte(indent_level);
|
||||||
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_plain_list_item_end(context, input, &line_indented_lte_matcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(line_indented_lte_matcher))
|
||||||
|
)]
|
||||||
|
fn _plain_list_item_end<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
line_indented_lte_matcher: impl for<'rr, 'ss> Fn(
|
||||||
|
Context<'rr, 'ss>,
|
||||||
|
OrgSource<'ss>,
|
||||||
|
) -> Res<OrgSource<'ss>, OrgSource<'ss>>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
opt(blank_line),
|
opt(blank_line),
|
||||||
parser_with_context!(line_indented_lte)(context),
|
parser_with_context!(line_indented_lte_matcher)(context),
|
||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
const fn line_indented_lte(
|
||||||
fn line_indented_lte<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
indent_level: usize,
|
||||||
let current_item_indent_level: &usize =
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
get_context_item_indent(context).ok_or(nom::Err::Error(CustomError::MyError(MyError(
|
move |context: Context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level)
|
||||||
"Not inside a plain list item",
|
}
|
||||||
))))?;
|
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn _line_indented_lte<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
indent_level: usize,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let matched = recognize(verify(
|
let matched = recognize(verify(
|
||||||
tuple((space0::<&str, _>, non_whitespace_character)),
|
tuple((space0::<OrgSource<'_>, _>, non_whitespace_character)),
|
||||||
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
||||||
|(_space0, _anychar)| _space0.len() <= *current_item_indent_level,
|
|(_space0, _anychar)| _space0.len() <= indent_level,
|
||||||
))(input)?;
|
))(input)?;
|
||||||
|
|
||||||
Ok(matched)
|
Ok(matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_context_item_indent<'r, 's>(context: Context<'r, 's>) -> Option<&'r usize> {
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
for thing in context.iter() {
|
fn item_tag<'r, 's>(
|
||||||
match thing.get_data() {
|
context: Context<'r, 's>,
|
||||||
ContextElement::ListItem(depth) => return Some(depth),
|
input: OrgSource<'s>,
|
||||||
_ => {}
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
};
|
let parser_context =
|
||||||
}
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
None
|
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::*;
|
||||||
use crate::parser::parser_context::ContextElement;
|
|
||||||
use crate::parser::parser_context::ContextTree;
|
use crate::parser::parser_context::ContextTree;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::Source;
|
use crate::parser::Source;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_list_item_empty() {
|
fn plain_list_item_empty() {
|
||||||
let input = "1.";
|
let input = OrgSource::new("1.");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&document_context);
|
|
||||||
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1.");
|
assert_eq!(result.source, "1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_list_item_simple() {
|
fn plain_list_item_simple() {
|
||||||
let input = "1. foo";
|
let input = OrgSource::new("1. foo");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&document_context);
|
|
||||||
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1. foo");
|
assert_eq!(result.source, "1. foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_list_empty() {
|
fn plain_list_empty() {
|
||||||
let input = "1.";
|
let input = OrgSource::new("1.");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
|
|
||||||
let (remaining, result) = plain_list_matcher(input).unwrap();
|
let (remaining, result) = plain_list_matcher(input).unwrap();
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1.");
|
assert_eq!(result.source, "1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_list_simple() {
|
fn plain_list_simple() {
|
||||||
let input = "1. foo";
|
let input = OrgSource::new("1. foo");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
|
|
||||||
let (remaining, result) = plain_list_matcher(input).unwrap();
|
let (remaining, result) = plain_list_matcher(input).unwrap();
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1. foo");
|
assert_eq!(result.source, "1. foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_list_cant_start_line_with_asterisk() {
|
fn plain_list_cant_start_line_with_asterisk() {
|
||||||
// Plain lists with an asterisk bullet must be indented or else they would be a headline
|
// Plain lists with an asterisk bullet must be indented or else they would be a headline
|
||||||
let input = "* foo";
|
let input = OrgSource::new("* foo");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
|
|
||||||
let result = plain_list_matcher(input);
|
let result = plain_list_matcher(input);
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
@@ -290,32 +392,30 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn indented_can_start_line_with_asterisk() {
|
fn indented_can_start_line_with_asterisk() {
|
||||||
// Plain lists with an asterisk bullet must be indented or else they would be a headline
|
// Plain lists with an asterisk bullet must be indented or else they would be a headline
|
||||||
let input = " * foo";
|
let input = OrgSource::new(" * foo");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_matcher = parser_with_context!(plain_list)(&document_context);
|
|
||||||
let result = plain_list_matcher(input);
|
let result = plain_list_matcher(input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_blank_lines_ends_list() {
|
fn two_blank_lines_ends_list() {
|
||||||
let input = r#"1. foo
|
let input = OrgSource::new(
|
||||||
|
r#"1. foo
|
||||||
2. bar
|
2. bar
|
||||||
baz
|
baz
|
||||||
3. lorem
|
3. lorem
|
||||||
|
|
||||||
|
|
||||||
ipsum
|
ipsum
|
||||||
"#;
|
"#,
|
||||||
|
);
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_matcher = parser_with_context!(element(true))(&document_context);
|
|
||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(remaining, " ipsum\n");
|
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_source(),
|
result.get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
@@ -330,18 +430,18 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_blank_lines_ends_nested_list() {
|
fn two_blank_lines_ends_nested_list() {
|
||||||
let input = r#"1. foo
|
let input = OrgSource::new(
|
||||||
|
r#"1. foo
|
||||||
1. bar
|
1. bar
|
||||||
|
|
||||||
|
|
||||||
baz"#;
|
baz"#,
|
||||||
|
);
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_matcher = parser_with_context!(element(true))(&document_context);
|
|
||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(remaining, "baz");
|
assert_eq!(Into::<&str>::into(remaining), "baz");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_source(),
|
result.get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
@@ -354,7 +454,8 @@ baz"#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interior_trailing_whitespace() {
|
fn interior_trailing_whitespace() {
|
||||||
let input = r#"1. foo
|
let input = OrgSource::new(
|
||||||
|
r#"1. foo
|
||||||
|
|
||||||
bar
|
bar
|
||||||
|
|
||||||
@@ -365,14 +466,13 @@ baz"#;
|
|||||||
ipsum
|
ipsum
|
||||||
|
|
||||||
|
|
||||||
dolar"#;
|
dolar"#,
|
||||||
|
);
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_list_matcher = parser_with_context!(element(true))(&document_context);
|
|
||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(remaining, "dolar");
|
assert_eq!(Into::<&str>::into(remaining), "dolar");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_source(),
|
result.get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
@@ -389,4 +489,40 @@ dolar"#;
|
|||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detect_line_break() {
|
||||||
|
let input = OrgSource::new(
|
||||||
|
r#"+
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detect_eof() {
|
||||||
|
let input = OrgSource::new(r#"+"#);
|
||||||
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detect_no_gap() {
|
||||||
|
let input = OrgSource::new(r#"+foo"#);
|
||||||
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
|
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
||||||
|
assert!(result.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn detect_with_gap() {
|
||||||
|
let input = OrgSource::new(r#"+ foo"#);
|
||||||
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use nom::combinator::verify;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
|
||||||
use super::object::PlainText;
|
use super::object::PlainText;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::radio_link::RematchObject;
|
use super::radio_link::RematchObject;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
@@ -17,7 +18,10 @@ use crate::parser::parser_with_context::parser_with_context;
|
|||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn plain_text<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, PlainText<'s>> {
|
pub fn plain_text<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, PlainText<'s>> {
|
||||||
let (remaining, source) = recognize(verify(
|
let (remaining, source) = recognize(verify(
|
||||||
many_till(
|
many_till(
|
||||||
anychar,
|
anychar,
|
||||||
@@ -29,11 +33,19 @@ pub fn plain_text<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
|(children, _exit_contents)| !children.is_empty(),
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
|
|
||||||
Ok((remaining, PlainText { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
PlainText {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn plain_text_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn plain_text_end<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(parser_with_context!(any_object_except_plain_text)(context))(input)
|
recognize(parser_with_context!(any_object_except_plain_text)(context))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +54,12 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
|
|||||||
fn rematch_object<'r, 's>(
|
fn rematch_object<'r, 's>(
|
||||||
&'x self,
|
&'x self,
|
||||||
_context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
map(tag(self.source), |s| {
|
map(tag(self.source), |s| {
|
||||||
Object::PlainText(PlainText { source: s })
|
Object::PlainText(PlainText {
|
||||||
|
source: Into::<&str>::into(s),
|
||||||
|
})
|
||||||
})(input)
|
})(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,20 +69,17 @@ mod tests {
|
|||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::parser::parser_context::ContextElement;
|
|
||||||
use crate::parser::parser_context::ContextTree;
|
use crate::parser::parser_context::ContextTree;
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
use crate::parser::source::Source;
|
use crate::parser::source::Source;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_text_simple() {
|
fn plain_text_simple() {
|
||||||
let input = "foobarbaz";
|
let input = OrgSource::new("foobarbaz");
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context =
|
let plain_text_matcher = parser_with_context!(plain_text)(&initial_context);
|
||||||
initial_context.with_additional_node(ContextElement::DocumentRoot(input));
|
|
||||||
let plain_text_matcher = parser_with_context!(plain_text)(&document_context);
|
|
||||||
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
|
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_source(), input);
|
assert_eq!(result.get_source(), Into::<&str>::into(input));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use nom::combinator::eof;
|
|||||||
use nom::multi::separated_list1;
|
use nom::multi::separated_list1;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::lesser_element::Planning;
|
use crate::parser::lesser_element::Planning;
|
||||||
@@ -16,19 +17,27 @@ use crate::parser::util::get_consumed;
|
|||||||
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 planning<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Planning<'s>> {
|
pub fn planning<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Planning<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?;
|
let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?;
|
||||||
let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
|
let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Planning { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Planning {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn planning_parameter<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn planning_parameter<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _planning_type) = alt((
|
let (remaining, _planning_type) = alt((
|
||||||
tag_no_case("DEADLINE"),
|
tag_no_case("DEADLINE"),
|
||||||
tag_no_case("SCHEDULED"),
|
tag_no_case("SCHEDULED"),
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ 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;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -22,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;
|
||||||
@@ -32,18 +33,18 @@ 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 property_drawer<'r, 's>(
|
pub fn property_drawer<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, PropertyDrawer<'s>> {
|
) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
|
||||||
if immediate_in_section(context, "property-drawer") {
|
if immediate_in_section(context, "property-drawer") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element",
|
"Cannot nest objects of the same element".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
let (
|
let (
|
||||||
remaining,
|
remaining,
|
||||||
(_start_of_line, _leading_whitespace, _open_tag, _trailing_whitespace, _line_ending),
|
(_start_of_line, _leading_whitespace, _open_tag, _trailing_whitespace, _line_ending),
|
||||||
) = tuple((
|
) = tuple((
|
||||||
parser_with_context!(start_of_line)(context),
|
start_of_line,
|
||||||
space0,
|
space0,
|
||||||
tag_no_case(":PROPERTIES:"),
|
tag_no_case(":PROPERTIES:"),
|
||||||
space0,
|
space0,
|
||||||
@@ -68,13 +69,22 @@ pub fn property_drawer<'r, 's>(
|
|||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, PropertyDrawer { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
PropertyDrawer {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn property_drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn property_drawer_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
parser_with_context!(start_of_line)(context),
|
start_of_line,
|
||||||
space0,
|
space0,
|
||||||
tag_no_case(":end:"),
|
tag_no_case(":end:"),
|
||||||
space0,
|
space0,
|
||||||
@@ -85,23 +95,27 @@ fn property_drawer_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn node_property<'r, 's>(
|
fn node_property<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, NodeProperty<'s>> {
|
) -> Res<OrgSource<'s>, NodeProperty<'s>> {
|
||||||
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, _name, _close_colon)) =
|
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, _name, _close_colon)) =
|
||||||
tuple((
|
tuple((
|
||||||
parser_with_context!(start_of_line)(context),
|
start_of_line,
|
||||||
space0,
|
space0,
|
||||||
tag(":"),
|
tag(":"),
|
||||||
parser_with_context!(node_property_name)(context),
|
parser_with_context!(node_property_name)(context),
|
||||||
tag(":"),
|
tag(":"),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
match tuple((space0::<&str, nom::error::Error<&str>>, line_ending))(remaining) {
|
match tuple((
|
||||||
|
space0::<OrgSource<'_>, nom::error::Error<OrgSource<'_>>>,
|
||||||
|
line_ending,
|
||||||
|
))(remaining)
|
||||||
|
{
|
||||||
Ok((remaining, _ws)) => {
|
Ok((remaining, _ws)) => {
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
NodeProperty {
|
NodeProperty {
|
||||||
source,
|
source: source.into(),
|
||||||
value: None,
|
value: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -113,8 +127,8 @@ fn node_property<'r, 's>(
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
NodeProperty {
|
NodeProperty {
|
||||||
source,
|
source: source.into(),
|
||||||
value: Some(value),
|
value: Some(value.into()),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -122,7 +136,10 @@ fn node_property<'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn node_property_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn node_property_name<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
@@ -130,18 +147,23 @@ fn node_property_name<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn node_property_name_end<'r, 's>(
|
fn node_property_name_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((tag("+:"), tag(":")))(input)
|
alt((tag("+:"), tag(":")))(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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::Context;
|
use super::Context;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -21,7 +22,10 @@ use crate::parser::RadioLink;
|
|||||||
use crate::parser::RadioTarget;
|
use crate::parser::RadioTarget;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, RadioLink<'s>> {
|
pub fn radio_link<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, RadioLink<'s>> {
|
||||||
let radio_targets = context
|
let radio_targets = context
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|context_element| match context_element.get_data() {
|
.filter_map(|context_element| match context_element.get_data() {
|
||||||
@@ -37,14 +41,14 @@ pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
return Ok((
|
return Ok((
|
||||||
remaining,
|
remaining,
|
||||||
RadioLink {
|
RadioLink {
|
||||||
source,
|
source: source.into(),
|
||||||
children: rematched_target,
|
children: rematched_target,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"NoRadioLink",
|
"NoRadioLink".into(),
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,8 +56,8 @@ pub fn radio_link<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s s
|
|||||||
pub fn rematch_target<'x, 'r, 's>(
|
pub fn rematch_target<'x, 'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
target: &'x Vec<Object<'x>>,
|
target: &'x Vec<Object<'x>>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
let mut remaining = input;
|
let mut remaining = input;
|
||||||
let mut new_matches = Vec::with_capacity(target.len());
|
let mut new_matches = Vec::with_capacity(target.len());
|
||||||
for original_object in target {
|
for original_object in target {
|
||||||
@@ -71,7 +75,7 @@ pub fn rematch_target<'x, 'r, 's>(
|
|||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"OnlyMinimalSetObjectsAllowed",
|
"OnlyMinimalSetObjectsAllowed".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -82,12 +86,12 @@ pub fn rematch_target<'x, 'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn radio_target<'r, 's>(
|
pub fn radio_target<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, RadioTarget<'s>> {
|
) -> Res<OrgSource<'s>, RadioTarget<'s>> {
|
||||||
let (remaining, _opening) = tag("<<<")(input)?;
|
let (remaining, _opening) = tag("<<<")(input)?;
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &radio_target_end,
|
exit_matcher: &radio_target_end,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -102,11 +106,20 @@ pub fn radio_target<'r, 's>(
|
|||||||
let (remaining, _closing) = tag(">>>")(remaining)?;
|
let (remaining, _closing) = tag(">>>")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) = space0(remaining)?;
|
let (remaining, _trailing_whitespace) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, RadioTarget { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
RadioTarget {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn radio_target_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn radio_target_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((tag("<"), tag(">"), line_ending))(input)
|
alt((tag("<"), tag(">"), line_ending))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,8 +127,8 @@ pub trait RematchObject<'x> {
|
|||||||
fn rematch_object<'r, 's>(
|
fn rematch_object<'r, 's>(
|
||||||
&'x self,
|
&'x self,
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Object<'s>>;
|
) -> Res<OrgSource<'s>, Object<'s>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -131,11 +144,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_text_radio_target() {
|
fn plain_text_radio_target() {
|
||||||
let input = "foo bar baz";
|
let input = OrgSource::new("foo bar baz");
|
||||||
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
|
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context = initial_context
|
let document_context = initial_context
|
||||||
.with_additional_node(ContextElement::DocumentRoot(input))
|
|
||||||
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
|
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
||||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||||
@@ -143,7 +155,7 @@ mod tests {
|
|||||||
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "foo bar baz");
|
assert_eq!(first_paragraph.get_source(), "foo bar baz");
|
||||||
assert_eq!(first_paragraph.children.len(), 3);
|
assert_eq!(first_paragraph.children.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -160,22 +172,22 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bold_radio_target() {
|
fn bold_radio_target() {
|
||||||
let input = "foo *bar* baz";
|
let input = OrgSource::new("foo *bar* baz");
|
||||||
let radio_target_match = vec![Object::Bold(Bold {
|
let radio_target_match = vec![Object::Bold(Bold {
|
||||||
source: "*bar*",
|
source: "*bar*",
|
||||||
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
||||||
})];
|
})];
|
||||||
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
let initial_context: ContextTree<'_, '_> = ContextTree::new();
|
||||||
let document_context = initial_context
|
let document_context = initial_context
|
||||||
.with_additional_node(ContextElement::DocumentRoot(input))
|
|
||||||
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
|
.with_additional_node(ContextElement::RadioTarget(vec![&radio_target_match]));
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
let paragraph_matcher = parser_with_context!(element(true))(&document_context);
|
||||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
let (remaining, first_paragraph) =
|
||||||
|
paragraph_matcher(input.into()).expect("Parse first paragraph");
|
||||||
let first_paragraph = match first_paragraph {
|
let first_paragraph = match first_paragraph {
|
||||||
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
crate::parser::Element::Paragraph(paragraph) => paragraph,
|
||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
|
assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
|
||||||
assert_eq!(first_paragraph.children.len(), 3);
|
assert_eq!(first_paragraph.children.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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::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::Context;
|
use super::Context;
|
||||||
@@ -22,8 +23,8 @@ use crate::parser::util::exit_matcher_parser;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn regular_link<'r, 's>(
|
pub fn regular_link<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, RegularLink<'s>> {
|
) -> Res<OrgSource<'s>, RegularLink<'s>> {
|
||||||
alt((
|
alt((
|
||||||
parser_with_context!(regular_link_without_description)(context),
|
parser_with_context!(regular_link_without_description)(context),
|
||||||
parser_with_context!(regular_link_with_description)(context),
|
parser_with_context!(regular_link_with_description)(context),
|
||||||
@@ -33,21 +34,26 @@ pub fn regular_link<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn regular_link_without_description<'r, 's>(
|
pub fn regular_link_without_description<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, RegularLink<'s>> {
|
) -> Res<OrgSource<'s>, RegularLink<'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) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, RegularLink { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
RegularLink {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn regular_link_with_description<'r, 's>(
|
pub fn regular_link_with_description<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, RegularLink<'s>> {
|
) -> Res<OrgSource<'s>, RegularLink<'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)?;
|
||||||
@@ -55,11 +61,19 @@ pub fn regular_link_with_description<'r, 's>(
|
|||||||
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
|
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) = space0(remaining)?;
|
let (remaining, _trailing_whitespace) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, RegularLink { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
RegularLink {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn pathreg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
pub fn pathreg<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, path) = escaped(
|
let (remaining, path) = escaped(
|
||||||
take_till1(|c| match c {
|
take_till1(|c| match c {
|
||||||
'\\' | ']' => true,
|
'\\' | ']' => true,
|
||||||
@@ -74,8 +88,8 @@ pub fn pathreg<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn description<'r, 's>(
|
pub fn description<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
@@ -93,6 +107,9 @@ pub fn description<'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn description_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn description_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
tag("]]")(input)
|
tag("]]")(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ use nom::sequence::delimited;
|
|||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::convert_error;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
|
use super::util::get_consumed;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -28,50 +31,10 @@ pub enum Token<'s> {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextWithProperties<'s> {
|
pub struct TextWithProperties<'s> {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub text: &'s str,
|
pub text: &'s str,
|
||||||
#[allow(dead_code)]
|
|
||||||
pub properties: Vec<Token<'s>>,
|
pub properties: Vec<Token<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> TextWithProperties<'s> {
|
|
||||||
pub fn unquote(&self) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let mut out = String::with_capacity(self.text.len());
|
|
||||||
if !self.text.starts_with(r#"""#) {
|
|
||||||
return Err("Quoted text does not start with quote.".into());
|
|
||||||
}
|
|
||||||
if !self.text.ends_with(r#"""#) {
|
|
||||||
return Err("Quoted text does not end with quote.".into());
|
|
||||||
}
|
|
||||||
let interior_text = &self.text[1..(self.text.len() - 1)];
|
|
||||||
let mut state = ParseState::Normal;
|
|
||||||
for current_char in interior_text.chars().into_iter() {
|
|
||||||
state = match (state, current_char) {
|
|
||||||
(ParseState::Normal, '\\') => ParseState::Escape,
|
|
||||||
(ParseState::Normal, _) => {
|
|
||||||
out.push(current_char);
|
|
||||||
ParseState::Normal
|
|
||||||
}
|
|
||||||
(ParseState::Escape, 'n') => {
|
|
||||||
out.push('\n');
|
|
||||||
ParseState::Normal
|
|
||||||
}
|
|
||||||
(ParseState::Escape, '\\') => {
|
|
||||||
out.push('\\');
|
|
||||||
ParseState::Normal
|
|
||||||
}
|
|
||||||
(ParseState::Escape, '"') => {
|
|
||||||
out.push('"');
|
|
||||||
ParseState::Normal
|
|
||||||
}
|
|
||||||
_ => todo!(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ParseState {
|
enum ParseState {
|
||||||
Normal,
|
Normal,
|
||||||
Escape,
|
Escape,
|
||||||
@@ -81,28 +44,28 @@ impl<'s> Token<'s> {
|
|||||||
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Token::Vector(children) => Ok(children),
|
Token::Vector(children) => Ok(children),
|
||||||
_ => Err(format!("wrong token type {:?}", self)),
|
_ => Err(format!("wrong token type, expected vector: {:?}", self)),
|
||||||
}?)
|
}?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Token::List(children) => Ok(children),
|
Token::List(children) => Ok(children),
|
||||||
_ => Err(format!("wrong token type {:?}", self)),
|
_ => Err(format!("wrong token type, expected list: {:?}", self)),
|
||||||
}?)
|
}?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
|
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Token::Atom(body) => Ok(*body),
|
Token::Atom(body) => Ok(*body),
|
||||||
_ => Err(format!("wrong token type {:?}", self)),
|
_ => Err(format!("wrong token type, expected atom: {:?}", self)),
|
||||||
}?)
|
}?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Token::TextWithProperties(body) => Ok(body),
|
Token::TextWithProperties(body) => Ok(body),
|
||||||
_ => Err(format!("wrong token type {:?}", self)),
|
_ => Err(format!("wrong token type, expected text: {:?}", self)),
|
||||||
}?)
|
}?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,27 +95,66 @@ impl<'s> Token<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let mut out = String::with_capacity(text.len());
|
||||||
|
if !text.starts_with(r#"""#) {
|
||||||
|
return Err("Quoted text does not start with quote.".into());
|
||||||
|
}
|
||||||
|
if !text.ends_with(r#"""#) {
|
||||||
|
return Err("Quoted text does not end with quote.".into());
|
||||||
|
}
|
||||||
|
let interior_text = &text[1..(text.len() - 1)];
|
||||||
|
let mut state = ParseState::Normal;
|
||||||
|
for current_char in interior_text.chars().into_iter() {
|
||||||
|
state = match (state, current_char) {
|
||||||
|
(ParseState::Normal, '\\') => ParseState::Escape,
|
||||||
|
(ParseState::Normal, _) => {
|
||||||
|
out.push(current_char);
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
(ParseState::Escape, 'n') => {
|
||||||
|
out.push('\n');
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
(ParseState::Escape, '\\') => {
|
||||||
|
out.push('\\');
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
(ParseState::Escape, '"') => {
|
||||||
|
out.push('"');
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
_ => todo!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
let (remaining, _) = multispace0(input)?;
|
let (remaining, _) = multispace0(input)?;
|
||||||
let (remaining, tkn) = token(remaining)?;
|
let remaining = OrgSource::new(remaining);
|
||||||
|
let (remaining, tkn) = token(remaining)
|
||||||
|
.map(|(rem, out)| (Into::<&str>::into(rem), out))
|
||||||
|
.map_err(convert_error)?;
|
||||||
let (remaining, _) = multispace0(remaining)?;
|
let (remaining, _) = multispace0(remaining)?;
|
||||||
Ok((remaining, tkn))
|
Ok((remaining, tkn))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn sexp<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
pub fn sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
let (remaining, tkn) = token(input)?;
|
let (remaining, tkn) = token(input)?;
|
||||||
Ok((remaining, tkn))
|
Ok((remaining, tkn))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn token<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn token<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
alt((list, vector, atom))(input)
|
alt((list, vector, atom))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
let (remaining, _) = tag("(")(input)?;
|
let (remaining, _) = tag("(")(input)?;
|
||||||
let (remaining, children) = delimited(
|
let (remaining, children) = delimited(
|
||||||
multispace0,
|
multispace0,
|
||||||
@@ -164,7 +166,7 @@ fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
let (remaining, children) = delimited(
|
let (remaining, children) = delimited(
|
||||||
multispace0,
|
multispace0,
|
||||||
@@ -176,7 +178,7 @@ fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
not(peek(one_of(")]")))(input)?;
|
not(peek(one_of(")]")))(input)?;
|
||||||
alt((
|
alt((
|
||||||
text_with_properties,
|
text_with_properties,
|
||||||
@@ -187,16 +189,16 @@ fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
let (remaining, body) = take_till1(|c| match c {
|
let (remaining, body) = take_till1(|c| match c {
|
||||||
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
|
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
})(input)?;
|
})(input)?;
|
||||||
Ok((remaining, Token::Atom(body)))
|
Ok((remaining, Token::Atom(body.into())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
let (remaining, _) = tag(r#"""#)(input)?;
|
let (remaining, _) = tag(r#"""#)(input)?;
|
||||||
let (remaining, _) = escaped(
|
let (remaining, _) = escaped(
|
||||||
take_till1(|c| match c {
|
take_till1(|c| match c {
|
||||||
@@ -208,11 +210,11 @@ fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
)(remaining)?;
|
)(remaining)?;
|
||||||
let (remaining, _) = tag(r#"""#)(remaining)?;
|
let (remaining, _) = tag(r#"""#)(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Token::Atom(source)))
|
Ok((remaining, Token::Atom(source.into())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
let (remaining, _) = tag("#<")(input)?;
|
let (remaining, _) = tag("#<")(input)?;
|
||||||
let (remaining, _body) = take_till1(|c| match c {
|
let (remaining, _body) = take_till1(|c| match c {
|
||||||
'>' => true,
|
'>' => true,
|
||||||
@@ -220,10 +222,10 @@ fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
})(remaining)?;
|
})(remaining)?;
|
||||||
let (remaining, _) = tag(">")(remaining)?;
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Token::Atom(source)))
|
Ok((remaining, Token::Atom(source.into())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn text_with_properties<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||||
let (remaining, _) = tag("#(")(input)?;
|
let (remaining, _) = tag("#(")(input)?;
|
||||||
let (remaining, (text, props)) = delimited(
|
let (remaining, (text, props)) = delimited(
|
||||||
multispace0,
|
multispace0,
|
||||||
@@ -246,25 +248,6 @@ fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
|
||||||
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
|
||||||
assert!(is_slice_of(input, remaining));
|
|
||||||
let source = {
|
|
||||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
|
||||||
&input[..offset]
|
|
||||||
};
|
|
||||||
source
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the child string slice is a slice of the parent string slice.
|
|
||||||
fn is_slice_of(parent: &str, child: &str) -> bool {
|
|
||||||
let parent_start = parent.as_ptr() as usize;
|
|
||||||
let parent_end = parent_start + parent.len();
|
|
||||||
let child_start = child.as_ptr() as usize;
|
|
||||||
let child_end = child_start + child.len();
|
|
||||||
child_start >= parent_start && child_end <= parent_end
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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::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;
|
||||||
@@ -12,8 +13,8 @@ use crate::parser::StatisticsCookie;
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn statistics_cookie<'r, 's>(
|
pub fn statistics_cookie<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, StatisticsCookie<'s>> {
|
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
|
||||||
alt((
|
alt((
|
||||||
parser_with_context!(percent_statistics_cookie)(context),
|
parser_with_context!(percent_statistics_cookie)(context),
|
||||||
parser_with_context!(fraction_statistics_cookie)(context),
|
parser_with_context!(fraction_statistics_cookie)(context),
|
||||||
@@ -22,20 +23,25 @@ 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: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, 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, _) = space0(remaining)?;
|
||||||
Ok((remaining, StatisticsCookie { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
StatisticsCookie {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, StatisticsCookie<'s>> {
|
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
|
||||||
let (remaining, source) = recognize(tuple((
|
let (remaining, source) = recognize(tuple((
|
||||||
tag("["),
|
tag("["),
|
||||||
nom::character::complete::u64,
|
nom::character::complete::u64,
|
||||||
@@ -44,5 +50,10 @@ pub fn fraction_statistics_cookie<'r, 's>(
|
|||||||
tag("]"),
|
tag("]"),
|
||||||
)))(input)?;
|
)))(input)?;
|
||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
Ok((remaining, StatisticsCookie { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
StatisticsCookie {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ 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::Context;
|
use super::Context;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -20,50 +22,58 @@ use crate::parser::exiting::ExitClass;
|
|||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
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_context::SubscriptSuperscriptBrace;
|
|
||||||
use crate::parser::parser_with_context::parser_with_context;
|
use crate::parser::parser_with_context::parser_with_context;
|
||||||
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::get_one_before;
|
|
||||||
use crate::parser::Subscript;
|
use crate::parser::Subscript;
|
||||||
use crate::parser::Superscript;
|
use crate::parser::Superscript;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn subscript<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Subscript<'s>> {
|
pub fn subscript<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Subscript<'s>> {
|
||||||
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
|
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
|
||||||
let (remaining, _) = tag("_")(input)?;
|
let (remaining, _) = tag("_")(input)?;
|
||||||
pre(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, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Subscript { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Subscript {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn superscript<'r, 's>(
|
pub fn superscript<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Superscript<'s>> {
|
) -> Res<OrgSource<'s>, Superscript<'s>> {
|
||||||
// We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
|
// We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
|
||||||
let (remaining, _) = tag("^")(input)?;
|
let (remaining, _) = tag("^")(input)?;
|
||||||
pre(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, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Superscript { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Superscript {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
let document_root = context.get_document_root().unwrap();
|
let preceding_character = input.get_preceding_character();
|
||||||
let preceding_character = get_one_before(document_root, input)
|
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten();
|
|
||||||
match preceding_character {
|
match preceding_character {
|
||||||
Some(c) if !c.is_whitespace() => {}
|
Some(c) if !c.is_whitespace() => {}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Must be preceded by a non-whitespace character.",
|
"Must be preceded by a non-whitespace character.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -77,27 +87,36 @@ enum ScriptBody<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn script_body<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ScriptBody<'s>> {
|
fn script_body<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ScriptBody<'s>> {
|
||||||
alt((
|
alt((
|
||||||
map(parser_with_context!(script_asterisk)(context), |body| {
|
map(parser_with_context!(script_asterisk)(context), |body| {
|
||||||
ScriptBody::Braceless(body)
|
ScriptBody::Braceless(body.into())
|
||||||
}),
|
}),
|
||||||
map(parser_with_context!(script_alphanum)(context), |body| {
|
map(parser_with_context!(script_alphanum)(context), |body| {
|
||||||
ScriptBody::Braceless(body)
|
ScriptBody::Braceless(body.into())
|
||||||
}),
|
}),
|
||||||
map(parser_with_context!(script_with_braces)(context), |body| {
|
map(parser_with_context!(script_with_braces)(context), |body| {
|
||||||
ScriptBody::WithBraces(body)
|
ScriptBody::WithBraces(body.into())
|
||||||
}),
|
}),
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn script_asterisk<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn script_asterisk<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
tag("*")(input)
|
tag("*")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn script_alphanum<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn script_alphanum<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?;
|
let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?;
|
||||||
let (remaining, _script) = many_till(
|
let (remaining, _script) = many_till(
|
||||||
parser_with_context!(script_alphanum_character)(context),
|
parser_with_context!(script_alphanum_character)(context),
|
||||||
@@ -109,9 +128,9 @@ fn script_alphanum<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn script_alphanum_character<'r, 's>(
|
fn script_alphanum_character<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(verify(anychar, |c| {
|
recognize(verify(anychar, |c| {
|
||||||
c.is_alphanumeric() || r#",.\"#.contains(*c)
|
c.is_alphanumeric() || r#",.\"#.contains(*c)
|
||||||
}))(input)
|
}))(input)
|
||||||
@@ -120,8 +139,8 @@ fn script_alphanum_character<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn end_script_alphanum_character<'r, 's>(
|
fn end_script_alphanum_character<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
|
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
|
||||||
peek(not(parser_with_context!(script_alphanum_character)(
|
peek(not(parser_with_context!(script_alphanum_character)(
|
||||||
context,
|
context,
|
||||||
@@ -132,19 +151,14 @@ fn end_script_alphanum_character<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn script_with_braces<'r, 's>(
|
fn script_with_braces<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
let (remaining, _) = tag("{")(input)?;
|
let (remaining, _) = tag("{")(input)?;
|
||||||
let parser_context = context
|
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
|
||||||
.with_additional_node(ContextElement::SubscriptSuperscriptBrace(
|
let parser_context =
|
||||||
SubscriptSuperscriptBrace {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
position: remaining,
|
class: ExitClass::Gamma,
|
||||||
depth: 0,
|
exit_matcher: &exit_with_depth,
|
||||||
},
|
|
||||||
))
|
|
||||||
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
|
||||||
class: ExitClass::Beta,
|
|
||||||
exit_matcher: &script_with_braces_end,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let (remaining, (children, _exit_contents)) = many_till(
|
let (remaining, (children, _exit_contents)) = many_till(
|
||||||
@@ -156,49 +170,30 @@ fn script_with_braces<'r, 's>(
|
|||||||
Ok((remaining, children))
|
Ok((remaining, children))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
fn script_with_braces_end(
|
||||||
fn script_with_braces_end<'r, 's>(
|
starting_brace_depth: BracketDepth,
|
||||||
context: Context<'r, 's>,
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
input: &'s str,
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
) -> Res<&'s str, &'s str> {
|
_script_with_braces_end(context, input, starting_brace_depth)
|
||||||
let context_depth = get_bracket_depth(context)
|
|
||||||
.expect("This function should only be called from inside a subscript or superscript.");
|
|
||||||
let text_since_context_entry = get_consumed(context_depth.position, input);
|
|
||||||
let mut current_depth = context_depth.depth;
|
|
||||||
for c in text_since_context_entry.chars() {
|
|
||||||
match c {
|
|
||||||
'{' => {
|
|
||||||
current_depth += 1;
|
|
||||||
}
|
}
|
||||||
'}' if current_depth == 0 => {
|
|
||||||
panic!("Exceeded subscript or superscript brace depth.")
|
|
||||||
}
|
|
||||||
'}' if current_depth > 0 => {
|
|
||||||
current_depth -= 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current_depth == 0 {
|
|
||||||
let close_bracket = tag::<&str, &str, CustomError<&str>>("}")(input);
|
|
||||||
if close_bracket.is_ok() {
|
|
||||||
return close_bracket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"Not a valid end for subscript or superscript.",
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn get_bracket_depth<'r, 's>(
|
fn _script_with_braces_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
) -> Option<&'r SubscriptSuperscriptBrace<'s>> {
|
input: OrgSource<'s>,
|
||||||
for node in context.iter() {
|
starting_brace_depth: BracketDepth,
|
||||||
match node.get_data() {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
ContextElement::SubscriptSuperscriptBrace(depth) => return Some(depth),
|
let current_depth = input.get_brace_depth() - starting_brace_depth;
|
||||||
_ => {}
|
if current_depth > 0 {
|
||||||
|
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Not a valid end for subscript or superscript.".into(),
|
||||||
|
))));
|
||||||
}
|
}
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
|
||||||
|
unreachable!("Exceeded subscript or superscript brace depth.")
|
||||||
}
|
}
|
||||||
None
|
tag("}")(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ use nom::multi::many1;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::object_parser::table_cell_set_object;
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
use crate::parser::greater_element::TableRow;
|
use crate::parser::greater_element::TableRow;
|
||||||
use crate::parser::lesser_element::TableCell;
|
use crate::parser::lesser_element::TableCell;
|
||||||
use crate::parser::object::Object;
|
|
||||||
use crate::parser::object_parser::minimal_set_object;
|
|
||||||
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;
|
||||||
@@ -31,8 +31,11 @@ use crate::parser::Table;
|
|||||||
///
|
///
|
||||||
/// This is not the table.el style.
|
/// This is not the table.el style.
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn org_mode_table<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Table<'s>> {
|
pub fn org_mode_table<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Table<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
peek(tuple((space0, tag("|"))))(input)?;
|
peek(tuple((space0, tag("|"))))(input)?;
|
||||||
|
|
||||||
let parser_context = context
|
let parser_context = context
|
||||||
@@ -52,20 +55,29 @@ pub fn org_mode_table<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&
|
|||||||
// TODO: Consume trailing formulas
|
// TODO: Consume trailing formulas
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Table { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Table {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn table_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn table_end<'r, 's>(
|
||||||
start_of_line(context, input)?;
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
recognize(tuple((space0, not(tag("|")))))(input)
|
recognize(tuple((space0, not(tag("|")))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn org_mode_table_row<'r, 's>(
|
pub fn org_mode_table_row<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, TableRow<'s>> {
|
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
||||||
alt((
|
alt((
|
||||||
parser_with_context!(org_mode_table_row_rule)(context),
|
parser_with_context!(org_mode_table_row_rule)(context),
|
||||||
parser_with_context!(org_mode_table_row_regular)(context),
|
parser_with_context!(org_mode_table_row_regular)(context),
|
||||||
@@ -74,16 +86,16 @@ pub fn org_mode_table_row<'r, 's>(
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn org_mode_table_row_rule<'r, 's>(
|
pub fn org_mode_table_row_rule<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, TableRow<'s>> {
|
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?;
|
let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
TableRow {
|
TableRow {
|
||||||
source,
|
source: source.into(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -92,22 +104,28 @@ pub fn org_mode_table_row_rule<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn org_mode_table_row_regular<'r, 's>(
|
pub fn org_mode_table_row_regular<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, TableRow<'s>> {
|
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
||||||
start_of_line(context, input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _) = tuple((space0, tag("|")))(input)?;
|
let (remaining, _) = tuple((space0, tag("|")))(input)?;
|
||||||
let (remaining, children) =
|
let (remaining, children) =
|
||||||
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
|
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
|
||||||
let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
|
let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, TableRow { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
TableRow {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn org_mode_table_cell<'r, 's>(
|
pub fn org_mode_table_cell<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, TableCell<'s>> {
|
) -> Res<OrgSource<'s>, TableCell<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Beta,
|
||||||
@@ -118,31 +136,28 @@ pub fn org_mode_table_cell<'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, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(table_cell_set_object_matcher, exit_matcher),
|
many_till(table_cell_set_object_matcher, exit_matcher),
|
||||||
|(children, exit_contents)| !children.is_empty() || exit_contents.ends_with("|"),
|
|(children, exit_contents)| {
|
||||||
|
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with("|")
|
||||||
|
},
|
||||||
)(input)?;
|
)(input)?;
|
||||||
|
|
||||||
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
|
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, TableCell { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
TableCell {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn org_mode_table_cell_end<'r, 's>(
|
fn org_mode_table_cell_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input)
|
recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
pub fn table_cell_set_object<'r, 's>(
|
|
||||||
context: Context<'r, 's>,
|
|
||||||
input: &'s str,
|
|
||||||
) -> Res<&'s str, Object<'s>> {
|
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
|
||||||
|
|
||||||
parser_with_context!(minimal_set_object)(context)(input)
|
|
||||||
// TODO: add citations, export snippets, footnote references, links, macros, radio targets, targets, and timestamps.
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -17,11 +18,13 @@ 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::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::get_one_before;
|
|
||||||
use crate::parser::Target;
|
use crate::parser::Target;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn target<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Target<'s>> {
|
pub fn target<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Target<'s>> {
|
||||||
let (remaining, _) = tag("<<")(input)?;
|
let (remaining, _) = tag("<<")(input)?;
|
||||||
let (remaining, _) = peek(verify(anychar, |c| {
|
let (remaining, _) = peek(verify(anychar, |c| {
|
||||||
!c.is_whitespace() && !"<>\n".contains(*c)
|
!c.is_whitespace() && !"<>\n".contains(*c)
|
||||||
@@ -37,24 +40,30 @@ pub fn target<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str,
|
|||||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let document_root = context.get_document_root().unwrap();
|
let preceding_character = remaining
|
||||||
let preceding_character = get_one_before(document_root, remaining)
|
.get_preceding_character()
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten()
|
|
||||||
.expect("We cannot be at the start of the file because we are inside a target.");
|
.expect("We cannot be at the start of the file because we are inside a target.");
|
||||||
if preceding_character.is_whitespace() {
|
if preceding_character.is_whitespace() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Targets cannot end with whitespace.",
|
"Targets cannot end with whitespace.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
let (remaining, _) = tag(">>")(remaining)?;
|
let (remaining, _) = tag(">>")(remaining)?;
|
||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Target { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Target {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn target_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn target_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(one_of("<>\n"))(input)
|
recognize(one_of("<>\n"))(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use nom::sequence::terminated;
|
|||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
use tracing::span;
|
use tracing::span;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::radio_link::RematchObject;
|
use super::radio_link::RematchObject;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -28,7 +29,6 @@ use crate::parser::parser_with_context::parser_with_context;
|
|||||||
use crate::parser::radio_link::rematch_target;
|
use crate::parser::radio_link::rematch_target;
|
||||||
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::get_one_before;
|
|
||||||
use crate::parser::util::preceded_by_whitespace;
|
use crate::parser::util::preceded_by_whitespace;
|
||||||
use crate::parser::Bold;
|
use crate::parser::Bold;
|
||||||
use crate::parser::Code;
|
use crate::parser::Code;
|
||||||
@@ -39,7 +39,10 @@ use crate::parser::Underline;
|
|||||||
use crate::parser::Verbatim;
|
use crate::parser::Verbatim;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn text_markup<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Object<'s>> {
|
pub fn text_markup<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
alt((
|
alt((
|
||||||
map(parser_with_context!(bold)(context), Object::Bold),
|
map(parser_with_context!(bold)(context), Object::Bold),
|
||||||
map(parser_with_context!(italic)(context), Object::Italic),
|
map(parser_with_context!(italic)(context), Object::Italic),
|
||||||
@@ -54,76 +57,129 @@ pub fn text_markup<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn bold<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Bold<'s>> {
|
pub fn bold<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Bold<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("*");
|
let text_markup_object_specialized = text_markup_object("*");
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Bold { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Bold {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn italic<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Italic<'s>> {
|
pub fn italic<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Italic<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("/");
|
let text_markup_object_specialized = text_markup_object("/");
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Italic { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Italic {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn underline<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Underline<'s>> {
|
pub fn underline<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Underline<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("_");
|
let text_markup_object_specialized = text_markup_object("_");
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Underline { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Underline {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn strike_through<'r, 's>(
|
pub fn strike_through<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, StrikeThrough<'s>> {
|
) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("+");
|
let text_markup_object_specialized = text_markup_object("+");
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, StrikeThrough { source, children }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
StrikeThrough {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn verbatim<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Verbatim<'s>> {
|
pub fn verbatim<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Verbatim<'s>> {
|
||||||
let text_markup_string_specialized = text_markup_string("=");
|
let text_markup_string_specialized = text_markup_string("=");
|
||||||
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Verbatim { source, contents }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Verbatim {
|
||||||
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn code<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Code<'s>> {
|
pub fn code<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Code<'s>> {
|
||||||
let text_markup_string_specialized = text_markup_string("~");
|
let text_markup_string_specialized = text_markup_string("~");
|
||||||
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Code { source, contents }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Code {
|
||||||
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_markup_object(
|
fn text_markup_object(
|
||||||
marker_symbol: &str,
|
marker_symbol: &str,
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
let marker_symbol = marker_symbol.to_owned();
|
let marker_symbol = marker_symbol.to_owned();
|
||||||
move |context: Context, input: &str| _text_markup_object(context, input, marker_symbol.as_str())
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_text_markup_object(context, input, marker_symbol.as_str())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _text_markup_object<'r, 's, 'x>(
|
fn _text_markup_object<'r, 's, 'x>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'x str,
|
marker_symbol: &'x str,
|
||||||
) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
||||||
let text_markup_end_specialized = text_markup_end(open);
|
let text_markup_end_specialized = text_markup_end(open.into());
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &text_markup_end_specialized,
|
exit_matcher: &text_markup_end_specialized,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -142,7 +198,7 @@ fn _text_markup_object<'r, 's, 'x>(
|
|||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
if exit_matcher_parser(context, remaining).is_ok() {
|
if exit_matcher_parser(context, remaining).is_ok() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Parent exit matcher is triggering.",
|
"Parent exit matcher is triggering.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,24 +210,26 @@ fn _text_markup_object<'r, 's, 'x>(
|
|||||||
|
|
||||||
fn text_markup_string(
|
fn text_markup_string(
|
||||||
marker_symbol: &str,
|
marker_symbol: &str,
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let marker_symbol = marker_symbol.to_owned();
|
let marker_symbol = marker_symbol.to_owned();
|
||||||
move |context: Context, input: &str| _text_markup_string(context, input, marker_symbol.as_str())
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_text_markup_string(context, input, marker_symbol.as_str())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _text_markup_string<'r, 's, 'x>(
|
fn _text_markup_string<'r, 's, 'x>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'x str,
|
marker_symbol: &'x str,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
||||||
let text_markup_end_specialized = text_markup_end(open);
|
let text_markup_end_specialized = text_markup_end(open.into());
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &text_markup_end_specialized,
|
exit_matcher: &text_markup_end_specialized,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -190,7 +248,7 @@ fn _text_markup_string<'r, 's, 'x>(
|
|||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
if exit_matcher_parser(context, remaining).is_ok() {
|
if exit_matcher_parser(context, remaining).is_ok() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Parent exit matcher is triggering.",
|
"Parent exit matcher is triggering.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,11 +259,8 @@ fn _text_markup_string<'r, 's, 'x>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
pub fn pre<'r, 's>(_context: Context<'r, 's>, input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
let document_root = context.get_document_root().unwrap();
|
let preceding_character = input.get_preceding_character();
|
||||||
let preceding_character = get_one_before(document_root, input)
|
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten();
|
|
||||||
match preceding_character {
|
match preceding_character {
|
||||||
// If None, we are at the start of the file which is technically the beginning of a line.
|
// If None, we are at the start of the file which is technically the beginning of a line.
|
||||||
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
|
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
|
||||||
@@ -213,7 +268,7 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
|
|||||||
Some(_) => {
|
Some(_) => {
|
||||||
// Not at start of line, cannot be a heading
|
// Not at start of line, cannot be a heading
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not a valid pre character for text markup.",
|
"Not a valid pre character for text markup.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -221,25 +276,27 @@ pub fn pre<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()>
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[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: &'s str) -> Res<&'s str, ()> {
|
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, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_markup_end(
|
fn text_markup_end(
|
||||||
marker_symbol: &str,
|
marker_symbol: &str,
|
||||||
) -> impl for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str> {
|
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let marker_symbol = marker_symbol.to_owned();
|
let marker_symbol = marker_symbol.to_owned();
|
||||||
move |context: Context, input: &str| _text_markup_end(context, input, marker_symbol.as_str())
|
move |context: Context, input: OrgSource<'_>| {
|
||||||
|
_text_markup_end(context, input, marker_symbol.as_str())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _text_markup_end<'r, 's, 'x>(
|
fn _text_markup_end<'r, 's, 'x>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'x str,
|
marker_symbol: &'x str,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
not(parser_with_context!(preceded_by_whitespace)(context))(input)?;
|
not(preceded_by_whitespace)(input)?;
|
||||||
let (remaining, _marker) = terminated(
|
let (remaining, _marker) = terminated(
|
||||||
tag(marker_symbol),
|
tag(marker_symbol),
|
||||||
peek(parser_with_context!(post)(context)),
|
peek(parser_with_context!(post)(context)),
|
||||||
@@ -253,29 +310,35 @@ impl<'x> RematchObject<'x> for Bold<'x> {
|
|||||||
fn rematch_object<'r, 's>(
|
fn rematch_object<'r, 's>(
|
||||||
&'x self,
|
&'x self,
|
||||||
_context: Context<'r, 's>,
|
_context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, children) =
|
let (remaining, children) =
|
||||||
_rematch_text_markup_object(_context, input, "*", &self.children)?;
|
_rematch_text_markup_object(_context, input, "*", &self.children)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Object::Bold(Bold { source, children })))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Object::Bold(Bold {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _rematch_text_markup_object<'r, 's, 'x>(
|
fn _rematch_text_markup_object<'r, 's, 'x>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'static str,
|
marker_symbol: &'static str,
|
||||||
original_match_children: &'x Vec<Object<'x>>,
|
original_match_children: &'x Vec<Object<'x>>,
|
||||||
) -> Res<&'s str, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
||||||
let text_markup_end_specialized = text_markup_end(open);
|
let text_markup_end_specialized = text_markup_end(open.into());
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &text_markup_end_specialized,
|
exit_matcher: &text_markup_end_specialized,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -290,7 +353,7 @@ fn _rematch_text_markup_object<'r, 's, 'x>(
|
|||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
if exit_matcher_parser(context, remaining).is_ok() {
|
if exit_matcher_parser(context, remaining).is_ok() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Parent exit matcher is triggering.",
|
"Parent exit matcher is triggering.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use nom::combinator::verify;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::exiting::ExitClass;
|
use crate::parser::exiting::ExitClass;
|
||||||
@@ -23,7 +24,10 @@ use crate::parser::util::get_consumed;
|
|||||||
use crate::parser::Timestamp;
|
use crate::parser::Timestamp;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Timestamp<'s>> {
|
pub fn timestamp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
// TODO: This would be more efficient if we didn't throw away the parse result of the first half of an active/inactive date range timestamp if the parse fails (as in, the first thing active_date_range_timestamp parses is a active_timestamp but then we throw that away if it doesn't turn out to be a full active_date_range_timestamp despite the active_timestamp parse being completely valid). I am going with the simplest/cleanest approach for the first implementation.
|
// TODO: This would be more efficient if we didn't throw away the parse result of the first half of an active/inactive date range timestamp if the parse fails (as in, the first thing active_date_range_timestamp parses is a active_timestamp but then we throw that away if it doesn't turn out to be a full active_date_range_timestamp despite the active_timestamp parse being completely valid). I am going with the simplest/cleanest approach for the first implementation.
|
||||||
alt((
|
alt((
|
||||||
// Order matters here. If its a date range, we need to parse the entire date range instead of just the first timestamp. If its a time range, we need to make sure thats parsed as a time range instead of as the "rest" portion of a single timestamp.
|
// Order matters here. If its a date range, we need to parse the entire date range instead of just the first timestamp. If its a time range, we need to make sure thats parsed as a time range instead of as the "rest" portion of a single timestamp.
|
||||||
@@ -40,22 +44,30 @@ pub fn timestamp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s st
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn diary_timestamp<'r, 's>(
|
fn diary_timestamp<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'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, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Timestamp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Timestamp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn sexp<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &sexp_end,
|
exit_matcher: &sexp_end,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -71,20 +83,23 @@ fn sexp<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn sexp_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn sexp_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((tag(")>"), recognize(one_of(">\n"))))(input)
|
alt((tag(")>"), recognize(one_of(">\n"))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn active_timestamp<'r, 's>(
|
fn active_timestamp<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("<")(input)?;
|
let (remaining, _) = tag("<")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
let time_context =
|
let time_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &active_time_rest_end,
|
exit_matcher: &active_time_rest_end,
|
||||||
}));
|
}));
|
||||||
let (remaining, _time) =
|
let (remaining, _time) =
|
||||||
@@ -100,19 +115,24 @@ fn active_timestamp<'r, 's>(
|
|||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Timestamp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Timestamp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn inactive_timestamp<'r, 's>(
|
fn inactive_timestamp<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
let time_context =
|
let time_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &inactive_time_rest_end,
|
exit_matcher: &inactive_time_rest_end,
|
||||||
}));
|
}));
|
||||||
let (remaining, _time) =
|
let (remaining, _time) =
|
||||||
@@ -128,14 +148,19 @@ fn inactive_timestamp<'r, 's>(
|
|||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Timestamp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Timestamp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn active_date_range_timestamp<'r, 's>(
|
fn active_date_range_timestamp<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _first_timestamp) = active_timestamp(context, input)?;
|
let (remaining, _first_timestamp) = active_timestamp(context, input)?;
|
||||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||||
let (remaining, _separator) = tag("--")(remaining)?;
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
@@ -144,24 +169,29 @@ fn active_date_range_timestamp<'r, 's>(
|
|||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Timestamp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Timestamp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn active_time_range_timestamp<'r, 's>(
|
fn active_time_range_timestamp<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("<")(input)?;
|
let (remaining, _) = tag("<")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
let time_context =
|
let time_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &active_time_rest_end,
|
exit_matcher: &active_time_rest_end,
|
||||||
}));
|
}));
|
||||||
let first_time_context =
|
let first_time_context =
|
||||||
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &time_range_rest_end,
|
exit_matcher: &time_range_rest_end,
|
||||||
}));
|
}));
|
||||||
let (remaining, _first_time) =
|
let (remaining, _first_time) =
|
||||||
@@ -179,14 +209,19 @@ fn active_time_range_timestamp<'r, 's>(
|
|||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Timestamp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Timestamp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn inactive_date_range_timestamp<'r, 's>(
|
fn inactive_date_range_timestamp<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _first_timestamp) = inactive_timestamp(context, input)?;
|
let (remaining, _first_timestamp) = inactive_timestamp(context, input)?;
|
||||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||||
let (remaining, _separator) = tag("--")(remaining)?;
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
@@ -195,24 +230,29 @@ fn inactive_date_range_timestamp<'r, 's>(
|
|||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Timestamp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Timestamp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn inactive_time_range_timestamp<'r, 's>(
|
fn inactive_time_range_timestamp<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, _date) = date(context, remaining)?;
|
||||||
let time_context =
|
let time_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &inactive_time_rest_end,
|
exit_matcher: &inactive_time_rest_end,
|
||||||
}));
|
}));
|
||||||
let first_time_context =
|
let first_time_context =
|
||||||
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
time_context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &time_range_rest_end,
|
exit_matcher: &time_range_rest_end,
|
||||||
}));
|
}));
|
||||||
let (remaining, _first_time) =
|
let (remaining, _first_time) =
|
||||||
@@ -230,17 +270,26 @@ fn inactive_time_range_timestamp<'r, 's>(
|
|||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, _) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((remaining, Timestamp { source }))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Timestamp {
|
||||||
|
source: source.into(),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn date<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn date<'r, 's>(
|
||||||
let (remaining, _year) = verify(digit1, |year: &str| year.len() == 4)(input)?;
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let (remaining, _year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
|
||||||
let (remaining, _) = tag("-")(remaining)?;
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
let (remaining, _month) = verify(digit1, |month: &str| month.len() == 2)(remaining)?;
|
let (remaining, _month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
|
||||||
let (remaining, _) = tag("-")(remaining)?;
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
let (remaining, _day_of_month) =
|
let (remaining, _day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
|
||||||
verify(digit1, |day_of_month: &str| day_of_month.len() == 2)(remaining)?;
|
day_of_month.len() == 2
|
||||||
|
})(remaining)?;
|
||||||
let (remaining, _dayname) =
|
let (remaining, _dayname) =
|
||||||
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
|
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -248,10 +297,13 @@ fn date<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s st
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn dayname<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn dayname<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Beta,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &dayname_end,
|
exit_matcher: &dayname_end,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -267,25 +319,36 @@ fn dayname<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn dayname_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn dayname_end<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(verify(anychar, |c| {
|
recognize(verify(anychar, |c| {
|
||||||
c.is_whitespace() || "+-]>0123456789\n".contains(*c)
|
c.is_whitespace() || "+-]>0123456789\n".contains(*c)
|
||||||
}))(input)
|
}))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn time<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn time<'r, 's>(
|
||||||
let (remaining, _hour) =
|
context: Context<'r, 's>,
|
||||||
verify(digit1, |hour: &str| hour.len() >= 1 && hour.len() <= 2)(input)?;
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let (remaining, _hour) = verify(digit1, |hour: &OrgSource<'_>| {
|
||||||
|
hour.len() >= 1 && hour.len() <= 2
|
||||||
|
})(input)?;
|
||||||
let (remaining, _) = tag(":")(remaining)?;
|
let (remaining, _) = tag(":")(remaining)?;
|
||||||
let (remaining, _minute) = verify(digit1, |minute: &str| minute.len() == 2)(remaining)?;
|
let (remaining, _minute) =
|
||||||
|
verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?;
|
||||||
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
|
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn time_rest<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn time_rest<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, body) = recognize(verify(
|
let (remaining, body) = recognize(verify(
|
||||||
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|
||||||
|(body, _end_contents)| !body.is_empty(),
|
|(body, _end_contents)| !body.is_empty(),
|
||||||
@@ -295,7 +358,10 @@ fn time_rest<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn active_time_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn active_time_rest_end<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
alt((
|
||||||
recognize(verify(anychar, |c| ">\n".contains(*c))),
|
recognize(verify(anychar, |c| ">\n".contains(*c))),
|
||||||
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
|
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
|
||||||
@@ -309,8 +375,8 @@ fn active_time_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn inactive_time_rest_end<'r, 's>(
|
fn inactive_time_rest_end<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
alt((
|
||||||
recognize(verify(anychar, |c| "]\n".contains(*c))),
|
recognize(verify(anychar, |c| "]\n".contains(*c))),
|
||||||
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
|
recognize(tuple((space1, parser_with_context!(repeater)(context)))),
|
||||||
@@ -322,7 +388,10 @@ fn inactive_time_rest_end<'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn time_range_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn time_range_rest_end<'r, 's>(
|
||||||
|
context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
|
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
|
||||||
let parent_node = context.iter().next().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
|
let parent_node = context.iter().next().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
|
||||||
let parent_tree = ContextTree::branch_from(parent_node);
|
let parent_tree = ContextTree::branch_from(parent_node);
|
||||||
@@ -332,7 +401,10 @@ fn time_range_rest_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn repeater<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn repeater<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// + for cumulative type
|
// + for cumulative type
|
||||||
// ++ for catch-up type
|
// ++ for catch-up type
|
||||||
// .+ for restart type
|
// .+ for restart type
|
||||||
@@ -345,7 +417,10 @@ fn repeater<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn warning_delay<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
fn warning_delay<'r, 's>(
|
||||||
|
_context: Context<'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// - for all type
|
// - for all type
|
||||||
// -- for first type
|
// -- for first type
|
||||||
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
|
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
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::multispace0;
|
|
||||||
use nom::character::complete::none_of;
|
use nom::character::complete::none_of;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
@@ -14,6 +13,7 @@ use nom::multi::many0;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::OrgSource;
|
||||||
use super::parser_context::ContextElement;
|
use super::parser_context::ContextElement;
|
||||||
use super::Context;
|
use super::Context;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
@@ -48,90 +48,34 @@ pub fn immediate_in_section<'r, 's, 'x>(context: Context<'r, 's>, section_name:
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get one character from before the current position.
|
|
||||||
pub fn get_one_before<'s>(document: &'s str, current_position: &'s str) -> Option<&'s str> {
|
|
||||||
assert!(is_slice_of(document, current_position));
|
|
||||||
if document.as_ptr() as usize == current_position.as_ptr() as usize {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let offset = current_position.as_ptr() as usize - document.as_ptr() as usize;
|
|
||||||
let previous_character_offset = document.floor_char_boundary(offset - 1);
|
|
||||||
Some(&document[previous_character_offset..offset])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the line current_position is on up until current_position
|
|
||||||
pub fn get_current_line_before_position<'s>(
|
|
||||||
document: &'s str,
|
|
||||||
current_position: &'s str,
|
|
||||||
) -> Option<&'s str> {
|
|
||||||
assert!(is_slice_of(document, current_position));
|
|
||||||
if document.as_ptr() as usize == current_position.as_ptr() as usize {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let offset = current_position.as_ptr() as usize - document.as_ptr() as usize;
|
|
||||||
let mut previous_character_offset = offset;
|
|
||||||
loop {
|
|
||||||
let new_offset = document.floor_char_boundary(previous_character_offset - 1);
|
|
||||||
let new_line = &document[new_offset..offset];
|
|
||||||
let leading_char = new_line
|
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.expect("Impossible to not have at least 1 character to read.");
|
|
||||||
if "\r\n".contains(leading_char) || new_offset == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
previous_character_offset = new_offset;
|
|
||||||
}
|
|
||||||
Some(&document[previous_character_offset..offset])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the child string slice is a slice of the parent string slice.
|
|
||||||
fn is_slice_of(parent: &str, child: &str) -> bool {
|
|
||||||
let parent_start = parent.as_ptr() as usize;
|
|
||||||
let parent_end = parent_start + parent.len();
|
|
||||||
let child_start = child.as_ptr() as usize;
|
|
||||||
let child_end = child_start + child.len();
|
|
||||||
child_start >= parent_start && child_end <= parent_end
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
||||||
pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> {
|
||||||
assert!(is_slice_of(input, remaining));
|
input.get_until(remaining)
|
||||||
let source = {
|
|
||||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
|
||||||
&input[..offset]
|
|
||||||
};
|
|
||||||
source
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A line containing only whitespace and then a line break
|
/// A line containing only whitespace and then a line break
|
||||||
///
|
///
|
||||||
/// It is up to the caller to ensure this is called at the start of a line.
|
/// It is up to the caller to ensure this is called at the start of a line.
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn blank_line(input: &str) -> Res<&str, &str> {
|
pub fn blank_line(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> {
|
||||||
not(eof)(input)?;
|
not(eof)(input)?;
|
||||||
recognize(tuple((space0, alt((line_ending, eof)))))(input)
|
recognize(tuple((space0, alt((line_ending, eof)))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn element_trailing_whitespace<'r, 's>(
|
pub fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
context: Context<'r, 's>,
|
start_of_line(input)?;
|
||||||
input: &'s str,
|
|
||||||
) -> Res<&'s str, &'s str> {
|
|
||||||
start_of_line(context, input)?;
|
|
||||||
alt((eof, recognize(many0(blank_line))))(input)
|
alt((eof, recognize(many0(blank_line))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Option<&'s str>> {
|
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
|
||||||
if context.should_consume_trailing_whitespace() && exit_matcher_parser(context, input).is_err()
|
if context.should_consume_trailing_whitespace() && exit_matcher_parser(context, input).is_err()
|
||||||
{
|
{
|
||||||
Ok(opt(parser_with_context!(element_trailing_whitespace)(
|
Ok(opt(element_trailing_whitespace)(input)?)
|
||||||
context,
|
|
||||||
))(input)?)
|
|
||||||
} else {
|
} else {
|
||||||
Ok((input, None))
|
Ok((input, None))
|
||||||
}
|
}
|
||||||
@@ -140,59 +84,37 @@ pub fn maybe_consume_trailing_whitespace_if_not_exiting<'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn maybe_consume_trailing_whitespace<'r, 's>(
|
pub fn maybe_consume_trailing_whitespace<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, Option<&'s str>> {
|
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
|
||||||
if context.should_consume_trailing_whitespace() {
|
if context.should_consume_trailing_whitespace() {
|
||||||
Ok(opt(parser_with_context!(element_trailing_whitespace)(
|
Ok(opt(element_trailing_whitespace)(input)?)
|
||||||
context,
|
|
||||||
))(input)?)
|
|
||||||
} else {
|
} else {
|
||||||
Ok((input, None))
|
Ok((input, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
pub fn trailing_whitespace(input: &str) -> Res<&str, &str> {
|
|
||||||
alt((eof, recognize(tuple((line_ending, many0(blank_line))))))(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that we are at the start of a line
|
/// Check that we are at the start of a line
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn start_of_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, ()> {
|
pub fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
let document_root = context.get_document_root().unwrap();
|
if input.is_at_start_of_line() {
|
||||||
let preceding_character = get_one_before(document_root, input)
|
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten();
|
|
||||||
match preceding_character {
|
|
||||||
Some('\n') => {}
|
|
||||||
Some(_) => {
|
|
||||||
// Not at start of line, cannot be a heading
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"Not at start of line",
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
// If None, we are at the start of the file which allows for headings
|
|
||||||
None => {}
|
|
||||||
};
|
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
|
} else {
|
||||||
|
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Not at start of line".into(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check that we are at the start of a line
|
/// Check that we are at the start of a line
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn preceded_by_whitespace<'r, 's>(
|
pub fn preceded_by_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||||
context: Context<'r, 's>,
|
let preceding_character = input.get_preceding_character();
|
||||||
input: &'s str,
|
|
||||||
) -> Res<&'s str, ()> {
|
|
||||||
let document_root = context.get_document_root().unwrap();
|
|
||||||
let preceding_character = get_one_before(document_root, input)
|
|
||||||
.map(|slice| slice.chars().next())
|
|
||||||
.flatten();
|
|
||||||
match preceding_character {
|
match preceding_character {
|
||||||
Some('\n') | Some('\r') | Some(' ') | Some('\t') => {}
|
Some('\n') | Some('\r') | Some(' ') | Some('\t') => {}
|
||||||
// If None, we are at the start of the file which is not allowed
|
// If None, we are at the start of the file which is not allowed
|
||||||
None | Some(_) => {
|
None | Some(_) => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not preceded by whitespace.",
|
"Not preceded by whitespace.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -203,7 +125,7 @@ pub fn preceded_by_whitespace<'r, 's>(
|
|||||||
///
|
///
|
||||||
/// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace.
|
/// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace.
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn non_whitespace_character(input: &str) -> Res<&str, char> {
|
pub fn non_whitespace_character(input: OrgSource<'_>) -> Res<OrgSource<'_>, char> {
|
||||||
none_of(" \t\r\n")(input)
|
none_of(" \t\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,25 +133,16 @@ pub fn non_whitespace_character(input: &str) -> Res<&str, char> {
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn exit_matcher_parser<'r, 's>(
|
pub fn exit_matcher_parser<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: &'s str,
|
input: OrgSource<'s>,
|
||||||
) -> Res<&'s str, &'s str> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
peek(|i| context.check_exit_matcher(i))(input)
|
peek(|i| context.check_exit_matcher(i))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub fn always_fail<'r, 's>(_context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
pub fn text_until_exit<'r, 's>(
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
context: Context<'r, 's>,
|
||||||
"Always fail",
|
input: OrgSource<'s>,
|
||||||
))))
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
pub fn whitespace_eof(input: &str) -> Res<&str, &str> {
|
|
||||||
recognize(tuple((multispace0, eof)))(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
pub fn text_until_exit<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
|
|
||||||
recognize(verify(
|
recognize(verify(
|
||||||
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)),
|
||||||
|(children, _exit_contents)| !children.is_empty(),
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
@@ -237,23 +150,21 @@ pub fn text_until_exit<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn not_yet_implemented() -> Res<&'static str, ()> {
|
pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not implemented yet.",
|
"Not implemented yet.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[allow(dead_code)]
|
||||||
mod tests {
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
use super::*;
|
/// Text from the current point until the next line break or end of file
|
||||||
|
///
|
||||||
#[test]
|
/// Useful for debugging.
|
||||||
fn get_one_before_unicode() {
|
pub fn text_until_eol<'r, 's>(
|
||||||
let input = "🧡💛💚💙💜";
|
input: OrgSource<'s>,
|
||||||
let (green_heart_index, _) = input.char_indices().skip(2).next().unwrap();
|
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
|
||||||
let starting_with_green_heart = &input[green_heart_index..];
|
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
|
||||||
let yellow_heart = get_one_before(input, starting_with_green_heart).unwrap();
|
.map(|(_remaining, line)| Into::<&str>::into(line))?;
|
||||||
assert!(is_slice_of(input, yellow_heart));
|
Ok(line.trim())
|
||||||
assert_eq!(yellow_heart, "💛");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
|
#[cfg(feature = "compare")]
|
||||||
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
||||||
|
|||||||
@@ -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, "");
|
||||||
|
|||||||
Reference in New Issue
Block a user