Compare commits
248 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
320b5f8568 | ||
|
|
99b2af6c99 | ||
|
|
6e71acdb7d | ||
|
|
8406d37991 | ||
|
|
64bb597908 | ||
|
|
068864ea87 | ||
|
|
03a3ddbd63 | ||
|
|
122adee23b | ||
|
|
556afecbb8 | ||
|
|
e4407cbdd1 | ||
|
|
f57d60dab0 | ||
|
|
0aa3939a75 | ||
|
|
52cb81e75e | ||
|
|
945121202d | ||
|
|
f4e0dddd9d | ||
|
|
6b62176fd0 | ||
|
|
44483b4d54 | ||
|
|
48d3de77fe | ||
|
|
680b176501 | ||
|
|
dc0338e978 | ||
|
|
ff3e0a50af | ||
|
|
03c8c07fe0 | ||
|
|
3a6fc5b669 | ||
|
|
d258cdb839 | ||
|
|
aa5629354e | ||
|
|
efc4a04829 | ||
|
|
dd611ea64a | ||
|
|
4bd5f3bec7 | ||
|
|
c2b3509b6a | ||
|
|
7f3f5fb889 | ||
|
|
e0fbf17226 | ||
|
|
4e18cbafba | ||
|
|
46c36d7f3e | ||
|
|
c46a935cfc | ||
|
|
f50415cb32 | ||
|
|
4f1a151e97 | ||
|
|
c8e3fdba51 | ||
|
|
4b3fc20c62 | ||
|
|
3131f8ac64 | ||
|
|
60a4835590 | ||
|
|
172d72aa46 | ||
|
|
b4fcc6500b | ||
|
|
ddb6f31562 | ||
|
|
dc080b30fc | ||
|
|
9901e17437 | ||
|
|
ea000894f0 | ||
|
|
e7742b529a | ||
|
|
8eba0c4923 | ||
|
|
e0c0070a13 | ||
|
|
65ce116998 | ||
|
|
e348e7d4e3 | ||
|
|
492090470c | ||
|
|
3ec900c8df | ||
|
|
d0a008ed22 | ||
|
|
f2292f1c07 | ||
|
|
44392cfcca | ||
|
|
110630d230 | ||
|
|
ebe12d96c1 | ||
|
|
24c8ac8e21 | ||
|
|
259ad6e242 | ||
|
|
dd1f7c7777 | ||
|
|
c1b471208d | ||
|
|
606bab9e6d | ||
|
|
0edf5620a2 | ||
|
|
cdf87641c5 | ||
|
|
eb2995dd3b | ||
|
|
cd6a64c015 | ||
|
|
a4a83d047d | ||
|
|
a4414369ce | ||
|
|
83e4b72307 | ||
|
|
34b3e4fa7b | ||
|
|
c0e879dc1e | ||
|
|
fa31b001f4 | ||
|
|
0897061ff6 | ||
|
|
28a3e1bc7b | ||
|
|
3fd3d20722 | ||
|
|
90735586b5 | ||
|
|
78befc7665 | ||
|
|
ef549d3b19 | ||
|
|
777c756a7f | ||
|
|
037caf369c | ||
|
|
54085b5833 | ||
|
|
2bfa8e59e7 | ||
|
|
5d31db39a4 | ||
|
|
adcd0de7e4 | ||
|
|
c2f9789a64 | ||
|
|
579cbb5d11 | ||
|
|
cad2be43bf | ||
|
|
a0a4f0eb90 | ||
|
|
9f4f8e79ce | ||
|
|
77e0dbb42e | ||
|
|
eff5cdbf40 | ||
|
|
eef3571299 | ||
|
|
f227d8405e | ||
|
|
9520e5814b | ||
|
|
28ad4fd046 | ||
|
|
7626a69fa1 | ||
|
|
121c0ce516 | ||
|
|
5a64db98fe | ||
|
|
abfae9c6c0 | ||
|
|
5272e2f1b4 | ||
|
|
90d4b11922 | ||
|
|
d552ef6569 | ||
|
|
f050e9b6a8 | ||
|
|
a5e108bc37 | ||
|
|
58290515b5 | ||
|
|
423f65046e | ||
|
|
badeaf8246 | ||
|
|
d38100581c | ||
|
|
f4eff5ca56 | ||
|
|
5b02c21ebf | ||
|
|
5f1668702a | ||
|
|
1faaeeebf1 | ||
|
|
20a7c89084 | ||
|
|
e83417b243 | ||
|
|
36b80dc093 | ||
|
|
1812b1a56e | ||
|
|
1a70b3d2c0 | ||
|
|
abf066701e | ||
|
|
4984ea4179 | ||
|
|
3cb251ea6c | ||
|
|
4bfea41291 | ||
|
|
99376515ef | ||
|
|
23f4ba4205 | ||
|
|
55ad136283 | ||
|
|
c717541099 | ||
|
|
c2e921c2dc | ||
|
|
e499169f0e | ||
|
|
84c088df67 | ||
|
|
f210f95f99 | ||
|
|
17b81c7c72 | ||
|
|
2911fce7cc | ||
|
|
e622d9fa6b | ||
|
|
8186fbb8b3 | ||
|
|
68ccff74fa | ||
|
|
9a13cb72c6 | ||
|
|
65abaa332f | ||
|
|
67e5829fd9 | ||
|
|
995b41e697 | ||
|
|
eb51bdfe2f | ||
|
|
bbb9ec637a | ||
|
|
dc012b49f5 | ||
|
|
13863a68f7 | ||
|
|
2962f76c81 | ||
|
|
b9b3ef6e74 | ||
|
|
310ab2eab2 | ||
|
|
53320070da | ||
|
|
2d5593681f | ||
|
|
b3f97dbb40 | ||
|
|
a48d76321e | ||
|
|
59222c58b1 | ||
|
|
4d95a7f244 | ||
|
|
5a8159eed7 | ||
|
|
e24fcb9ded | ||
|
|
4b94dc60d2 | ||
|
|
2046603d01 | ||
|
|
30412361e1 | ||
|
|
e846c85188 | ||
|
|
99b74095e6 | ||
|
|
6b802d36bf | ||
|
|
33ca43ca40 | ||
|
|
f5280a3090 | ||
|
|
c28d8ccea4 | ||
|
|
9690545901 | ||
|
|
eba4fb94cf | ||
|
|
565978225a | ||
|
|
cce9ca87fa | ||
|
|
683c523ece | ||
|
|
7a4dc20dc9 | ||
|
|
022dda06eb | ||
|
|
7b88a2d248 | ||
|
|
fce5b92091 | ||
|
|
45a506334c | ||
|
|
e47901a67f | ||
|
|
7430daa768 | ||
|
|
6ce25c8a3b | ||
|
|
7b8fa1eb4a | ||
|
|
ffa5349f25 | ||
|
|
bb472b63cc | ||
|
|
57f566a7a1 | ||
|
|
2181993246 | ||
|
|
60d1ecfa75 | ||
|
|
3962db12a8 | ||
|
|
f192507cd9 | ||
|
|
252be3e001 | ||
|
|
28f12a04f7 | ||
|
|
d6232dc49c | ||
|
|
68a220aa1c | ||
|
|
2e7db0f8bd | ||
|
|
175ff1e6c4 | ||
|
|
0b42139393 | ||
|
|
67a9103b07 | ||
|
|
f141a4e186 | ||
|
|
aba29df34c | ||
|
|
87ce7d7432 | ||
|
|
68dccd54b1 | ||
|
|
4753f4c7c6 | ||
|
|
13c62bf29f | ||
|
|
670209e9fc | ||
|
|
4af0d3141f | ||
|
|
ab281de3c6 | ||
|
|
d556d28f49 | ||
|
|
9cfb2fa052 | ||
|
|
30c03b5529 | ||
|
|
b943f90766 | ||
|
|
0108f5b0b1 | ||
|
|
50145c6cf2 | ||
|
|
4a8607726c | ||
|
|
9bcba4020d | ||
|
|
8fd9ff3848 | ||
|
|
3fb7cb82cd | ||
|
|
e0ec5c115f | ||
|
|
f0868ba3ed | ||
|
|
425bc12353 | ||
|
|
03754be71e | ||
|
|
70002800c2 | ||
|
|
281c35677b | ||
|
|
92d15c3d91 | ||
|
|
b1773ac90e | ||
|
|
645d9abf9c | ||
|
|
d2f2bdf88d | ||
|
|
90ba17b68c | ||
|
|
31406fd520 | ||
|
|
49bc51ba89 | ||
|
|
92592104a4 | ||
|
|
33f4614d28 | ||
|
|
6c197c376a | ||
|
|
bcf1b49db2 | ||
|
|
49f6e70a19 | ||
|
|
31fb815681 | ||
|
|
7dfe24ff98 | ||
|
|
a5627d0cee | ||
|
|
93cfa71df2 | ||
|
|
78320d3265 | ||
|
|
9e908935f8 | ||
|
|
b18a703529 | ||
|
|
ea52dc60be | ||
|
|
f5699ce830 | ||
|
|
10aa0956ee | ||
|
|
816c164996 | ||
|
|
ee201e1336 | ||
|
|
4897952330 | ||
|
|
e1d85c6dc2 | ||
|
|
c420ccd029 | ||
|
|
a880629831 | ||
|
|
5e2dea1f28 | ||
|
|
f47d688be4 | ||
|
|
acfc5e5e68 |
@@ -192,6 +192,54 @@ spec:
|
|||||||
]
|
]
|
||||||
- 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: run-image-wasm
|
||||||
|
taskRef:
|
||||||
|
name: run-docker-image
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
runAfter:
|
||||||
|
- run-image-all
|
||||||
|
params:
|
||||||
|
- name: args
|
||||||
|
value:
|
||||||
|
[
|
||||||
|
"--target",
|
||||||
|
"wasm32-unknown-unknown",
|
||||||
|
"--profile",
|
||||||
|
"wasm",
|
||||||
|
"--bin",
|
||||||
|
"wasm",
|
||||||
|
"--no-default-features",
|
||||||
|
"--features",
|
||||||
|
"wasm",
|
||||||
|
]
|
||||||
|
- name: docker-image
|
||||||
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: run-image-wasm-test
|
||||||
|
taskRef:
|
||||||
|
name: run-docker-image
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
runAfter:
|
||||||
|
- run-image-wasm
|
||||||
|
params:
|
||||||
|
- name: args
|
||||||
|
value:
|
||||||
|
[
|
||||||
|
"--bin",
|
||||||
|
"wasm_test",
|
||||||
|
"--no-default-features",
|
||||||
|
"--features",
|
||||||
|
"wasm_test",
|
||||||
|
]
|
||||||
|
- name: docker-image
|
||||||
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
finally:
|
finally:
|
||||||
- name: report-success
|
- name: report-success
|
||||||
when:
|
when:
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ spec:
|
|||||||
[
|
[
|
||||||
--no-default-features,
|
--no-default-features,
|
||||||
--features,
|
--features,
|
||||||
compare,
|
"compare,wasm_test",
|
||||||
--no-fail-fast,
|
--no-fail-fast,
|
||||||
--lib,
|
--lib,
|
||||||
--test,
|
--test,
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ spec:
|
|||||||
- name: command
|
- name: command
|
||||||
value: ["cargo", "fix"]
|
value: ["cargo", "fix"]
|
||||||
- name: args
|
- name: args
|
||||||
value: ["--allow-dirty"]
|
value: ["--all-targets", "--all-features", "--allow-dirty"]
|
||||||
- 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: commit-changes
|
- name: commit-changes
|
||||||
|
|||||||
25
Cargo.toml
25
Cargo.toml
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "organic"
|
name = "organic"
|
||||||
version = "0.1.12"
|
version = "0.1.15"
|
||||||
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"
|
||||||
@@ -39,17 +39,33 @@ path = "src/lib.rs"
|
|||||||
path = "src/bin_foreign_document_test.rs"
|
path = "src/bin_foreign_document_test.rs"
|
||||||
required-features = ["foreign_document_test"]
|
required-features = ["foreign_document_test"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "wasm"
|
||||||
|
path = "src/bin_wasm.rs"
|
||||||
|
required-features = ["wasm"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
# This bin exists for development purposes only. The real target of this crate is the library.
|
||||||
|
name = "wasm_test"
|
||||||
|
path = "src/bin_wasm_test.rs"
|
||||||
|
required-features = ["wasm_test"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = { version = "0.3.28", optional = true }
|
futures = { version = "0.3.28", optional = true }
|
||||||
|
gloo-utils = "0.2.0"
|
||||||
nom = "7.1.1"
|
nom = "7.1.1"
|
||||||
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
|
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
|
||||||
opentelemetry-otlp = { version = "0.13.0", optional = true }
|
opentelemetry-otlp = { version = "0.13.0", optional = true }
|
||||||
opentelemetry-semantic-conventions = { version = "0.12.0", optional = true }
|
opentelemetry-semantic-conventions = { version = "0.12.0", optional = true }
|
||||||
|
serde = { version = "1.0.193", optional = true, features = ["derive"] }
|
||||||
|
serde-wasm-bindgen = { version = "0.6.3", optional = true }
|
||||||
|
serde_json = { version = "1.0.108", optional = true }
|
||||||
tokio = { version = "1.30.0", optional = true, default-features = false, features = ["rt", "rt-multi-thread"] }
|
tokio = { version = "1.30.0", optional = true, default-features = false, features = ["rt", "rt-multi-thread"] }
|
||||||
tracing = { version = "0.1.37", optional = true }
|
tracing = { version = "0.1.37", optional = true }
|
||||||
tracing-opentelemetry = { version = "0.20.0", optional = true }
|
tracing-opentelemetry = { version = "0.20.0", optional = true }
|
||||||
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
|
||||||
walkdir = { version = "2.3.3", optional = true }
|
walkdir = { version = "2.3.3", optional = true }
|
||||||
|
wasm-bindgen = { version = "0.2.89", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
walkdir = "2.3.3"
|
walkdir = "2.3.3"
|
||||||
@@ -60,6 +76,8 @@ compare = ["tokio/process", "tokio/macros"]
|
|||||||
foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"]
|
foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"]
|
||||||
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"]
|
||||||
event_count = []
|
event_count = []
|
||||||
|
wasm = ["dep:serde", "dep:wasm-bindgen", "dep:serde-wasm-bindgen"]
|
||||||
|
wasm_test = ["wasm", "dep:serde_json", "tokio/process", "tokio/macros"]
|
||||||
|
|
||||||
# Optimized build for any sort of release.
|
# Optimized build for any sort of release.
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
@@ -79,3 +97,8 @@ strip = "symbols"
|
|||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = true
|
lto = true
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
[profile.wasm]
|
||||||
|
inherits = "release"
|
||||||
|
lto = true
|
||||||
|
strip = true
|
||||||
|
|||||||
21
Makefile
21
Makefile
@@ -7,6 +7,7 @@ MAKEFLAGS += --no-builtin-rules
|
|||||||
TESTJOBS := 4
|
TESTJOBS := 4
|
||||||
OS:=$(shell uname -s)
|
OS:=$(shell uname -s)
|
||||||
RELEASEFLAGS :=
|
RELEASEFLAGS :=
|
||||||
|
WASMTARGET := bundler # or web
|
||||||
|
|
||||||
ifeq ($(OS),Linux)
|
ifeq ($(OS),Linux)
|
||||||
TESTJOBS:=$(shell nproc)
|
TESTJOBS:=$(shell nproc)
|
||||||
@@ -29,6 +30,11 @@ build:
|
|||||||
release:
|
release:
|
||||||
> cargo build --release $(RELEASEFLAGS)
|
> cargo build --release $(RELEASEFLAGS)
|
||||||
|
|
||||||
|
.PHONY: wasm
|
||||||
|
wasm:
|
||||||
|
> cargo build --target=wasm32-unknown-unknown --profile wasm --bin wasm --features wasm
|
||||||
|
> wasm-bindgen --target $(WASMTARGET) --out-dir target/wasm32-unknown-unknown/js target/wasm32-unknown-unknown/wasm/wasm.wasm
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
> cargo clean
|
> cargo clean
|
||||||
@@ -45,19 +51,24 @@ dockerclippy:
|
|||||||
clippy:
|
clippy:
|
||||||
> cargo clippy --no-deps --all-targets --all-features -- -D warnings
|
> cargo clippy --no-deps --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
.PHONY: clippyfix
|
|
||||||
clippyfix:
|
|
||||||
> cargo clippy --fix --lib -p organic --all-features
|
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||||
|
|
||||||
|
.PHONY: doc
|
||||||
|
doc:
|
||||||
|
> cargo doc --no-deps --open --lib --release --all-features
|
||||||
|
|
||||||
.PHONY: dockertest
|
.PHONY: dockertest
|
||||||
dockertest:
|
dockertest:
|
||||||
> $(MAKE) -C docker/organic_test
|
> $(MAKE) -C docker/organic_test
|
||||||
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||||
|
|
||||||
|
.PHONY: dockerwasmtest
|
||||||
|
dockerwasmtest:
|
||||||
|
> $(MAKE) -C docker/organic_test
|
||||||
|
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare,wasm_test --no-fail-fast --lib --test test_loader autogen_wasm_ -- --test-threads $(TESTJOBS)
|
||||||
|
|
||||||
.PHONY: buildtest
|
.PHONY: buildtest
|
||||||
buildtest:
|
buildtest:
|
||||||
> cargo build --no-default-features
|
> cargo build --no-default-features
|
||||||
@@ -66,6 +77,8 @@ buildtest:
|
|||||||
> cargo build --no-default-features --features compare,tracing
|
> cargo build --no-default-features --features compare,tracing
|
||||||
> cargo build --no-default-features --features compare,foreign_document_test
|
> cargo build --no-default-features --features compare,foreign_document_test
|
||||||
> cargo build --no-default-features --features compare,tracing,foreign_document_test
|
> cargo build --no-default-features --features compare,tracing,foreign_document_test
|
||||||
|
> cargo build --target wasm32-unknown-unknown --profile wasm --bin wasm --no-default-features --features wasm
|
||||||
|
> cargo build --bin wasm_test --no-default-features --features wasm_test
|
||||||
|
|
||||||
.PHONY: foreign_document_test
|
.PHONY: foreign_document_test
|
||||||
foreign_document_test:
|
foreign_document_test:
|
||||||
|
|||||||
@@ -10,17 +10,16 @@ Currently, Organic parses most documents the same as the official org-mode parse
|
|||||||
|
|
||||||
### Project Goals
|
### Project Goals
|
||||||
- We aim to provide perfect parity with the emacs org-mode parser. In that regard, any document that parses differently between Emacs and Organic is considered a bug.
|
- We aim to provide perfect parity with the emacs org-mode parser. In that regard, any document that parses differently between Emacs and Organic is considered a bug.
|
||||||
- The parser should have minimal dependencies. This should reduce effort w.r.t.: security audits, legal compliance, portability.
|
- The parser should have minimal dependencies.
|
||||||
- The parser should be usable everywhere. In the interest of getting org-mode used in as many places as possible, this parser should be usable by everyone everywhere. This means:
|
- The parser should be usable everywhere. In the interest of getting org used in as many places as possible, this parser should be usable by everyone everywhere. This means:
|
||||||
- It must have a permissive license.
|
- It must have a permissive license.
|
||||||
- We will investigate compiling to WASM. This is an important goal of the project and will definitely happen, but only after the parser has a more stable API.
|
- It compiles to both natively and to wasm.
|
||||||
- We will investigate compiling to a C library for native linking to other code. This is more of a maybe-goal for the project.
|
- We will investigate compiling to a C library for native linking to other code. This is more of a maybe-goal for the project.
|
||||||
### Project Non-Goals
|
### Project Non-Goals
|
||||||
- This project will not include an elisp engine since that would drastically increase the complexity of the code. Any features requiring an elisp engine will not be implemented (for example, Emacs supports embedded eval expressions in documents but this parser will never support that).
|
- This project will not include an elisp engine since that would drastically increase the complexity of the code. Any features requiring an elisp engine will not be implemented (for example, Emacs supports embedded eval expressions in documents but this parser will never support that).
|
||||||
- This project is exclusively an org-mode **parser**. This limits its scope to roughly the output of `(org-element-parse-buffer)`. It will not render org-mode documents in other formats like HTML or LaTeX.
|
- This project is exclusively an org-mode **parser**. This limits its scope to roughly the output of `(org-element-parse-buffer)`. It will not render org-mode documents in other formats like HTML or LaTeX.
|
||||||
### Project Maybe-Goals
|
### Project Maybe-Goals
|
||||||
- table.el support. Currently we support org-mode tables but org-mode also allows table.el tables. So far, their use in org-mode documents seems rather uncommon so this is a low-priority feature.
|
- table.el support. Currently we support org-mode tables but org-mode also allows table.el tables. So far, their use in org-mode documents seems rather uncommon so this is a low-priority feature.
|
||||||
- Document editing support. I do not anticipate any advanced editing features to make editing ergonomic, but it should be relatively easy to be able to parse an org-mode document and serialize it back into org-mode. This would enable cool features to be built on top of the library like auto-formatters. To accomplish this feature, We'd have to capture all of the various separators and whitespace that we are currently simply throwing away. This would add many additional fields to the parsed structs and it would add more noise to the parsers themselves, so I do not want to approach this feature until the parser is more complete since it would make modifications and refactoring more difficult.
|
|
||||||
### Supported Versions
|
### Supported Versions
|
||||||
This project targets the version of Emacs and Org-mode that are built into the [organic-test docker image](docker/organic_test/Dockerfile). This is newer than the version of Org-mode that shipped with Emacs 29.1. The parser itself does not depend on Emacs or Org-mode though, so this only matters for development purposes when running the automated tests that compare against upstream Org-mode.
|
This project targets the version of Emacs and Org-mode that are built into the [organic-test docker image](docker/organic_test/Dockerfile). This is newer than the version of Org-mode that shipped with Emacs 29.1. The parser itself does not depend on Emacs or Org-mode though, so this only matters for development purposes when running the automated tests that compare against upstream Org-mode.
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ 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
|
||||||
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
ENTRYPOINT ["cargo", "build"]
|
ENTRYPOINT ["cargo", "build"]
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ ifdef REMOTE_REPO
|
|||||||
else
|
else
|
||||||
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
||||||
endif
|
endif
|
||||||
docker volume rm cargo-cache
|
docker volume rm rust-cache cargo-cache
|
||||||
|
|
||||||
# NOTE: This target will write to folders underneath the git-root
|
# NOTE: This target will write to folders underneath the git-root
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run: build
|
run: build
|
||||||
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
|
docker run --rm --init -t --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
|
||||||
|
|
||||||
.PHONY: shell
|
.PHONY: shell
|
||||||
shell: build
|
shell: build
|
||||||
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
|
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
|
||||||
|
|||||||
@@ -93,6 +93,12 @@ ARG WORG_PATH=/foreign_documents/worg
|
|||||||
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
|
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
|
||||||
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
|
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
|
||||||
|
|
||||||
|
ARG LITERATE_BUILD_EMACS_VERSION=e3ac1afe1e40af601be7af12c1d13d96308ab209
|
||||||
|
ARG LITERATE_BUILD_EMACS_PATH=/foreign_documents/literate_build_emacs
|
||||||
|
ARG LITERATE_BUILD_EMACS_REPO=https://gitlab.com/spudlyo/orgdemo2.git
|
||||||
|
RUN mkdir -p $LITERATE_BUILD_EMACS_PATH && git -C $LITERATE_BUILD_EMACS_PATH init --initial-branch=main && git -C $LITERATE_BUILD_EMACS_PATH remote add origin $LITERATE_BUILD_EMACS_REPO && git -C $LITERATE_BUILD_EMACS_PATH fetch origin $LITERATE_BUILD_EMACS_VERSION && git -C $LITERATE_BUILD_EMACS_PATH checkout FETCH_HEAD
|
||||||
|
# unused/aws.org contains invalid paths for setupfile which causes both upstream org-mode and Organic to error out.
|
||||||
|
RUN rm $LITERATE_BUILD_EMACS_PATH/unused/aws.org
|
||||||
|
|
||||||
FROM tester as foreign-document-test
|
FROM tester as foreign-document-test
|
||||||
RUN apk add --no-cache bash coreutils
|
RUN apk add --no-cache bash coreutils
|
||||||
@@ -100,6 +106,7 @@ RUN mkdir /foreign_documents
|
|||||||
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
|
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
|
||||||
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
|
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
|
||||||
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
|
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
|
||||||
|
COPY --from=foreign-document-gather /foreign_documents/literate_build_emacs /foreign_documents/literate_build_emacs
|
||||||
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
|
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
|
||||||
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
|
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
|
||||||
ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]
|
ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]
|
||||||
|
|||||||
5
org_mode_samples/affiliated_keyword/empty_caption.org
Normal file
5
org_mode_samples/affiliated_keyword/empty_caption.org
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#+caption:
|
||||||
|
#+caption: *foo*
|
||||||
|
#+caption[bar]:
|
||||||
|
#+begin_src bash
|
||||||
|
#+end_src
|
||||||
0
org_mode_samples/document/empty.org
Normal file
0
org_mode_samples/document/empty.org
Normal file
4
org_mode_samples/document/only_line_breaks.org
Normal file
4
org_mode_samples/document/only_line_breaks.org
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
5
org_mode_samples/document/post_blank.org
Normal file
5
org_mode_samples/document/post_blank.org
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
* foo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,32 @@
|
|||||||
|
* Empty
|
||||||
|
:PROPERTIES:
|
||||||
|
:END:
|
||||||
|
* Single new line
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
|
|
||||||
|
:END:
|
||||||
|
* Single line with spaces
|
||||||
|
:PROPERTIES:
|
||||||
|
|
||||||
|
:END:
|
||||||
|
* Many lines, first line without spaces
|
||||||
|
:PROPERTIES:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:END:
|
||||||
|
* Many lines, first line with spaces
|
||||||
|
:PROPERTIES:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
:END:
|
||||||
|
* Many lines, first line with spaces, later line with spaces
|
||||||
|
:PROPERTIES:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
:END:
|
:END:
|
||||||
|
|||||||
@@ -5,3 +5,5 @@
|
|||||||
#+call: dolar cat(dog)
|
#+call: dolar cat(dog)
|
||||||
|
|
||||||
#+call: (bat)
|
#+call: (bat)
|
||||||
|
|
||||||
|
#+call:
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
: foo
|
||||||
|
:
|
||||||
|
: bar
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
1. foo
|
||||||
|
#+begin_src text
|
||||||
|
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
2. baz
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
foo <<bar>> baz
|
<<FOO>> bar
|
||||||
|
|
||||||
lorem << ipsum >> dolar
|
[[FOO][baz]]
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
* foo
|
||||||
|
|
||||||
|
** bar
|
||||||
|
|
||||||
|
* baz
|
||||||
76
scripts/build_all_feature_flag_combinations.bash
Executable file
76
scripts/build_all_feature_flag_combinations.bash
Executable file
@@ -0,0 +1,76 @@
|
|||||||
|
#!/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:="debug"}
|
||||||
|
|
||||||
|
############## Setup #########################
|
||||||
|
|
||||||
|
function cleanup {
|
||||||
|
for f in "${folders[@]}"; do
|
||||||
|
log "Deleting $f"
|
||||||
|
rm -rf "$f"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
folders=()
|
||||||
|
for sig in EXIT INT QUIT HUP TERM; do
|
||||||
|
trap "set +e; cleanup" "$sig"
|
||||||
|
done
|
||||||
|
|
||||||
|
function die {
|
||||||
|
local status_code="$1"
|
||||||
|
shift
|
||||||
|
(>&2 echo "${@}")
|
||||||
|
exit "$status_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
function log {
|
||||||
|
(>&2 echo "${@}")
|
||||||
|
}
|
||||||
|
|
||||||
|
############## Program #########################
|
||||||
|
|
||||||
|
function main {
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
export CARGO_TARGET_DIR="$1"
|
||||||
|
else
|
||||||
|
local work_directory=$(mktemp -d -t 'organic.XXXXXX')
|
||||||
|
folders+=("$work_directory")
|
||||||
|
export CARGO_TARGET_DIR="$work_directory"
|
||||||
|
fi
|
||||||
|
local features=(compare foreign_document_test tracing event_count wasm wasm_test)
|
||||||
|
ENABLED_FEATURES= for_each_combination "${features[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function for_each_combination {
|
||||||
|
local additional_flags=()
|
||||||
|
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||||
|
PROFILE="debug"
|
||||||
|
else
|
||||||
|
additional_flags+=(--profile "$PROFILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
local flag=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
ENABLED_FEATURES="$ENABLED_FEATURES" for_each_combination "${@}"
|
||||||
|
elif [ -z "$ENABLED_FEATURES" ]; then
|
||||||
|
(cd "$DIR/../" && printf "\n\n\n========== no features ==========\n\n\n" && set -x && cargo build "${additional_flags[@]}" --no-default-features)
|
||||||
|
else
|
||||||
|
(cd "$DIR/../" && printf "\n\n\n========== %s ==========\n\n\n" "${ENABLED_FEATURES:1}" && set -x && cargo build "${additional_flags[@]}" --no-default-features --features "${ENABLED_FEATURES:1}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENABLED_FEATURES="$ENABLED_FEATURES,$flag"
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
ENABLED_FEATURES="$ENABLED_FEATURES" for_each_combination "${@}"
|
||||||
|
else
|
||||||
|
(cd "$DIR/../" && printf "\n\n\n========== %s ==========\n\n\n" "${ENABLED_FEATURES:1}" && set -x && cargo build "${additional_flags[@]}" --no-default-features --features "${ENABLED_FEATURES:1}")
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "${@}"
|
||||||
111
scripts/run_docker_wasm_compare.bash
Executable file
111
scripts/run_docker_wasm_compare.bash
Executable file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
: ${SHELL:="NO"} # or YES to launch a shell instead of running the test
|
||||||
|
: ${TRACE:="NO"} # or YES to send traces to jaeger
|
||||||
|
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
|
||||||
|
: ${NO_COLOR:=""} # Set to anything to disable color output
|
||||||
|
: ${PROFILE:="debug"}
|
||||||
|
|
||||||
|
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||||
|
MAKE=$(command -v gmake || command -v make)
|
||||||
|
|
||||||
|
############## Setup #########################
|
||||||
|
|
||||||
|
function die {
|
||||||
|
local status_code="$1"
|
||||||
|
shift
|
||||||
|
(>&2 echo "${@}")
|
||||||
|
exit "$status_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
function log {
|
||||||
|
(>&2 echo "${@}")
|
||||||
|
}
|
||||||
|
|
||||||
|
############## Program #########################
|
||||||
|
|
||||||
|
function main {
|
||||||
|
build_container
|
||||||
|
launch_container "${@}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_container {
|
||||||
|
$MAKE -C "$DIR/../docker/organic_test"
|
||||||
|
}
|
||||||
|
|
||||||
|
function launch_container {
|
||||||
|
local additional_flags=()
|
||||||
|
local features=(wasm_test)
|
||||||
|
|
||||||
|
if [ "$NO_COLOR" != "" ]; then
|
||||||
|
additional_flags+=(--env "NO_COLOR=$NO_COLOR")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$TRACE" = "YES" ]; then
|
||||||
|
# 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)
|
||||||
|
features+=(tracing)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SHELL" != "YES" ]; then
|
||||||
|
additional_flags+=(--read-only)
|
||||||
|
else
|
||||||
|
additional_flags+=(-t)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$BACKTRACE" = "YES" ]; then
|
||||||
|
additional_flags+=(--env RUST_BACKTRACE=full)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$SHELL" = "YES" ]; then
|
||||||
|
exec docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/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 /bin/sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
local features_joined
|
||||||
|
features_joined=$(IFS=","; echo "${features[*]}")
|
||||||
|
|
||||||
|
local build_flags=()
|
||||||
|
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||||
|
PROFILE="debug"
|
||||||
|
else
|
||||||
|
build_flags+=(--profile "$PROFILE")
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
# If we passed in args, we need to forward them along
|
||||||
|
for path in "${@}"; do
|
||||||
|
local full_path
|
||||||
|
full_path=$($REALPATH "$path")
|
||||||
|
init_script=$(cat <<EOF
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=\$'\n\t'
|
||||||
|
|
||||||
|
cargo build --bin wasm_test --no-default-features --features "$features_joined" ${build_flags[@]}
|
||||||
|
exec /target/${PROFILE}/wasm_test "/input${full_path}"
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/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"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
local current_directory init_script
|
||||||
|
current_directory=$(pwd)
|
||||||
|
init_script=$(cat <<EOF
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=\$'\n\t'
|
||||||
|
|
||||||
|
cargo build --bin wasm_test --no-default-features --features "$features_joined" ${build_flags[@]}
|
||||||
|
cd /input${current_directory}
|
||||||
|
exec /target/${PROFILE}/wasm_test
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/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"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "${@}"
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![feature(exit_status_error)]
|
||||||
#![feature(round_char_boundary)]
|
#![feature(round_char_boundary)]
|
||||||
#![feature(exact_size_is_empty)]
|
#![feature(exact_size_is_empty)]
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|||||||
@@ -53,6 +53,9 @@ async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
|||||||
let layer = layer.chain(compare_group("doomemacs", || {
|
let layer = layer.chain(compare_group("doomemacs", || {
|
||||||
compare_all_org_document("/foreign_documents/doomemacs")
|
compare_all_org_document("/foreign_documents/doomemacs")
|
||||||
}));
|
}));
|
||||||
|
let layer = layer.chain(compare_group("literate_build_emacs", || {
|
||||||
|
compare_all_org_document("/foreign_documents/literate_build_emacs")
|
||||||
|
}));
|
||||||
|
|
||||||
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
|
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
|
||||||
let mut any_failed = false;
|
let mut any_failed = false;
|
||||||
|
|||||||
10
src/bin_wasm.rs
Normal file
10
src/bin_wasm.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn parse_org(org_contents: &str) -> wasm_bindgen::JsValue {
|
||||||
|
organic::wasm_cli::parse_org(org_contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
62
src/bin_wasm_test.rs
Normal file
62
src/bin_wasm_test.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#![feature(exact_size_is_empty)]
|
||||||
|
#![feature(exit_status_error)]
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
use organic::wasm_test::wasm_run_anonymous_compare;
|
||||||
|
use organic::wasm_test::wasm_run_compare_on_file;
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use crate::init_tracing::init_telemetry;
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
use crate::init_tracing::shutdown_telemetry;
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
mod init_tracing;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tracing"))]
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
|
rt.block_on(async {
|
||||||
|
let main_body_result = main_body().await;
|
||||||
|
main_body_result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tracing")]
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
|
rt.block_on(async {
|
||||||
|
init_telemetry()?;
|
||||||
|
let main_body_result = main_body().await;
|
||||||
|
shutdown_telemetry()?;
|
||||||
|
main_body_result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
async fn main_body() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let args = std::env::args().skip(1);
|
||||||
|
if args.is_empty() {
|
||||||
|
let org_contents = read_stdin_to_string()?;
|
||||||
|
if wasm_run_anonymous_compare(org_contents).await? {
|
||||||
|
} else {
|
||||||
|
Err("Diff results do not match.")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
for arg in args {
|
||||||
|
if wasm_run_compare_on_file(arg).await? {
|
||||||
|
} else {
|
||||||
|
Err("Diff results do not match.")?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let mut stdin_contents = String::new();
|
||||||
|
std::io::stdin()
|
||||||
|
.lock()
|
||||||
|
.read_to_string(&mut stdin_contents)?;
|
||||||
|
Ok(stdin_contents)
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::compare::diff::compare_document;
|
use crate::compare::diff::compare_document;
|
||||||
use crate::compare::diff::DiffResult;
|
|
||||||
use crate::compare::parse::emacs_parse_anonymous_org_document;
|
|
||||||
use crate::compare::parse::emacs_parse_file_org_document;
|
|
||||||
use crate::compare::parse::get_emacs_version;
|
|
||||||
use crate::compare::parse::get_org_mode_version;
|
|
||||||
use crate::compare::sexp::sexp;
|
|
||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::LocalFileAccessInterface;
|
use crate::context::LocalFileAccessInterface;
|
||||||
use crate::parser::parse_file_with_settings;
|
use crate::parser::parse_file_with_settings;
|
||||||
use crate::parser::parse_with_settings;
|
use crate::parser::parse_with_settings;
|
||||||
|
use crate::util::cli::emacs_parse_anonymous_org_document;
|
||||||
|
use crate::util::cli::emacs_parse_file_org_document;
|
||||||
|
use crate::util::cli::print_versions;
|
||||||
|
use crate::util::elisp::sexp;
|
||||||
|
use crate::util::terminal::foreground_color;
|
||||||
|
use crate::util::terminal::reset_color;
|
||||||
|
|
||||||
pub async fn run_anonymous_compare<P: AsRef<str>>(
|
pub async fn run_anonymous_compare<P: AsRef<str>>(
|
||||||
org_contents: P,
|
org_contents: P,
|
||||||
@@ -68,8 +68,8 @@ pub async fn run_anonymous_compare_with_settings<'g, 's, P: AsRef<str>>(
|
|||||||
} else if !silent {
|
} else if !silent {
|
||||||
println!(
|
println!(
|
||||||
"{color}Entire document passes.{reset}",
|
"{color}Entire document passes.{reset}",
|
||||||
color = DiffResult::foreground_color(0, 255, 0),
|
color = foreground_color(0, 255, 0),
|
||||||
reset = DiffResult::reset_color(),
|
reset = reset_color(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,19 +121,10 @@ pub async fn run_compare_on_file_with_settings<'g, 's, P: AsRef<Path>>(
|
|||||||
} else if !silent {
|
} else if !silent {
|
||||||
println!(
|
println!(
|
||||||
"{color}Entire document passes.{reset}",
|
"{color}Entire document passes.{reset}",
|
||||||
color = DiffResult::foreground_color(0, 255, 0),
|
color = foreground_color(0, 255, 0),
|
||||||
reset = DiffResult::reset_color(),
|
reset = reset_color(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
eprintln!("Using emacs version: {}", get_emacs_version().await?.trim());
|
|
||||||
eprintln!(
|
|
||||||
"Using org-mode version: {}",
|
|
||||||
get_org_mode_version().await?.trim()
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use std::borrow::Borrow;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -7,8 +9,6 @@ use super::diff::artificial_owned_diff_scope;
|
|||||||
use super::diff::compare_ast_node;
|
use super::diff::compare_ast_node;
|
||||||
use super::diff::DiffEntry;
|
use super::diff::DiffEntry;
|
||||||
use super::diff::DiffStatus;
|
use super::diff::DiffStatus;
|
||||||
use super::sexp::unquote;
|
|
||||||
use super::sexp::Token;
|
|
||||||
use super::util::get_property;
|
use super::util::get_property;
|
||||||
use super::util::get_property_numeric;
|
use super::util::get_property_numeric;
|
||||||
use super::util::get_property_quoted_string;
|
use super::util::get_property_quoted_string;
|
||||||
@@ -18,6 +18,8 @@ use crate::types::CharOffsetInLine;
|
|||||||
use crate::types::LineNumber;
|
use crate::types::LineNumber;
|
||||||
use crate::types::RetainLabels;
|
use crate::types::RetainLabels;
|
||||||
use crate::types::SwitchNumberLines;
|
use crate::types::SwitchNumberLines;
|
||||||
|
use crate::util::elisp::unquote;
|
||||||
|
use crate::util::elisp::Token;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum EmacsField<'s> {
|
pub(crate) enum EmacsField<'s> {
|
||||||
@@ -262,11 +264,11 @@ pub(crate) fn compare_property_set_of_quoted_string<
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|e| e.as_atom())
|
.map(|e| e.as_atom())
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let value: Vec<String> = value
|
let value: Vec<Cow<'_, str>> = value
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(unquote)
|
.map(unquote)
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
let value: BTreeSet<&str> = value.iter().map(|e| e.as_str()).collect();
|
let value: BTreeSet<&str> = value.iter().map(|e| e.borrow()).collect();
|
||||||
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
|
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
|
||||||
if !mismatched.is_empty() {
|
if !mismatched.is_empty() {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
@@ -546,6 +548,21 @@ where
|
|||||||
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
|
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(outer_rust_list.len());
|
||||||
|
|
||||||
for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) {
|
for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) {
|
||||||
|
match (kw_e.as_atom(), kw_r) {
|
||||||
|
(Ok("nil"), (None, mandatory_value)) if mandatory_value.is_empty() => {
|
||||||
|
// If its an empty keyword then it becomes nil in the elisp.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
(Ok("nil"), _) => {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, kw_e, kw_r
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
let kw_e = kw_e.as_list()?;
|
let kw_e = kw_e.as_list()?;
|
||||||
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
|
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
|
||||||
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
|
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
|
||||||
@@ -554,6 +571,17 @@ where
|
|||||||
let mut kw_e = kw_e.iter();
|
let mut kw_e = kw_e.iter();
|
||||||
// First element is a list representing the mandatory value.
|
// First element is a list representing the mandatory value.
|
||||||
if let Some(val_e) = kw_e.next() {
|
if let Some(val_e) = kw_e.next() {
|
||||||
|
match (val_e.as_atom(), kw_r) {
|
||||||
|
(Ok("nil"), (_, mandatory_value)) if mandatory_value.is_empty() => {}
|
||||||
|
(Ok("nil"), _) => {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, kw_e, kw_r
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
let el = val_e.as_list()?;
|
let el = val_e.as_list()?;
|
||||||
if el.len() != kw_r.1.len() {
|
if el.len() != kw_r.1.len() {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
@@ -566,6 +594,8 @@ where
|
|||||||
for (e, r) in el.iter().zip(kw_r.1.iter()) {
|
for (e, r) in el.iter().zip(kw_r.1.iter()) {
|
||||||
child_status.push(compare_ast_node(source, e, r.into())?);
|
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
@@ -653,7 +683,7 @@ pub(crate) fn compare_property_number_lines<
|
|||||||
(Some(number_lines), Some(rust_number_lines)) => {
|
(Some(number_lines), Some(rust_number_lines)) => {
|
||||||
let token_list = number_lines.as_list()?;
|
let token_list = number_lines.as_list()?;
|
||||||
let number_type = token_list
|
let number_type = token_list
|
||||||
.get(0)
|
.first()
|
||||||
.map(Token::as_atom)
|
.map(Token::as_atom)
|
||||||
.map_or(Ok(None), |r| r.map(Some))?
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
.ok_or(":number-lines should have a type.")?;
|
.ok_or(":number-lines should have a type.")?;
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ use super::compare_field::compare_property_retain_labels;
|
|||||||
use super::compare_field::compare_property_set_of_quoted_string;
|
use super::compare_field::compare_property_set_of_quoted_string;
|
||||||
use super::compare_field::compare_property_single_ast_node;
|
use super::compare_field::compare_property_single_ast_node;
|
||||||
use super::compare_field::compare_property_unquoted_atom;
|
use super::compare_field::compare_property_unquoted_atom;
|
||||||
use super::elisp_fact::ElispFact;
|
|
||||||
use super::elisp_fact::GetElispFact;
|
|
||||||
use super::sexp::unquote;
|
|
||||||
use super::sexp::Token;
|
|
||||||
use super::util::affiliated_keywords_names;
|
use super::util::affiliated_keywords_names;
|
||||||
use super::util::assert_no_children;
|
use super::util::assert_no_children;
|
||||||
use super::util::compare_additional_properties;
|
use super::util::compare_additional_properties;
|
||||||
@@ -57,7 +53,6 @@ use crate::types::FixedWidthArea;
|
|||||||
use crate::types::FootnoteDefinition;
|
use crate::types::FootnoteDefinition;
|
||||||
use crate::types::FootnoteReference;
|
use crate::types::FootnoteReference;
|
||||||
use crate::types::FootnoteReferenceType;
|
use crate::types::FootnoteReferenceType;
|
||||||
use crate::types::GetStandardProperties;
|
|
||||||
use crate::types::Heading;
|
use crate::types::Heading;
|
||||||
use crate::types::HorizontalRule;
|
use crate::types::HorizontalRule;
|
||||||
use crate::types::Hour;
|
use crate::types::Hour;
|
||||||
@@ -110,6 +105,12 @@ use crate::types::Verbatim;
|
|||||||
use crate::types::VerseBlock;
|
use crate::types::VerseBlock;
|
||||||
use crate::types::WarningDelayType;
|
use crate::types::WarningDelayType;
|
||||||
use crate::types::Year;
|
use crate::types::Year;
|
||||||
|
use crate::util::elisp::unquote;
|
||||||
|
use crate::util::elisp::Token;
|
||||||
|
use crate::util::elisp_fact::ElispFact;
|
||||||
|
use crate::util::elisp_fact::GetElispFact;
|
||||||
|
use crate::util::terminal::foreground_color;
|
||||||
|
use crate::util::terminal::reset_color;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DiffEntry<'b, 's> {
|
pub enum DiffEntry<'b, 's> {
|
||||||
@@ -128,7 +129,7 @@ pub struct DiffResult<'b, 's> {
|
|||||||
emacs_token: &'b Token<'s>,
|
emacs_token: &'b Token<'s>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum DiffStatus {
|
pub(crate) enum DiffStatus {
|
||||||
Good,
|
Good,
|
||||||
Bad,
|
Bad,
|
||||||
@@ -164,7 +165,7 @@ impl<'b, 's> DiffEntry<'b, 's> {
|
|||||||
|
|
||||||
fn is_immediately_bad(&self) -> bool {
|
fn is_immediately_bad(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad,
|
DiffEntry::DiffResult(diff) => matches!(diff.status, DiffStatus::Bad),
|
||||||
DiffEntry::DiffLayer(_) => false,
|
DiffEntry::DiffLayer(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,21 +202,21 @@ impl<'b, 's> DiffResult<'b, 's> {
|
|||||||
if self.has_bad_children() {
|
if self.has_bad_children() {
|
||||||
format!(
|
format!(
|
||||||
"{color}BADCHILD{reset}",
|
"{color}BADCHILD{reset}",
|
||||||
color = DiffResult::foreground_color(255, 255, 0),
|
color = foreground_color(255, 255, 0),
|
||||||
reset = DiffResult::reset_color(),
|
reset = reset_color(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"{color}GOOD{reset}",
|
"{color}GOOD{reset}",
|
||||||
color = DiffResult::foreground_color(0, 255, 0),
|
color = foreground_color(0, 255, 0),
|
||||||
reset = DiffResult::reset_color(),
|
reset = reset_color(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiffStatus::Bad => format!(
|
DiffStatus::Bad => format!(
|
||||||
"{color}BAD{reset}",
|
"{color}BAD{reset}",
|
||||||
color = DiffResult::foreground_color(255, 0, 0),
|
color = foreground_color(255, 0, 0),
|
||||||
reset = DiffResult::reset_color(),
|
reset = reset_color(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -240,45 +241,6 @@ impl<'b, 's> DiffResult<'b, 's> {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|child| child.is_immediately_bad() || child.has_bad_children())
|
.any(|child| child.is_immediately_bad() || child.has_bad_children())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn foreground_color(red: u8, green: u8, blue: u8) -> String {
|
|
||||||
if DiffResult::should_use_color() {
|
|
||||||
format!(
|
|
||||||
"\x1b[38;2;{red};{green};{blue}m",
|
|
||||||
red = red,
|
|
||||||
green = green,
|
|
||||||
blue = blue
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn background_color(red: u8, green: u8, blue: u8) -> String {
|
|
||||||
if DiffResult::should_use_color() {
|
|
||||||
format!(
|
|
||||||
"\x1b[48;2;{red};{green};{blue}m",
|
|
||||||
red = red,
|
|
||||||
green = green,
|
|
||||||
blue = blue
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn reset_color() -> &'static str {
|
|
||||||
if DiffResult::should_use_color() {
|
|
||||||
"\x1b[0m"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_use_color() -> bool {
|
|
||||||
!std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'b, 's> DiffLayer<'b, 's> {
|
impl<'b, 's> DiffLayer<'b, 's> {
|
||||||
@@ -296,14 +258,14 @@ impl<'b, 's> DiffLayer<'b, 's> {
|
|||||||
let status_text = if self.has_bad_children() {
|
let status_text = if self.has_bad_children() {
|
||||||
format!(
|
format!(
|
||||||
"{color}BADCHILD{reset}",
|
"{color}BADCHILD{reset}",
|
||||||
color = DiffResult::foreground_color(255, 255, 0),
|
color = foreground_color(255, 255, 0),
|
||||||
reset = DiffResult::reset_color(),
|
reset = reset_color(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
format!(
|
format!(
|
||||||
"{color}GOOD{reset}",
|
"{color}GOOD{reset}",
|
||||||
color = DiffResult::foreground_color(0, 255, 0),
|
color = foreground_color(0, 255, 0),
|
||||||
reset = DiffResult::reset_color(),
|
reset = reset_color(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
println!(
|
println!(
|
||||||
@@ -413,7 +375,7 @@ pub(crate) fn compare_ast_node<'b, 's>(
|
|||||||
name: rust.get_elisp_fact().get_elisp_name(),
|
name: rust.get_elisp_fact().get_elisp_name(),
|
||||||
message: Some(e.to_string()),
|
message: Some(e.to_string()),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
rust_source: rust.get_standard_properties().get_source(),
|
rust_source: rust.get_source(),
|
||||||
emacs_token: emacs,
|
emacs_token: emacs,
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
@@ -1576,7 +1538,7 @@ fn compare_example_block<'b, 's>(
|
|||||||
[],
|
[],
|
||||||
(
|
(
|
||||||
EmacsField::Required(":value"),
|
EmacsField::Required(":value"),
|
||||||
|r| Some(r.contents.as_str()),
|
|r| Some(r.get_value()),
|
||||||
compare_property_quoted_string
|
compare_property_quoted_string
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -1654,7 +1616,7 @@ fn compare_export_block<'b, 's>(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
EmacsField::Required(":value"),
|
EmacsField::Required(":value"),
|
||||||
|r| Some(r.contents.as_str()),
|
|r| Some(r.get_value()),
|
||||||
compare_property_quoted_string
|
compare_property_quoted_string
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -1702,7 +1664,7 @@ fn compare_src_block<'b, 's>(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
EmacsField::Required(":value"),
|
EmacsField::Required(":value"),
|
||||||
|r| Some(r.contents.as_str()),
|
|r| Some(r.get_value()),
|
||||||
compare_property_quoted_string
|
compare_property_quoted_string
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -2153,7 +2115,7 @@ fn compare_plain_text<'b, 's>(
|
|||||||
let text = emacs.as_text()?;
|
let text = emacs.as_text()?;
|
||||||
let start_ind: usize = text
|
let start_ind: usize = text
|
||||||
.properties
|
.properties
|
||||||
.get(0)
|
.first()
|
||||||
.expect("Should have start index.")
|
.expect("Should have start index.")
|
||||||
.as_atom()?
|
.as_atom()?
|
||||||
.parse()?;
|
.parse()?;
|
||||||
|
|||||||
@@ -2,10 +2,7 @@
|
|||||||
mod compare;
|
mod compare;
|
||||||
mod compare_field;
|
mod compare_field;
|
||||||
mod diff;
|
mod diff;
|
||||||
mod elisp_fact;
|
|
||||||
mod macros;
|
mod macros;
|
||||||
mod parse;
|
|
||||||
mod sexp;
|
|
||||||
mod util;
|
mod util;
|
||||||
pub use compare::run_anonymous_compare;
|
pub use compare::run_anonymous_compare;
|
||||||
pub use compare::run_anonymous_compare_with_settings;
|
pub use compare::run_anonymous_compare_with_settings;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::compare_field::compare_property_list_of_quoted_string;
|
use super::compare_field::compare_property_list_of_quoted_string;
|
||||||
@@ -7,15 +8,15 @@ use super::compare_field::compare_property_quoted_string;
|
|||||||
use super::compare_field::ComparePropertiesResult;
|
use super::compare_field::ComparePropertiesResult;
|
||||||
use super::diff::DiffEntry;
|
use super::diff::DiffEntry;
|
||||||
use super::diff::DiffStatus;
|
use super::diff::DiffStatus;
|
||||||
use super::elisp_fact::GetElispFact;
|
|
||||||
use super::sexp::Token;
|
|
||||||
use crate::compare::diff::compare_ast_node;
|
use crate::compare::diff::compare_ast_node;
|
||||||
use crate::compare::sexp::unquote;
|
|
||||||
use crate::types::AffiliatedKeywordValue;
|
use crate::types::AffiliatedKeywordValue;
|
||||||
use crate::types::AstNode;
|
use crate::types::AstNode;
|
||||||
use crate::types::GetAffiliatedKeywords;
|
use crate::types::GetAffiliatedKeywords;
|
||||||
use crate::types::GetStandardProperties;
|
|
||||||
use crate::types::StandardProperties;
|
use crate::types::StandardProperties;
|
||||||
|
use crate::util::elisp::get_emacs_standard_properties;
|
||||||
|
use crate::util::elisp::unquote;
|
||||||
|
use crate::util::elisp::Token;
|
||||||
|
use crate::util::elisp_fact::GetElispFact;
|
||||||
|
|
||||||
/// Check if the child string slice is a slice of the parent string slice.
|
/// Check if the child string slice is a slice of the parent string slice.
|
||||||
fn is_slice_of(parent: &str, child: &str) -> bool {
|
fn is_slice_of(parent: &str, child: &str) -> bool {
|
||||||
@@ -29,32 +30,29 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
|
|||||||
/// Get the byte offset into source that the rust object exists at.
|
/// Get the byte offset into source that the rust object exists at.
|
||||||
///
|
///
|
||||||
/// These offsets are zero-based unlike the elisp ones.
|
/// These offsets are zero-based unlike the elisp ones.
|
||||||
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
fn get_rust_byte_offsets(original_document: &str, subset: &str) -> (usize, usize) {
|
||||||
original_document: &'s str,
|
debug_assert!(is_slice_of(original_document, subset));
|
||||||
rust_ast_node: &'b S,
|
let offset = subset.as_ptr() as usize - original_document.as_ptr() as usize;
|
||||||
) -> (usize, usize) {
|
let end = offset + subset.len();
|
||||||
let rust_object_source = rust_ast_node.get_source();
|
|
||||||
debug_assert!(is_slice_of(original_document, rust_object_source));
|
|
||||||
let offset = rust_object_source.as_ptr() as usize - original_document.as_ptr() as usize;
|
|
||||||
let end = offset + rust_object_source.len();
|
|
||||||
(offset, end)
|
(offset, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compare_standard_properties<
|
pub(crate) fn compare_standard_properties<
|
||||||
'b,
|
'b,
|
||||||
's,
|
's,
|
||||||
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized,
|
S: StandardProperties<'s> + GetElispFact<'s> + ?Sized,
|
||||||
>(
|
>(
|
||||||
original_document: &'s str,
|
original_document: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust: &'b S,
|
rust: &'b S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
|
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
|
||||||
assert_bounds(original_document, emacs, rust.get_standard_properties())?;
|
assert_bounds(original_document, emacs, rust)?;
|
||||||
|
assert_post_blank(emacs, rust)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_name<S: AsRef<str>>(
|
fn assert_name<S: AsRef<str>>(
|
||||||
emacs: &Token<'_>,
|
emacs: &Token<'_>,
|
||||||
name: S,
|
name: S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@@ -77,101 +75,75 @@ pub(crate) fn assert_name<S: AsRef<str>>(
|
|||||||
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
|
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
|
||||||
///
|
///
|
||||||
/// This does **not** handle plain text because plain text is a special case.
|
/// This does **not** handle plain text because plain text is a special case.
|
||||||
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||||
original_document: &'s str,
|
original_document: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust: &'b S,
|
rust: &'b S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
||||||
|
|
||||||
|
// Check begin/end
|
||||||
|
{
|
||||||
let (begin, end) = (
|
let (begin, end) = (
|
||||||
standard_properties
|
standard_properties
|
||||||
.begin
|
.begin
|
||||||
.ok_or("Token should have a begin.")?,
|
.ok_or("Token should have a begin.")?,
|
||||||
standard_properties.end.ok_or("Token should have an end.")?,
|
standard_properties.end.ok_or("Token should have an end.")?,
|
||||||
);
|
);
|
||||||
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust); // 0-based
|
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust.get_source()); // 0-based
|
||||||
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
|
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
|
||||||
let rust_end_char_offset =
|
let rust_end_char_offset =
|
||||||
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
|
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
|
||||||
if rust_begin_char_offset != begin || rust_end_char_offset != end {
|
if rust_begin_char_offset != begin || rust_end_char_offset != end {
|
||||||
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
|
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check contents-begin/contents-end
|
||||||
|
{
|
||||||
|
if let Some(rust_contents) = rust.get_contents() {
|
||||||
|
let (begin, end) = (
|
||||||
|
standard_properties
|
||||||
|
.contents_begin
|
||||||
|
.ok_or("Token should have a contents-begin.")?,
|
||||||
|
standard_properties
|
||||||
|
.contents_end
|
||||||
|
.ok_or("Token should have an contents-end.")?,
|
||||||
|
);
|
||||||
|
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust_contents); // 0-based
|
||||||
|
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
|
||||||
|
let rust_end_char_offset =
|
||||||
|
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
|
||||||
|
if rust_begin_char_offset != begin || rust_end_char_offset != end {
|
||||||
|
Err(format!("Rust contents bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs contents bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
|
||||||
|
}
|
||||||
|
} else if standard_properties.contents_begin.is_some()
|
||||||
|
|| standard_properties.contents_end.is_some()
|
||||||
|
{
|
||||||
|
Err(format!("Rust contents is None but emacs contents bounds are ({emacs_begin:?}, {emacs_end:?})", emacs_begin=standard_properties.contents_begin, emacs_end=standard_properties.contents_end))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EmacsStandardProperties {
|
/// Assert that the post blank matches between emacs and organic.
|
||||||
begin: Option<usize>,
|
///
|
||||||
#[allow(dead_code)]
|
/// This does **not** handle plain text because plain text is a special case.
|
||||||
post_affiliated: Option<usize>,
|
fn assert_post_blank<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||||
#[allow(dead_code)]
|
emacs: &'b Token<'s>,
|
||||||
contents_begin: Option<usize>,
|
rust: &'b S,
|
||||||
#[allow(dead_code)]
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
contents_end: Option<usize>,
|
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
||||||
end: Option<usize>,
|
let rust_post_blank = rust.get_post_blank();
|
||||||
#[allow(dead_code)]
|
let emacs_post_blank = standard_properties
|
||||||
post_blank: Option<usize>,
|
.post_blank
|
||||||
}
|
.ok_or("Token should have a post-blank.")?;
|
||||||
|
if rust_post_blank as usize != emacs_post_blank {
|
||||||
|
Err(format!("Rust post-blank {rust_post_blank} does not match emacs post-blank ({emacs_post_blank})", rust_post_blank = rust_post_blank, emacs_post_blank = emacs_post_blank))?;
|
||||||
|
}
|
||||||
|
|
||||||
fn get_emacs_standard_properties(
|
Ok(())
|
||||||
emacs: &Token<'_>,
|
|
||||||
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
|
|
||||||
let children = emacs.as_list()?;
|
|
||||||
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
|
|
||||||
let attributes_map = attributes_child.as_map()?;
|
|
||||||
let standard_properties = attributes_map.get(":standard-properties");
|
|
||||||
Ok(if standard_properties.is_some() {
|
|
||||||
let mut std_props = standard_properties
|
|
||||||
.expect("if statement proves its Some")
|
|
||||||
.as_vector()?
|
|
||||||
.iter();
|
|
||||||
let begin = maybe_token_to_usize(std_props.next())?;
|
|
||||||
let post_affiliated = maybe_token_to_usize(std_props.next())?;
|
|
||||||
let contents_begin = maybe_token_to_usize(std_props.next())?;
|
|
||||||
let contents_end = maybe_token_to_usize(std_props.next())?;
|
|
||||||
let end = maybe_token_to_usize(std_props.next())?;
|
|
||||||
let post_blank = maybe_token_to_usize(std_props.next())?;
|
|
||||||
EmacsStandardProperties {
|
|
||||||
begin,
|
|
||||||
post_affiliated,
|
|
||||||
contents_begin,
|
|
||||||
contents_end,
|
|
||||||
end,
|
|
||||||
post_blank,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let begin = maybe_token_to_usize(attributes_map.get(":begin").copied())?;
|
|
||||||
let end = maybe_token_to_usize(attributes_map.get(":end").copied())?;
|
|
||||||
let contents_begin = maybe_token_to_usize(attributes_map.get(":contents-begin").copied())?;
|
|
||||||
let contents_end = maybe_token_to_usize(attributes_map.get(":contents-end").copied())?;
|
|
||||||
let post_blank = maybe_token_to_usize(attributes_map.get(":post-blank").copied())?;
|
|
||||||
let post_affiliated =
|
|
||||||
maybe_token_to_usize(attributes_map.get(":post-affiliated").copied())?;
|
|
||||||
EmacsStandardProperties {
|
|
||||||
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))?
|
|
||||||
.and_then(|val| {
|
|
||||||
if val == "nil" {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(val.parse::<usize>())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_or(Ok(None), |r| r.map(Some))?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a named property from the emacs token.
|
/// Get a named property from the emacs token.
|
||||||
@@ -206,10 +178,10 @@ pub(crate) fn get_property_unquoted_atom<'s>(
|
|||||||
/// Get a named property containing an quoted string from the emacs token.
|
/// Get a named property containing an quoted string from the emacs token.
|
||||||
///
|
///
|
||||||
/// Returns None if key is not found.
|
/// Returns None if key is not found.
|
||||||
pub(crate) fn get_property_quoted_string(
|
pub(crate) fn get_property_quoted_string<'s>(
|
||||||
emacs: &Token<'_>,
|
emacs: &Token<'s>,
|
||||||
key: &str,
|
key: &str,
|
||||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
) -> Result<Option<Cow<'s, str>>, Box<dyn std::error::Error>> {
|
||||||
get_property(emacs, key)?
|
get_property(emacs, key)?
|
||||||
.map(Token::as_atom)
|
.map(Token::as_atom)
|
||||||
.map_or(Ok(None), |r| r.map(Some))?
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
@@ -240,7 +212,7 @@ where
|
|||||||
pub(crate) fn compare_children<'b, 's, 'x, RC>(
|
pub(crate) fn compare_children<'b, 's, 'x, RC>(
|
||||||
source: &'s str,
|
source: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust_children: &'x Vec<RC>,
|
rust_children: &'x [RC],
|
||||||
child_status: &mut Vec<DiffEntry<'b, 's>>,
|
child_status: &mut Vec<DiffEntry<'b, 's>>,
|
||||||
this_status: &mut DiffStatus,
|
this_status: &mut DiffStatus,
|
||||||
message: &mut Option<String>,
|
message: &mut Option<String>,
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ pub(crate) type Res<T, U> = IResult<T, U, CustomError>;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CustomError {
|
pub enum CustomError {
|
||||||
#[allow(dead_code)]
|
|
||||||
Text(String),
|
|
||||||
Static(&'static str),
|
Static(&'static str),
|
||||||
IO(std::io::Error),
|
IO(std::io::Error),
|
||||||
Parser(ErrorKind),
|
Parser(ErrorKind),
|
||||||
@@ -35,9 +33,3 @@ impl From<&'static str> for CustomError {
|
|||||||
CustomError::Static(value)
|
CustomError::Static(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for CustomError {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
CustomError::Text(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ pub(crate) fn record_event(event_type: EventType, input: OrgSource<'_>) {
|
|||||||
*db.entry(key).or_insert(0) += 1;
|
*db.entry(key).or_insert(0) += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report(original_document: &str) {
|
pub(crate) fn report(original_document: &str) {
|
||||||
let mut db = GLOBAL_DATA.lock().unwrap();
|
let mut db = GLOBAL_DATA.lock().unwrap();
|
||||||
let db = db.get_or_insert_with(HashMap::new);
|
let db = db.get_or_insert_with(HashMap::new);
|
||||||
let mut results: Vec<_> = db.iter().map(|(k, v)| (k, v)).collect();
|
let mut results: Vec<_> = db.iter().collect();
|
||||||
results.sort_by_key(|(_k, v)| *v);
|
results.sort_by_key(|(_k, v)| *v);
|
||||||
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
|
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
|
||||||
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));
|
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ mod database;
|
|||||||
mod event_type;
|
mod event_type;
|
||||||
|
|
||||||
pub(crate) use database::record_event;
|
pub(crate) use database::record_event;
|
||||||
pub use database::report;
|
pub(crate) use database::report;
|
||||||
pub(crate) use event_type::EventType;
|
pub(crate) use event_type::EventType;
|
||||||
|
|||||||
@@ -90,12 +90,11 @@ impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 's> IntoIterator for AstNode<'r, 's> {
|
impl<'r, 's> AstNode<'r, 's> {
|
||||||
type Item = AstNode<'r, 's>;
|
/// Iterate all AST nodes.
|
||||||
|
///
|
||||||
type IntoIter = AllAstNodeIter<'r, 's>;
|
/// This is different from the iter/into_iter functions which iterate a single level of the children. This iterates the entire tree including returning the root node itself.
|
||||||
|
pub fn iter_all_ast_nodes(self) -> AllAstNodeIter<'r, 's> {
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
AllAstNodeIter {
|
AllAstNodeIter {
|
||||||
root: Some(self),
|
root: Some(self),
|
||||||
queue: VecDeque::new(),
|
queue: VecDeque::new(),
|
||||||
|
|||||||
13
src/lib.rs
13
src/lib.rs
@@ -3,6 +3,8 @@
|
|||||||
#![feature(path_file_prefix)]
|
#![feature(path_file_prefix)]
|
||||||
#![feature(is_sorted)]
|
#![feature(is_sorted)]
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
|
#![feature(iter_intersperse)]
|
||||||
|
#![feature(exact_size_is_empty)]
|
||||||
// TODO: #![warn(missing_docs)]
|
// TODO: #![warn(missing_docs)]
|
||||||
#![allow(clippy::bool_assert_comparison)] // Sometimes you want the long form because its easier to see at a glance.
|
#![allow(clippy::bool_assert_comparison)] // Sometimes you want the long form because its easier to see at a glance.
|
||||||
|
|
||||||
@@ -10,11 +12,20 @@ extern crate test;
|
|||||||
|
|
||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
pub mod compare;
|
pub mod compare;
|
||||||
|
pub mod parse_cli;
|
||||||
|
#[cfg(any(feature = "compare", feature = "wasm", feature = "wasm_test"))]
|
||||||
|
mod util;
|
||||||
|
#[cfg(any(feature = "wasm", feature = "wasm_test"))]
|
||||||
|
mod wasm;
|
||||||
|
#[cfg(any(feature = "wasm", feature = "wasm_test"))]
|
||||||
|
pub mod wasm_cli;
|
||||||
|
#[cfg(feature = "wasm_test")]
|
||||||
|
pub mod wasm_test;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
mod error;
|
mod error;
|
||||||
#[cfg(feature = "event_count")]
|
#[cfg(feature = "event_count")]
|
||||||
pub mod event_count;
|
mod event_count;
|
||||||
mod iter;
|
mod iter;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|||||||
62
src/main.rs
62
src/main.rs
@@ -1,12 +1,4 @@
|
|||||||
#![feature(round_char_boundary)]
|
use organic::parse_cli::main_body;
|
||||||
#![feature(exact_size_is_empty)]
|
|
||||||
use std::io::Read;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use ::organic::parser::parse;
|
|
||||||
use organic::parser::parse_with_settings;
|
|
||||||
use organic::settings::GlobalSettings;
|
|
||||||
use organic::settings::LocalFileAccessInterface;
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
use crate::init_tracing::init_telemetry;
|
use crate::init_tracing::init_telemetry;
|
||||||
@@ -30,55 +22,3 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
main_body_result
|
main_body_result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let args = std::env::args().skip(1);
|
|
||||||
if args.is_empty() {
|
|
||||||
let org_contents = read_stdin_to_string()?;
|
|
||||||
run_anonymous_parse(org_contents)
|
|
||||||
} else {
|
|
||||||
for arg in args {
|
|
||||||
run_parse_on_file(arg)?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let mut stdin_contents = String::new();
|
|
||||||
std::io::stdin()
|
|
||||||
.lock()
|
|
||||||
.read_to_string(&mut stdin_contents)?;
|
|
||||||
Ok(stdin_contents)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let org_contents = org_contents.as_ref();
|
|
||||||
let rust_parsed = parse(org_contents)?;
|
|
||||||
println!("{:#?}", rust_parsed);
|
|
||||||
#[cfg(feature = "event_count")]
|
|
||||||
organic::event_count::report(org_contents);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let org_path = org_path.as_ref();
|
|
||||||
let parent_directory = org_path
|
|
||||||
.parent()
|
|
||||||
.ok_or("Should be contained inside a directory.")?;
|
|
||||||
let org_contents = std::fs::read_to_string(org_path)?;
|
|
||||||
let org_contents = org_contents.as_str();
|
|
||||||
let file_access_interface = LocalFileAccessInterface {
|
|
||||||
working_directory: Some(parent_directory.to_path_buf()),
|
|
||||||
};
|
|
||||||
let global_settings = GlobalSettings {
|
|
||||||
file_access: &file_access_interface,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
|
|
||||||
println!("{:#?}", rust_parsed);
|
|
||||||
#[cfg(feature = "event_count")]
|
|
||||||
organic::event_count::report(org_contents);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
59
src/parse_cli/mod.rs
Normal file
59
src/parse_cli/mod.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::parser::parse;
|
||||||
|
use crate::parser::parse_with_settings;
|
||||||
|
use crate::settings::GlobalSettings;
|
||||||
|
use crate::settings::LocalFileAccessInterface;
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
pub fn main_body() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let args = std::env::args().skip(1);
|
||||||
|
if args.is_empty() {
|
||||||
|
let org_contents = read_stdin_to_string()?;
|
||||||
|
run_anonymous_parse(org_contents)
|
||||||
|
} else {
|
||||||
|
for arg in args {
|
||||||
|
run_parse_on_file(arg)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let mut stdin_contents = String::new();
|
||||||
|
std::io::stdin()
|
||||||
|
.lock()
|
||||||
|
.read_to_string(&mut stdin_contents)?;
|
||||||
|
Ok(stdin_contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let org_contents = org_contents.as_ref();
|
||||||
|
let rust_parsed = parse(org_contents)?;
|
||||||
|
println!("{:#?}", rust_parsed);
|
||||||
|
#[cfg(feature = "event_count")]
|
||||||
|
crate::event_count::report(org_contents);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let org_path = org_path.as_ref();
|
||||||
|
let parent_directory = org_path
|
||||||
|
.parent()
|
||||||
|
.ok_or("Should be contained inside a directory.")?;
|
||||||
|
let org_contents = std::fs::read_to_string(org_path)?;
|
||||||
|
let org_contents = org_contents.as_str();
|
||||||
|
let file_access_interface = LocalFileAccessInterface {
|
||||||
|
working_directory: Some(parent_directory.to_path_buf()),
|
||||||
|
};
|
||||||
|
let global_settings = GlobalSettings {
|
||||||
|
file_access: &file_access_interface,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
|
||||||
|
println!("{:#?}", rust_parsed);
|
||||||
|
#[cfg(feature = "event_count")]
|
||||||
|
crate::event_count::report(org_contents);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
|
|||||||
parser_with_context!(parse_angle_link)(context),
|
parser_with_context!(parse_angle_link)(context),
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let (remaining, _) = tag(">")(remaining)?;
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -59,6 +59,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
|
|||||||
raw_link: raw_link.into(),
|
raw_link: raw_link.into(),
|
||||||
search_option: parsed_link.search_option,
|
search_option: parsed_link.search_option,
|
||||||
application: parsed_link.application,
|
application: parsed_link.application,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use nom::bytes::complete::tag_no_case;
|
|||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::combinator::consumed;
|
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
@@ -43,32 +42,10 @@ where
|
|||||||
start_of_line(remaining)?;
|
start_of_line(remaining)?;
|
||||||
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
|
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
|
||||||
|
|
||||||
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
|
|
||||||
let (remaining, _trailing_ws) =
|
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
|
||||||
return Ok((
|
|
||||||
remaining,
|
|
||||||
BabelCall {
|
|
||||||
source: Into::<&str>::into(source),
|
|
||||||
affiliated_keywords: parse_affiliated_keywords(
|
|
||||||
context.get_global_settings(),
|
|
||||||
affiliated_keywords,
|
|
||||||
),
|
|
||||||
value: Into::<&str>::into(line_break.take(0)),
|
|
||||||
call: None,
|
|
||||||
inside_header: None,
|
|
||||||
arguments: None,
|
|
||||||
end_header: None,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (remaining, _ws) = space0(remaining)?;
|
let (remaining, _ws) = space0(remaining)?;
|
||||||
let (remaining, (value, babel_call_value)) = consumed(babel_call_value)(remaining)?;
|
let (remaining, babel_call_value) = babel_call_value(remaining)?;
|
||||||
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -80,17 +57,22 @@ where
|
|||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
value: Into::<&str>::into(value).trim_end(),
|
value: Into::<&str>::into(babel_call_value.value),
|
||||||
call: babel_call_value.call.map(Into::<&str>::into),
|
call: babel_call_value.call.map(Into::<&str>::into),
|
||||||
inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
|
inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
|
||||||
arguments: babel_call_value.arguments.map(Into::<&str>::into),
|
arguments: babel_call_value.arguments.map(Into::<&str>::into),
|
||||||
end_header: babel_call_value.end_header.map(Into::<&str>::into),
|
end_header: babel_call_value.end_header.map(Into::<&str>::into),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct BabelCallValue<'s> {
|
struct BabelCallValue<'s> {
|
||||||
|
/// The entire string to the right of "#+call: " without the trailing line break.
|
||||||
|
value: OrgSource<'s>,
|
||||||
|
|
||||||
|
/// The function name which may contain a line break if there are no headers/arguments.
|
||||||
call: Option<OrgSource<'s>>,
|
call: Option<OrgSource<'s>>,
|
||||||
inside_header: Option<OrgSource<'s>>,
|
inside_header: Option<OrgSource<'s>>,
|
||||||
arguments: Option<OrgSource<'s>>,
|
arguments: Option<OrgSource<'s>>,
|
||||||
@@ -99,13 +81,45 @@ struct BabelCallValue<'s> {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
|
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
|
||||||
let (remaining, call) = opt(babel_call_call)(input)?;
|
alt((
|
||||||
let (remaining, inside_header) = opt(inside_header)(remaining)?;
|
babel_call_value_without_headers,
|
||||||
let (remaining, arguments) = opt(arguments)(remaining)?;
|
babel_call_value_with_headers,
|
||||||
let (remaining, end_header) = opt(end_header)(remaining)?;
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn babel_call_value_without_headers<'s>(
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
|
||||||
|
let (remaining, value) = babel_call_call_with_headers(input)?;
|
||||||
|
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
let call = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
BabelCallValue {
|
BabelCallValue {
|
||||||
|
value,
|
||||||
|
call: Some(call),
|
||||||
|
inside_header: None,
|
||||||
|
arguments: None,
|
||||||
|
end_header: None,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn babel_call_value_with_headers<'s>(
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
|
||||||
|
let (remaining, call) = opt(babel_call_call_with_headers)(input)?;
|
||||||
|
let (remaining, inside_header) = opt(inside_header)(remaining)?;
|
||||||
|
let (remaining, arguments) = opt(arguments)(remaining)?;
|
||||||
|
let (remaining, end_header) = opt(end_header)(remaining)?;
|
||||||
|
let value = get_consumed(input, remaining);
|
||||||
|
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
BabelCallValue {
|
||||||
|
value,
|
||||||
call,
|
call,
|
||||||
inside_header,
|
inside_header,
|
||||||
arguments: arguments.flatten(),
|
arguments: arguments.flatten(),
|
||||||
@@ -115,14 +129,15 @@ fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallVal
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn babel_call_call_with_headers<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
// When babel call contains no arguments or headers (for example: "#+call: lorem ipsum\n") then the trailing line break is part of the call. Otherwise, it is not.
|
||||||
verify(
|
verify(
|
||||||
recognize(many_till(
|
recognize(many_till(
|
||||||
anychar,
|
anychar,
|
||||||
alt((
|
peek(alt((
|
||||||
peek(recognize(one_of("[("))),
|
recognize(one_of("[(")),
|
||||||
recognize(tuple((space0, org_line_ending))),
|
recognize(tuple((space0, org_line_ending))),
|
||||||
)),
|
))),
|
||||||
)),
|
)),
|
||||||
|s| s.len() > 0,
|
|s| s.len() > 0,
|
||||||
)(input)
|
)(input)
|
||||||
@@ -225,26 +240,10 @@ fn impl_balanced_bracket<
|
|||||||
let contents_end = remaining;
|
let contents_end = remaining;
|
||||||
|
|
||||||
let (remaining, _) = end_parser(remaining)?;
|
let (remaining, _) = end_parser(remaining)?;
|
||||||
let contents = if contents_start != contents_end {
|
let contents = if Into::<&str>::into(contents_start) != Into::<&str>::into(contents_end) {
|
||||||
Some(contents_start.get_until(contents_end))
|
Some(contents_start.get_until(contents_end))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
Ok((remaining, contents))
|
Ok((remaining, contents))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use nom::combinator::opt;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let input = OrgSource::new("()");
|
|
||||||
let (remaining, call) = opt(babel_call_call)(input)?;
|
|
||||||
assert_eq!(Into::<&str>::into(remaining), "()");
|
|
||||||
assert_eq!(call, None);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,10 +15,8 @@ use crate::context::RefContext;
|
|||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::macros::element;
|
use crate::parser::macros::element;
|
||||||
use crate::types::AffiliatedKeywords;
|
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
use crate::types::Paragraph;
|
use crate::types::Paragraph;
|
||||||
use crate::types::PlainText;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
@@ -65,6 +63,7 @@ pub(crate) fn broken_end<'b, 'g, 'r, 's>(
|
|||||||
match paragraph.children.first_mut() {
|
match paragraph.children.first_mut() {
|
||||||
Some(Object::PlainText(plain_text)) => {
|
Some(Object::PlainText(plain_text)) => {
|
||||||
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
|
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
|
||||||
|
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
|
||||||
}
|
}
|
||||||
Some(obj) => {
|
Some(obj) => {
|
||||||
panic!("Unhandled first object type inside bullshitium {:?}", obj);
|
panic!("Unhandled first object type inside bullshitium {:?}", obj);
|
||||||
@@ -75,18 +74,19 @@ pub(crate) fn broken_end<'b, 'g, 'r, 's>(
|
|||||||
};
|
};
|
||||||
Ok((remaining, paragraph))
|
Ok((remaining, paragraph))
|
||||||
} else {
|
} else {
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
|
||||||
|
|
||||||
|
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Paragraph {
|
Paragraph::of_text(
|
||||||
source: input.get_until(remaining).into(),
|
input.get_until(remaining).into(),
|
||||||
affiliated_keywords: AffiliatedKeywords::default(),
|
body,
|
||||||
children: vec![Object::PlainText(PlainText {
|
if !body.is_empty() { Some(body) } else { None },
|
||||||
source: input.get_until(lead_in_remaining).into(),
|
post_blank.map(Into::<&str>::into),
|
||||||
})],
|
),
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,6 +124,7 @@ pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
|
|||||||
match paragraph.children.first_mut() {
|
match paragraph.children.first_mut() {
|
||||||
Some(Object::PlainText(plain_text)) => {
|
Some(Object::PlainText(plain_text)) => {
|
||||||
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
|
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
|
||||||
|
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
|
||||||
}
|
}
|
||||||
Some(obj) => {
|
Some(obj) => {
|
||||||
panic!("Unhandled first object type inside bullshitium {:?}", obj);
|
panic!("Unhandled first object type inside bullshitium {:?}", obj);
|
||||||
@@ -134,18 +135,19 @@ pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
|
|||||||
};
|
};
|
||||||
Ok((remaining, paragraph))
|
Ok((remaining, paragraph))
|
||||||
} else {
|
} else {
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
|
||||||
|
|
||||||
|
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Paragraph {
|
Paragraph::of_text(
|
||||||
source: input.get_until(remaining).into(),
|
input.get_until(remaining).into(),
|
||||||
affiliated_keywords: AffiliatedKeywords::default(),
|
body,
|
||||||
children: vec![Object::PlainText(PlainText {
|
if !body.is_empty() { Some(body) } else { None },
|
||||||
source: input.get_until(lead_in_remaining).into(),
|
post_blank.map(Into::<&str>::into),
|
||||||
})],
|
),
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,16 +46,22 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, prefix) =
|
let (remaining, prefix) =
|
||||||
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
|
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
|
||||||
|
|
||||||
|
let contents_begin = 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 contents_end = {
|
||||||
|
let (rem, _) = opt(tag(";"))(remaining)?;
|
||||||
|
rem
|
||||||
|
};
|
||||||
let (remaining, suffix) = must_balance_bracket(opt(map(
|
let (remaining, suffix) = must_balance_bracket(opt(map(
|
||||||
tuple((tag(";"), parser_with_context!(global_suffix)(context))),
|
tuple((tag(";"), parser_with_context!(global_suffix)(context))),
|
||||||
|(_, suffix)| suffix,
|
|(_, suffix)| suffix,
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
let (remaining, _) = tag("]")(remaining)?;
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
let contents = contents_begin.get_until(contents_end);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Citation {
|
Citation {
|
||||||
@@ -64,6 +70,8 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
|
|||||||
prefix: prefix.unwrap_or(Vec::new()),
|
prefix: prefix.unwrap_or(Vec::new()),
|
||||||
suffix: suffix.unwrap_or(Vec::new()),
|
suffix: suffix.unwrap_or(Vec::new()),
|
||||||
children: references,
|
children: references,
|
||||||
|
contents: Into::<&str>::into(contents),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -210,12 +218,11 @@ mod tests {
|
|||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::types::CitationReference;
|
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::GetStandardProperties;
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn citation_simple() {
|
fn citation_simple() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let input = OrgSource::new("[cite:@foo]");
|
let input = OrgSource::new("[cite:@foo]");
|
||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
@@ -227,28 +234,33 @@ mod tests {
|
|||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
|
||||||
first_paragraph.get_standard_properties().get_source(),
|
|
||||||
"[cite:@foo]"
|
|
||||||
);
|
|
||||||
assert_eq!(first_paragraph.children.len(), 1);
|
assert_eq!(first_paragraph.children.len(), 1);
|
||||||
assert_eq!(
|
|
||||||
first_paragraph
|
match first_paragraph
|
||||||
.children
|
.children
|
||||||
.get(0)
|
.first()
|
||||||
.expect("Len already asserted to be 1"),
|
.expect("Len already asserted to be 1.")
|
||||||
&Object::Citation(Citation {
|
{
|
||||||
source: "[cite:@foo]",
|
Object::Citation(inner) => {
|
||||||
style: None,
|
assert_eq!(inner.get_source(), "[cite:@foo]");
|
||||||
prefix: vec![],
|
assert_eq!(inner.children.len(), 1);
|
||||||
suffix: vec![],
|
assert!(inner.prefix.is_empty());
|
||||||
children: vec![CitationReference {
|
assert!(inner.suffix.is_empty());
|
||||||
source: "@foo",
|
assert!(inner.style.is_none());
|
||||||
key: "foo",
|
let citation_reference = inner
|
||||||
prefix: vec![],
|
.children
|
||||||
suffix: vec![]
|
.first()
|
||||||
}]
|
.expect("Len already asserted to be 1.");
|
||||||
})
|
assert_eq!(citation_reference.get_source(), "@foo");
|
||||||
);
|
assert_eq!(citation_reference.key, "foo");
|
||||||
|
assert!(citation_reference.prefix.is_empty());
|
||||||
|
assert!(citation_reference.suffix.is_empty());
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err("Child should be a citation.".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?;
|
let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?;
|
||||||
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -54,6 +54,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
|
|||||||
} else {
|
} else {
|
||||||
ClockStatus::Running
|
ClockStatus::Running
|
||||||
},
|
},
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -81,7 +82,7 @@ fn clock_timestamp<'b, 'g, 'r, 's>(
|
|||||||
|(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)),
|
|(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)),
|
||||||
),
|
),
|
||||||
map(
|
map(
|
||||||
parser_with_context!(inactive_timestamp)(context),
|
parser_with_context!(inactive_timestamp(true))(context),
|
||||||
|timestamp| (timestamp, None),
|
|timestamp| (timestamp, None),
|
||||||
),
|
),
|
||||||
))(input)
|
))(input)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, mut remaining_lines) =
|
let (remaining, mut remaining_lines) =
|
||||||
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
|
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
|
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
|
||||||
@@ -67,6 +67,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
|
|||||||
Comment {
|
Comment {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
value,
|
value,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ where
|
|||||||
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
|
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
|
||||||
let (remaining, _eol) = org_line_ending(remaining)?;
|
let (remaining, _eol) = org_line_ending(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -43,6 +43,7 @@ where
|
|||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
value: Into::<&str>::into(value),
|
value: Into::<&str>::into(value),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::path::Path;
|
|||||||
use nom::combinator::all_consuming;
|
use nom::combinator::all_consuming;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
|
use nom::InputTake;
|
||||||
|
|
||||||
use super::headline::heading;
|
use super::headline::heading;
|
||||||
use super::in_buffer_settings::apply_in_buffer_settings;
|
use super::in_buffer_settings::apply_in_buffer_settings;
|
||||||
@@ -143,7 +144,7 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
|||||||
{
|
{
|
||||||
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
||||||
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
|
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
|
||||||
.into_iter()
|
.iter_all_ast_nodes()
|
||||||
.filter_map(|ast_node| {
|
.filter_map(|ast_node| {
|
||||||
if let AstNode::RadioTarget(ast_node) = ast_node {
|
if let AstNode::RadioTarget(ast_node) = ast_node {
|
||||||
Some(ast_node)
|
Some(ast_node)
|
||||||
@@ -181,8 +182,10 @@ fn _document<'b, 'g, 'r, '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(0))(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 contents_begin = remaining;
|
||||||
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)?;
|
||||||
|
let contents = get_consumed(contents_begin, remaining);
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -192,6 +195,11 @@ fn _document<'b, 'g, 'r, 's>(
|
|||||||
path: None,
|
path: None,
|
||||||
zeroth_section,
|
zeroth_section,
|
||||||
children,
|
children,
|
||||||
|
contents: if contents.len() > 0 {
|
||||||
|
Into::<&str>::into(contents)
|
||||||
|
} else {
|
||||||
|
Into::<&str>::into(remaining.take(0))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use nom::bytes::complete::tag_no_case;
|
|||||||
use nom::bytes::complete::take_while;
|
use nom::bytes::complete::take_while;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
@@ -12,7 +13,9 @@ use nom::sequence::tuple;
|
|||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
|
use super::paragraph::empty_paragraph;
|
||||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
|
use crate::context::bind_context;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::ExitClass;
|
use crate::context::ExitClass;
|
||||||
@@ -21,7 +24,6 @@ use crate::context::RefContext;
|
|||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::parser::util::blank_line;
|
|
||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::immediate_in_section;
|
use crate::parser::util::immediate_in_section;
|
||||||
@@ -30,8 +32,6 @@ use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
|
|||||||
use crate::types::Drawer;
|
use crate::types::Drawer;
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::types::Paragraph;
|
|
||||||
use crate::types::SetSource;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
@@ -71,30 +71,12 @@ where
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
|
let (remaining, (contents, children)) =
|
||||||
|
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
|
||||||
|
|
||||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
|
||||||
let (remaining, children) = match tuple((
|
|
||||||
not(exit_matcher),
|
|
||||||
blank_line,
|
|
||||||
many_till(blank_line, exit_matcher),
|
|
||||||
))(remaining)
|
|
||||||
{
|
|
||||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
|
||||||
let source = get_consumed(remaining, remain);
|
|
||||||
element.set_source(source.into());
|
|
||||||
(remain, vec![element])
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
let (remaining, (children, _exit_contents)) =
|
|
||||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
|
||||||
(remaining, children)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (remaining, _end) = drawer_end(&parser_context, remaining)?;
|
let (remaining, _end) = drawer_end(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -108,10 +90,34 @@ where
|
|||||||
),
|
),
|
||||||
drawer_name: drawer_name.into(),
|
drawer_name: drawer_name.into(),
|
||||||
children,
|
children,
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
fn children<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Vec<Element<'s>>> {
|
||||||
|
let element_matcher = parser_with_context!(element(true))(context);
|
||||||
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
||||||
|
|
||||||
|
if let Ok((remaining, (_not_exit, empty_para))) =
|
||||||
|
tuple((not(exit_matcher), bind_context!(empty_paragraph, context)))(input)
|
||||||
|
{
|
||||||
|
return Ok((remaining, vec![Element::Paragraph(empty_para)]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (remaining, (children, _exit_contents)) = many_till(element_matcher, exit_matcher)(input)?;
|
||||||
|
|
||||||
|
Ok((remaining, children))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
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)
|
||||||
|
|||||||
@@ -6,20 +6,20 @@ 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::consumed;
|
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many0;
|
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::preceded;
|
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
|
use super::greater_block::leading_blank_lines_end;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
|
use super::paragraph::empty_paragraph;
|
||||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
|
use crate::context::bind_context;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::ExitClass;
|
use crate::context::ExitClass;
|
||||||
@@ -28,7 +28,6 @@ use crate::context::RefContext;
|
|||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::parser::util::blank_line;
|
|
||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::immediate_in_section;
|
use crate::parser::util::immediate_in_section;
|
||||||
@@ -36,8 +35,6 @@ use crate::parser::util::start_of_line;
|
|||||||
use crate::types::DynamicBlock;
|
use crate::types::DynamicBlock;
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::types::Paragraph;
|
|
||||||
use crate::types::SetSource;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
@@ -82,25 +79,25 @@ where
|
|||||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
not(exit_matcher)(remaining)?;
|
not(exit_matcher)(remaining)?;
|
||||||
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
let contents_begin = remaining;
|
||||||
blank_line,
|
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
many0(preceded(not(exit_matcher), blank_line)),
|
class: ExitClass::Alpha,
|
||||||
))))(remaining)?;
|
exit_matcher: &leading_blank_lines_end,
|
||||||
let leading_blank_lines =
|
|
||||||
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
|
||||||
element.set_source(source.into());
|
|
||||||
element
|
|
||||||
});
|
});
|
||||||
|
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
|
||||||
|
|
||||||
|
let (remaining, leading_blank_lines) =
|
||||||
|
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
|
||||||
let (remaining, (mut children, _exit_contents)) =
|
let (remaining, (mut children, _exit_contents)) =
|
||||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||||
if let Some(lines) = leading_blank_lines {
|
if let Some(lines) = leading_blank_lines {
|
||||||
children.insert(0, lines);
|
children.insert(0, Element::Paragraph(lines));
|
||||||
}
|
}
|
||||||
|
let contents = get_consumed(contents_begin, remaining);
|
||||||
|
|
||||||
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
|
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -114,6 +111,12 @@ where
|
|||||||
block_name: name.into(),
|
block_name: name.into(),
|
||||||
parameters: parameters.map(|val| val.into()),
|
parameters: parameters.map(|val| val.into()),
|
||||||
children,
|
children,
|
||||||
|
contents: if contents.len() > 0 {
|
||||||
|
Some(Into::<&str>::into(contents))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _) = tag("\\")(input)?;
|
let (remaining, _) = tag("\\")(input)?;
|
||||||
let (remaining, (entity_definition, entity_name, use_brackets)) = name(context, remaining)?;
|
let (remaining, (entity_definition, entity_name, use_brackets)) = name(context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -43,6 +43,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
|
|||||||
ascii: entity_definition.ascii,
|
ascii: entity_definition.ascii,
|
||||||
utf8: entity_definition.utf8,
|
utf8: entity_definition.utf8,
|
||||||
use_brackets,
|
use_brackets,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
|
|||||||
parser_with_context!(contents)(&parser_context),
|
parser_with_context!(contents)(&parser_context),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
let (remaining, _) = tag("@@")(remaining)?;
|
let (remaining, _) = tag("@@")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -48,6 +48,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
|
|||||||
source: source.into(),
|
source: source.into(),
|
||||||
backend: backend_name.into(),
|
backend: backend_name.into(),
|
||||||
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
|
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,15 @@ use nom::branch::alt;
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
use nom::InputTake;
|
||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
@@ -35,28 +38,25 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's, AK>(
|
|||||||
where
|
where
|
||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
|
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
||||||
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;
|
let (remaining, first_line) = fixed_width_area_line(remaining)?;
|
||||||
let (remaining, mut remaining_lines) =
|
let (remaining, remaining_lines) = many0(preceded(
|
||||||
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
|
not(tuple((org_line_ending, exit_matcher))),
|
||||||
|
map(
|
||||||
|
tuple((org_line_ending, fixed_width_area_line)),
|
||||||
|
|(_line_ending, line_contents)| line_contents,
|
||||||
|
),
|
||||||
|
))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let post_blank_begin = remaining;
|
||||||
|
let (remaining, _first_line_break) = org_line_ending(remaining)?;
|
||||||
|
let (remaining, _additional_post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
let post_blank = get_consumed(post_blank_begin, remaining);
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
|
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
|
||||||
let last_line = remaining_lines.pop();
|
|
||||||
if let Some(last_line) = last_line {
|
|
||||||
value.push(Into::<&str>::into(first_line));
|
value.push(Into::<&str>::into(first_line));
|
||||||
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
|
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
|
||||||
let last_line = Into::<&str>::into(last_line);
|
|
||||||
// Trim the line ending from the final line.
|
|
||||||
value.push(&last_line[..(last_line.len() - 1)])
|
|
||||||
} else {
|
|
||||||
// Trim the line ending from the only line.
|
|
||||||
let only_line = Into::<&str>::into(first_line);
|
|
||||||
value.push(&only_line[..(only_line.len() - 1)])
|
|
||||||
}
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FixedWidthArea {
|
FixedWidthArea {
|
||||||
@@ -66,25 +66,24 @@ where
|
|||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
value,
|
value,
|
||||||
|
post_blank: if post_blank.len() > 0 {
|
||||||
|
Some(Into::<&str>::into(post_blank))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
feature = "tracing",
|
fn fixed_width_area_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
tracing::instrument(ret, level = "debug", skip(_context))
|
|
||||||
)]
|
|
||||||
fn fixed_width_area_line<'b, 'g, 'r, 's>(
|
|
||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
|
||||||
input: OrgSource<'s>,
|
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _) = tuple((space0, tag(":")))(input)?;
|
let (remaining, _) = tuple((space0, tag(":")))(input)?;
|
||||||
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
|
if let Ok((_remain, _line_break)) = org_line_ending(remaining) {
|
||||||
return Ok((remaining, line_break));
|
return Ok((remaining, remaining.take(0)));
|
||||||
}
|
}
|
||||||
let (remaining, _) = tag(" ")(remaining)?;
|
let (remaining, _) = tag(" ")(remaining)?;
|
||||||
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
|
let (remaining, value) = recognize(many_till(anychar, peek(org_line_ending)))(remaining)?;
|
||||||
Ok((remaining, value))
|
Ok((remaining, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ where
|
|||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
|
let before_contents = remaining;
|
||||||
let (mut remaining, (mut children, _exit_contents)) =
|
let (mut remaining, (mut children, _exit_contents)) =
|
||||||
many_till(include_input(element_matcher), exit_matcher)(remaining)?;
|
many_till(include_input(element_matcher), exit_matcher)(remaining)?;
|
||||||
|
|
||||||
@@ -90,13 +91,16 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let contents = get_consumed(before_contents, remaining);
|
||||||
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteDefinition {
|
FootnoteDefinition {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
affiliated_keywords: parse_affiliated_keywords(
|
affiliated_keywords: parse_affiliated_keywords(
|
||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
@@ -160,7 +164,7 @@ mod tests {
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::types::GetStandardProperties;
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_paragraphs() {
|
fn two_paragraphs() {
|
||||||
@@ -181,17 +185,13 @@ line footnote.",
|
|||||||
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_footnote_definition
|
first_footnote_definition.get_source(),
|
||||||
.get_standard_properties()
|
|
||||||
.get_source(),
|
|
||||||
"[fn:1] A footnote.
|
"[fn:1] A footnote.
|
||||||
|
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
second_footnote_definition
|
second_footnote_definition.get_source(),
|
||||||
.get_standard_properties()
|
|
||||||
.get_source(),
|
|
||||||
"[fn:2] A multi-
|
"[fn:2] A multi-
|
||||||
|
|
||||||
line footnote."
|
line footnote."
|
||||||
@@ -216,9 +216,7 @@ not in the footnote.",
|
|||||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
|
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_footnote_definition
|
first_footnote_definition.get_source(),
|
||||||
.get_standard_properties()
|
|
||||||
.get_source(),
|
|
||||||
"[fn:2] A multi-
|
"[fn:2] A multi-
|
||||||
|
|
||||||
line footnote.
|
line footnote.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use nom::branch::alt;
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::combinator::all_consuming;
|
use nom::combinator::all_consuming;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::map_parser;
|
use nom::combinator::map_parser;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many1;
|
use nom::multi::many1;
|
||||||
@@ -59,7 +60,7 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
|
|||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
|
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
|
||||||
|
|
||||||
let (remaining, children) = map_parser(
|
let (remaining, (contents, children)) = consumed(map_parser(
|
||||||
verify(
|
verify(
|
||||||
parser_with_context!(text_until_exit)(&parser_context),
|
parser_with_context!(text_until_exit)(&parser_context),
|
||||||
|text| text.len() > 0,
|
|text| text.len() > 0,
|
||||||
@@ -69,17 +70,19 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
|
|||||||
&initial_context,
|
&initial_context,
|
||||||
)))(i)
|
)))(i)
|
||||||
}),
|
}),
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _) = tag("]")(remaining)?;
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteReference {
|
FootnoteReference {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
label: None,
|
label: None,
|
||||||
definition: children,
|
definition: children,
|
||||||
},
|
},
|
||||||
@@ -106,7 +109,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
|
|||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
|
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
|
||||||
|
|
||||||
let (remaining, children) = map_parser(
|
let (remaining, (contents, children)) = consumed(map_parser(
|
||||||
verify(
|
verify(
|
||||||
parser_with_context!(text_until_exit)(&parser_context),
|
parser_with_context!(text_until_exit)(&parser_context),
|
||||||
|text| text.len() > 0,
|
|text| text.len() > 0,
|
||||||
@@ -116,17 +119,19 @@ fn inline_footnote<'b, 'g, 'r, 's>(
|
|||||||
&initial_context,
|
&initial_context,
|
||||||
)))(i)
|
)))(i)
|
||||||
}),
|
}),
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _) = tag("]")(remaining)?;
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteReference {
|
FootnoteReference {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
label: Some(label_contents.into()),
|
label: Some(label_contents.into()),
|
||||||
definition: children,
|
definition: children,
|
||||||
},
|
},
|
||||||
@@ -144,13 +149,15 @@ fn footnote_reference_only<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _) = tag_no_case("[fn:")(input)?;
|
let (remaining, _) = tag_no_case("[fn:")(input)?;
|
||||||
let (remaining, label_contents) = label(remaining)?;
|
let (remaining, label_contents) = label(remaining)?;
|
||||||
let (remaining, _) = tag("]")(remaining)?;
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
FootnoteReference {
|
FootnoteReference {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: None,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
label: Some(label_contents.into()),
|
label: Some(label_contents.into()),
|
||||||
definition: Vec::with_capacity(0),
|
definition: Vec::with_capacity(0),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,22 +5,21 @@ 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::consumed;
|
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many0;
|
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::preceded;
|
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
|
use super::paragraph::empty_paragraph;
|
||||||
use super::util::in_section;
|
use super::util::in_section;
|
||||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
|
use crate::context::bind_context;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::ContextMatcher;
|
use crate::context::ContextMatcher;
|
||||||
@@ -37,9 +36,7 @@ use crate::parser::util::start_of_line;
|
|||||||
use crate::types::CenterBlock;
|
use crate::types::CenterBlock;
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::types::Paragraph;
|
|
||||||
use crate::types::QuoteBlock;
|
use crate::types::QuoteBlock;
|
||||||
use crate::types::SetSource;
|
|
||||||
use crate::types::SpecialBlock;
|
use crate::types::SpecialBlock;
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -103,7 +100,7 @@ fn center_block<'b, 'g, 'r, 's, AK>(
|
|||||||
where
|
where
|
||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
let (remaining, (source, children)) = greater_block_body(
|
let (remaining, body) = greater_block_body(
|
||||||
context,
|
context,
|
||||||
input,
|
input,
|
||||||
pre_affiliated_keywords_input,
|
pre_affiliated_keywords_input,
|
||||||
@@ -113,12 +110,14 @@ where
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Element::CenterBlock(CenterBlock {
|
Element::CenterBlock(CenterBlock {
|
||||||
source,
|
source: body.source,
|
||||||
affiliated_keywords: parse_affiliated_keywords(
|
affiliated_keywords: parse_affiliated_keywords(
|
||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
children,
|
children: body.children,
|
||||||
|
contents: body.contents,
|
||||||
|
post_blank: body.post_blank,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -136,7 +135,7 @@ fn quote_block<'b, 'g, 'r, 's, AK>(
|
|||||||
where
|
where
|
||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
let (remaining, (source, children)) = greater_block_body(
|
let (remaining, body) = greater_block_body(
|
||||||
context,
|
context,
|
||||||
input,
|
input,
|
||||||
pre_affiliated_keywords_input,
|
pre_affiliated_keywords_input,
|
||||||
@@ -146,12 +145,14 @@ where
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Element::QuoteBlock(QuoteBlock {
|
Element::QuoteBlock(QuoteBlock {
|
||||||
source,
|
source: body.source,
|
||||||
affiliated_keywords: parse_affiliated_keywords(
|
affiliated_keywords: parse_affiliated_keywords(
|
||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
children,
|
children: body.children,
|
||||||
|
contents: body.contents,
|
||||||
|
post_blank: body.post_blank,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -197,7 +198,7 @@ where
|
|||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
|
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
|
||||||
let (remaining, (source, children)) = greater_block_body(
|
let (remaining, body) = greater_block_body(
|
||||||
context,
|
context,
|
||||||
remaining,
|
remaining,
|
||||||
pre_affiliated_keywords_input,
|
pre_affiliated_keywords_input,
|
||||||
@@ -207,18 +208,28 @@ where
|
|||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Element::SpecialBlock(SpecialBlock {
|
Element::SpecialBlock(SpecialBlock {
|
||||||
source,
|
source: body.source,
|
||||||
affiliated_keywords: parse_affiliated_keywords(
|
affiliated_keywords: parse_affiliated_keywords(
|
||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
children,
|
children: body.children,
|
||||||
block_type: name,
|
block_type: name,
|
||||||
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
|
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
|
||||||
|
contents: body.contents,
|
||||||
|
post_blank: body.post_blank,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct GreaterBlockBody<'s> {
|
||||||
|
source: &'s str,
|
||||||
|
children: Vec<Element<'s>>,
|
||||||
|
contents: Option<&'s str>,
|
||||||
|
post_blank: Option<&'s str>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(context))
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
@@ -229,7 +240,7 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
|||||||
pre_affiliated_keywords_input: OrgSource<'s>,
|
pre_affiliated_keywords_input: OrgSource<'s>,
|
||||||
name: &'c str,
|
name: &'c str,
|
||||||
context_name: &'c str,
|
context_name: &'c str,
|
||||||
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
|
) -> Res<OrgSource<'s>, GreaterBlockBody<'s>> {
|
||||||
if in_section(context, context_name) {
|
if in_section(context, context_name) {
|
||||||
return Err(nom::Err::Error(CustomError::Static(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Cannot nest objects of the same element",
|
"Cannot nest objects of the same element",
|
||||||
@@ -251,30 +262,43 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
|||||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
not(exit_matcher)(remaining)?;
|
not(exit_matcher)(remaining)?;
|
||||||
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
let contents_begin = remaining;
|
||||||
blank_line,
|
|
||||||
many0(preceded(not(exit_matcher), blank_line)),
|
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
))))(remaining)?;
|
class: ExitClass::Alpha,
|
||||||
let leading_blank_lines =
|
exit_matcher: &leading_blank_lines_end,
|
||||||
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
|
||||||
element.set_source(source.into());
|
|
||||||
element
|
|
||||||
});
|
});
|
||||||
|
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
|
||||||
|
|
||||||
|
let (remaining, leading_blank_lines) =
|
||||||
|
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
|
||||||
let (remaining, (mut children, _exit_contents)) =
|
let (remaining, (mut children, _exit_contents)) =
|
||||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||||
if let Some(lines) = leading_blank_lines {
|
if let Some(lines) = leading_blank_lines {
|
||||||
children.insert(0, lines);
|
children.insert(0, Element::Paragraph(lines));
|
||||||
}
|
}
|
||||||
|
let contents = get_consumed(contents_begin, remaining);
|
||||||
|
|
||||||
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
|
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
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(pre_affiliated_keywords_input, remaining);
|
let source = get_consumed(pre_affiliated_keywords_input, remaining);
|
||||||
Ok((remaining, (Into::<&str>::into(source), children)))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
GreaterBlockBody {
|
||||||
|
source: Into::<&str>::into(source),
|
||||||
|
children,
|
||||||
|
contents: if contents.len() > 0 {
|
||||||
|
Some(Into::<&str>::into(contents))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -310,3 +334,14 @@ fn _greater_block_end<'b, 'g, 'r, 's, 'c>(
|
|||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(_context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn leading_blank_lines_end<'b, 'g, 'r, 's, 'c>(
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
recognize(not(blank_line))(input)
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
|
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
|
||||||
let section_matcher = bind_context!(section, context);
|
let section_matcher = bind_context!(section, context);
|
||||||
let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
|
let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
|
||||||
|
let (contents_begin, _) = opt(many0(blank_line))(remaining)?;
|
||||||
|
let maybe_post_blank = get_consumed(remaining, contents_begin);
|
||||||
let (remaining, maybe_section) =
|
let (remaining, maybe_section) =
|
||||||
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
|
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
|
||||||
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
|
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
|
||||||
@@ -82,7 +84,8 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
children.insert(0, section);
|
children.insert(0, section);
|
||||||
}
|
}
|
||||||
let remaining = if children.is_empty() {
|
let has_children = !children.is_empty();
|
||||||
|
let remaining = if !has_children {
|
||||||
// Support empty headings
|
// Support empty headings
|
||||||
let (remain, _ws) = many0(blank_line)(remaining)?;
|
let (remain, _ws) = many0(blank_line)(remaining)?;
|
||||||
remain
|
remain
|
||||||
@@ -91,6 +94,7 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
};
|
};
|
||||||
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
|
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
|
||||||
|
|
||||||
|
let contents = get_consumed(contents_begin, remaining);
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -112,6 +116,16 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
scheduled,
|
scheduled,
|
||||||
deadline,
|
deadline,
|
||||||
closed,
|
closed,
|
||||||
|
contents: if contents.len() > 0 {
|
||||||
|
Some(Into::<&str>::into(contents))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
post_blank: if has_children {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Into::<&str>::into(maybe_post_blank))
|
||||||
|
},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ where
|
|||||||
space0,
|
space0,
|
||||||
alt((line_ending, eof)),
|
alt((line_ending, eof)),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -49,6 +49,7 @@ where
|
|||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
}) {
|
}) {
|
||||||
let (_, (in_progress_words, complete_words)) =
|
let (_, (in_progress_words, complete_words)) =
|
||||||
todo_keywords(kw.value).map_err(|err| match err {
|
todo_keywords(kw.value).map_err(|err| match err {
|
||||||
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()),
|
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
|
||||||
nom::Err::Error(e) => e,
|
nom::Err::Error(e) => e,
|
||||||
nom::Err::Failure(e) => e,
|
nom::Err::Failure(e) => e,
|
||||||
})?;
|
})?;
|
||||||
@@ -123,7 +123,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
kw.value,
|
kw.value,
|
||||||
)
|
)
|
||||||
.map_err(|err: nom::Err<_>| match err {
|
.map_err(|err: nom::Err<_>| match err {
|
||||||
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()),
|
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
|
||||||
nom::Err::Error(e) => e,
|
nom::Err::Error(e) => e,
|
||||||
nom::Err::Failure(e) => e,
|
nom::Err::Failure(e) => e,
|
||||||
})?;
|
})?;
|
||||||
@@ -141,7 +141,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
.filter(|kw| kw.key.eq_ignore_ascii_case("link"))
|
.filter(|kw| kw.key.eq_ignore_ascii_case("link"))
|
||||||
{
|
{
|
||||||
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err {
|
let (_, (link_key, link_value)) = link_template(kw.value).map_err(|err| match err {
|
||||||
nom::Err::Incomplete(_) => CustomError::Text(err.to_string()),
|
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
|
||||||
nom::Err::Error(e) => e,
|
nom::Err::Error(e) => e,
|
||||||
nom::Err::Failure(e) => e,
|
nom::Err::Failure(e) => e,
|
||||||
})?;
|
})?;
|
||||||
@@ -157,7 +157,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
|
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
|
||||||
document.category = Into::<AstNode>::into(&*document)
|
document.category = Into::<AstNode>::into(&*document)
|
||||||
.into_iter()
|
.iter_all_ast_nodes()
|
||||||
.filter_map(|ast_node| {
|
.filter_map(|ast_node| {
|
||||||
if let AstNode::Keyword(ast_node) = ast_node {
|
if let AstNode::Keyword(ast_node) = ast_node {
|
||||||
if ast_node.key.eq_ignore_ascii_case("category") {
|
if ast_node.key.eq_ignore_ascii_case("category") {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, arguments) = argument(context, remaining)?;
|
let (remaining, arguments) = argument(context, remaining)?;
|
||||||
let (remaining, end_header) = opt(parser_with_context!(header)(context))(remaining)?;
|
let (remaining, end_header) = opt(parser_with_context!(header)(context))(remaining)?;
|
||||||
let value = get_consumed(input, remaining);
|
let value = get_consumed(input, remaining);
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -54,6 +54,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
|
|||||||
None
|
None
|
||||||
},
|
},
|
||||||
end_header: end_header.map(Into::<&str>::into),
|
end_header: end_header.map(Into::<&str>::into),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, language) = lang(context, remaining)?;
|
let (remaining, language) = lang(context, remaining)?;
|
||||||
let (remaining, parameters) = opt(parser_with_context!(header)(context))(remaining)?;
|
let (remaining, parameters) = opt(parser_with_context!(header)(context))(remaining)?;
|
||||||
let (remaining, value) = body(context, remaining)?;
|
let (remaining, value) = body(context, remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -48,6 +48,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
|
|||||||
language: language.into(),
|
language: language.into(),
|
||||||
parameters: parameters.map(Into::<&str>::into),
|
parameters: parameters.map(Into::<&str>::into),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
|
|||||||
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
|
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
|
||||||
key: parsed_key.into(),
|
key: parsed_key.into(),
|
||||||
value: "",
|
value: "",
|
||||||
|
post_blank: None,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -71,6 +72,7 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
|
|||||||
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
|
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
|
||||||
key: parsed_key.into(),
|
key: parsed_key.into(),
|
||||||
value: parsed_value.into(),
|
value: parsed_value.into(),
|
||||||
|
post_blank: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -89,12 +91,13 @@ where
|
|||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
|
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
kw.affiliated_keywords =
|
kw.affiliated_keywords =
|
||||||
parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords);
|
parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords);
|
||||||
kw.source = Into::<&str>::into(source);
|
kw.source = Into::<&str>::into(source);
|
||||||
|
kw.post_blank = post_blank.map(Into::<&str>::into);
|
||||||
Ok((remaining, kw))
|
Ok((remaining, kw))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ where
|
|||||||
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
|
||||||
let value_end = remaining;
|
let value_end = remaining;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
let value = get_consumed(value_start, value_end);
|
let value = get_consumed(value_start, value_end);
|
||||||
@@ -70,6 +70,7 @@ where
|
|||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
|
|||||||
parser_with_context!(bordered_dollar_fragment)(context),
|
parser_with_context!(bordered_dollar_fragment)(context),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
let value = get_consumed(input, remaining);
|
let value = get_consumed(input, remaining);
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -47,6 +47,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
|
|||||||
LatexFragment {
|
LatexFragment {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,22 +80,28 @@ where
|
|||||||
let object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
let object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
// Check for a completely empty block
|
// Check for a completely empty block
|
||||||
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
|
let (remaining, contents, children) =
|
||||||
|
match consumed(many_till(blank_line, exit_matcher))(remaining) {
|
||||||
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
|
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
|
||||||
remaining,
|
remaining,
|
||||||
|
whitespace,
|
||||||
|
if whitespace.len() > 0 {
|
||||||
vec![Object::PlainText(PlainText {
|
vec![Object::PlainText(PlainText {
|
||||||
source: whitespace.into(),
|
source: whitespace.into(),
|
||||||
})],
|
})]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let (remaining, (children, _exit_contents)) =
|
let (remaining, (contents, (children, _exit_contents))) =
|
||||||
many_till(object_matcher, exit_matcher)(remaining)?;
|
consumed(many_till(object_matcher, exit_matcher))(remaining)?;
|
||||||
(remaining, children)
|
(remaining, contents, children)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -108,6 +114,8 @@ where
|
|||||||
),
|
),
|
||||||
data: parameters.map(Into::<&str>::into),
|
data: parameters.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
|
contents: Into::<&str>::into(contents),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -144,7 +152,7 @@ where
|
|||||||
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
|
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -156,6 +164,7 @@ where
|
|||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
contents: contents.into(),
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -202,10 +211,10 @@ where
|
|||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
|
|
||||||
let (remaining, contents) = content(&parser_context, remaining)?;
|
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
|
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
|
||||||
@@ -236,7 +245,8 @@ where
|
|||||||
retain_labels,
|
retain_labels,
|
||||||
use_labels,
|
use_labels,
|
||||||
label_format,
|
label_format,
|
||||||
contents,
|
value: Into::<&str>::into(contents),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -276,10 +286,10 @@ where
|
|||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
|
|
||||||
let (remaining, contents) = content(&parser_context, remaining)?;
|
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -292,7 +302,8 @@ where
|
|||||||
),
|
),
|
||||||
export_type: export_type.map(Into::<&str>::into),
|
export_type: export_type.map(Into::<&str>::into),
|
||||||
data: parameters.map(Into::<&str>::into),
|
data: parameters.map(Into::<&str>::into),
|
||||||
contents,
|
value: Into::<&str>::into(contents),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -331,10 +342,10 @@ where
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
let (remaining, contents) = content(&parser_context, remaining)?;
|
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
|
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
|
||||||
@@ -371,7 +382,8 @@ where
|
|||||||
retain_labels,
|
retain_labels,
|
||||||
use_labels,
|
use_labels,
|
||||||
label_format,
|
label_format,
|
||||||
contents,
|
value: Into::<&str>::into(contents),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -650,51 +662,3 @@ fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
is_not(" \t\r\n"),
|
is_not(" \t\r\n"),
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "tracing",
|
|
||||||
tracing::instrument(ret, level = "debug", skip(context))
|
|
||||||
)]
|
|
||||||
pub(crate) fn content<'b, 'g, 'r, 's>(
|
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
|
||||||
input: OrgSource<'s>,
|
|
||||||
) -> Res<OrgSource<'s>, String> {
|
|
||||||
let mut ret = String::new();
|
|
||||||
let mut remaining = input;
|
|
||||||
let exit_matcher_parser = parser_with_context!(exit_matcher_parser)(context);
|
|
||||||
loop {
|
|
||||||
if exit_matcher_parser(remaining).is_ok() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
|
|
||||||
if let Some(val) = pre_escape_whitespace {
|
|
||||||
ret.push_str(Into::<&str>::into(val));
|
|
||||||
}
|
|
||||||
ret.push_str(line.into());
|
|
||||||
remaining = remain;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((remaining, ret))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
fn content_line<'s>(
|
|
||||||
input: OrgSource<'s>,
|
|
||||||
) -> Res<OrgSource<'s>, (Option<OrgSource<'s>>, OrgSource<'s>)> {
|
|
||||||
let (remaining, pre_escape_whitespace) = opt(map(
|
|
||||||
tuple((
|
|
||||||
recognize(tuple((
|
|
||||||
space0,
|
|
||||||
many_till(
|
|
||||||
tag(","),
|
|
||||||
peek(tuple((tag(","), alt((tag("#+"), tag("*")))))),
|
|
||||||
),
|
|
||||||
))),
|
|
||||||
tag(","),
|
|
||||||
)),
|
|
||||||
|(pre_comma, _)| pre_comma,
|
|
||||||
))(input)?;
|
|
||||||
let (remaining, line_post_escape) = recognize(many_till(anychar, line_ending))(remaining)?;
|
|
||||||
Ok((remaining, (pre_escape_whitespace, line_post_escape)))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
|
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
|
||||||
let (remaining, _) = tag("}}}")(remaining)?;
|
let (remaining, _) = tag("}}}")(remaining)?;
|
||||||
let macro_value = get_consumed(input, remaining);
|
let macro_value = get_consumed(input, remaining);
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -47,6 +47,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
|
|||||||
.map(|arg| arg.into())
|
.map(|arg| arg.into())
|
||||||
.collect(),
|
.collect(),
|
||||||
value: Into::<&str>::into(macro_value),
|
value: Into::<&str>::into(macro_value),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use nom::Slice;
|
|||||||
|
|
||||||
pub(crate) type BracketDepth = i16;
|
pub(crate) type BracketDepth = i16;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) struct OrgSource<'s> {
|
pub(crate) struct OrgSource<'s> {
|
||||||
full_source: &'s str,
|
full_source: &'s str,
|
||||||
start: usize,
|
start: usize,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
|
use nom::character::complete::space1;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
|
use nom::combinator::opt;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many1;
|
use nom::multi::many1;
|
||||||
@@ -12,6 +15,7 @@ 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::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
|
use super::util::org_line_ending;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::ExitClass;
|
use crate::context::ExitClass;
|
||||||
@@ -45,14 +49,14 @@ where
|
|||||||
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
|
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
|
||||||
many_till(standard_set_object_matcher, exit_matcher),
|
many_till(standard_set_object_matcher, exit_matcher),
|
||||||
|(children, _exit_contents)| !children.is_empty(),
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
|
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -60,6 +64,8 @@ where
|
|||||||
remaining,
|
remaining,
|
||||||
Paragraph {
|
Paragraph {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
affiliated_keywords: parse_affiliated_keywords(
|
affiliated_keywords: parse_affiliated_keywords(
|
||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
@@ -69,6 +75,57 @@ where
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn empty_paragraph<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Paragraph<'s>> {
|
||||||
|
// If it is just a single newline then source, contents, and post-blank are "\n".
|
||||||
|
// If it has multiple newlines then contents is the first "\n" and post-blank is all the new lines.
|
||||||
|
// If there are any spaces on the first line then post-blank excludes the first line.
|
||||||
|
|
||||||
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
||||||
|
|
||||||
|
let (remaining, first_line_with_spaces) =
|
||||||
|
opt(recognize(tuple((space1, org_line_ending))))(input)?;
|
||||||
|
|
||||||
|
let post_blank_begin = remaining;
|
||||||
|
|
||||||
|
if let Some(first_line_with_spaces) = first_line_with_spaces {
|
||||||
|
let (remaining, _additional_lines) =
|
||||||
|
recognize(many_till(blank_line, exit_matcher))(remaining)?;
|
||||||
|
let post_blank = get_consumed(post_blank_begin, remaining);
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Paragraph::of_text(
|
||||||
|
Into::<&str>::into(source),
|
||||||
|
Into::<&str>::into(first_line_with_spaces),
|
||||||
|
Some(Into::<&str>::into(first_line_with_spaces)),
|
||||||
|
Some(Into::<&str>::into(post_blank)),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let (remaining, first_line) = blank_line(remaining)?;
|
||||||
|
let (remaining, _additional_lines) =
|
||||||
|
recognize(many_till(blank_line, exit_matcher))(remaining)?;
|
||||||
|
let post_blank = get_consumed(post_blank_begin, remaining);
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Paragraph::of_text(
|
||||||
|
Into::<&str>::into(source),
|
||||||
|
Into::<&str>::into(first_line),
|
||||||
|
Some(Into::<&str>::into(first_line)),
|
||||||
|
Some(Into::<&str>::into(post_blank)),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(context))
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
@@ -96,7 +153,8 @@ mod tests {
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::parser::org_source::OrgSource;
|
use crate::parser::org_source::OrgSource;
|
||||||
use crate::types::GetStandardProperties;
|
use crate::parser::paragraph::empty_paragraph;
|
||||||
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_paragraphs() {
|
fn two_paragraphs() {
|
||||||
@@ -109,13 +167,20 @@ mod tests {
|
|||||||
let (remaining, second_paragraph) =
|
let (remaining, second_paragraph) =
|
||||||
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
|
||||||
first_paragraph.get_standard_properties().get_source(),
|
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
|
||||||
"foo bar baz\n\n"
|
}
|
||||||
);
|
|
||||||
assert_eq!(
|
#[test]
|
||||||
second_paragraph.get_standard_properties().get_source(),
|
fn paragraph_whitespace() {
|
||||||
"lorem ipsum"
|
let input = OrgSource::new("\n");
|
||||||
);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let paragraph_matcher = bind_context!(empty_paragraph, &initial_context);
|
||||||
|
let (remaining, paragraph) = paragraph_matcher(input).expect("Parse paragraph");
|
||||||
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
|
assert_eq!(paragraph.get_source(), "\n");
|
||||||
|
assert_eq!(paragraph.get_contents(), Some("\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, path_plain) = parse_path_plain(context, remaining)?;
|
let (remaining, path_plain) = parse_path_plain(context, remaining)?;
|
||||||
peek(parser_with_context!(post)(context))(remaining)?;
|
peek(parser_with_context!(post)(context))(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -66,6 +66,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
|
|||||||
raw_link: path_plain.raw_link,
|
raw_link: path_plain.raw_link,
|
||||||
search_option: path_plain.search_option,
|
search_option: path_plain.search_option,
|
||||||
application: path_plain.application,
|
application: path_plain.application,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use nom::character::complete::multispace1;
|
|||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
@@ -152,6 +153,7 @@ where
|
|||||||
let mut children = Vec::new();
|
let mut children = Vec::new();
|
||||||
let mut first_item_indentation: Option<IndentationLevel> = None;
|
let mut first_item_indentation: Option<IndentationLevel> = None;
|
||||||
let mut first_item_list_type: Option<PlainListType> = None;
|
let mut first_item_list_type: Option<PlainListType> = None;
|
||||||
|
let contents_begin = remaining;
|
||||||
let mut remaining = remaining;
|
let mut remaining = remaining;
|
||||||
|
|
||||||
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
|
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
|
||||||
@@ -195,7 +197,8 @@ where
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let contents = get_consumed(contents_begin, remaining);
|
||||||
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -208,6 +211,8 @@ where
|
|||||||
),
|
),
|
||||||
list_type: first_item_list_type.expect("Plain lists require at least one element."),
|
list_type: first_item_list_type.expect("Plain lists require at least one element."),
|
||||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||||
|
contents: Some(Into::<&str>::into(contents)),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -265,7 +270,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
let maybe_contentless_item: Res<OrgSource<'_>, ()> =
|
let maybe_contentless_item: Res<OrgSource<'_>, ()> =
|
||||||
detect_contentless_item_contents(&parser_context, remaining);
|
detect_contentless_item_contents(&parser_context, remaining);
|
||||||
if let Ok((_rem, _ws)) = maybe_contentless_item {
|
if let Ok((_rem, _ws)) = maybe_contentless_item {
|
||||||
let (remaining, _trailing_ws) = if tuple((
|
let (remaining, post_blank) = if tuple((
|
||||||
blank_line,
|
blank_line,
|
||||||
bind_context!(final_item_whitespace_cutoff, context),
|
bind_context!(final_item_whitespace_cutoff, context),
|
||||||
))(remaining)
|
))(remaining)
|
||||||
@@ -291,6 +296,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
.unwrap_or(Vec::new()),
|
.unwrap_or(Vec::new()),
|
||||||
pre_blank: 0,
|
pre_blank: 0,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
contents: None,
|
||||||
|
post_blank: if post_blank.len() > 0 {
|
||||||
|
Some(Into::<&str>::into(post_blank))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
@@ -301,13 +312,13 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
.filter(|b| *b == b'\n')
|
.filter(|b| *b == b'\n')
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let (remaining, (children, _exit_contents)) = many_till(
|
let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
|
||||||
include_input(bind_context!(element(true), &parser_context)),
|
include_input(bind_context!(element(true), &parser_context)),
|
||||||
bind_context!(exit_matcher_parser, &parser_context),
|
bind_context!(exit_matcher_parser, &parser_context),
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
// We have to use the parser_context here to include the whitespace cut-off
|
// We have to use the parser_context here to include the whitespace cut-off
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -329,6 +340,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
pre_blank: PlainListItemPreBlank::try_from(pre_blank)
|
pre_blank: PlainListItemPreBlank::try_from(pre_blank)
|
||||||
.expect("pre-blank cannot be larger than 2."),
|
.expect("pre-blank cannot be larger than 2."),
|
||||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||||
|
contents: if contents.len() > 0 {
|
||||||
|
Some(contents.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
@@ -629,7 +646,7 @@ mod tests {
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::types::GetStandardProperties;
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_list_item_empty() {
|
fn plain_list_item_empty() {
|
||||||
@@ -640,7 +657,7 @@ mod tests {
|
|||||||
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
|
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
|
||||||
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
assert_eq!(result.get_source(), "1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -652,7 +669,7 @@ mod tests {
|
|||||||
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
|
let plain_list_item_matcher = bind_context!(plain_list_item, &initial_context);
|
||||||
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
assert_eq!(result.get_source(), "1. foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -664,7 +681,7 @@ mod tests {
|
|||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
assert_eq!(result.get_source(), "1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -676,7 +693,7 @@ mod tests {
|
|||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
assert_eq!(result.get_source(), "1. foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -721,7 +738,7 @@ mod tests {
|
|||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
|
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_standard_properties().get_source(),
|
result.get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
2. bar
|
2. bar
|
||||||
baz
|
baz
|
||||||
@@ -749,7 +766,7 @@ baz"#,
|
|||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "baz");
|
assert_eq!(Into::<&str>::into(remaining), "baz");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_standard_properties().get_source(),
|
result.get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
1. bar
|
1. bar
|
||||||
|
|
||||||
@@ -782,7 +799,7 @@ dolar"#,
|
|||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "dolar");
|
assert_eq!(Into::<&str>::into(remaining), "dolar");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_standard_properties().get_source(),
|
result.get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
|
|
||||||
bar
|
bar
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ mod tests {
|
|||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
|
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
|
||||||
use crate::types::GetStandardProperties;
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_text_simple() {
|
fn plain_text_simple() {
|
||||||
@@ -160,9 +160,6 @@ mod tests {
|
|||||||
)(input)
|
)(input)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(result.get_source(), Into::<&str>::into(input));
|
||||||
result.get_standard_properties().get_source(),
|
|
||||||
Into::<&str>::into(input)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
|
|||||||
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
|
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
|
||||||
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
|
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
|
|||||||
scheduled,
|
scheduled,
|
||||||
deadline,
|
deadline,
|
||||||
closed,
|
closed,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
@@ -64,14 +65,11 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
|
let (remaining, (contents, children)) =
|
||||||
let node_property_matcher = parser_with_context!(node_property)(&parser_context);
|
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
|
||||||
let (remaining, (children, _exit_contents)) =
|
|
||||||
many_till(node_property_matcher, exit_matcher)(remaining)?;
|
|
||||||
let (remaining, _end) = property_drawer_end(&parser_context, remaining)?;
|
let (remaining, _end) = property_drawer_end(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -80,10 +78,31 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
|
|||||||
PropertyDrawer {
|
PropertyDrawer {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
children,
|
children,
|
||||||
|
contents: if contents.len() > 0 {
|
||||||
|
Some(contents.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
fn children<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Vec<NodeProperty<'s>>> {
|
||||||
|
let node_property_matcher = parser_with_context!(node_property)(context);
|
||||||
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
||||||
|
let (remaining, (children, _exit_contents)) =
|
||||||
|
many_till(node_property_matcher, exit_matcher)(input)?;
|
||||||
|
Ok((remaining, children))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(_context))
|
tracing::instrument(ret, level = "debug", skip(_context))
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
|
|||||||
let rematched_target = rematch_target(context, radio_target, input);
|
let rematched_target = rematch_target(context, radio_target, input);
|
||||||
if let Ok((remaining, rematched_target)) = rematched_target {
|
if let Ok((remaining, rematched_target)) = rematched_target {
|
||||||
let path = get_consumed(input, remaining);
|
let path = get_consumed(input, remaining);
|
||||||
let (remaining, _) = space0(remaining)?;
|
let (remaining, post_blank) = space0(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
return Ok((
|
return Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -47,6 +47,11 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
|
|||||||
source: source.into(),
|
source: source.into(),
|
||||||
children: rematched_target,
|
children: rematched_target,
|
||||||
path: path.into(),
|
path: path.into(),
|
||||||
|
post_blank: if post_blank.len() > 0 {
|
||||||
|
Some(Into::<&str>::into(post_blank))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -134,7 +139,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
|
|||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _closing) = tag(">>>")(remaining)?;
|
let (remaining, _closing) = tag(">>>")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -142,6 +147,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
|
|||||||
RadioTarget {
|
RadioTarget {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
value: raw_value.into(),
|
value: raw_value.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -175,11 +181,11 @@ mod tests {
|
|||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::types::Bold;
|
use crate::types::Bold;
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::GetStandardProperties;
|
|
||||||
use crate::types::PlainText;
|
use crate::types::PlainText;
|
||||||
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_text_radio_target() {
|
fn plain_text_radio_target() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let input = OrgSource::new("foo bar baz");
|
let input = OrgSource::new("foo bar baz");
|
||||||
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
|
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
|
||||||
let global_settings = GlobalSettings {
|
let global_settings = GlobalSettings {
|
||||||
@@ -195,29 +201,38 @@ mod tests {
|
|||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(first_paragraph.get_source(), "foo bar baz");
|
||||||
first_paragraph.get_standard_properties().get_source(),
|
|
||||||
"foo bar baz"
|
|
||||||
);
|
|
||||||
assert_eq!(first_paragraph.children.len(), 3);
|
assert_eq!(first_paragraph.children.len(), 3);
|
||||||
assert_eq!(
|
match first_paragraph
|
||||||
first_paragraph
|
|
||||||
.children
|
.children
|
||||||
.get(1)
|
.get(1)
|
||||||
.expect("Len already asserted to be 3"),
|
.expect("Len already asserted to be 3.")
|
||||||
&Object::RadioLink(RadioLink {
|
{
|
||||||
source: "bar ",
|
Object::RadioLink(inner) => {
|
||||||
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
assert_eq!(inner.get_source(), "bar ");
|
||||||
path: "bar"
|
assert_eq!(inner.path, "bar");
|
||||||
})
|
assert_eq!(inner.children.len(), 1);
|
||||||
);
|
let child = inner
|
||||||
|
.children
|
||||||
|
.first()
|
||||||
|
.expect("Length already asserted to be 1.");
|
||||||
|
assert!(matches!(child, Object::PlainText(_)));
|
||||||
|
assert_eq!(child.get_source(), "bar");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err("Child should be a radio link.".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn bold_radio_target() {
|
fn bold_radio_target() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let input = OrgSource::new("foo *bar* baz");
|
let input = OrgSource::new("foo *bar* baz");
|
||||||
let radio_target_match = vec![Object::Bold(Bold {
|
let radio_target_match = vec![Object::Bold(Bold {
|
||||||
source: "*bar*",
|
source: "*bar*",
|
||||||
|
contents: "bar",
|
||||||
|
post_blank: Some(" "),
|
||||||
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
||||||
})];
|
})];
|
||||||
let global_settings = GlobalSettings {
|
let global_settings = GlobalSettings {
|
||||||
@@ -234,24 +249,28 @@ mod tests {
|
|||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
|
||||||
first_paragraph.get_standard_properties().get_source(),
|
|
||||||
"foo *bar* baz"
|
|
||||||
);
|
|
||||||
assert_eq!(first_paragraph.children.len(), 3);
|
assert_eq!(first_paragraph.children.len(), 3);
|
||||||
assert_eq!(
|
match first_paragraph
|
||||||
first_paragraph
|
|
||||||
.children
|
.children
|
||||||
.get(1)
|
.get(1)
|
||||||
.expect("Len already asserted to be 3"),
|
.expect("Len already asserted to be 3.")
|
||||||
&Object::RadioLink(RadioLink {
|
{
|
||||||
source: "*bar* ",
|
Object::RadioLink(inner) => {
|
||||||
children: vec![Object::Bold(Bold {
|
assert_eq!(inner.get_source(), "*bar* ");
|
||||||
source: "*bar* ",
|
assert_eq!(inner.path, "*bar* ");
|
||||||
children: vec![Object::PlainText(PlainText { source: "bar" })]
|
assert_eq!(inner.children.len(), 1);
|
||||||
})],
|
let child = inner
|
||||||
path: "*bar* "
|
.children
|
||||||
})
|
.first()
|
||||||
);
|
.expect("Length already asserted to be 1.");
|
||||||
|
assert!(matches!(child, Object::Bold(_)));
|
||||||
|
assert_eq!(child.get_source(), "*bar* ");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err("Child should be a radio link.".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _opening_bracket) = tag("[[")(input)?;
|
let (remaining, _opening_bracket) = tag("[[")(input)?;
|
||||||
let (remaining, path) = pathreg(context, remaining)?;
|
let (remaining, path) = pathreg(context, remaining)?;
|
||||||
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
|
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -84,6 +84,8 @@ fn regular_link_without_description<'b, 'g, 'r, 's>(
|
|||||||
path: path.path,
|
path: path.path,
|
||||||
raw_link: path.raw_link,
|
raw_link: path.raw_link,
|
||||||
search_option: path.search_option,
|
search_option: path.search_option,
|
||||||
|
contents: None,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
application: path.application,
|
application: path.application,
|
||||||
},
|
},
|
||||||
@@ -101,9 +103,10 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _opening_bracket) = tag("[[")(input)?;
|
let (remaining, _opening_bracket) = tag("[[")(input)?;
|
||||||
let (remaining, path) = pathreg(context, remaining)?;
|
let (remaining, path) = pathreg(context, remaining)?;
|
||||||
let (remaining, _closing_bracket) = tag("][")(remaining)?;
|
let (remaining, _closing_bracket) = tag("][")(remaining)?;
|
||||||
let (remaining, description) = description(context, remaining)?;
|
let (remaining, (contents, description)) =
|
||||||
|
consumed(parser_with_context!(description)(context))(remaining)?;
|
||||||
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
|
let (remaining, _closing_bracket) = tag("]]")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -114,6 +117,8 @@ fn regular_link_with_description<'b, 'g, 'r, 's>(
|
|||||||
path: path.path,
|
path: path.path,
|
||||||
raw_link: path.raw_link,
|
raw_link: path.raw_link,
|
||||||
search_option: path.search_option,
|
search_option: path.search_option,
|
||||||
|
contents: Some(Into::<&str>::into(contents)),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children: description,
|
children: description,
|
||||||
application: path.application,
|
application: path.application,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -80,6 +80,7 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Section {
|
Section {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -128,7 +129,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
|
|||||||
children.insert(0, ele)
|
children.insert(0, ele)
|
||||||
}
|
}
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -136,6 +137,7 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Section {
|
Section {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
|
|||||||
tag("%]"),
|
tag("%]"),
|
||||||
)))(input)?;
|
)))(input)?;
|
||||||
let value = get_consumed(input, remaining);
|
let value = get_consumed(input, remaining);
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -48,6 +48,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
|
|||||||
StatisticsCookie {
|
StatisticsCookie {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
|
|||||||
tag("]"),
|
tag("]"),
|
||||||
)))(input)?;
|
)))(input)?;
|
||||||
let value = get_consumed(input, remaining);
|
let value = get_consumed(input, remaining);
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -76,6 +77,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
|
|||||||
StatisticsCookie {
|
StatisticsCookie {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
|
|||||||
use nom::bytes::complete::take_while;
|
use nom::bytes::complete::take_while;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
@@ -54,17 +55,20 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Subscript<'s>> {
|
) -> Res<OrgSource<'s>, Subscript<'s>> {
|
||||||
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
|
|
||||||
let (remaining, _) = tag("_")(input)?;
|
let (remaining, _) = tag("_")(input)?;
|
||||||
pre(input)?;
|
pre(input)?;
|
||||||
let (remaining, body) = script_body(context, remaining)?;
|
let (remaining, body) = script_body(context, remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
let (use_brackets, body) = match body {
|
let (use_brackets, contents, body) = match body {
|
||||||
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]),
|
ScriptBody::Braceless(text) => (
|
||||||
ScriptBody::WithBraces(body) => (true, body),
|
false,
|
||||||
|
text,
|
||||||
|
vec![Object::PlainText(PlainText { source: text })],
|
||||||
|
),
|
||||||
|
ScriptBody::WithBraces(contents, body) => (true, contents, body),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@@ -72,6 +76,8 @@ pub(crate) fn subscript<'b, 'g, 'r, 's>(
|
|||||||
Subscript {
|
Subscript {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
use_brackets,
|
use_brackets,
|
||||||
|
contents,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children: body,
|
children: body,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -89,13 +95,17 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _) = tag("^")(input)?;
|
let (remaining, _) = tag("^")(input)?;
|
||||||
pre(input)?;
|
pre(input)?;
|
||||||
let (remaining, body) = script_body(context, remaining)?;
|
let (remaining, body) = script_body(context, remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
let (use_brackets, body) = match body {
|
let (use_brackets, contents, body) = match body {
|
||||||
ScriptBody::Braceless(text) => (false, vec![Object::PlainText(PlainText { source: text })]),
|
ScriptBody::Braceless(text) => (
|
||||||
ScriptBody::WithBraces(body) => (true, body),
|
false,
|
||||||
|
text,
|
||||||
|
vec![Object::PlainText(PlainText { source: text })],
|
||||||
|
),
|
||||||
|
ScriptBody::WithBraces(contents, body) => (true, contents, body),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@@ -103,6 +113,8 @@ pub(crate) fn superscript<'b, 'g, 'r, 's>(
|
|||||||
Superscript {
|
Superscript {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
use_brackets,
|
use_brackets,
|
||||||
|
contents,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children: body,
|
children: body,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -117,7 +129,7 @@ fn pre<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum ScriptBody<'s> {
|
enum ScriptBody<'s> {
|
||||||
Braceless(&'s str),
|
Braceless(&'s str),
|
||||||
WithBraces(Vec<Object<'s>>),
|
WithBraces(&'s str, Vec<Object<'s>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -135,9 +147,10 @@ fn script_body<'b, 'g, 'r, 's>(
|
|||||||
map(parser_with_context!(script_alphanum)(context), |body| {
|
map(parser_with_context!(script_alphanum)(context), |body| {
|
||||||
ScriptBody::Braceless(body.into())
|
ScriptBody::Braceless(body.into())
|
||||||
}),
|
}),
|
||||||
map(parser_with_context!(script_with_braces)(context), |body| {
|
map(
|
||||||
ScriptBody::WithBraces(body)
|
parser_with_context!(script_with_braces)(context),
|
||||||
}),
|
|(contents, body)| ScriptBody::WithBraces(Into::<&str>::into(contents), body),
|
||||||
|
),
|
||||||
map(
|
map(
|
||||||
parser_with_context!(script_with_parenthesis)(context),
|
parser_with_context!(script_with_parenthesis)(context),
|
||||||
|body| ScriptBody::Braceless(body.into()),
|
|body| ScriptBody::Braceless(body.into()),
|
||||||
@@ -195,7 +208,7 @@ fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>,
|
|||||||
fn script_with_braces<'b, 'g, 'r, 's>(
|
fn script_with_braces<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>)> {
|
||||||
let (remaining, _) = tag("{")(input)?;
|
let (remaining, _) = tag("{")(input)?;
|
||||||
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
|
let exit_with_depth = script_with_braces_end(remaining.get_brace_depth());
|
||||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
@@ -204,13 +217,13 @@ fn script_with_braces<'b, 'g, 'r, 's>(
|
|||||||
});
|
});
|
||||||
let parser_context = context.with_additional_node(&parser_context);
|
let parser_context = context.with_additional_node(&parser_context);
|
||||||
|
|
||||||
let (remaining, (children, _exit_contents)) = many_till(
|
let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
|
||||||
parser_with_context!(standard_set_object)(&parser_context),
|
parser_with_context!(standard_set_object)(&parser_context),
|
||||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _) = tag("}")(remaining)?;
|
let (remaining, _) = tag("}")(remaining)?;
|
||||||
Ok((remaining, children))
|
Ok((remaining, (contents, children)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher {
|
fn script_with_braces_end(starting_brace_depth: BracketDepth) -> impl ContextMatcher {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use nom::bytes::complete::is_not;
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
@@ -67,13 +68,13 @@ where
|
|||||||
let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&parser_context);
|
let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
|
|
||||||
let (remaining, (children, _exit_contents)) =
|
let (remaining, (contents, (children, _exit_contents))) =
|
||||||
many_till(org_mode_table_row_matcher, exit_matcher)(remaining)?;
|
consumed(many_till(org_mode_table_row_matcher, exit_matcher))(remaining)?;
|
||||||
|
|
||||||
let (remaining, formulas) =
|
let (remaining, formulas) =
|
||||||
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
|
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -87,6 +88,8 @@ where
|
|||||||
),
|
),
|
||||||
formulas,
|
formulas,
|
||||||
children,
|
children,
|
||||||
|
contents: Into::<&str>::into(contents),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -150,6 +153,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
|
|||||||
TableRow {
|
TableRow {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
contents: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -164,8 +168,8 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _) = tuple((space0, tag("|")))(input)?;
|
let (remaining, _) = tuple((space0, tag("|")))(input)?;
|
||||||
let (remaining, children) =
|
let (remaining, (contents, children)) =
|
||||||
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
|
consumed(many1(parser_with_context!(org_mode_table_cell)(context)))(remaining)?;
|
||||||
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
|
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
@@ -173,6 +177,11 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
|
|||||||
TableRow {
|
TableRow {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
children,
|
children,
|
||||||
|
contents: if contents.len() > 0 {
|
||||||
|
Some(Into::<&str>::into(contents))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -194,12 +203,12 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
|
|||||||
parser_with_context!(table_cell_set_object)(&parser_context);
|
parser_with_context!(table_cell_set_object)(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
let (remaining, _) = space0(input)?;
|
let (remaining, _) = space0(input)?;
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
|
||||||
many_till(table_cell_set_object_matcher, exit_matcher),
|
many_till(table_cell_set_object_matcher, exit_matcher),
|
||||||
|(children, exit_contents)| {
|
|(children, exit_contents)| {
|
||||||
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
|
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
|
||||||
},
|
},
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
|
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
|
||||||
|
|
||||||
@@ -210,6 +219,7 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
|
|||||||
TableCell {
|
TableCell {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
children,
|
children,
|
||||||
|
contents: Into::<&str>::into(contents),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let (remaining, _) = tag(">>")(remaining)?;
|
let (remaining, _) = tag(">>")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -55,6 +55,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
|
|||||||
Target {
|
Target {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
value: body.into(),
|
value: body.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ use nom::bytes::complete::tag;
|
|||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::multispace1;
|
use nom::character::complete::multispace1;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::all_consuming;
|
use nom::combinator::all_consuming;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::combinator::map_parser;
|
use nom::combinator::map_parser;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
@@ -76,12 +78,14 @@ fn bold<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Bold<'s>> {
|
) -> Res<OrgSource<'s>, Bold<'s>> {
|
||||||
let (remaining, children) = text_markup_object("*")(context, input)?;
|
let (remaining, (contents, children, post_blank)) = text_markup_object("*")(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Bold {
|
Bold {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -95,12 +99,14 @@ fn italic<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Italic<'s>> {
|
) -> Res<OrgSource<'s>, Italic<'s>> {
|
||||||
let (remaining, children) = text_markup_object("/")(context, input)?;
|
let (remaining, (contents, children, post_blank)) = text_markup_object("/")(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Italic {
|
Italic {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -114,12 +120,14 @@ fn underline<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Underline<'s>> {
|
) -> Res<OrgSource<'s>, Underline<'s>> {
|
||||||
let (remaining, children) = text_markup_object("_")(context, input)?;
|
let (remaining, (contents, children, post_blank)) = text_markup_object("_")(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Underline {
|
Underline {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -133,12 +141,14 @@ fn strike_through<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
|
) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
|
||||||
let (remaining, children) = text_markup_object("+")(context, input)?;
|
let (remaining, (contents, children, post_blank)) = text_markup_object("+")(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
StrikeThrough {
|
StrikeThrough {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -152,13 +162,14 @@ fn verbatim<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Verbatim<'s>> {
|
) -> Res<OrgSource<'s>, Verbatim<'s>> {
|
||||||
let (remaining, contents) = text_markup_string("=")(context, input)?;
|
let (remaining, (contents, post_blank)) = text_markup_string("=")(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Verbatim {
|
Verbatim {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
contents: contents.into(),
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -171,13 +182,14 @@ fn code<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Code<'s>> {
|
) -> Res<OrgSource<'s>, Code<'s>> {
|
||||||
let (remaining, contents) = text_markup_string("~")(context, input)?;
|
let (remaining, (contents, post_blank)) = text_markup_string("~")(context, input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Code {
|
Code {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
contents: contents.into(),
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -187,8 +199,10 @@ fn text_markup_object(
|
|||||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||||
RefContext<'b, 'g, 'r, 's>,
|
RefContext<'b, 'g, 'r, 's>,
|
||||||
OrgSource<'s>,
|
OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>>
|
) -> Res<
|
||||||
+ '_ {
|
OrgSource<'s>,
|
||||||
|
(OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>),
|
||||||
|
> + '_ {
|
||||||
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
|
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +214,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'c str,
|
marker_symbol: &'c str,
|
||||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) =
|
let (remaining, _peek_not_whitespace) =
|
||||||
@@ -215,7 +229,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
|||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
|
let initial_context = Context::new(context.get_global_settings(), List::new(&initial_context));
|
||||||
|
|
||||||
let (remaining, children) = map_parser(
|
let (remaining, (contents, children)) = consumed(map_parser(
|
||||||
verify(
|
verify(
|
||||||
parser_with_context!(text_until_exit)(&parser_context),
|
parser_with_context!(text_until_exit)(&parser_context),
|
||||||
|text| text.len() > 0,
|
|text| text.len() > 0,
|
||||||
@@ -225,7 +239,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
|||||||
&initial_context,
|
&initial_context,
|
||||||
)))(i)
|
)))(i)
|
||||||
}),
|
}),
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
@@ -240,9 +254,9 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
Ok((remaining, children))
|
Ok((remaining, (contents, children, post_blank)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_markup_string(
|
fn text_markup_string(
|
||||||
@@ -250,7 +264,7 @@ fn text_markup_string(
|
|||||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||||
RefContext<'b, 'g, 'r, 's>,
|
RefContext<'b, 'g, 'r, 's>,
|
||||||
OrgSource<'s>,
|
OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>>
|
) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)>
|
||||||
+ '_ {
|
+ '_ {
|
||||||
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
|
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
|
||||||
}
|
}
|
||||||
@@ -263,7 +277,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'c str,
|
marker_symbol: &'c str,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, (OrgSource<'s>, Option<OrgSource<'s>>)> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) =
|
let (remaining, _peek_not_whitespace) =
|
||||||
@@ -296,9 +310,9 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
Ok((remaining, contents))
|
Ok((remaining, (contents, post_blank)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -382,13 +396,15 @@ impl<'x> RematchObject<'x> for Bold<'x> {
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, children) =
|
let (remaining, (contents, children, post_blank)) =
|
||||||
_rematch_text_markup_object(_context, input, "*", &self.children)?;
|
_rematch_text_markup_object(_context, input, "*", &self.children)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Object::Bold(Bold {
|
Object::Bold(Bold {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -405,13 +421,15 @@ impl<'x> RematchObject<'x> for Italic<'x> {
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, children) =
|
let (remaining, (contents, children, post_blank)) =
|
||||||
_rematch_text_markup_object(_context, input, "/", &self.children)?;
|
_rematch_text_markup_object(_context, input, "/", &self.children)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Object::Italic(Italic {
|
Object::Italic(Italic {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -428,13 +446,15 @@ impl<'x> RematchObject<'x> for Underline<'x> {
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, children) =
|
let (remaining, (contents, children, post_blank)) =
|
||||||
_rematch_text_markup_object(_context, input, "_", &self.children)?;
|
_rematch_text_markup_object(_context, input, "_", &self.children)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Object::Underline(Underline {
|
Object::Underline(Underline {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -451,13 +471,15 @@ impl<'x> RematchObject<'x> for StrikeThrough<'x> {
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, children) =
|
let (remaining, (contents, children, post_blank)) =
|
||||||
_rematch_text_markup_object(_context, input, "+", &self.children)?;
|
_rematch_text_markup_object(_context, input, "+", &self.children)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Object::StrikeThrough(StrikeThrough {
|
Object::StrikeThrough(StrikeThrough {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: contents.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -473,7 +495,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'static str,
|
marker_symbol: &'static str,
|
||||||
original_match_children: &'x Vec<Object<'x>>,
|
original_match_children: &'x Vec<Object<'x>>,
|
||||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
|
) -> Res<OrgSource<'s>, (OrgSource<'s>, Vec<Object<'s>>, Option<OrgSource<'s>>)> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
||||||
@@ -484,6 +506,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
|||||||
});
|
});
|
||||||
let parser_context = context.with_additional_node(&parser_context);
|
let parser_context = context.with_additional_node(&parser_context);
|
||||||
|
|
||||||
|
let contents_begin = remaining;
|
||||||
let (remaining, children) =
|
let (remaining, children) =
|
||||||
// TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code.
|
// TODO: This doesn't really check the exit matcher between each object. I think it may be possible to construct an org document that parses incorrectly with the current code.
|
||||||
rematch_target(&parser_context, original_match_children, remaining)?;
|
rematch_target(&parser_context, original_match_children, remaining)?;
|
||||||
@@ -499,8 +522,10 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let contents_end = remaining;
|
||||||
|
let contents = contents_begin.get_until(contents_end);
|
||||||
|
|
||||||
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
let (remaining, _close) = text_markup_end_specialized(context, remaining)?;
|
||||||
let (remaining, _trailing_whitespace) = space0(remaining)?;
|
let (remaining, post_blank) = opt(space1)(remaining)?;
|
||||||
Ok((remaining, children))
|
Ok((remaining, (contents, children, post_blank)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,8 +53,8 @@ pub(crate) fn timestamp<'b, 'g, 'r, 's>(
|
|||||||
parser_with_context!(inactive_time_range_timestamp)(context),
|
parser_with_context!(inactive_time_range_timestamp)(context),
|
||||||
parser_with_context!(active_date_range_timestamp)(context),
|
parser_with_context!(active_date_range_timestamp)(context),
|
||||||
parser_with_context!(inactive_date_range_timestamp)(context),
|
parser_with_context!(inactive_date_range_timestamp)(context),
|
||||||
parser_with_context!(active_timestamp)(context),
|
parser_with_context!(active_timestamp(true))(context),
|
||||||
parser_with_context!(inactive_timestamp)(context),
|
parser_with_context!(inactive_timestamp(true))(context),
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _) = tag("<%%(")(input)?;
|
let (remaining, _) = tag("<%%(")(input)?;
|
||||||
let (remaining, _body) = sexp(context, remaining)?;
|
let (remaining, _body) = sexp(context, remaining)?;
|
||||||
let (remaining, _) = tag(")>")(remaining)?;
|
let (remaining, _) = tag(")>")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -85,6 +85,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
|
|||||||
end_time: None,
|
end_time: None,
|
||||||
repeater: None,
|
repeater: None,
|
||||||
warning_delay: None,
|
warning_delay: None,
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -125,13 +126,23 @@ fn sexp_end<'b, 'g, 'r, 's>(
|
|||||||
alt((tag(")>"), recognize(one_of(">\n"))))(input)
|
alt((tag(")>"), recognize(one_of(">\n"))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn active_timestamp(
|
||||||
|
allow_post_blank: bool,
|
||||||
|
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||||
|
RefContext<'b, 'g, 'r, 's>,
|
||||||
|
OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
|
move |context, input| impl_active_timestamp(context, input, allow_post_blank)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(context))
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
)]
|
)]
|
||||||
fn active_timestamp<'b, 'g, 'r, 's>(
|
fn impl_active_timestamp<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
|
allow_post_blank: bool,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("<")(input)?;
|
let (remaining, _) = tag("<")(input)?;
|
||||||
let (remaining, start) = date(context, remaining)?;
|
let (remaining, start) = date(context, remaining)?;
|
||||||
@@ -159,8 +170,11 @@ fn active_timestamp<'b, 'g, 'r, 's>(
|
|||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
let (remaining, _) = tag(">")(remaining)?;
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) = if allow_post_blank {
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
|
||||||
|
} else {
|
||||||
|
(remaining, None)
|
||||||
|
};
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@@ -175,17 +189,28 @@ fn active_timestamp<'b, 'g, 'r, 's>(
|
|||||||
end_time: time.map(|(_, time)| time),
|
end_time: time.map(|(_, time)| time),
|
||||||
repeater: repeater.map(|(_, repeater)| repeater),
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn inactive_timestamp(
|
||||||
|
allow_post_blank: bool,
|
||||||
|
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||||
|
RefContext<'b, 'g, 'r, 's>,
|
||||||
|
OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
|
move |context, input| impl_inactive_timestamp(context, input, allow_post_blank)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(context))
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
)]
|
)]
|
||||||
pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
|
fn impl_inactive_timestamp<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
|
allow_post_blank: bool,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
let (remaining, start) = date(context, remaining)?;
|
let (remaining, start) = date(context, remaining)?;
|
||||||
@@ -213,8 +238,11 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
|
|||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
let (remaining, _) = tag("]")(remaining)?;
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) = if allow_post_blank {
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
|
||||||
|
} else {
|
||||||
|
(remaining, None)
|
||||||
|
};
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@@ -229,6 +257,7 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
|
|||||||
end_time: time.map(|(_, time)| time),
|
end_time: time.map(|(_, time)| time),
|
||||||
repeater: repeater.map(|(_, repeater)| repeater),
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -241,12 +270,12 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, first_timestamp) = active_timestamp(context, input)?;
|
let (remaining, first_timestamp) = impl_active_timestamp(context, input, false)?;
|
||||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
// 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)?;
|
||||||
let (remaining, second_timestamp) = active_timestamp(context, remaining)?;
|
let (remaining, second_timestamp) = impl_active_timestamp(context, remaining, false)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -264,6 +293,7 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
warning_delay: first_timestamp
|
warning_delay: first_timestamp
|
||||||
.warning_delay
|
.warning_delay
|
||||||
.or(second_timestamp.warning_delay),
|
.or(second_timestamp.warning_delay),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -302,7 +332,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
let (remaining, _) = tag(">")(remaining)?;
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -318,6 +348,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
end_time: Some(second_time),
|
end_time: Some(second_time),
|
||||||
repeater: repeater.map(|(_, repeater)| repeater),
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -330,12 +361,12 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, first_timestamp) = inactive_timestamp(context, input)?;
|
let (remaining, first_timestamp) = impl_inactive_timestamp(context, input, false)?;
|
||||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
// 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)?;
|
||||||
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?;
|
let (remaining, second_timestamp) = impl_inactive_timestamp(context, remaining, false)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -354,6 +385,7 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
warning_delay: first_timestamp
|
warning_delay: first_timestamp
|
||||||
.warning_delay
|
.warning_delay
|
||||||
.or(second_timestamp.warning_delay),
|
.or(second_timestamp.warning_delay),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -392,7 +424,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
let (remaining, _) = tag("]")(remaining)?;
|
let (remaining, _) = tag("]")(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -408,6 +440,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
end_time: Some(second_time),
|
end_time: Some(second_time),
|
||||||
repeater: repeater.map(|(_, repeater)| repeater),
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,14 +81,21 @@ pub(crate) fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
|
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
|
||||||
// We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::").
|
// We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::").
|
||||||
let (remaining, _) = many_till(
|
let (remaining, post_blank) = recognize(many_till(
|
||||||
one_of(" \t"),
|
one_of(" \t"),
|
||||||
alt((
|
alt((
|
||||||
peek(recognize(none_of(" \t"))),
|
peek(recognize(none_of(" \t"))),
|
||||||
parser_with_context!(exit_matcher_parser)(context),
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
)),
|
)),
|
||||||
)(input)?;
|
))(input)?;
|
||||||
Ok((remaining, None))
|
Ok((
|
||||||
|
remaining,
|
||||||
|
if post_blank.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(post_blank)
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use super::macros::to_ast_node;
|
use super::macros::to_ast_node;
|
||||||
use super::CenterBlock;
|
use super::CenterBlock;
|
||||||
|
use super::PostBlank;
|
||||||
use super::QuoteBlock;
|
use super::QuoteBlock;
|
||||||
use super::SpecialBlock;
|
use super::SpecialBlock;
|
||||||
|
use super::StandardProperties;
|
||||||
use crate::types::AngleLink;
|
use crate::types::AngleLink;
|
||||||
use crate::types::BabelCall;
|
use crate::types::BabelCall;
|
||||||
use crate::types::Bold;
|
use crate::types::Bold;
|
||||||
@@ -24,7 +26,6 @@ use crate::types::ExportSnippet;
|
|||||||
use crate::types::FixedWidthArea;
|
use crate::types::FixedWidthArea;
|
||||||
use crate::types::FootnoteDefinition;
|
use crate::types::FootnoteDefinition;
|
||||||
use crate::types::FootnoteReference;
|
use crate::types::FootnoteReference;
|
||||||
use crate::types::GetStandardProperties;
|
|
||||||
use crate::types::Heading;
|
use crate::types::Heading;
|
||||||
use crate::types::HorizontalRule;
|
use crate::types::HorizontalRule;
|
||||||
use crate::types::InlineBabelCall;
|
use crate::types::InlineBabelCall;
|
||||||
@@ -259,67 +260,193 @@ to_ast_node!(&'r Superscript<'s>, AstNode::Superscript);
|
|||||||
to_ast_node!(&'r TableCell<'s>, AstNode::TableCell);
|
to_ast_node!(&'r TableCell<'s>, AstNode::TableCell);
|
||||||
to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp);
|
to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp);
|
||||||
|
|
||||||
impl<'r, 's> GetStandardProperties<'s> for AstNode<'r, 's> {
|
impl<'r, 's> StandardProperties<'s> for AstNode<'r, 's> {
|
||||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn crate::types::StandardProperties<'s> {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
match self {
|
match self {
|
||||||
AstNode::Document(inner) => *inner,
|
AstNode::Document(inner) => inner.get_source(),
|
||||||
AstNode::Heading(inner) => *inner,
|
AstNode::Heading(inner) => inner.get_source(),
|
||||||
AstNode::Section(inner) => *inner,
|
AstNode::Section(inner) => inner.get_source(),
|
||||||
AstNode::Paragraph(inner) => *inner,
|
AstNode::Paragraph(inner) => inner.get_source(),
|
||||||
AstNode::PlainList(inner) => *inner,
|
AstNode::PlainList(inner) => inner.get_source(),
|
||||||
AstNode::PlainListItem(inner) => *inner,
|
AstNode::PlainListItem(inner) => inner.get_source(),
|
||||||
AstNode::CenterBlock(inner) => *inner,
|
AstNode::CenterBlock(inner) => inner.get_source(),
|
||||||
AstNode::QuoteBlock(inner) => *inner,
|
AstNode::QuoteBlock(inner) => inner.get_source(),
|
||||||
AstNode::SpecialBlock(inner) => *inner,
|
AstNode::SpecialBlock(inner) => inner.get_source(),
|
||||||
AstNode::DynamicBlock(inner) => *inner,
|
AstNode::DynamicBlock(inner) => inner.get_source(),
|
||||||
AstNode::FootnoteDefinition(inner) => *inner,
|
AstNode::FootnoteDefinition(inner) => inner.get_source(),
|
||||||
AstNode::Comment(inner) => *inner,
|
AstNode::Comment(inner) => inner.get_source(),
|
||||||
AstNode::Drawer(inner) => *inner,
|
AstNode::Drawer(inner) => inner.get_source(),
|
||||||
AstNode::PropertyDrawer(inner) => *inner,
|
AstNode::PropertyDrawer(inner) => inner.get_source(),
|
||||||
AstNode::NodeProperty(inner) => *inner,
|
AstNode::NodeProperty(inner) => inner.get_source(),
|
||||||
AstNode::Table(inner) => *inner,
|
AstNode::Table(inner) => inner.get_source(),
|
||||||
AstNode::TableRow(inner) => *inner,
|
AstNode::TableRow(inner) => inner.get_source(),
|
||||||
AstNode::VerseBlock(inner) => *inner,
|
AstNode::VerseBlock(inner) => inner.get_source(),
|
||||||
AstNode::CommentBlock(inner) => *inner,
|
AstNode::CommentBlock(inner) => inner.get_source(),
|
||||||
AstNode::ExampleBlock(inner) => *inner,
|
AstNode::ExampleBlock(inner) => inner.get_source(),
|
||||||
AstNode::ExportBlock(inner) => *inner,
|
AstNode::ExportBlock(inner) => inner.get_source(),
|
||||||
AstNode::SrcBlock(inner) => *inner,
|
AstNode::SrcBlock(inner) => inner.get_source(),
|
||||||
AstNode::Clock(inner) => *inner,
|
AstNode::Clock(inner) => inner.get_source(),
|
||||||
AstNode::DiarySexp(inner) => *inner,
|
AstNode::DiarySexp(inner) => inner.get_source(),
|
||||||
AstNode::Planning(inner) => *inner,
|
AstNode::Planning(inner) => inner.get_source(),
|
||||||
AstNode::FixedWidthArea(inner) => *inner,
|
AstNode::FixedWidthArea(inner) => inner.get_source(),
|
||||||
AstNode::HorizontalRule(inner) => *inner,
|
AstNode::HorizontalRule(inner) => inner.get_source(),
|
||||||
AstNode::Keyword(inner) => *inner,
|
AstNode::Keyword(inner) => inner.get_source(),
|
||||||
AstNode::BabelCall(inner) => *inner,
|
AstNode::BabelCall(inner) => inner.get_source(),
|
||||||
AstNode::LatexEnvironment(inner) => *inner,
|
AstNode::LatexEnvironment(inner) => inner.get_source(),
|
||||||
AstNode::Bold(inner) => *inner,
|
AstNode::Bold(inner) => inner.get_source(),
|
||||||
AstNode::Italic(inner) => *inner,
|
AstNode::Italic(inner) => inner.get_source(),
|
||||||
AstNode::Underline(inner) => *inner,
|
AstNode::Underline(inner) => inner.get_source(),
|
||||||
AstNode::StrikeThrough(inner) => *inner,
|
AstNode::StrikeThrough(inner) => inner.get_source(),
|
||||||
AstNode::Code(inner) => *inner,
|
AstNode::Code(inner) => inner.get_source(),
|
||||||
AstNode::Verbatim(inner) => *inner,
|
AstNode::Verbatim(inner) => inner.get_source(),
|
||||||
AstNode::PlainText(inner) => *inner,
|
AstNode::PlainText(inner) => inner.get_source(),
|
||||||
AstNode::RegularLink(inner) => *inner,
|
AstNode::RegularLink(inner) => inner.get_source(),
|
||||||
AstNode::RadioLink(inner) => *inner,
|
AstNode::RadioLink(inner) => inner.get_source(),
|
||||||
AstNode::RadioTarget(inner) => *inner,
|
AstNode::RadioTarget(inner) => inner.get_source(),
|
||||||
AstNode::PlainLink(inner) => *inner,
|
AstNode::PlainLink(inner) => inner.get_source(),
|
||||||
AstNode::AngleLink(inner) => *inner,
|
AstNode::AngleLink(inner) => inner.get_source(),
|
||||||
AstNode::OrgMacro(inner) => *inner,
|
AstNode::OrgMacro(inner) => inner.get_source(),
|
||||||
AstNode::Entity(inner) => *inner,
|
AstNode::Entity(inner) => inner.get_source(),
|
||||||
AstNode::LatexFragment(inner) => *inner,
|
AstNode::LatexFragment(inner) => inner.get_source(),
|
||||||
AstNode::ExportSnippet(inner) => *inner,
|
AstNode::ExportSnippet(inner) => inner.get_source(),
|
||||||
AstNode::FootnoteReference(inner) => *inner,
|
AstNode::FootnoteReference(inner) => inner.get_source(),
|
||||||
AstNode::Citation(inner) => *inner,
|
AstNode::Citation(inner) => inner.get_source(),
|
||||||
AstNode::CitationReference(inner) => *inner,
|
AstNode::CitationReference(inner) => inner.get_source(),
|
||||||
AstNode::InlineBabelCall(inner) => *inner,
|
AstNode::InlineBabelCall(inner) => inner.get_source(),
|
||||||
AstNode::InlineSourceBlock(inner) => *inner,
|
AstNode::InlineSourceBlock(inner) => inner.get_source(),
|
||||||
AstNode::LineBreak(inner) => *inner,
|
AstNode::LineBreak(inner) => inner.get_source(),
|
||||||
AstNode::Target(inner) => *inner,
|
AstNode::Target(inner) => inner.get_source(),
|
||||||
AstNode::StatisticsCookie(inner) => *inner,
|
AstNode::StatisticsCookie(inner) => inner.get_source(),
|
||||||
AstNode::Subscript(inner) => *inner,
|
AstNode::Subscript(inner) => inner.get_source(),
|
||||||
AstNode::Superscript(inner) => *inner,
|
AstNode::Superscript(inner) => inner.get_source(),
|
||||||
AstNode::TableCell(inner) => *inner,
|
AstNode::TableCell(inner) => inner.get_source(),
|
||||||
AstNode::Timestamp(inner) => *inner,
|
AstNode::Timestamp(inner) => inner.get_source(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
match self {
|
||||||
|
AstNode::Document(inner) => inner.get_contents(),
|
||||||
|
AstNode::Heading(inner) => inner.get_contents(),
|
||||||
|
AstNode::Section(inner) => inner.get_contents(),
|
||||||
|
AstNode::Paragraph(inner) => inner.get_contents(),
|
||||||
|
AstNode::PlainList(inner) => inner.get_contents(),
|
||||||
|
AstNode::PlainListItem(inner) => inner.get_contents(),
|
||||||
|
AstNode::CenterBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::QuoteBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::SpecialBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::DynamicBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::FootnoteDefinition(inner) => inner.get_contents(),
|
||||||
|
AstNode::Comment(inner) => inner.get_contents(),
|
||||||
|
AstNode::Drawer(inner) => inner.get_contents(),
|
||||||
|
AstNode::PropertyDrawer(inner) => inner.get_contents(),
|
||||||
|
AstNode::NodeProperty(inner) => inner.get_contents(),
|
||||||
|
AstNode::Table(inner) => inner.get_contents(),
|
||||||
|
AstNode::TableRow(inner) => inner.get_contents(),
|
||||||
|
AstNode::VerseBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::CommentBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::ExampleBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::ExportBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::SrcBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::Clock(inner) => inner.get_contents(),
|
||||||
|
AstNode::DiarySexp(inner) => inner.get_contents(),
|
||||||
|
AstNode::Planning(inner) => inner.get_contents(),
|
||||||
|
AstNode::FixedWidthArea(inner) => inner.get_contents(),
|
||||||
|
AstNode::HorizontalRule(inner) => inner.get_contents(),
|
||||||
|
AstNode::Keyword(inner) => inner.get_contents(),
|
||||||
|
AstNode::BabelCall(inner) => inner.get_contents(),
|
||||||
|
AstNode::LatexEnvironment(inner) => inner.get_contents(),
|
||||||
|
AstNode::Bold(inner) => inner.get_contents(),
|
||||||
|
AstNode::Italic(inner) => inner.get_contents(),
|
||||||
|
AstNode::Underline(inner) => inner.get_contents(),
|
||||||
|
AstNode::StrikeThrough(inner) => inner.get_contents(),
|
||||||
|
AstNode::Code(inner) => inner.get_contents(),
|
||||||
|
AstNode::Verbatim(inner) => inner.get_contents(),
|
||||||
|
AstNode::PlainText(inner) => inner.get_contents(),
|
||||||
|
AstNode::RegularLink(inner) => inner.get_contents(),
|
||||||
|
AstNode::RadioLink(inner) => inner.get_contents(),
|
||||||
|
AstNode::RadioTarget(inner) => inner.get_contents(),
|
||||||
|
AstNode::PlainLink(inner) => inner.get_contents(),
|
||||||
|
AstNode::AngleLink(inner) => inner.get_contents(),
|
||||||
|
AstNode::OrgMacro(inner) => inner.get_contents(),
|
||||||
|
AstNode::Entity(inner) => inner.get_contents(),
|
||||||
|
AstNode::LatexFragment(inner) => inner.get_contents(),
|
||||||
|
AstNode::ExportSnippet(inner) => inner.get_contents(),
|
||||||
|
AstNode::FootnoteReference(inner) => inner.get_contents(),
|
||||||
|
AstNode::Citation(inner) => inner.get_contents(),
|
||||||
|
AstNode::CitationReference(inner) => inner.get_contents(),
|
||||||
|
AstNode::InlineBabelCall(inner) => inner.get_contents(),
|
||||||
|
AstNode::InlineSourceBlock(inner) => inner.get_contents(),
|
||||||
|
AstNode::LineBreak(inner) => inner.get_contents(),
|
||||||
|
AstNode::Target(inner) => inner.get_contents(),
|
||||||
|
AstNode::StatisticsCookie(inner) => inner.get_contents(),
|
||||||
|
AstNode::Subscript(inner) => inner.get_contents(),
|
||||||
|
AstNode::Superscript(inner) => inner.get_contents(),
|
||||||
|
AstNode::TableCell(inner) => inner.get_contents(),
|
||||||
|
AstNode::Timestamp(inner) => inner.get_contents(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
match self {
|
||||||
|
AstNode::Document(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Heading(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Section(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Paragraph(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::PlainList(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::PlainListItem(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::CenterBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::QuoteBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::SpecialBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::DynamicBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::FootnoteDefinition(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Comment(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Drawer(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::PropertyDrawer(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::NodeProperty(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Table(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::TableRow(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::VerseBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::CommentBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::ExampleBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::ExportBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::SrcBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Clock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::DiarySexp(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Planning(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::FixedWidthArea(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::HorizontalRule(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Keyword(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::BabelCall(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::LatexEnvironment(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Bold(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Italic(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Underline(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::StrikeThrough(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Code(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Verbatim(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::PlainText(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::RegularLink(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::RadioLink(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::RadioTarget(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::PlainLink(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::AngleLink(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::OrgMacro(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Entity(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::LatexFragment(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::ExportSnippet(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::FootnoteReference(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Citation(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::CitationReference(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::InlineBabelCall(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::InlineSourceBlock(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::LineBreak(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Target(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::StatisticsCookie(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Subscript(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Superscript(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::TableCell(inner) => inner.get_post_blank(),
|
||||||
|
AstNode::Timestamp(inner) => inner.get_post_blank(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::Element;
|
use super::Element;
|
||||||
use super::GetStandardProperties;
|
|
||||||
use super::NodeProperty;
|
use super::NodeProperty;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
|
use super::PostBlank;
|
||||||
use super::StandardProperties;
|
use super::StandardProperties;
|
||||||
use super::Timestamp;
|
use super::Timestamp;
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ pub struct Document<'s> {
|
|||||||
pub path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
pub zeroth_section: Option<Section<'s>>,
|
pub zeroth_section: Option<Section<'s>>,
|
||||||
pub children: Vec<Heading<'s>>,
|
pub children: Vec<Heading<'s>>,
|
||||||
|
pub contents: &'s str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -34,11 +35,14 @@ pub struct Heading<'s> {
|
|||||||
pub scheduled: Option<Timestamp<'s>>,
|
pub scheduled: Option<Timestamp<'s>>,
|
||||||
pub deadline: Option<Timestamp<'s>>,
|
pub deadline: Option<Timestamp<'s>>,
|
||||||
pub closed: Option<Timestamp<'s>>,
|
pub closed: Option<Timestamp<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Section<'s> {
|
pub struct Section<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,41 +58,60 @@ pub enum TodoKeywordType {
|
|||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
|
|
||||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
|
||||||
match self {
|
|
||||||
DocumentElement::Heading(inner) => inner,
|
|
||||||
DocumentElement::Section(inner) => inner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Document<'s> {
|
impl<'s> StandardProperties<'s> for Document<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
Some(self.contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Section<'s> {
|
impl<'s> StandardProperties<'s> for Section<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
Some(self.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Heading<'s> {
|
impl<'s> StandardProperties<'s> for Heading<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Heading<'s> {
|
impl<'s> Heading<'s> {
|
||||||
pub fn get_raw_value(&self) -> String {
|
pub fn get_raw_value(&self) -> String {
|
||||||
// TODO: I think this could just return a string slice instead of an owned string.
|
// TODO: I think this could just return a string slice instead of an owned string.
|
||||||
let title_source: String = self
|
let title_source: String = self.title.iter().map(|obj| obj.get_source()).collect();
|
||||||
.title
|
|
||||||
.iter()
|
|
||||||
.map(|obj| obj.get_standard_properties().get_source())
|
|
||||||
.collect();
|
|
||||||
title_source
|
title_source
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,3 +155,26 @@ impl<'s> Document<'s> {
|
|||||||
.flat_map(|property_drawer| property_drawer.children.iter())
|
.flat_map(|property_drawer| property_drawer.children.iter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'s> StandardProperties<'s> for DocumentElement<'s> {
|
||||||
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
|
match self {
|
||||||
|
DocumentElement::Heading(inner) => inner.get_source(),
|
||||||
|
DocumentElement::Section(inner) => inner.get_source(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
match self {
|
||||||
|
DocumentElement::Heading(inner) => inner.get_contents(),
|
||||||
|
DocumentElement::Section(inner) => inner.get_contents(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
match self {
|
||||||
|
DocumentElement::Heading(inner) => inner.get_post_blank(),
|
||||||
|
DocumentElement::Section(inner) => inner.get_post_blank(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,9 +20,8 @@ use super::lesser_element::SrcBlock;
|
|||||||
use super::lesser_element::VerseBlock;
|
use super::lesser_element::VerseBlock;
|
||||||
use super::CenterBlock;
|
use super::CenterBlock;
|
||||||
use super::Drawer;
|
use super::Drawer;
|
||||||
use super::GetStandardProperties;
|
use super::PostBlank;
|
||||||
use super::QuoteBlock;
|
use super::QuoteBlock;
|
||||||
use super::SetSource;
|
|
||||||
use super::SpecialBlock;
|
use super::SpecialBlock;
|
||||||
use super::StandardProperties;
|
use super::StandardProperties;
|
||||||
|
|
||||||
@@ -55,65 +54,91 @@ pub enum Element<'s> {
|
|||||||
LatexEnvironment(LatexEnvironment<'s>),
|
LatexEnvironment(LatexEnvironment<'s>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> SetSource<'s> for Element<'s> {
|
impl<'s> StandardProperties<'s> for Element<'s> {
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
fn set_source(&mut self, source: &'s str) {
|
|
||||||
match self {
|
match self {
|
||||||
Element::Paragraph(obj) => obj.source = source,
|
Element::Paragraph(inner) => inner.get_source(),
|
||||||
Element::PlainList(obj) => obj.source = source,
|
Element::PlainList(inner) => inner.get_source(),
|
||||||
Element::CenterBlock(obj) => obj.source = source,
|
Element::CenterBlock(inner) => inner.get_source(),
|
||||||
Element::QuoteBlock(obj) => obj.source = source,
|
Element::QuoteBlock(inner) => inner.get_source(),
|
||||||
Element::SpecialBlock(obj) => obj.source = source,
|
Element::SpecialBlock(inner) => inner.get_source(),
|
||||||
Element::DynamicBlock(obj) => obj.source = source,
|
Element::DynamicBlock(inner) => inner.get_source(),
|
||||||
Element::FootnoteDefinition(obj) => obj.source = source,
|
Element::FootnoteDefinition(inner) => inner.get_source(),
|
||||||
Element::Comment(obj) => obj.source = source,
|
Element::Comment(inner) => inner.get_source(),
|
||||||
Element::Drawer(obj) => obj.source = source,
|
Element::Drawer(inner) => inner.get_source(),
|
||||||
Element::PropertyDrawer(obj) => obj.source = source,
|
Element::PropertyDrawer(inner) => inner.get_source(),
|
||||||
Element::Table(obj) => obj.source = source,
|
Element::Table(inner) => inner.get_source(),
|
||||||
Element::VerseBlock(obj) => obj.source = source,
|
Element::VerseBlock(inner) => inner.get_source(),
|
||||||
Element::CommentBlock(obj) => obj.source = source,
|
Element::CommentBlock(inner) => inner.get_source(),
|
||||||
Element::ExampleBlock(obj) => obj.source = source,
|
Element::ExampleBlock(inner) => inner.get_source(),
|
||||||
Element::ExportBlock(obj) => obj.source = source,
|
Element::ExportBlock(inner) => inner.get_source(),
|
||||||
Element::SrcBlock(obj) => obj.source = source,
|
Element::SrcBlock(inner) => inner.get_source(),
|
||||||
Element::Clock(obj) => obj.source = source,
|
Element::Clock(inner) => inner.get_source(),
|
||||||
Element::DiarySexp(obj) => obj.source = source,
|
Element::DiarySexp(inner) => inner.get_source(),
|
||||||
Element::Planning(obj) => obj.source = source,
|
Element::Planning(inner) => inner.get_source(),
|
||||||
Element::FixedWidthArea(obj) => obj.source = source,
|
Element::FixedWidthArea(inner) => inner.get_source(),
|
||||||
Element::HorizontalRule(obj) => obj.source = source,
|
Element::HorizontalRule(inner) => inner.get_source(),
|
||||||
Element::Keyword(obj) => obj.source = source,
|
Element::Keyword(inner) => inner.get_source(),
|
||||||
Element::BabelCall(obj) => obj.source = source,
|
Element::BabelCall(inner) => inner.get_source(),
|
||||||
Element::LatexEnvironment(obj) => obj.source = source,
|
Element::LatexEnvironment(inner) => inner.get_source(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> GetStandardProperties<'s> for Element<'s> {
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
|
||||||
match self {
|
match self {
|
||||||
Element::Paragraph(inner) => inner,
|
Element::Paragraph(inner) => inner.get_contents(),
|
||||||
Element::PlainList(inner) => inner,
|
Element::PlainList(inner) => inner.get_contents(),
|
||||||
Element::CenterBlock(inner) => inner,
|
Element::CenterBlock(inner) => inner.get_contents(),
|
||||||
Element::QuoteBlock(inner) => inner,
|
Element::QuoteBlock(inner) => inner.get_contents(),
|
||||||
Element::SpecialBlock(inner) => inner,
|
Element::SpecialBlock(inner) => inner.get_contents(),
|
||||||
Element::DynamicBlock(inner) => inner,
|
Element::DynamicBlock(inner) => inner.get_contents(),
|
||||||
Element::FootnoteDefinition(inner) => inner,
|
Element::FootnoteDefinition(inner) => inner.get_contents(),
|
||||||
Element::Comment(inner) => inner,
|
Element::Comment(inner) => inner.get_contents(),
|
||||||
Element::Drawer(inner) => inner,
|
Element::Drawer(inner) => inner.get_contents(),
|
||||||
Element::PropertyDrawer(inner) => inner,
|
Element::PropertyDrawer(inner) => inner.get_contents(),
|
||||||
Element::Table(inner) => inner,
|
Element::Table(inner) => inner.get_contents(),
|
||||||
Element::VerseBlock(inner) => inner,
|
Element::VerseBlock(inner) => inner.get_contents(),
|
||||||
Element::CommentBlock(inner) => inner,
|
Element::CommentBlock(inner) => inner.get_contents(),
|
||||||
Element::ExampleBlock(inner) => inner,
|
Element::ExampleBlock(inner) => inner.get_contents(),
|
||||||
Element::ExportBlock(inner) => inner,
|
Element::ExportBlock(inner) => inner.get_contents(),
|
||||||
Element::SrcBlock(inner) => inner,
|
Element::SrcBlock(inner) => inner.get_contents(),
|
||||||
Element::Clock(inner) => inner,
|
Element::Clock(inner) => inner.get_contents(),
|
||||||
Element::DiarySexp(inner) => inner,
|
Element::DiarySexp(inner) => inner.get_contents(),
|
||||||
Element::Planning(inner) => inner,
|
Element::Planning(inner) => inner.get_contents(),
|
||||||
Element::FixedWidthArea(inner) => inner,
|
Element::FixedWidthArea(inner) => inner.get_contents(),
|
||||||
Element::HorizontalRule(inner) => inner,
|
Element::HorizontalRule(inner) => inner.get_contents(),
|
||||||
Element::Keyword(inner) => inner,
|
Element::Keyword(inner) => inner.get_contents(),
|
||||||
Element::BabelCall(inner) => inner,
|
Element::BabelCall(inner) => inner.get_contents(),
|
||||||
Element::LatexEnvironment(inner) => inner,
|
Element::LatexEnvironment(inner) => inner.get_contents(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
match self {
|
||||||
|
Element::Paragraph(inner) => inner.get_post_blank(),
|
||||||
|
Element::PlainList(inner) => inner.get_post_blank(),
|
||||||
|
Element::CenterBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::QuoteBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::SpecialBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::DynamicBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::FootnoteDefinition(inner) => inner.get_post_blank(),
|
||||||
|
Element::Comment(inner) => inner.get_post_blank(),
|
||||||
|
Element::Drawer(inner) => inner.get_post_blank(),
|
||||||
|
Element::PropertyDrawer(inner) => inner.get_post_blank(),
|
||||||
|
Element::Table(inner) => inner.get_post_blank(),
|
||||||
|
Element::VerseBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::CommentBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::ExampleBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::ExportBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::SrcBlock(inner) => inner.get_post_blank(),
|
||||||
|
Element::Clock(inner) => inner.get_post_blank(),
|
||||||
|
Element::DiarySexp(inner) => inner.get_post_blank(),
|
||||||
|
Element::Planning(inner) => inner.get_post_blank(),
|
||||||
|
Element::FixedWidthArea(inner) => inner.get_post_blank(),
|
||||||
|
Element::HorizontalRule(inner) => inner.get_post_blank(),
|
||||||
|
Element::Keyword(inner) => inner.get_post_blank(),
|
||||||
|
Element::BabelCall(inner) => inner.get_post_blank(),
|
||||||
|
Element::LatexEnvironment(inner) => inner.get_post_blank(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
use super::StandardProperties;
|
|
||||||
|
|
||||||
pub trait GetStandardProperties<'s> {
|
|
||||||
// TODO: Can I eliminate this dynamic dispatch, perhaps using nominal generic structs? Low prioritiy since this is not used during parsing.
|
|
||||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s, I: StandardProperties<'s>> GetStandardProperties<'s> for I {
|
|
||||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ use super::lesser_element::TableCell;
|
|||||||
use super::AffiliatedKeywords;
|
use super::AffiliatedKeywords;
|
||||||
use super::Keyword;
|
use super::Keyword;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
|
use super::PostBlank;
|
||||||
use super::StandardProperties;
|
use super::StandardProperties;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -12,6 +13,8 @@ pub struct PlainList<'s> {
|
|||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub list_type: PlainListType,
|
pub list_type: PlainListType,
|
||||||
pub children: Vec<PlainListItem<'s>>,
|
pub children: Vec<PlainListItem<'s>>,
|
||||||
|
pub contents: Option<&'s str>, // TODO: Can contents ever be None?
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
@@ -34,6 +37,8 @@ pub struct PlainListItem<'s> {
|
|||||||
pub tag: Vec<Object<'s>>,
|
pub tag: Vec<Object<'s>>,
|
||||||
pub pre_blank: PlainListItemPreBlank,
|
pub pre_blank: PlainListItemPreBlank,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PlainListItemCounter = u16;
|
pub type PlainListItemCounter = u16;
|
||||||
@@ -51,6 +56,8 @@ pub struct CenterBlock<'s> {
|
|||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -58,6 +65,8 @@ pub struct QuoteBlock<'s> {
|
|||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -67,6 +76,8 @@ pub struct SpecialBlock<'s> {
|
|||||||
pub block_type: &'s str,
|
pub block_type: &'s str,
|
||||||
pub parameters: Option<&'s str>,
|
pub parameters: Option<&'s str>,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -76,11 +87,15 @@ pub struct DynamicBlock<'s> {
|
|||||||
pub block_name: &'s str,
|
pub block_name: &'s str,
|
||||||
pub parameters: Option<&'s str>,
|
pub parameters: Option<&'s str>,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FootnoteDefinition<'s> {
|
pub struct FootnoteDefinition<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub label: &'s str,
|
pub label: &'s str,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
@@ -92,12 +107,16 @@ pub struct Drawer<'s> {
|
|||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub drawer_name: &'s str,
|
pub drawer_name: &'s str,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PropertyDrawer<'s> {
|
pub struct PropertyDrawer<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub children: Vec<NodeProperty<'s>>,
|
pub children: Vec<NodeProperty<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -113,12 +132,15 @@ pub struct Table<'s> {
|
|||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub formulas: Vec<Keyword<'s>>,
|
pub formulas: Vec<Keyword<'s>>,
|
||||||
pub children: Vec<TableRow<'s>>,
|
pub children: Vec<TableRow<'s>>,
|
||||||
|
pub contents: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TableRow<'s> {
|
pub struct TableRow<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub children: Vec<TableCell<'s>>,
|
pub children: Vec<TableCell<'s>>,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -131,72 +153,208 @@ impl<'s> StandardProperties<'s> for PlainList<'s> {
|
|||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for PlainListItem<'s> {
|
impl<'s> StandardProperties<'s> for PlainListItem<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for CenterBlock<'s> {
|
impl<'s> StandardProperties<'s> for CenterBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
|
impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
|
impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
|
impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
|
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Drawer<'s> {
|
impl<'s> StandardProperties<'s> for Drawer<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
|
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for NodeProperty<'s> {
|
impl<'s> StandardProperties<'s> for NodeProperty<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Table<'s> {
|
impl<'s> StandardProperties<'s> for Table<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
Some(self.contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for TableRow<'s> {
|
impl<'s> StandardProperties<'s> for TableRow<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> PlainListItem<'s> {
|
impl<'s> PlainListItem<'s> {
|
||||||
|
|||||||
@@ -1,13 +1,32 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::combinator::map;
|
||||||
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::object::Object;
|
use super::object::Object;
|
||||||
use super::AffiliatedKeywords;
|
use super::AffiliatedKeywords;
|
||||||
use super::GetAffiliatedKeywords;
|
use super::GetAffiliatedKeywords;
|
||||||
use super::PlainText;
|
use super::PlainText;
|
||||||
|
use super::PostBlank;
|
||||||
use super::StandardProperties;
|
use super::StandardProperties;
|
||||||
use super::Timestamp;
|
use super::Timestamp;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::Res;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Paragraph<'s> {
|
pub struct Paragraph<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub children: Vec<Object<'s>>,
|
pub children: Vec<Object<'s>>,
|
||||||
}
|
}
|
||||||
@@ -16,12 +35,14 @@ pub struct Paragraph<'s> {
|
|||||||
pub struct Comment<'s> {
|
pub struct Comment<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub value: Vec<&'s str>,
|
pub value: Vec<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TableCell<'s> {
|
pub struct TableCell<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub children: Vec<Object<'s>>,
|
pub children: Vec<Object<'s>>,
|
||||||
|
pub contents: &'s str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -30,6 +51,8 @@ pub struct VerseBlock<'s> {
|
|||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub data: Option<&'s str>,
|
pub data: Option<&'s str>,
|
||||||
pub children: Vec<Object<'s>>,
|
pub children: Vec<Object<'s>>,
|
||||||
|
pub contents: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -37,6 +60,7 @@ pub struct CommentBlock<'s> {
|
|||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub contents: &'s str,
|
pub contents: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type CharOffsetInLine = u16;
|
pub type CharOffsetInLine = u16;
|
||||||
@@ -59,7 +83,8 @@ pub struct ExampleBlock<'s> {
|
|||||||
pub retain_labels: RetainLabels,
|
pub retain_labels: RetainLabels,
|
||||||
pub use_labels: bool,
|
pub use_labels: bool,
|
||||||
pub label_format: Option<&'s str>,
|
pub label_format: Option<&'s str>,
|
||||||
pub contents: String,
|
pub value: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -68,7 +93,8 @@ pub struct ExportBlock<'s> {
|
|||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub export_type: Option<&'s str>,
|
pub export_type: Option<&'s str>,
|
||||||
pub data: Option<&'s str>,
|
pub data: Option<&'s str>,
|
||||||
pub contents: String,
|
pub value: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -83,7 +109,8 @@ pub struct SrcBlock<'s> {
|
|||||||
pub retain_labels: RetainLabels,
|
pub retain_labels: RetainLabels,
|
||||||
pub use_labels: bool,
|
pub use_labels: bool,
|
||||||
pub label_format: Option<&'s str>,
|
pub label_format: Option<&'s str>,
|
||||||
pub contents: String,
|
pub value: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -98,6 +125,7 @@ pub struct Clock<'s> {
|
|||||||
pub timestamp: Timestamp<'s>,
|
pub timestamp: Timestamp<'s>,
|
||||||
pub duration: Option<&'s str>,
|
pub duration: Option<&'s str>,
|
||||||
pub status: ClockStatus,
|
pub status: ClockStatus,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -105,6 +133,7 @@ pub struct DiarySexp<'s> {
|
|||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub value: &'s str,
|
pub value: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -113,6 +142,7 @@ pub struct Planning<'s> {
|
|||||||
pub scheduled: Option<Timestamp<'s>>,
|
pub scheduled: Option<Timestamp<'s>>,
|
||||||
pub deadline: Option<Timestamp<'s>>,
|
pub deadline: Option<Timestamp<'s>>,
|
||||||
pub closed: Option<Timestamp<'s>>,
|
pub closed: Option<Timestamp<'s>>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -120,12 +150,14 @@ pub struct FixedWidthArea<'s> {
|
|||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub value: Vec<&'s str>,
|
pub value: Vec<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HorizontalRule<'s> {
|
pub struct HorizontalRule<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -134,6 +166,7 @@ pub struct Keyword<'s> {
|
|||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub key: &'s str,
|
pub key: &'s str,
|
||||||
pub value: &'s str,
|
pub value: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -145,6 +178,7 @@ pub struct BabelCall<'s> {
|
|||||||
pub inside_header: Option<&'s str>,
|
pub inside_header: Option<&'s str>,
|
||||||
pub arguments: Option<&'s str>,
|
pub arguments: Option<&'s str>,
|
||||||
pub end_header: Option<&'s str>,
|
pub end_header: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -152,6 +186,7 @@ pub struct LatexEnvironment<'s> {
|
|||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub value: &'s str,
|
pub value: &'s str,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A line number used in switches to lesser blocks.
|
/// A line number used in switches to lesser blocks.
|
||||||
@@ -169,11 +204,18 @@ impl<'s> Paragraph<'s> {
|
|||||||
/// Generate a paragraph of the passed in text with no additional properties.
|
/// Generate a paragraph of the passed in text with no additional properties.
|
||||||
///
|
///
|
||||||
/// This is used for elements that support an "empty" content like greater blocks.
|
/// This is used for elements that support an "empty" content like greater blocks.
|
||||||
pub(crate) fn of_text(input: &'s str) -> Self {
|
pub(crate) fn of_text(
|
||||||
|
source: &'s str,
|
||||||
|
body: &'s str,
|
||||||
|
contents: Option<&'s str>,
|
||||||
|
post_blank: Option<&'s str>,
|
||||||
|
) -> Self {
|
||||||
Paragraph {
|
Paragraph {
|
||||||
source: input,
|
source,
|
||||||
|
contents,
|
||||||
|
post_blank,
|
||||||
affiliated_keywords: AffiliatedKeywords::default(),
|
affiliated_keywords: AffiliatedKeywords::default(),
|
||||||
children: vec![Object::PlainText(PlainText { source: input })],
|
children: vec![Object::PlainText(PlainText { source: body })],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,92 +224,280 @@ impl<'s> StandardProperties<'s> for Paragraph<'s> {
|
|||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for TableCell<'s> {
|
impl<'s> StandardProperties<'s> for TableCell<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
Some(self.contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Comment<'s> {
|
impl<'s> StandardProperties<'s> for Comment<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for VerseBlock<'s> {
|
impl<'s> StandardProperties<'s> for VerseBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
Some(self.contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
|
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
|
impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for ExportBlock<'s> {
|
impl<'s> StandardProperties<'s> for ExportBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for SrcBlock<'s> {
|
impl<'s> StandardProperties<'s> for SrcBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Clock<'s> {
|
impl<'s> StandardProperties<'s> for Clock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for DiarySexp<'s> {
|
impl<'s> StandardProperties<'s> for DiarySexp<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Planning<'s> {
|
impl<'s> StandardProperties<'s> for Planning<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
|
impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
|
impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Keyword<'s> {
|
impl<'s> StandardProperties<'s> for Keyword<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for BabelCall<'s> {
|
impl<'s> StandardProperties<'s> for BabelCall<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
|
impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Comment<'s> {
|
impl<'s> Comment<'s> {
|
||||||
@@ -284,13 +514,14 @@ impl<'s> Comment<'s> {
|
|||||||
|
|
||||||
impl<'s> FixedWidthArea<'s> {
|
impl<'s> FixedWidthArea<'s> {
|
||||||
pub fn get_value(&self) -> String {
|
pub fn get_value(&self) -> String {
|
||||||
let final_size = self.value.iter().map(|line| line.len()).sum();
|
self.value.join("\n")
|
||||||
let mut ret = String::with_capacity(final_size);
|
|
||||||
for line in &self.value {
|
|
||||||
ret.push_str(line);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ret
|
impl<'s> ExampleBlock<'s> {
|
||||||
|
/// Gets the contents of the lesser block, handling the escaping of lines with leading commas.
|
||||||
|
pub fn get_value(&self) -> Cow<'s, str> {
|
||||||
|
lesser_block_content(self.value).expect("This parser should never fail.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,6 +532,18 @@ impl<'s> ExportBlock<'s> {
|
|||||||
pub fn get_export_type(&self) -> Option<String> {
|
pub fn get_export_type(&self) -> Option<String> {
|
||||||
self.export_type.map(|s| s.to_uppercase())
|
self.export_type.map(|s| s.to_uppercase())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the contents of the lesser block, handling the escaping of lines with leading commas.
|
||||||
|
pub fn get_value(&self) -> Cow<'s, str> {
|
||||||
|
lesser_block_content(self.value).expect("This parser should never fail.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> SrcBlock<'s> {
|
||||||
|
/// Gets the contents of the lesser block, handling the escaping of lines with leading commas.
|
||||||
|
pub fn get_value(&self) -> Cow<'s, str> {
|
||||||
|
lesser_block_content(self.value).expect("This parser should never fail.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> GetAffiliatedKeywords<'s> for Paragraph<'s> {
|
impl<'s> GetAffiliatedKeywords<'s> for Paragraph<'s> {
|
||||||
@@ -374,3 +617,82 @@ impl<'s> GetAffiliatedKeywords<'s> for VerseBlock<'s> {
|
|||||||
&self.affiliated_keywords
|
&self.affiliated_keywords
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ContentState {
|
||||||
|
Normal,
|
||||||
|
Modified(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn lesser_block_content<'s>(input: &'s str) -> Result<Cow<'s, str>, CustomError> {
|
||||||
|
let mut state = ContentState::Normal;
|
||||||
|
let mut remaining = input;
|
||||||
|
loop {
|
||||||
|
if remaining.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (remain, (pre_escape_whitespace, line)) =
|
||||||
|
content_line(remaining).map_err(|err| match err {
|
||||||
|
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
|
||||||
|
nom::Err::Error(e) => e,
|
||||||
|
nom::Err::Failure(e) => e,
|
||||||
|
})?;
|
||||||
|
if let Some(val) = pre_escape_whitespace {
|
||||||
|
if let ContentState::Modified(ref mut ret) = state {
|
||||||
|
ret.push_str(val);
|
||||||
|
} else {
|
||||||
|
let mut ret = String::new();
|
||||||
|
ret.push_str(get_str_until(input, remaining));
|
||||||
|
ret.push_str(val);
|
||||||
|
state = ContentState::Modified(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let ContentState::Modified(ref mut ret) = state {
|
||||||
|
ret.push_str(line);
|
||||||
|
}
|
||||||
|
remaining = remain;
|
||||||
|
}
|
||||||
|
|
||||||
|
match state {
|
||||||
|
ContentState::Normal => Ok(Cow::Borrowed(get_str_until(input, remaining))),
|
||||||
|
ContentState::Modified(ret) => Ok(Cow::Owned(ret)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn content_line<'s>(input: &'s str) -> Res<&'s str, (Option<&'s str>, &'s str)> {
|
||||||
|
let (remaining, pre_escape_whitespace) = opt(map(
|
||||||
|
tuple((
|
||||||
|
recognize(tuple((
|
||||||
|
space0,
|
||||||
|
many_till(
|
||||||
|
tag(","),
|
||||||
|
peek(tuple((tag(","), alt((tag("#+"), tag("*")))))),
|
||||||
|
),
|
||||||
|
))),
|
||||||
|
tag(","),
|
||||||
|
)),
|
||||||
|
|(pre_comma, _)| pre_comma,
|
||||||
|
))(input)?;
|
||||||
|
let (remaining, line_post_escape) = recognize(many_till(anychar, line_ending))(remaining)?;
|
||||||
|
Ok((remaining, (pre_escape_whitespace, line_post_escape)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
/// Check if the child string slice is a slice of the parent string slice.
|
||||||
|
pub(crate) fn is_slice_of(parent: &str, child: &str) -> bool {
|
||||||
|
let parent_start = parent.as_ptr() as usize;
|
||||||
|
let parent_end = parent_start + parent.len();
|
||||||
|
let child_start = child.as_ptr() as usize;
|
||||||
|
let child_end = child_start + child.len();
|
||||||
|
child_start >= parent_start && child_end <= parent_end
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn get_str_until<'s>(parent: &'s str, child: &'s str) -> &'s str {
|
||||||
|
debug_assert!(is_slice_of(parent, child));
|
||||||
|
let parent_start = parent.as_ptr() as usize;
|
||||||
|
let child_start = child.as_ptr() as usize;
|
||||||
|
&parent[..(child_start - parent_start)]
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,19 +2,18 @@ mod affiliated_keyword;
|
|||||||
mod ast_node;
|
mod ast_node;
|
||||||
mod document;
|
mod document;
|
||||||
mod element;
|
mod element;
|
||||||
mod get_standard_properties;
|
|
||||||
mod greater_element;
|
mod greater_element;
|
||||||
mod lesser_element;
|
mod lesser_element;
|
||||||
mod macros;
|
mod macros;
|
||||||
mod object;
|
mod object;
|
||||||
mod source;
|
mod remove_trailing;
|
||||||
mod standard_properties;
|
mod standard_properties;
|
||||||
mod util;
|
mod util;
|
||||||
pub use affiliated_keyword::AffiliatedKeyword;
|
pub use affiliated_keyword::AffiliatedKeyword;
|
||||||
pub use affiliated_keyword::AffiliatedKeywordValue;
|
pub use affiliated_keyword::AffiliatedKeywordValue;
|
||||||
pub use affiliated_keyword::AffiliatedKeywords;
|
pub use affiliated_keyword::AffiliatedKeywords;
|
||||||
pub use affiliated_keyword::GetAffiliatedKeywords;
|
pub use affiliated_keyword::GetAffiliatedKeywords;
|
||||||
pub(crate) use ast_node::AstNode;
|
pub use ast_node::AstNode;
|
||||||
pub use document::Document;
|
pub use document::Document;
|
||||||
pub use document::DocumentElement;
|
pub use document::DocumentElement;
|
||||||
pub use document::Heading;
|
pub use document::Heading;
|
||||||
@@ -23,7 +22,6 @@ pub use document::PriorityCookie;
|
|||||||
pub use document::Section;
|
pub use document::Section;
|
||||||
pub use document::TodoKeywordType;
|
pub use document::TodoKeywordType;
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
pub use get_standard_properties::GetStandardProperties;
|
|
||||||
pub use greater_element::CenterBlock;
|
pub use greater_element::CenterBlock;
|
||||||
pub use greater_element::CheckboxType;
|
pub use greater_element::CheckboxType;
|
||||||
pub use greater_element::Drawer;
|
pub use greater_element::Drawer;
|
||||||
@@ -113,5 +111,5 @@ pub use object::WarningDelay;
|
|||||||
pub use object::WarningDelayType;
|
pub use object::WarningDelayType;
|
||||||
pub use object::Year;
|
pub use object::Year;
|
||||||
pub use object::YearInner;
|
pub use object::YearInner;
|
||||||
pub(crate) use source::SetSource;
|
pub use standard_properties::PostBlank;
|
||||||
pub use standard_properties::StandardProperties;
|
pub use standard_properties::StandardProperties;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
56
src/types/remove_trailing.rs
Normal file
56
src/types/remove_trailing.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
pub(crate) trait RemoveTrailing: Iterator + Sized {
|
||||||
|
fn remove_trailing<R: Into<usize>>(self, amount_to_remove: R) -> RemoveTrailingIter<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> RemoveTrailing for I
|
||||||
|
where
|
||||||
|
I: Iterator,
|
||||||
|
{
|
||||||
|
fn remove_trailing<R: Into<usize>>(self, amount_to_remove: R) -> RemoveTrailingIter<Self> {
|
||||||
|
RemoveTrailingIter {
|
||||||
|
inner: self,
|
||||||
|
buffer: Vec::new(),
|
||||||
|
next_to_pop: 0,
|
||||||
|
amount_to_remove: amount_to_remove.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct RemoveTrailingIter<I: Iterator> {
|
||||||
|
inner: I,
|
||||||
|
buffer: Vec<I::Item>,
|
||||||
|
next_to_pop: usize,
|
||||||
|
amount_to_remove: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: Iterator> Iterator for RemoveTrailingIter<I> {
|
||||||
|
type Item = I::Item;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.buffer.len() < self.amount_to_remove {
|
||||||
|
self.buffer.reserve_exact(self.amount_to_remove);
|
||||||
|
}
|
||||||
|
while self.buffer.len() < self.amount_to_remove {
|
||||||
|
if let Some(elem) = self.inner.next() {
|
||||||
|
self.buffer.push(elem);
|
||||||
|
} else {
|
||||||
|
// The inner was smaller than amount_to_remove, so never return anything.
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_value = self.inner.next();
|
||||||
|
if self.amount_to_remove == 0 {
|
||||||
|
return new_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(new_value) = new_value {
|
||||||
|
let ret = std::mem::replace(&mut self.buffer[self.next_to_pop], new_value);
|
||||||
|
self.next_to_pop = (self.next_to_pop + 1) % self.amount_to_remove;
|
||||||
|
Some(ret)
|
||||||
|
} else {
|
||||||
|
// We have exactly the amount in the buffer than we wanted to cut off, so stop returning values.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pub(crate) trait SetSource<'s> {
|
|
||||||
fn set_source(&mut self, source: &'s str);
|
|
||||||
}
|
|
||||||
@@ -5,10 +5,15 @@ pub trait StandardProperties<'s> {
|
|||||||
/// This corresponds to :begin to :end in upstream org-mode's standard properties.
|
/// This corresponds to :begin to :end in upstream org-mode's standard properties.
|
||||||
fn get_source<'b>(&'b self) -> &'s str;
|
fn get_source<'b>(&'b self) -> &'s str;
|
||||||
|
|
||||||
// Get the slice of the AST node's contents.
|
/// Get the slice of the AST node's contents.
|
||||||
//
|
///
|
||||||
// This corresponds to :contents-begin to :contents-end
|
/// This corresponds to :contents-begin to :contents-end
|
||||||
// fn get_contents(&'s self) -> &'s str;
|
fn get_contents<'b>(&'b self) -> Option<&'s str>;
|
||||||
|
|
||||||
|
/// Get the ast node's post-blank.
|
||||||
|
///
|
||||||
|
/// For objects this is a count of the characters of whitespace after the object. For elements this is a count of the line breaks following an element.
|
||||||
|
fn get_post_blank(&self) -> PostBlank;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Write some debugging code to alert when any of the unknown fields below are non-nil in our test data so we can see what these fields represent.
|
// TODO: Write some debugging code to alert when any of the unknown fields below are non-nil in our test data so we can see what these fields represent.
|
||||||
@@ -56,3 +61,5 @@ pub trait StandardProperties<'s> {
|
|||||||
// X :parent - Some weird numeric reference to the containing object. Since we output a tree structure, I do not see any value in including this, especially considering the back-references would be a nightmare in rust.
|
// X :parent - Some weird numeric reference to the containing object. Since we output a tree structure, I do not see any value in including this, especially considering the back-references would be a nightmare in rust.
|
||||||
|
|
||||||
// Special case: Plain text. Plain text counts :begin and :end from the start of the text (so :begin is always 0 AFAICT) and instead of including the full set of standard properties, it only includes :begin, :end, and :parent.
|
// Special case: Plain text. Plain text counts :begin and :end from the start of the text (so :begin is always 0 AFAICT) and instead of including the full set of standard properties, it only includes :begin, :end, and :parent.
|
||||||
|
|
||||||
|
pub type PostBlank = u8;
|
||||||
|
|||||||
@@ -2,28 +2,53 @@ use std::path::Path;
|
|||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::context::HeadlineLevelFilter;
|
|
||||||
use crate::settings::GlobalSettings;
|
use crate::settings::GlobalSettings;
|
||||||
|
use crate::settings::HeadlineLevelFilter;
|
||||||
|
|
||||||
/// Generate elisp to configure org-mode parsing settings
|
pub async fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
///
|
eprintln!("Using emacs version: {}", get_emacs_version().await?.trim());
|
||||||
/// Currently only org-list-allow-alphabetical is supported.
|
eprintln!(
|
||||||
fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
|
"Using org-mode version: {}",
|
||||||
// This string concatenation is wildly inefficient but its only called in tests 🤷.
|
get_org_mode_version().await?.trim()
|
||||||
let mut ret = "".to_owned();
|
);
|
||||||
if global_settings.list_allow_alphabetical {
|
Ok(())
|
||||||
ret += "(setq org-list-allow-alphabetical t)\n"
|
}
|
||||||
}
|
|
||||||
if global_settings.tab_width != crate::settings::DEFAULT_TAB_WIDTH {
|
pub(crate) async fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||||
ret += format!("(setq-default tab-width {})", global_settings.tab_width).as_str();
|
let elisp_script = r#"(progn
|
||||||
}
|
(message "%s" (version))
|
||||||
if global_settings.odd_levels_only != HeadlineLevelFilter::default() {
|
)"#;
|
||||||
ret += match global_settings.odd_levels_only {
|
let mut cmd = Command::new("emacs");
|
||||||
HeadlineLevelFilter::Odd => "(setq org-odd-levels-only t)\n",
|
let cmd = cmd
|
||||||
HeadlineLevelFilter::OddEven => "(setq org-odd-levels-only nil)\n",
|
.arg("-q")
|
||||||
};
|
.arg("--no-site-file")
|
||||||
}
|
.arg("--no-splash")
|
||||||
ret
|
.arg("--batch")
|
||||||
|
.arg("--eval")
|
||||||
|
.arg(elisp_script);
|
||||||
|
|
||||||
|
let out = cmd.output().await?;
|
||||||
|
out.status.exit_ok()?;
|
||||||
|
Ok(String::from_utf8(out.stderr)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async 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 cmd = cmd
|
||||||
|
.arg("-q")
|
||||||
|
.arg("--no-site-file")
|
||||||
|
.arg("--no-splash")
|
||||||
|
.arg("--batch")
|
||||||
|
.arg("--eval")
|
||||||
|
.arg(elisp_script);
|
||||||
|
|
||||||
|
let out = cmd.output().await?;
|
||||||
|
out.status.exit_ok()?;
|
||||||
|
Ok(String::from_utf8(out.stderr)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn emacs_parse_anonymous_org_document<'g, 's, C>(
|
pub(crate) async fn emacs_parse_anonymous_org_document<'g, 's, C>(
|
||||||
@@ -144,39 +169,23 @@ where
|
|||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
|
/// Generate elisp to configure org-mode parsing settings
|
||||||
let elisp_script = r#"(progn
|
///
|
||||||
(message "%s" (version))
|
/// Currently only org-list-allow-alphabetical is supported.
|
||||||
)"#;
|
fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
|
||||||
let mut cmd = Command::new("emacs");
|
// This string concatenation is wildly inefficient but its only called in tests 🤷.
|
||||||
let cmd = cmd
|
let mut ret = "".to_owned();
|
||||||
.arg("-q")
|
if global_settings.list_allow_alphabetical {
|
||||||
.arg("--no-site-file")
|
ret += "(setq org-list-allow-alphabetical t)\n"
|
||||||
.arg("--no-splash")
|
}
|
||||||
.arg("--batch")
|
if global_settings.tab_width != crate::settings::DEFAULT_TAB_WIDTH {
|
||||||
.arg("--eval")
|
ret += format!("(setq-default tab-width {})", global_settings.tab_width).as_str();
|
||||||
.arg(elisp_script);
|
}
|
||||||
|
if global_settings.odd_levels_only != HeadlineLevelFilter::default() {
|
||||||
let out = cmd.output().await?;
|
ret += match global_settings.odd_levels_only {
|
||||||
out.status.exit_ok()?;
|
HeadlineLevelFilter::Odd => "(setq org-odd-levels-only t)\n",
|
||||||
Ok(String::from_utf8(out.stderr)?)
|
HeadlineLevelFilter::OddEven => "(setq org-odd-levels-only nil)\n",
|
||||||
}
|
};
|
||||||
|
}
|
||||||
pub async fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
|
ret
|
||||||
let elisp_script = r#"(progn
|
|
||||||
(org-mode)
|
|
||||||
(message "%s" (org-version nil t nil))
|
|
||||||
)"#;
|
|
||||||
let mut cmd = Command::new("emacs");
|
|
||||||
let cmd = cmd
|
|
||||||
.arg("-q")
|
|
||||||
.arg("--no-site-file")
|
|
||||||
.arg("--no-splash")
|
|
||||||
.arg("--batch")
|
|
||||||
.arg("--eval")
|
|
||||||
.arg(elisp_script);
|
|
||||||
|
|
||||||
let out = cmd.output().await?;
|
|
||||||
out.status.exit_ok()?;
|
|
||||||
Ok(String::from_utf8(out.stderr)?)
|
|
||||||
}
|
}
|
||||||
14
src/util/elisp/mod.rs
Normal file
14
src/util/elisp/mod.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
mod sexp;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub use sexp::sexp;
|
||||||
|
pub(crate) use sexp::unquote;
|
||||||
|
#[cfg(feature = "wasm_test")]
|
||||||
|
pub(crate) use sexp::TextWithProperties;
|
||||||
|
pub use sexp::Token;
|
||||||
|
#[cfg(feature = "compare")]
|
||||||
|
pub(crate) use util::get_emacs_standard_properties;
|
||||||
|
#[cfg(feature = "wasm_test")]
|
||||||
|
pub(crate) use util::maybe_token_to_usize;
|
||||||
|
#[cfg(feature = "wasm_test")]
|
||||||
|
pub(crate) use util::EmacsStandardProperties;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
@@ -36,12 +37,6 @@ pub struct TextWithProperties<'s> {
|
|||||||
pub(crate) properties: Vec<Token<'s>>,
|
pub(crate) properties: Vec<Token<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ParseState {
|
|
||||||
Normal,
|
|
||||||
Escape,
|
|
||||||
Octal(Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> Token<'s> {
|
impl<'s> Token<'s> {
|
||||||
pub(crate) fn as_vector<'p>(
|
pub(crate) fn as_vector<'p>(
|
||||||
&'p self,
|
&'p self,
|
||||||
@@ -66,6 +61,7 @@ impl<'s> Token<'s> {
|
|||||||
}?)
|
}?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "compare")]
|
||||||
pub(crate) fn as_text<'p>(
|
pub(crate) fn as_text<'p>(
|
||||||
&'p self,
|
&'p self,
|
||||||
) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
||||||
@@ -117,8 +113,27 @@ fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
|||||||
&input[..offset]
|
&input[..offset]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
#[derive(Debug)]
|
||||||
let mut out: Vec<u8> = Vec::with_capacity(text.len());
|
enum UnquoteState {
|
||||||
|
Normal,
|
||||||
|
Escape,
|
||||||
|
HasEscape {
|
||||||
|
out: Vec<u8>,
|
||||||
|
},
|
||||||
|
HasEscapeEscape {
|
||||||
|
out: Vec<u8>,
|
||||||
|
},
|
||||||
|
Octal {
|
||||||
|
octal_begin_offset: usize,
|
||||||
|
octal: Vec<u8>,
|
||||||
|
},
|
||||||
|
HasEscapeOctal {
|
||||||
|
out: Vec<u8>,
|
||||||
|
octal: Vec<u8>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unquote(text: &str) -> Result<Cow<'_, str>, Box<dyn std::error::Error>> {
|
||||||
if !text.starts_with('"') {
|
if !text.starts_with('"') {
|
||||||
return Err("Quoted text does not start with quote.".into());
|
return Err("Quoted text does not start with quote.".into());
|
||||||
}
|
}
|
||||||
@@ -126,54 +141,143 @@ pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>>
|
|||||||
return Err("Quoted text does not end with quote.".into());
|
return Err("Quoted text does not end with quote.".into());
|
||||||
}
|
}
|
||||||
let interior_text = &text[1..(text.len() - 1)];
|
let interior_text = &text[1..(text.len() - 1)];
|
||||||
let mut state = ParseState::Normal;
|
let mut state = UnquoteState::Normal;
|
||||||
for current_char in interior_text.bytes() {
|
for (offset, current_char) in interior_text.bytes().enumerate() {
|
||||||
// Check to see if octal finished
|
// Check to see if octal finished
|
||||||
state = match (state, current_char) {
|
state = match (state, current_char) {
|
||||||
(ParseState::Octal(octal), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => {
|
(
|
||||||
ParseState::Octal(octal)
|
UnquoteState::Octal {
|
||||||
|
octal_begin_offset,
|
||||||
|
octal,
|
||||||
|
},
|
||||||
|
b'0'..=b'7',
|
||||||
|
) if octal.len() < MAX_OCTAL_LENGTH => UnquoteState::Octal {
|
||||||
|
octal_begin_offset,
|
||||||
|
octal,
|
||||||
|
},
|
||||||
|
(
|
||||||
|
UnquoteState::Octal {
|
||||||
|
octal_begin_offset,
|
||||||
|
octal,
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
) => {
|
||||||
|
let octal_number_string = String::from_utf8(octal)?;
|
||||||
|
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
|
||||||
|
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
|
||||||
|
out.extend_from_slice(&interior_text.as_bytes()[..octal_begin_offset]);
|
||||||
|
out.push(decoded_byte);
|
||||||
|
UnquoteState::HasEscape { out }
|
||||||
}
|
}
|
||||||
(ParseState::Octal(octal), _) => {
|
(UnquoteState::HasEscapeOctal { out, octal }, b'0'..=b'7')
|
||||||
|
if octal.len() < MAX_OCTAL_LENGTH =>
|
||||||
|
{
|
||||||
|
UnquoteState::HasEscapeOctal { out, octal }
|
||||||
|
}
|
||||||
|
(UnquoteState::HasEscapeOctal { mut out, octal }, _) => {
|
||||||
let octal_number_string = String::from_utf8(octal)?;
|
let octal_number_string = String::from_utf8(octal)?;
|
||||||
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
|
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
|
||||||
out.push(decoded_byte);
|
out.push(decoded_byte);
|
||||||
ParseState::Normal
|
UnquoteState::HasEscape { out }
|
||||||
}
|
}
|
||||||
(state, _) => state,
|
(state, _) => state,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = match (state, current_char) {
|
state = match (state, current_char) {
|
||||||
(ParseState::Normal, b'\\') => ParseState::Escape,
|
(UnquoteState::Normal, b'\\') => UnquoteState::Escape,
|
||||||
(ParseState::Normal, _) => {
|
(UnquoteState::Normal, _) => UnquoteState::Normal,
|
||||||
|
(UnquoteState::HasEscape { out }, b'\\') => UnquoteState::HasEscapeEscape { out },
|
||||||
|
(UnquoteState::HasEscape { mut out }, _) => {
|
||||||
out.push(current_char);
|
out.push(current_char);
|
||||||
ParseState::Normal
|
UnquoteState::HasEscape { out }
|
||||||
}
|
}
|
||||||
(ParseState::Escape, b'n') => {
|
(UnquoteState::Escape, b'n') => {
|
||||||
|
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
|
||||||
|
// Subtract 1 from offset to account for backslash.
|
||||||
|
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
|
||||||
out.push(b'\n');
|
out.push(b'\n');
|
||||||
ParseState::Normal
|
UnquoteState::HasEscape { out }
|
||||||
}
|
}
|
||||||
(ParseState::Escape, b'\\') => {
|
(UnquoteState::HasEscapeEscape { mut out }, b'n') => {
|
||||||
|
out.push(b'\n');
|
||||||
|
UnquoteState::HasEscape { out }
|
||||||
|
}
|
||||||
|
(UnquoteState::Escape, b'\\') => {
|
||||||
|
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
|
||||||
|
// Subtract 1 from offset to account for backslash.
|
||||||
|
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
|
||||||
out.push(b'\\');
|
out.push(b'\\');
|
||||||
ParseState::Normal
|
UnquoteState::HasEscape { out }
|
||||||
}
|
}
|
||||||
(ParseState::Escape, b'"') => {
|
(UnquoteState::HasEscapeEscape { mut out }, b'\\') => {
|
||||||
|
out.push(b'\\');
|
||||||
|
UnquoteState::HasEscape { out }
|
||||||
|
}
|
||||||
|
(UnquoteState::Escape, b'"') => {
|
||||||
|
let mut out: Vec<u8> = Vec::with_capacity(interior_text.len());
|
||||||
|
// Subtract 1 from offset to account for backslash.
|
||||||
|
out.extend_from_slice(&interior_text.as_bytes()[..(offset - 1)]);
|
||||||
out.push(b'"');
|
out.push(b'"');
|
||||||
ParseState::Normal
|
UnquoteState::HasEscape { out }
|
||||||
}
|
}
|
||||||
(ParseState::Escape, b'0'..=b'7') => {
|
(UnquoteState::HasEscapeEscape { mut out }, b'"') => {
|
||||||
|
out.push(b'"');
|
||||||
|
UnquoteState::HasEscape { out }
|
||||||
|
}
|
||||||
|
(UnquoteState::Escape, b'0'..=b'7') => {
|
||||||
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
|
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
|
||||||
octal.push(current_char);
|
octal.push(current_char);
|
||||||
ParseState::Octal(octal)
|
// Substract 1 from offset to account for backslash
|
||||||
|
UnquoteState::Octal {
|
||||||
|
octal_begin_offset: offset - 1,
|
||||||
|
octal,
|
||||||
}
|
}
|
||||||
(ParseState::Octal(mut octal), b'0'..=b'7') => {
|
}
|
||||||
|
(UnquoteState::HasEscapeEscape { out }, b'0'..=b'7') => {
|
||||||
|
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
|
||||||
octal.push(current_char);
|
octal.push(current_char);
|
||||||
ParseState::Octal(octal)
|
// Substract 1 from offset to account for backslash
|
||||||
|
UnquoteState::HasEscapeOctal { out, octal }
|
||||||
}
|
}
|
||||||
_ => panic!("Invalid state unquoting string."),
|
(
|
||||||
|
UnquoteState::Octal {
|
||||||
|
octal_begin_offset,
|
||||||
|
mut octal,
|
||||||
|
},
|
||||||
|
b'0'..=b'7',
|
||||||
|
) => {
|
||||||
|
octal.push(current_char);
|
||||||
|
UnquoteState::Octal {
|
||||||
|
octal_begin_offset,
|
||||||
|
octal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(UnquoteState::HasEscapeOctal { out, mut octal }, b'0'..=b'7') => {
|
||||||
|
octal.push(current_char);
|
||||||
|
UnquoteState::HasEscapeOctal { out, octal }
|
||||||
|
}
|
||||||
|
(state, _) => panic!(
|
||||||
|
"Invalid state unquoting string: {:?} | {} | {:?}",
|
||||||
|
state, offset, interior_text
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(String::from_utf8(out)?)
|
match state {
|
||||||
|
UnquoteState::Normal | UnquoteState::Escape | UnquoteState::Octal { .. } => {
|
||||||
|
Ok(Cow::Borrowed(interior_text))
|
||||||
|
}
|
||||||
|
UnquoteState::HasEscape { out } => Ok(Cow::Owned(String::from_utf8(out)?)),
|
||||||
|
UnquoteState::HasEscapeEscape { mut out } => {
|
||||||
|
out.push(b'\\');
|
||||||
|
Ok(Cow::Owned(String::from_utf8(out)?))
|
||||||
|
}
|
||||||
|
UnquoteState::HasEscapeOctal { mut out, octal } => {
|
||||||
|
out.push(b'\\');
|
||||||
|
out.extend(octal);
|
||||||
|
Ok(Cow::Owned(String::from_utf8(out)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
76
src/util/elisp/util.rs
Normal file
76
src/util/elisp/util.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use super::Token;
|
||||||
|
|
||||||
|
pub(crate) 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))?
|
||||||
|
.and_then(|val| {
|
||||||
|
if val == "nil" {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(val.parse::<usize>())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct EmacsStandardProperties {
|
||||||
|
pub(crate) begin: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) post_affiliated: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) contents_begin: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) contents_end: Option<usize>,
|
||||||
|
pub(crate) end: Option<usize>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) post_blank: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "compare")]
|
||||||
|
pub(crate) fn get_emacs_standard_properties(
|
||||||
|
emacs: &Token<'_>,
|
||||||
|
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
|
||||||
|
let children = emacs.as_list()?;
|
||||||
|
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
|
||||||
|
let attributes_map = attributes_child.as_map()?;
|
||||||
|
let standard_properties = attributes_map.get(":standard-properties");
|
||||||
|
Ok(if standard_properties.is_some() {
|
||||||
|
let mut std_props = standard_properties
|
||||||
|
.expect("if statement proves its Some")
|
||||||
|
.as_vector()?
|
||||||
|
.iter();
|
||||||
|
let begin = maybe_token_to_usize(std_props.next())?;
|
||||||
|
let post_affiliated = maybe_token_to_usize(std_props.next())?;
|
||||||
|
let contents_begin = maybe_token_to_usize(std_props.next())?;
|
||||||
|
let contents_end = maybe_token_to_usize(std_props.next())?;
|
||||||
|
let end = maybe_token_to_usize(std_props.next())?;
|
||||||
|
let post_blank = maybe_token_to_usize(std_props.next())?;
|
||||||
|
EmacsStandardProperties {
|
||||||
|
begin,
|
||||||
|
post_affiliated,
|
||||||
|
contents_begin,
|
||||||
|
contents_end,
|
||||||
|
end,
|
||||||
|
post_blank,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let begin = maybe_token_to_usize(attributes_map.get(":begin").copied())?;
|
||||||
|
let end = maybe_token_to_usize(attributes_map.get(":end").copied())?;
|
||||||
|
let contents_begin = maybe_token_to_usize(attributes_map.get(":contents-begin").copied())?;
|
||||||
|
let contents_end = maybe_token_to_usize(attributes_map.get(":contents-end").copied())?;
|
||||||
|
let post_blank = maybe_token_to_usize(attributes_map.get(":post-blank").copied())?;
|
||||||
|
let post_affiliated =
|
||||||
|
maybe_token_to_usize(attributes_map.get(":post-affiliated").copied())?;
|
||||||
|
EmacsStandardProperties {
|
||||||
|
begin,
|
||||||
|
post_affiliated,
|
||||||
|
contents_begin,
|
||||||
|
contents_end,
|
||||||
|
end,
|
||||||
|
post_blank,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
8
src/util/mod.rs
Normal file
8
src/util/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#[cfg(any(feature = "compare", feature = "wasm_test"))]
|
||||||
|
pub mod cli;
|
||||||
|
#[cfg(any(feature = "compare", feature = "wasm_test"))]
|
||||||
|
pub mod elisp;
|
||||||
|
#[cfg(any(feature = "compare", feature = "wasm", feature = "wasm_test"))]
|
||||||
|
pub mod elisp_fact;
|
||||||
|
#[cfg(any(feature = "compare", feature = "wasm_test"))]
|
||||||
|
pub mod terminal;
|
||||||
42
src/util/terminal.rs
Normal file
42
src/util/terminal.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
fn should_use_color() -> bool {
|
||||||
|
!std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn foreground_color(red: u8, green: u8, blue: u8) -> Cow<'static, str> {
|
||||||
|
if should_use_color() {
|
||||||
|
format!(
|
||||||
|
"\x1b[38;2;{red};{green};{blue}m",
|
||||||
|
red = red,
|
||||||
|
green = green,
|
||||||
|
blue = blue
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
Cow::from("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn background_color(red: u8, green: u8, blue: u8) -> Cow<'static, str> {
|
||||||
|
if should_use_color() {
|
||||||
|
format!(
|
||||||
|
"\x1b[48;2;{red};{green};{blue}m",
|
||||||
|
red = red,
|
||||||
|
green = green,
|
||||||
|
blue = blue
|
||||||
|
)
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
Cow::from("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn reset_color() -> &'static str {
|
||||||
|
if should_use_color() {
|
||||||
|
"\x1b[0m"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/wasm/additional_property.rs
Normal file
95
src/wasm/additional_property.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::macros::to_wasm;
|
||||||
|
use super::to_wasm::ToWasm;
|
||||||
|
use super::WasmAstNode;
|
||||||
|
use crate::types::AffiliatedKeywordValue;
|
||||||
|
use crate::types::AffiliatedKeywords;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum AdditionalPropertyValue {
|
||||||
|
SingleString(String),
|
||||||
|
ListOfStrings(Vec<String>),
|
||||||
|
OptionalPair {
|
||||||
|
optval: Option<String>,
|
||||||
|
val: String,
|
||||||
|
},
|
||||||
|
ObjectTree {
|
||||||
|
#[serde(rename = "object-tree")]
|
||||||
|
object_tree: Vec<(Option<Vec<WasmAstNode>>, Vec<WasmAstNode>)>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||||
|
pub struct AdditionalProperties {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub(crate) properties: HashMap<String, AdditionalPropertyValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
to_wasm!(
|
||||||
|
AdditionalProperties,
|
||||||
|
AffiliatedKeywords<'s>,
|
||||||
|
original,
|
||||||
|
wasm_context,
|
||||||
|
{
|
||||||
|
let mut additional_properties = AdditionalProperties::default();
|
||||||
|
for (name, val) in original.keywords.iter() {
|
||||||
|
let converted_val = match val {
|
||||||
|
AffiliatedKeywordValue::SingleString(val) => {
|
||||||
|
AdditionalPropertyValue::SingleString((*val).to_owned())
|
||||||
|
}
|
||||||
|
AffiliatedKeywordValue::ListOfStrings(val) => {
|
||||||
|
AdditionalPropertyValue::ListOfStrings(
|
||||||
|
val.iter().map(|s| (*s).to_owned()).collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AffiliatedKeywordValue::OptionalPair { optval, val } => {
|
||||||
|
AdditionalPropertyValue::OptionalPair {
|
||||||
|
optval: optval.map(|s| (*s).to_owned()),
|
||||||
|
val: (*val).to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AffiliatedKeywordValue::ObjectTree(val) => {
|
||||||
|
let mut ret = Vec::with_capacity(val.len());
|
||||||
|
|
||||||
|
for (optval, value) in val {
|
||||||
|
let converted_optval = if let Some(optval) = optval {
|
||||||
|
Some(
|
||||||
|
optval
|
||||||
|
.iter()
|
||||||
|
.map(|child| {
|
||||||
|
child
|
||||||
|
.to_wasm(wasm_context.clone())
|
||||||
|
.map(Into::<WasmAstNode>::into)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let converted_value = value
|
||||||
|
.iter()
|
||||||
|
.map(|child| {
|
||||||
|
child
|
||||||
|
.to_wasm(wasm_context.clone())
|
||||||
|
.map(Into::<WasmAstNode>::into)
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
ret.push((converted_optval, converted_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
AdditionalPropertyValue::ObjectTree { object_tree: ret }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
additional_properties
|
||||||
|
.properties
|
||||||
|
.insert(name.clone(), converted_val);
|
||||||
|
}
|
||||||
|
Ok(additional_properties)
|
||||||
|
}
|
||||||
|
);
|
||||||
54
src/wasm/angle_link.rs
Normal file
54
src/wasm/angle_link.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use super::ast_node::WasmAstNode;
|
||||||
|
use super::macros::to_wasm;
|
||||||
|
use super::to_wasm::ToWasm;
|
||||||
|
use crate::types::AngleLink;
|
||||||
|
use crate::types::LinkType;
|
||||||
|
use crate::util::elisp_fact::ElispFact;
|
||||||
|
use crate::wasm::to_wasm::ToWasmStandardProperties;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "format")]
|
||||||
|
#[serde(rename = "angle")]
|
||||||
|
pub struct WasmAngleLink {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) link_type: String,
|
||||||
|
pub(crate) path: String,
|
||||||
|
#[serde(rename = "raw-link")]
|
||||||
|
pub(crate) raw_link: String,
|
||||||
|
pub(crate) application: Option<String>,
|
||||||
|
#[serde(rename = "search-option")]
|
||||||
|
pub(crate) search_option: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
to_wasm!(
|
||||||
|
WasmAngleLink,
|
||||||
|
AngleLink<'s>,
|
||||||
|
original,
|
||||||
|
wasm_context,
|
||||||
|
{ WasmAstNode::AngleLink(original) },
|
||||||
|
{ "link".into() },
|
||||||
|
{
|
||||||
|
Ok((
|
||||||
|
Vec::new(),
|
||||||
|
WasmAngleLink {
|
||||||
|
link_type: match &original.link_type {
|
||||||
|
LinkType::File => "file".to_owned(),
|
||||||
|
LinkType::Protocol(protocol) => protocol.clone().into_owned(),
|
||||||
|
LinkType::Id => "id".to_owned(),
|
||||||
|
LinkType::CustomId => "custom-id".to_owned(),
|
||||||
|
LinkType::CodeRef => "coderef".to_owned(),
|
||||||
|
LinkType::Fuzzy => "fuzzy".to_owned(),
|
||||||
|
},
|
||||||
|
path: original.get_path().into_owned(),
|
||||||
|
raw_link: original.raw_link.to_owned(),
|
||||||
|
application: original.application.map(|c| c.to_owned()),
|
||||||
|
search_option: original.get_search_option().map(Cow::into_owned),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user