Compare commits
149 Commits
v0.1.10
...
175ff1e6c4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
503db94b2c | ||
|
|
a4381e5e39 | ||
|
|
e11de60def | ||
|
|
b2479e9de8 | ||
|
|
49d1cef7ae | ||
|
|
ba72cc1b29 | ||
|
|
c58b0e7c35 | ||
|
|
f19d262825 | ||
|
|
68f3f2e159 | ||
|
|
269e23c1b1 | ||
|
|
e111b8b9b8 | ||
|
|
353ff07420 | ||
|
|
94dec31130 | ||
|
|
cf5d3ed745 | ||
|
|
b0b287cd47 | ||
|
|
bcdf1f5e9d | ||
|
|
17d8e76e05 | ||
|
|
8db9038c53 | ||
|
|
a276ba70e0 | ||
|
|
b7442c1e92 | ||
|
|
364ba79517 | ||
|
|
47408763e5 | ||
|
|
bd187ebfe7 | ||
|
|
59cb3c2bbf | ||
|
|
44f7412a5c | ||
|
|
01464057ad | ||
|
|
0208020e3e | ||
|
|
a2f53361eb | ||
|
|
17db05c2c7 | ||
|
|
6139ea328d | ||
|
|
d20b4a410b | ||
|
|
05c64f53b1 | ||
|
|
f65d0bb82d | ||
|
|
50d2831081 | ||
|
|
bc9bd4f97b | ||
|
|
369d3e8c50 | ||
|
|
7d73eb6bd4 | ||
|
|
f59f153ee7 | ||
|
|
20c4a0f8f7 | ||
|
|
e776a051ad | ||
|
|
77e6c22ad8 | ||
|
|
c9d7251e3b | ||
|
|
8417b5fc9d | ||
|
|
acc29e7977 | ||
|
|
ebc0a30035 | ||
|
|
e2d55e13d3 | ||
|
|
e4d9c5f467 | ||
|
|
d8e3a85ef7 | ||
|
|
464685b52b | ||
|
|
5fed4e80a7 | ||
|
|
e53140426f | ||
|
|
9a4d290cf8 | ||
|
|
acd24d6198 | ||
|
|
880b00ef3f | ||
|
|
3069711447 | ||
|
|
4b6c717812 | ||
|
|
1d329cc310 | ||
|
|
b4f9a3b9b6 | ||
|
|
2dd5246506 | ||
|
|
4ba0e3611b | ||
|
|
728f79b86c | ||
|
|
192a4a2891 | ||
|
|
fafd85fb30 | ||
|
|
1c23065329 | ||
|
|
ed105b04ad | ||
|
|
f10efec21d | ||
|
|
72b4cf8e71 | ||
|
|
547fc40dbe | ||
|
|
9f1671658d | ||
|
|
18d0676fad | ||
|
|
7833a58461 | ||
|
|
0020d71089 | ||
|
|
cfdf39d1fa | ||
|
|
26f1eae9a1 | ||
|
|
3eff85059a | ||
|
|
d2d0e9e5dd | ||
|
|
c86d1000c0 | ||
|
|
911634cb42 | ||
|
|
0aa746fb1e | ||
|
|
33800c4a88 | ||
|
|
909ccadfa1 | ||
|
|
e352deb989 | ||
|
|
f5a6a26c43 | ||
|
|
dd7184da54 | ||
|
|
1168ddb1fe | ||
|
|
77ab636e6a | ||
|
|
f5dcacc79d | ||
|
|
e7c3c7aab6 | ||
|
|
7603b0a1cc | ||
|
|
dea3721b1c |
191
.lighthouse/pipeline-clippy.yaml
Normal file
191
.lighthouse/pipeline-clippy.yaml
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
apiVersion: tekton.dev/v1beta1
|
||||||
|
kind: PipelineRun
|
||||||
|
metadata:
|
||||||
|
name: clippy
|
||||||
|
spec:
|
||||||
|
pipelineSpec:
|
||||||
|
params:
|
||||||
|
- name: image-name
|
||||||
|
description: The name for the built image
|
||||||
|
type: string
|
||||||
|
- name: path-to-image-context
|
||||||
|
description: The path to the build context
|
||||||
|
type: string
|
||||||
|
- name: path-to-dockerfile
|
||||||
|
description: The path to the Dockerfile
|
||||||
|
type: string
|
||||||
|
- name: GIT_USER_NAME
|
||||||
|
description: The username for git
|
||||||
|
type: string
|
||||||
|
default: "fluxcdbot"
|
||||||
|
- name: GIT_USER_EMAIL
|
||||||
|
description: The email for git
|
||||||
|
type: string
|
||||||
|
default: "fluxcdbot@users.noreply.github.com"
|
||||||
|
tasks:
|
||||||
|
- name: do-stuff
|
||||||
|
taskSpec:
|
||||||
|
metadata: {}
|
||||||
|
stepTemplate:
|
||||||
|
image: alpine:3.18
|
||||||
|
name: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 600Mi
|
||||||
|
workingDir: /workspace/source
|
||||||
|
steps:
|
||||||
|
- image: alpine:3.18
|
||||||
|
name: do-stuff-step
|
||||||
|
script: |
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
echo "hello world"
|
||||||
|
- name: report-pending
|
||||||
|
taskRef:
|
||||||
|
name: gitea-set-status
|
||||||
|
runAfter:
|
||||||
|
- fetch-repository
|
||||||
|
params:
|
||||||
|
- name: CONTEXT
|
||||||
|
value: "$(params.JOB_NAME)"
|
||||||
|
- name: REPO_FULL_NAME
|
||||||
|
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
|
||||||
|
- name: GITEA_HOST_URL
|
||||||
|
value: code.fizz.buzz
|
||||||
|
- name: SHA
|
||||||
|
value: "$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: DESCRIPTION
|
||||||
|
value: "Build $(params.JOB_NAME) has started"
|
||||||
|
- name: STATE
|
||||||
|
value: pending
|
||||||
|
- name: TARGET_URL
|
||||||
|
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
|
||||||
|
- name: fetch-repository
|
||||||
|
taskRef:
|
||||||
|
name: git-clone
|
||||||
|
workspaces:
|
||||||
|
- name: output
|
||||||
|
workspace: git-source
|
||||||
|
params:
|
||||||
|
- name: url
|
||||||
|
value: $(params.REPO_URL)
|
||||||
|
- name: revision
|
||||||
|
value: $(params.PULL_BASE_SHA)
|
||||||
|
- name: deleteExisting
|
||||||
|
value: "true"
|
||||||
|
- name: build-image
|
||||||
|
taskRef:
|
||||||
|
name: kaniko
|
||||||
|
params:
|
||||||
|
- name: IMAGE
|
||||||
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: CONTEXT
|
||||||
|
value: $(params.path-to-image-context)
|
||||||
|
- name: DOCKERFILE
|
||||||
|
value: $(params.path-to-dockerfile)
|
||||||
|
- name: BUILDER_IMAGE
|
||||||
|
value: "gcr.io/kaniko-project/executor:v1.12.1"
|
||||||
|
- name: EXTRA_ARGS
|
||||||
|
value:
|
||||||
|
- --cache=true
|
||||||
|
- --cache-copy-layers
|
||||||
|
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
|
||||||
|
- --use-new-run # Should result in a speed-up
|
||||||
|
- --reproducible # To remove timestamps so layer caching works.
|
||||||
|
- --snapshot-mode=redo
|
||||||
|
- --skip-unused-stages=true
|
||||||
|
- --registry-mirror=dockerhub.dockerhub.svc.cluster.local
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: dockerconfig
|
||||||
|
workspace: docker-credentials
|
||||||
|
runAfter:
|
||||||
|
- fetch-repository
|
||||||
|
- name: clippy
|
||||||
|
taskRef:
|
||||||
|
name: run-docker-image
|
||||||
|
workspaces:
|
||||||
|
- name: source
|
||||||
|
workspace: git-source
|
||||||
|
- name: cargo-cache
|
||||||
|
workspace: cargo-cache
|
||||||
|
runAfter:
|
||||||
|
- build-image
|
||||||
|
params:
|
||||||
|
- name: docker-image
|
||||||
|
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||||
|
finally:
|
||||||
|
- name: report-success
|
||||||
|
when:
|
||||||
|
- input: "$(tasks.status)"
|
||||||
|
operator: in
|
||||||
|
values: ["Succeeded", "Completed"]
|
||||||
|
taskRef:
|
||||||
|
name: gitea-set-status
|
||||||
|
params:
|
||||||
|
- name: CONTEXT
|
||||||
|
value: "$(params.JOB_NAME)"
|
||||||
|
- name: REPO_FULL_NAME
|
||||||
|
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
|
||||||
|
- name: GITEA_HOST_URL
|
||||||
|
value: code.fizz.buzz
|
||||||
|
- name: SHA
|
||||||
|
value: "$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: DESCRIPTION
|
||||||
|
value: "Build $(params.JOB_NAME) has succeeded"
|
||||||
|
- name: STATE
|
||||||
|
value: success
|
||||||
|
- name: TARGET_URL
|
||||||
|
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
|
||||||
|
- name: report-failure
|
||||||
|
when:
|
||||||
|
- input: "$(tasks.status)"
|
||||||
|
operator: in
|
||||||
|
values: ["Failed"]
|
||||||
|
taskRef:
|
||||||
|
name: gitea-set-status
|
||||||
|
params:
|
||||||
|
- name: CONTEXT
|
||||||
|
value: "$(params.JOB_NAME)"
|
||||||
|
- name: REPO_FULL_NAME
|
||||||
|
value: "$(params.REPO_OWNER)/$(params.REPO_NAME)"
|
||||||
|
- name: GITEA_HOST_URL
|
||||||
|
value: code.fizz.buzz
|
||||||
|
- name: SHA
|
||||||
|
value: "$(tasks.fetch-repository.results.commit)"
|
||||||
|
- name: DESCRIPTION
|
||||||
|
value: "Build $(params.JOB_NAME) has failed"
|
||||||
|
- name: STATE
|
||||||
|
value: failure
|
||||||
|
- name: TARGET_URL
|
||||||
|
value: "https://tekton.fizz.buzz/#/namespaces/$(context.pipelineRun.namespace)/pipelineruns/$(context.pipelineRun.name)"
|
||||||
|
workspaces:
|
||||||
|
- name: git-source
|
||||||
|
- name: docker-credentials
|
||||||
|
workspaces:
|
||||||
|
- name: git-source
|
||||||
|
volumeClaimTemplate:
|
||||||
|
spec:
|
||||||
|
storageClassName: "nfs-client"
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
subPath: rust-source
|
||||||
|
- name: cargo-cache
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: organic-cargo-cache-clippy
|
||||||
|
- name: docker-credentials
|
||||||
|
secret:
|
||||||
|
secretName: harbor-plain
|
||||||
|
serviceAccountName: build-bot
|
||||||
|
timeout: 240h0m0s
|
||||||
|
params:
|
||||||
|
- name: image-name
|
||||||
|
value: "harbor.fizz.buzz/private/organic-clippy"
|
||||||
|
- name: path-to-image-context
|
||||||
|
value: docker/organic_clippy/
|
||||||
|
- name: path-to-dockerfile
|
||||||
|
value: docker/organic_clippy/Dockerfile
|
||||||
@@ -30,3 +30,10 @@ spec:
|
|||||||
skip_branches:
|
skip_branches:
|
||||||
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
|
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
|
||||||
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
|
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||||
|
- name: clippy
|
||||||
|
source: "pipeline-clippy.yaml"
|
||||||
|
# Override https-based url from lighthouse events.
|
||||||
|
clone_uri: "git@code.fizz.buzz:talexander/organic.git"
|
||||||
|
skip_branches:
|
||||||
|
# We already run on every commit, so running when the semver tags get pushed is causing needless double-processing.
|
||||||
|
- "^v[0-9]+\\.[0-9]+\\.[0-9]+$"
|
||||||
|
|||||||
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,8 @@
|
|||||||
|
# cargo-features = ["profile-rustflags"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "organic"
|
name = "organic"
|
||||||
version = "0.1.10"
|
version = "0.1.12"
|
||||||
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"
|
||||||
@@ -57,6 +59,7 @@ default = []
|
|||||||
compare = ["tokio/process", "tokio/macros"]
|
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 = []
|
||||||
|
|
||||||
# Optimized build for any sort of release.
|
# Optimized build for any sort of release.
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
@@ -64,6 +67,13 @@ inherits = "release"
|
|||||||
lto = true
|
lto = true
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|
||||||
|
# Optimized build for local execution.
|
||||||
|
# [profile.native]
|
||||||
|
# inherits = "release"
|
||||||
|
# lto = true
|
||||||
|
# strip = "symbols"
|
||||||
|
# rustflags = ["-C", "target-cpu=native"]
|
||||||
|
|
||||||
# Profile for performance testing with the "perf" tool. Notably keeps debug enabled and does not strip symbols to make reading the perf output easier.
|
# Profile for performance testing with the "perf" tool. Notably keeps debug enabled and does not strip symbols to make reading the perf output easier.
|
||||||
[profile.perf]
|
[profile.perf]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -37,6 +37,14 @@ clean:
|
|||||||
format:
|
format:
|
||||||
> $(MAKE) -C docker/cargo_fmt run
|
> $(MAKE) -C docker/cargo_fmt run
|
||||||
|
|
||||||
|
.PHONY: dockerclippy
|
||||||
|
dockerclippy:
|
||||||
|
> $(MAKE) -C docker/organic_clippy run
|
||||||
|
|
||||||
|
.PHONY: clippy
|
||||||
|
clippy:
|
||||||
|
> cargo clippy --no-deps --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
.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)
|
||||||
|
|||||||
12
build.rs
12
build.rs
@@ -14,7 +14,7 @@ use walkdir::WalkDir;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let out_dir = env::var("OUT_DIR").unwrap();
|
let out_dir = env::var("OUT_DIR").unwrap();
|
||||||
let destination = Path::new(&out_dir).join("tests.rs");
|
let destination = Path::new(&out_dir).join("tests.rs");
|
||||||
let mut test_file = File::create(&destination).unwrap();
|
let mut test_file = File::create(destination).unwrap();
|
||||||
|
|
||||||
// Re-generate the tests if any org-mode files change
|
// Re-generate the tests if any org-mode files change
|
||||||
println!("cargo:rerun-if-changed=org_mode_samples");
|
println!("cargo:rerun-if-changed=org_mode_samples");
|
||||||
@@ -51,7 +51,7 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
|
|||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
.strip_suffix(".org")
|
.strip_suffix(".org")
|
||||||
.expect("Should have .org extension")
|
.expect("Should have .org extension")
|
||||||
.replace("/", "_");
|
.replace('/', "_");
|
||||||
|
|
||||||
write!(
|
write!(
|
||||||
test_file,
|
test_file,
|
||||||
@@ -66,10 +66,6 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
fn is_expect_fail(name: &str) -> Option<&str> {
|
fn is_expect_fail(_name: &str) -> Option<&str> {
|
||||||
match name {
|
None
|
||||||
"greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
|
|
||||||
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
docker/organic_clippy/Dockerfile
Normal file
5
docker/organic_clippy/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FROM rustlang/rust:nightly-alpine3.17
|
||||||
|
|
||||||
|
RUN apk add --no-cache musl-dev
|
||||||
|
|
||||||
|
ENTRYPOINT ["cargo", "clippy", "--no-deps", "--all-targets", "--all-features", "--", "-D", "warnings"]
|
||||||
37
docker/organic_clippy/Makefile
Normal file
37
docker/organic_clippy/Makefile
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
IMAGE_NAME:=organic-clippy
|
||||||
|
# REMOTE_REPO:=harbor.fizz.buzz/private
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: build push
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
docker build -t $(IMAGE_NAME) -f Dockerfile .
|
||||||
|
|
||||||
|
.PHONY: push
|
||||||
|
push:
|
||||||
|
ifdef REMOTE_REPO
|
||||||
|
docker tag $(IMAGE_NAME) $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||||
|
docker push $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||||
|
else
|
||||||
|
@echo "REMOTE_REPO not defined, not pushing to a remote repo."
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
docker rmi $(IMAGE_NAME)
|
||||||
|
ifdef REMOTE_REPO
|
||||||
|
docker rmi $(REMOTE_REPO)/$(IMAGE_NAME)
|
||||||
|
else
|
||||||
|
@echo "REMOTE_REPO not defined, not removing from remote repo."
|
||||||
|
endif
|
||||||
|
docker volume rm rust-cache cargo-cache
|
||||||
|
|
||||||
|
# NOTE: This target will write to folders underneath the git-root
|
||||||
|
.PHONY: run
|
||||||
|
run: build
|
||||||
|
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
|
shell: build
|
||||||
|
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source: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"]
|
||||||
|
|||||||
42
notes/affiliated_keyword_investigation/analysis.org
Normal file
42
notes/affiliated_keyword_investigation/analysis.org
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
* Elisp Structure
|
||||||
|
| Keyword | Single | Double | Single Optval | Double Optval |
|
||||||
|
|---------+---------------+---------------+---------------+---------------|
|
||||||
|
| CAPTION | objtree | objtree | objtree | objtree |
|
||||||
|
| DATA | quoted(:name) | quoted(:name) | - | - |
|
||||||
|
| HEADER | list(quoted) | list(quoted) | - | - |
|
||||||
|
| NAME | quoted(:name) | quoted(:name) | - | - |
|
||||||
|
| PLOT | quoted(:plot) | quoted(:plot) | - | - |
|
||||||
|
| RESULTS | optional pair | optional pair | optional pair | optional pair |
|
||||||
|
* types
|
||||||
|
** objtree
|
||||||
|
Outer list: 1 per keyword
|
||||||
|
next list: first entry = list of objects for value. remaining entries = optval
|
||||||
|
** list(quoted)
|
||||||
|
List of quoted strings, 1 per keyword
|
||||||
|
** quoted(NAME)
|
||||||
|
Quoted string under the NAME property (for example quoted(:name))
|
||||||
|
** optional pair
|
||||||
|
When optval is supplied this is an alist with the field value being the real value and the 3nd value being the optval.
|
||||||
|
#+begin_src elisp
|
||||||
|
("*f*" . "*bar*")
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
When optval is not supplied this is a list containing a single string of the last occurrence of this keyword.
|
||||||
|
#+begin_src elisp
|
||||||
|
("*c*")
|
||||||
|
#+end_src
|
||||||
|
* Default settings
|
||||||
|
#+begin_src text
|
||||||
|
org-element-dual-keywords ("CAPTION" "RESULTS")
|
||||||
|
org-element-parsed-keywords ("CAPTION")
|
||||||
|
org-element-multiple-keywords ("CAPTION" "HEADER")
|
||||||
|
org-babel-results-keyword "RESULTS"
|
||||||
|
#+end_src
|
||||||
|
* Analysis
|
||||||
|
We don't have an example of a parsed non-dual keyword
|
||||||
|
|
||||||
|
Looks like multiple triggers list 1 per keyword
|
||||||
|
|
||||||
|
dual triggers support for optval
|
||||||
|
|
||||||
|
parsed triggers objects
|
||||||
31
notes/affiliated_keyword_investigation/run_test.bash
Executable file
31
notes/affiliated_keyword_investigation/run_test.bash
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
file_path="${DIR}/test_document.org"
|
||||||
|
|
||||||
|
for TARGET_VARIABLE in RESULTS CAPTION HEADER DATA NAME PLOT; do
|
||||||
|
INIT_SCRIPT=$(cat <<EOF
|
||||||
|
(progn
|
||||||
|
(erase-buffer)
|
||||||
|
(require 'org)
|
||||||
|
(defun org-table-align () t)
|
||||||
|
(setq vc-handled-backends nil)
|
||||||
|
(find-file "/input/${file_path}")
|
||||||
|
(org-mode)
|
||||||
|
(replace-regexp-in-region "foo" "${TARGET_VARIABLE}")
|
||||||
|
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||||
|
)
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
docker run --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -w /input --entrypoint "" organic-test emacs -q --no-site-file --no-splash --batch --eval "$INIT_SCRIPT" 2> "${DIR}/${TARGET_VARIABLE}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# exec docker run --init --rm -i -t --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -w /input --entrypoint "" organic-test emacs -q --no-site-file --no-splash --eval "$INIT_SCRIPT"
|
||||||
|
|
||||||
|
# org-element-dual-keywords ("CAPTION" "RESULTS")
|
||||||
|
# org-element-parsed-keywords ("CAPTION")
|
||||||
|
# org-element-multiple-keywords ("CAPTION" "HEADER")
|
||||||
|
# org-babel-results-keyword "RESULTS"
|
||||||
25
notes/affiliated_keyword_investigation/test_document.org
Normal file
25
notes/affiliated_keyword_investigation/test_document.org
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Single instance
|
||||||
|
#+foo: *a*
|
||||||
|
#+begin_example
|
||||||
|
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
# Two instances
|
||||||
|
#+foo: *b*
|
||||||
|
#+foo: *c*
|
||||||
|
#+begin_example
|
||||||
|
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
# Single with optval
|
||||||
|
#+foo[*bar*]: *d*
|
||||||
|
#+begin_example
|
||||||
|
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
# Two with optval
|
||||||
|
#+foo[*bar*]: *e*
|
||||||
|
#+foo[*bar*]: *f*
|
||||||
|
#+begin_example
|
||||||
|
|
||||||
|
#+end_example
|
||||||
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,0 +1,3 @@
|
|||||||
|
foo
|
||||||
|
:end:
|
||||||
|
bar
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
foo
|
||||||
|
:end:
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
** Foo
|
||||||
|
DEADLINE: <2023-10-16 Mon>
|
||||||
|
:PROPERTIES:
|
||||||
|
:foo: *a*
|
||||||
|
:Bar: *b*
|
||||||
|
:BAZ: *c*
|
||||||
|
:END:
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#+results[foo]: bar
|
||||||
|
#+results[lorem]: ipsum
|
||||||
|
#+begin_example
|
||||||
|
baz
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
#+caption[lorem]: ipsum
|
||||||
|
#+caption[foo]: bar
|
||||||
|
#+begin_example
|
||||||
|
baz
|
||||||
|
#+end_example
|
||||||
|
|
||||||
|
#+header[foo]: bar
|
||||||
|
#+begin_example
|
||||||
|
baz
|
||||||
|
#+end_example
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
foo <<bar>> baz
|
|
||||||
|
|
||||||
lorem << ipsum >> dolar
|
|
||||||
58
scripts/dump_ast.bash
Executable file
58
scripts/dump_ast.bash
Executable file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Dump the AST of an org-mode document from emacs
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
dump_ast_stdin "${@}"
|
||||||
|
else
|
||||||
|
dump_ast_file "${@}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function dump_ast_stdin {
|
||||||
|
# Until we can find a good way to encode stdin as an elisp string in bash, I cannot operate on stdin.
|
||||||
|
die 1 "This script only works on files."
|
||||||
|
}
|
||||||
|
|
||||||
|
function dump_ast_file {
|
||||||
|
local target_file mounted_file elisp_script
|
||||||
|
target_file=$($REALPATH "$1")
|
||||||
|
mounted_file="/input${target_file}"
|
||||||
|
elisp_script=$(cat <<EOF
|
||||||
|
(progn
|
||||||
|
(erase-buffer)
|
||||||
|
(require 'org)
|
||||||
|
(defun org-table-align () t)
|
||||||
|
(find-file-read-only "${mounted_file}")
|
||||||
|
(org-mode)
|
||||||
|
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||||
|
)
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
exec docker run --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" --entrypoint "" organic-test emacs -q --no-site-file --no-splash --batch --eval "$elisp_script"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
main "${@}"
|
||||||
@@ -14,7 +14,7 @@ function main {
|
|||||||
additional_flags+=(--profile "$PROFILE")
|
additional_flags+=(--profile "$PROFILE")
|
||||||
fi
|
fi
|
||||||
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
|
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
|
||||||
perf record --freq=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
|
perf record --freq=70000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
|
||||||
|
|
||||||
# Convert to a format firefox will read
|
# Convert to a format firefox will read
|
||||||
# flags to consider --show-info
|
# flags to consider --show-info
|
||||||
|
|||||||
@@ -15,23 +15,21 @@ mod init_tracing;
|
|||||||
#[cfg(not(feature = "tracing"))]
|
#[cfg(not(feature = "tracing"))]
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
let result = rt.block_on(async {
|
rt.block_on(async {
|
||||||
let main_body_result = main_body().await;
|
let main_body_result = main_body().await;
|
||||||
main_body_result
|
main_body_result
|
||||||
});
|
})
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
let result = rt.block_on(async {
|
rt.block_on(async {
|
||||||
init_telemetry()?;
|
init_telemetry()?;
|
||||||
let main_body_result = main_body().await;
|
let main_body_result = main_body().await;
|
||||||
shutdown_telemetry()?;
|
shutdown_telemetry()?;
|
||||||
main_body_result
|
main_body_result
|
||||||
});
|
})
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
|||||||
@@ -21,23 +21,21 @@ mod init_tracing;
|
|||||||
#[cfg(not(feature = "tracing"))]
|
#[cfg(not(feature = "tracing"))]
|
||||||
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
let result = rt.block_on(async {
|
rt.block_on(async {
|
||||||
let main_body_result = main_body().await;
|
let main_body_result = main_body().await;
|
||||||
main_body_result
|
main_body_result
|
||||||
});
|
})
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
let result = rt.block_on(async {
|
rt.block_on(async {
|
||||||
init_telemetry()?;
|
init_telemetry()?;
|
||||||
let main_body_result = main_body().await;
|
let main_body_result = main_body().await;
|
||||||
shutdown_telemetry()?;
|
shutdown_telemetry()?;
|
||||||
main_body_result
|
main_body_result
|
||||||
});
|
})
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -55,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;
|
||||||
@@ -108,10 +109,9 @@ fn compare_howard_abrams() -> impl Iterator<Item = TestConfig> {
|
|||||||
let layer = layer.chain(compare_group("clojure-yesql-xp", || {
|
let layer = layer.chain(compare_group("clojure-yesql-xp", || {
|
||||||
compare_all_org_document("/foreign_documents/howardabrams/clojure-yesql-xp")
|
compare_all_org_document("/foreign_documents/howardabrams/clojure-yesql-xp")
|
||||||
}));
|
}));
|
||||||
let layer = layer.chain(compare_group("veep", || {
|
layer.chain(compare_group("veep", || {
|
||||||
compare_all_org_document("/foreign_documents/howardabrams/veep")
|
compare_all_org_document("/foreign_documents/howardabrams/veep")
|
||||||
}));
|
}))
|
||||||
layer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_group<N: Into<String>, F: Fn() -> I, I: Iterator<Item = TestConfig>>(
|
fn compare_group<N: Into<String>, F: Fn() -> I, I: Iterator<Item = TestConfig>>(
|
||||||
@@ -195,7 +195,6 @@ struct ResultLayer {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SingleFileResult {
|
struct SingleFileResult {
|
||||||
name: String,
|
name: String,
|
||||||
file_path: PathBuf,
|
|
||||||
status: TestStatus,
|
status: TestStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +224,6 @@ impl SingleFile {
|
|||||||
let result = silent_compare_on_file(&self.file_path).await;
|
let result = silent_compare_on_file(&self.file_path).await;
|
||||||
Ok(SingleFileResult {
|
Ok(SingleFileResult {
|
||||||
name: self.name,
|
name: self.name,
|
||||||
file_path: self.file_path,
|
|
||||||
status: if let Ok(true) = result {
|
status: if let Ok(true) = result {
|
||||||
TestStatus::Pass
|
TestStatus::Pass
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -16,7 +18,6 @@ use super::util::get_property_unquoted_atom;
|
|||||||
use crate::types::AstNode;
|
use crate::types::AstNode;
|
||||||
use crate::types::CharOffsetInLine;
|
use crate::types::CharOffsetInLine;
|
||||||
use crate::types::LineNumber;
|
use crate::types::LineNumber;
|
||||||
use crate::types::Object;
|
|
||||||
use crate::types::RetainLabels;
|
use crate::types::RetainLabels;
|
||||||
use crate::types::SwitchNumberLines;
|
use crate::types::SwitchNumberLines;
|
||||||
|
|
||||||
@@ -57,11 +58,11 @@ impl<'b, 's> ComparePropertiesResult<'b, 's> {
|
|||||||
/// Do no comparison.
|
/// Do no comparison.
|
||||||
///
|
///
|
||||||
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
|
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
|
||||||
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
|
pub(crate) fn compare_noop<'b, 's, R, RG>(
|
||||||
_source: &'s str,
|
_source: &'s str,
|
||||||
_emacs: &'b Token<'s>,
|
_emacs: &'b Token<'s>,
|
||||||
_rust_node: R,
|
_rust_node: R,
|
||||||
_emacs_field: &'x str,
|
_emacs_field: &str,
|
||||||
_rust_value_getter: RG,
|
_rust_value_getter: RG,
|
||||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||||
Ok(ComparePropertiesResult::NoChange)
|
Ok(ComparePropertiesResult::NoChange)
|
||||||
@@ -70,18 +71,16 @@ pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
|
|||||||
/// Do no comparison.
|
/// Do no comparison.
|
||||||
///
|
///
|
||||||
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
|
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
|
||||||
pub(crate) fn compare_identity() -> () {
|
pub(crate) fn compare_identity() {}
|
||||||
()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assert that the emacs value is always nil or absent.
|
/// Assert that the emacs value is always nil or absent.
|
||||||
///
|
///
|
||||||
/// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property.
|
/// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property.
|
||||||
pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
|
pub(crate) fn compare_property_always_nil<'b, 's, R, RG>(
|
||||||
_source: &'s str,
|
_source: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
_rust_node: R,
|
_rust_node: R,
|
||||||
emacs_field: &'x str,
|
emacs_field: &str,
|
||||||
_rust_value_getter: RG,
|
_rust_value_getter: RG,
|
||||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||||
let value = get_property(emacs, emacs_field)?;
|
let value = get_property(emacs, emacs_field)?;
|
||||||
@@ -100,7 +99,6 @@ pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
|
|||||||
pub(crate) fn compare_property_quoted_string<
|
pub(crate) fn compare_property_quoted_string<
|
||||||
'b,
|
'b,
|
||||||
's,
|
's,
|
||||||
'x,
|
|
||||||
R,
|
R,
|
||||||
RV: AsRef<str> + std::fmt::Debug,
|
RV: AsRef<str> + std::fmt::Debug,
|
||||||
RG: Fn(R) -> Option<RV>,
|
RG: Fn(R) -> Option<RV>,
|
||||||
@@ -108,12 +106,12 @@ pub(crate) fn compare_property_quoted_string<
|
|||||||
_source: &'s str,
|
_source: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust_node: R,
|
rust_node: R,
|
||||||
emacs_field: &'x str,
|
emacs_field: &str,
|
||||||
rust_value_getter: RG,
|
rust_value_getter: RG,
|
||||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||||
let value = get_property_quoted_string(emacs, emacs_field)?;
|
let value = get_property_quoted_string(emacs, emacs_field)?;
|
||||||
let rust_value = rust_value_getter(rust_node);
|
let rust_value = rust_value_getter(rust_node);
|
||||||
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_ref().map(String::as_str) {
|
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_deref() {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
@@ -181,7 +179,6 @@ where
|
|||||||
pub(crate) fn compare_property_list_of_quoted_string<
|
pub(crate) fn compare_property_list_of_quoted_string<
|
||||||
'b,
|
'b,
|
||||||
's,
|
's,
|
||||||
'x,
|
|
||||||
R,
|
R,
|
||||||
RV: AsRef<str> + std::fmt::Debug,
|
RV: AsRef<str> + std::fmt::Debug,
|
||||||
RI: Iterator<Item = RV>,
|
RI: Iterator<Item = RV>,
|
||||||
@@ -190,7 +187,7 @@ pub(crate) fn compare_property_list_of_quoted_string<
|
|||||||
_source: &'s str,
|
_source: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust_node: R,
|
rust_node: R,
|
||||||
emacs_field: &'x str,
|
emacs_field: &str,
|
||||||
rust_value_getter: RG,
|
rust_value_getter: RG,
|
||||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||||
let value = get_property(emacs, emacs_field)?
|
let value = get_property(emacs, emacs_field)?
|
||||||
@@ -267,15 +264,12 @@ 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
|
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
|
||||||
.symmetric_difference(&rust_value)
|
|
||||||
.map(|val| *val)
|
|
||||||
.collect();
|
|
||||||
if !mismatched.is_empty() {
|
if !mismatched.is_empty() {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
@@ -288,11 +282,111 @@ pub(crate) fn compare_property_set_of_quoted_string<
|
|||||||
Ok(ComparePropertiesResult::NoChange)
|
Ok(ComparePropertiesResult::NoChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn compare_property_boolean<'b, 's, 'x, R, RG: Fn(R) -> bool>(
|
pub(crate) fn compare_property_optional_pair<
|
||||||
|
'b,
|
||||||
|
's,
|
||||||
|
R,
|
||||||
|
RV: AsRef<str> + std::fmt::Debug,
|
||||||
|
ROV: AsRef<str> + std::fmt::Debug,
|
||||||
|
RG: Fn(R) -> Option<(Option<ROV>, RV)>,
|
||||||
|
>(
|
||||||
_source: &'s str,
|
_source: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust_node: R,
|
rust_node: R,
|
||||||
emacs_field: &'x str,
|
emacs_field: &str,
|
||||||
|
rust_value_getter: RG,
|
||||||
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||||
|
let value = get_property(emacs, emacs_field)?
|
||||||
|
.map(Token::as_list)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?;
|
||||||
|
let rust_value = rust_value_getter(rust_node);
|
||||||
|
match (value, &rust_value) {
|
||||||
|
(None, None) => {}
|
||||||
|
(None, Some(_)) | (Some(_), None) => {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, value, rust_value
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
(Some(el), Some((Some(_), _))) if el.len() != 3 => {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, value, rust_value
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
(Some(el), Some((None, _))) if el.len() != 1 => {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, value, rust_value
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
(Some(el), Some((Some(orl), rl))) => {
|
||||||
|
let e = el
|
||||||
|
.first()
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.map(unquote)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.expect("Above match proved length to be 3.");
|
||||||
|
let oe = el
|
||||||
|
.get(2)
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.map(unquote)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.expect("Above match proved length to be 3.");
|
||||||
|
let r = rl.as_ref();
|
||||||
|
let or = orl.as_ref();
|
||||||
|
if e != r {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
|
||||||
|
emacs_field, e, r, value, rust_value
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
if oe != or {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
|
||||||
|
emacs_field, e, r, value, rust_value
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(el), Some((None, rl))) => {
|
||||||
|
let e = el
|
||||||
|
.first()
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.map(unquote)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.expect("Above match proved length to be 1.");
|
||||||
|
let r = rl.as_ref();
|
||||||
|
if e != r {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}. Full list: {:?} != {:?}",
|
||||||
|
emacs_field, e, r, value, rust_value
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ComparePropertiesResult::NoChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compare_property_boolean<'b, 's, R, RG: Fn(R) -> bool>(
|
||||||
|
_source: &'s str,
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
rust_node: R,
|
||||||
|
emacs_field: &str,
|
||||||
rust_value_getter: RG,
|
rust_value_getter: RG,
|
||||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||||
// get_property already converts nil to None.
|
// get_property already converts nil to None.
|
||||||
@@ -376,14 +470,7 @@ where
|
|||||||
match (value, rust_value) {
|
match (value, rust_value) {
|
||||||
(None, None) => {}
|
(None, None) => {}
|
||||||
(Some(el), None)
|
(Some(el), None)
|
||||||
if el.len() == 1
|
if el.len() == 1 && el.iter().all(|t| matches!(t.as_atom(), Ok(r#""""#))) => {}
|
||||||
&& el.into_iter().all(|t| {
|
|
||||||
if let Ok(r#""""#) = t.as_atom() {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}) => {}
|
|
||||||
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
|
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
@@ -412,118 +499,161 @@ where
|
|||||||
Ok(ComparePropertiesResult::NoChange)
|
Ok(ComparePropertiesResult::NoChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Special compare used for affiliate keywords that are parsed as objects.
|
pub(crate) fn compare_property_object_tree<
|
||||||
///
|
|
||||||
/// Org-mode seems to store these as a 3-deep list:
|
|
||||||
/// - Outer list with 1 element per #+caption keyword (or other parsed keyword).
|
|
||||||
/// - Middle list which has:
|
|
||||||
/// - first element is a list of objects representing the value after the colon.
|
|
||||||
/// - every additional element is a list of objects from inside the square brackets (the optional value).
|
|
||||||
pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
|
|
||||||
'b,
|
'b,
|
||||||
's,
|
's,
|
||||||
'x,
|
'x,
|
||||||
R,
|
R,
|
||||||
RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>,
|
RV: std::fmt::Debug + 'b,
|
||||||
|
ROV: std::fmt::Debug + 'b,
|
||||||
|
RI: Iterator<Item = &'b (Option<Vec<ROV>>, Vec<RV>)> + ExactSizeIterator + std::fmt::Debug,
|
||||||
|
RG: Fn(R) -> Option<RI>,
|
||||||
>(
|
>(
|
||||||
source: &'s str,
|
source: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust_node: R,
|
rust_node: R,
|
||||||
emacs_field: &'x str,
|
emacs_field: &'x str,
|
||||||
rust_value_getter: RG,
|
rust_value_getter: RG,
|
||||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>>
|
||||||
// TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier.
|
where
|
||||||
let rust_value = rust_value_getter(rust_node);
|
AstNode<'b, 's>: From<&'b RV>,
|
||||||
|
AstNode<'b, 's>: From<&'b ROV>,
|
||||||
|
{
|
||||||
let value = get_property(emacs, emacs_field)?
|
let value = get_property(emacs, emacs_field)?
|
||||||
.map(Token::as_list)
|
.map(Token::as_list)
|
||||||
.map_or(Ok(None), |r| r.map(Some))?;
|
.map_or(Ok(None), |r| r.map(Some))?;
|
||||||
let (value, rust_value) = match (value, rust_value) {
|
let rust_value = rust_value_getter(rust_node);
|
||||||
|
let (outer_emacs_list, outer_rust_list) = match (value, rust_value) {
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
return Ok(ComparePropertiesResult::NoChange);
|
return Ok(ComparePropertiesResult::NoChange);
|
||||||
}
|
}
|
||||||
(None, Some(_)) | (Some(_), None) => {
|
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field, value, rust_value
|
emacs_field, value, rv
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
(Some(value), Some(rust_value)) if value.len() != rust_value.len() => {
|
(Some(el), Some(rl)) if el.len() != rl.len() => {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field, value, rust_value
|
emacs_field, el, rl
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
(Some(value), Some(rust_value)) => (value, rust_value),
|
(Some(el), Some(rl)) => (el, rl),
|
||||||
};
|
};
|
||||||
|
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(rust_value.len());
|
for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) {
|
||||||
|
match (kw_e.as_atom(), kw_r) {
|
||||||
// Iterate the outer lists
|
(Ok("nil"), (None, mandatory_value)) if mandatory_value.is_empty() => {
|
||||||
for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) {
|
// If its an empty keyword then it becomes nil in the elisp.
|
||||||
let mut middle_value = value.as_list()?.iter();
|
continue;
|
||||||
// First element of middle list is the mandatory value (the value past the colon).
|
}
|
||||||
let mandatory_value = middle_value.next();
|
(Ok("nil"), _) => {
|
||||||
let mandatory_value = match mandatory_value {
|
|
||||||
Some(mandatory_value) => mandatory_value,
|
|
||||||
None => {
|
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field, value, rust_value
|
emacs_field, kw_e, kw_r
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
};
|
_ => {}
|
||||||
|
}
|
||||||
// Compare optional value
|
let kw_e = kw_e.as_list()?;
|
||||||
if let Some(rust_optional) = rust_optional {
|
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(rust_value.len());
|
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
|
||||||
if rust_optional.len() != middle_value.len() {
|
if let Some(or) = &kw_r.0 {
|
||||||
|
// if optional value
|
||||||
|
let mut kw_e = kw_e.iter();
|
||||||
|
// First element is a list representing the mandatory value.
|
||||||
|
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 this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field,
|
emacs_field, kw_e, kw_r
|
||||||
middle_value.len(),
|
|
||||||
rust_optional.len(),
|
|
||||||
rust_optional
|
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
for (e, r) in middle_value.zip(rust_optional) {
|
_ => {
|
||||||
|
let el = val_e.as_list()?;
|
||||||
|
if el.len() != kw_r.1.len() {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, kw_e, kw_r
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
for (e, r) in el.iter().zip(kw_r.1.iter()) {
|
||||||
child_status.push(compare_ast_node(source, e, r.into())?);
|
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||||
}
|
}
|
||||||
if !child_status.is_empty() {
|
|
||||||
let diff_scope = artificial_diff_scope("optional value", child_status)?;
|
|
||||||
full_status.push(diff_scope);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
} else {
|
||||||
// Compare mandatory value
|
|
||||||
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
|
|
||||||
let mandatory_value = mandatory_value.as_list()?;
|
|
||||||
if rust_value.len() != mandatory_value.len() {
|
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
emacs_field,
|
emacs_field, kw_e, kw_r
|
||||||
mandatory_value.len(),
|
|
||||||
rust_value.len(),
|
|
||||||
rust_value
|
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
for (e, r) in mandatory_value.iter().zip(rust_value) {
|
// Remaining elements are the optional value.
|
||||||
|
if kw_e.len() != or.len() {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, kw_e, kw_r
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
for (e, r) in kw_e.zip(or.iter()) {
|
||||||
child_status.push(compare_ast_node(source, e, r.into())?);
|
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// if no optional value
|
||||||
|
if !kw_e.len() == 1 {
|
||||||
|
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 e = kw_e
|
||||||
|
.first()
|
||||||
|
.map(Token::as_list)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.expect("The above if-statement proves this will be Some.")
|
||||||
|
.iter();
|
||||||
|
let r = kw_r.1.iter();
|
||||||
|
|
||||||
|
if e.len() != r.len() {
|
||||||
|
let this_status = DiffStatus::Bad;
|
||||||
|
let message = Some(format!(
|
||||||
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_field, kw_e, kw_r
|
||||||
|
));
|
||||||
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (e, r) in e.zip(r) {
|
||||||
|
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||||
|
}
|
||||||
|
}
|
||||||
if !child_status.is_empty() {
|
if !child_status.is_empty() {
|
||||||
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
|
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
|
||||||
full_status.push(diff_scope);
|
full_status.push(diff_scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if full_status.is_empty() {
|
if full_status.is_empty() {
|
||||||
Ok(ComparePropertiesResult::NoChange)
|
Ok(ComparePropertiesResult::NoChange)
|
||||||
} else {
|
} else {
|
||||||
@@ -553,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.")?;
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ use crate::types::FixedWidthArea;
|
|||||||
use crate::types::FootnoteDefinition;
|
use crate::types::FootnoteDefinition;
|
||||||
use crate::types::FootnoteReference;
|
use crate::types::FootnoteReference;
|
||||||
use crate::types::FootnoteReferenceType;
|
use crate::types::FootnoteReferenceType;
|
||||||
use crate::types::GetStandardProperties;
|
|
||||||
use crate::types::Heading;
|
use crate::types::Heading;
|
||||||
use crate::types::HorizontalRule;
|
use crate::types::HorizontalRule;
|
||||||
use crate::types::Hour;
|
use crate::types::Hour;
|
||||||
@@ -128,7 +127,7 @@ pub struct DiffResult<'b, 's> {
|
|||||||
emacs_token: &'b Token<'s>,
|
emacs_token: &'b Token<'s>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum DiffStatus {
|
pub(crate) enum DiffStatus {
|
||||||
Good,
|
Good,
|
||||||
Bad,
|
Bad,
|
||||||
@@ -164,7 +163,7 @@ impl<'b, 's> DiffEntry<'b, 's> {
|
|||||||
|
|
||||||
fn is_immediately_bad(&self) -> bool {
|
fn is_immediately_bad(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
DiffEntry::DiffResult(diff) => diff.status == DiffStatus::Bad,
|
DiffEntry::DiffResult(diff) => matches!(diff.status, DiffStatus::Bad),
|
||||||
DiffEntry::DiffLayer(_) => false,
|
DiffEntry::DiffLayer(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,7 +226,7 @@ impl<'b, 's> DiffResult<'b, 's> {
|
|||||||
status_text = status_text,
|
status_text = status_text,
|
||||||
name = self.name,
|
name = self.name,
|
||||||
char_offset = preceding_text.chars().count() + 1,
|
char_offset = preceding_text.chars().count() + 1,
|
||||||
message = self.message.as_ref().map(|m| m.as_str()).unwrap_or("")
|
message = self.message.as_deref().unwrap_or("")
|
||||||
);
|
);
|
||||||
for child in self.children.iter() {
|
for child in self.children.iter() {
|
||||||
child.print_indented(indentation + 1, original_document)?;
|
child.print_indented(indentation + 1, original_document)?;
|
||||||
@@ -330,8 +329,8 @@ pub(crate) fn artificial_diff_scope<'b, 's>(
|
|||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn artificial_owned_diff_scope<'b, 's, 'x>(
|
pub(crate) fn artificial_owned_diff_scope<'b, 's>(
|
||||||
name: &'x str,
|
name: &str,
|
||||||
children: Vec<DiffEntry<'b, 's>>,
|
children: Vec<DiffEntry<'b, 's>>,
|
||||||
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
|
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
|
||||||
Ok(DiffLayer {
|
Ok(DiffLayer {
|
||||||
@@ -413,7 +412,7 @@ pub(crate) fn compare_ast_node<'b, 's>(
|
|||||||
name: rust.get_elisp_fact().get_elisp_name(),
|
name: rust.get_elisp_fact().get_elisp_name(),
|
||||||
message: Some(e.to_string()),
|
message: Some(e.to_string()),
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
rust_source: rust.get_standard_properties().get_source(),
|
rust_source: rust.get_source(),
|
||||||
emacs_token: emacs,
|
emacs_token: emacs,
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
@@ -426,15 +425,10 @@ pub(crate) fn compare_ast_node<'b, 's>(
|
|||||||
|
|
||||||
// PlainText is a special case because upstream Org-Mode uses relative values for the bounds in plaintext rather than absolute so the below checks do not account for that.
|
// PlainText is a special case because upstream Org-Mode uses relative values for the bounds in plaintext rather than absolute so the below checks do not account for that.
|
||||||
if let AstNode::PlainText(_) = rust {
|
if let AstNode::PlainText(_) = rust {
|
||||||
} else {
|
} else if let Err(err) = compare_standard_properties(source, emacs, &rust) {
|
||||||
match compare_standard_properties(source, emacs, &rust) {
|
|
||||||
Err(err) => {
|
|
||||||
compare_result.status = DiffStatus::Bad;
|
compare_result.status = DiffStatus::Bad;
|
||||||
compare_result.message = Some(err.to_string())
|
compare_result.message = Some(err.to_string())
|
||||||
}
|
}
|
||||||
Ok(_) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(compare_result.into())
|
Ok(compare_result.into())
|
||||||
}
|
}
|
||||||
@@ -495,7 +489,7 @@ fn _compare_document<'b, 's>(
|
|||||||
.map(EmacsField::Required),
|
.map(EmacsField::Required),
|
||||||
(
|
(
|
||||||
EmacsField::Required(":path"),
|
EmacsField::Required(":path"),
|
||||||
|r| r.path.as_ref().map(|p| p.to_str()).flatten(),
|
|r| r.path.as_ref().and_then(|p| p.to_str()),
|
||||||
compare_property_quoted_string
|
compare_property_quoted_string
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -1581,7 +1575,7 @@ fn compare_example_block<'b, 's>(
|
|||||||
[],
|
[],
|
||||||
(
|
(
|
||||||
EmacsField::Required(":value"),
|
EmacsField::Required(":value"),
|
||||||
|r| Some(r.contents.as_str()),
|
|r| Some(r.get_value()),
|
||||||
compare_property_quoted_string
|
compare_property_quoted_string
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -1659,7 +1653,7 @@ fn compare_export_block<'b, 's>(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
EmacsField::Required(":value"),
|
EmacsField::Required(":value"),
|
||||||
|r| Some(r.contents.as_str()),
|
|r| Some(r.get_value()),
|
||||||
compare_property_quoted_string
|
compare_property_quoted_string
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -1707,7 +1701,7 @@ fn compare_src_block<'b, 's>(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
EmacsField::Required(":value"),
|
EmacsField::Required(":value"),
|
||||||
|r| Some(r.contents.as_str()),
|
|r| Some(r.get_value()),
|
||||||
compare_property_quoted_string
|
compare_property_quoted_string
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -2158,7 +2152,7 @@ fn compare_plain_text<'b, 's>(
|
|||||||
let text = emacs.as_text()?;
|
let text = emacs.as_text()?;
|
||||||
let start_ind: usize = text
|
let start_ind: usize = text
|
||||||
.properties
|
.properties
|
||||||
.get(0)
|
.first()
|
||||||
.expect("Should have start index.")
|
.expect("Should have start index.")
|
||||||
.as_atom()?
|
.as_atom()?
|
||||||
.parse()?;
|
.parse()?;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod compare;
|
mod compare;
|
||||||
mod compare_field;
|
mod compare_field;
|
||||||
mod diff;
|
mod diff;
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -113,70 +108,175 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
|
|||||||
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
||||||
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||||
debug_assert!(is_slice_of(input, remaining));
|
debug_assert!(is_slice_of(input, remaining));
|
||||||
let source = {
|
|
||||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
||||||
&input[..offset]
|
&input[..offset]
|
||||||
};
|
|
||||||
source.into()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
if !text.starts_with(r#"""#) {
|
Normal,
|
||||||
|
Escape,
|
||||||
|
HasEscape {
|
||||||
|
out: Vec<u8>,
|
||||||
|
},
|
||||||
|
HasEscapeEscape {
|
||||||
|
out: Vec<u8>,
|
||||||
|
},
|
||||||
|
Octal {
|
||||||
|
octal_begin_offset: usize,
|
||||||
|
octal: Vec<u8>,
|
||||||
|
},
|
||||||
|
HasEscapeOctal {
|
||||||
|
out: Vec<u8>,
|
||||||
|
octal: Vec<u8>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn unquote(text: &str) -> Result<Cow<'_, str>, Box<dyn std::error::Error>> {
|
||||||
|
if !text.starts_with('"') {
|
||||||
return Err("Quoted text does not start with quote.".into());
|
return Err("Quoted text does not start with quote.".into());
|
||||||
}
|
}
|
||||||
if !text.ends_with(r#"""#) {
|
if !text.ends_with('"') {
|
||||||
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().into_iter() {
|
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"))]
|
||||||
@@ -229,11 +329,9 @@ fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
let (remaining, body) = take_till1(|c| match c {
|
let (remaining, body) =
|
||||||
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
|
take_till1(|c| matches!(c, ' ' | '\t' | '\r' | '\n' | ')' | ']'))(input)?;
|
||||||
_ => false,
|
Ok((remaining, Token::Atom(body)))
|
||||||
})(input)?;
|
|
||||||
Ok((remaining, Token::Atom(body.into())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -264,22 +362,19 @@ fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
}
|
}
|
||||||
let (remaining, _) = tag(r#"""#)(remaining)?;
|
let (remaining, _) = tag(r#"""#)(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Token::Atom(source.into())))
|
Ok((remaining, Token::Atom(source)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
let (remaining, _) = tag("#<")(input)?;
|
let (remaining, _) = tag("#<")(input)?;
|
||||||
let (remaining, _body) = take_till1(|c| match c {
|
let (remaining, _body) = take_till1(|c| matches!(c, '>'))(remaining)?;
|
||||||
'>' => true,
|
|
||||||
_ => false,
|
|
||||||
})(remaining)?;
|
|
||||||
let (remaining, _) = tag(">")(remaining)?;
|
let (remaining, _) = tag(">")(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Token::Atom(source.into())))
|
Ok((remaining, Token::Atom(source)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn text_with_properties(input: &str) -> Res<&str, Token<'_>> {
|
||||||
let (remaining, _) = tag("#(")(input)?;
|
let (remaining, _) = tag("#(")(input)?;
|
||||||
let (remaining, (text, props)) = delimited(
|
let (remaining, (text, props)) = delimited(
|
||||||
multispace0,
|
multispace0,
|
||||||
@@ -348,10 +443,7 @@ mod tests {
|
|||||||
let input = r#" (foo "b(a)r" baz ) "#;
|
let input = r#" (foo "b(a)r" baz ) "#;
|
||||||
let (remaining, parsed) = sexp(input).expect("Parse the input");
|
let (remaining, parsed) = sexp(input).expect("Parse the input");
|
||||||
assert_eq!(remaining, "");
|
assert_eq!(remaining, "");
|
||||||
assert!(match parsed {
|
assert!(matches!(parsed, Token::List(_)));
|
||||||
Token::List(_) => true,
|
|
||||||
_ => false,
|
|
||||||
});
|
|
||||||
let children = match parsed {
|
let children = match parsed {
|
||||||
Token::List(children) => children,
|
Token::List(children) => children,
|
||||||
_ => panic!("Should be a list."),
|
_ => panic!("Should be a list."),
|
||||||
@@ -364,14 +456,14 @@ mod tests {
|
|||||||
r#"foo"#
|
r#"foo"#
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
match children.iter().nth(1) {
|
match children.get(1) {
|
||||||
Some(Token::Atom(body)) => *body,
|
Some(Token::Atom(body)) => *body,
|
||||||
_ => panic!("Second child should be an atom."),
|
_ => panic!("Second child should be an atom."),
|
||||||
},
|
},
|
||||||
r#""b(a)r""#
|
r#""b(a)r""#
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
match children.iter().nth(2) {
|
match children.get(2) {
|
||||||
Some(Token::Atom(body)) => *body,
|
Some(Token::Atom(body)) => *body,
|
||||||
_ => panic!("Third child should be an atom."),
|
_ => panic!("Third child should be an atom."),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use super::compare_field::compare_property_list_of_list_of_list_of_ast_nodes;
|
|
||||||
use super::compare_field::compare_property_list_of_quoted_string;
|
use super::compare_field::compare_property_list_of_quoted_string;
|
||||||
|
use super::compare_field::compare_property_object_tree;
|
||||||
|
use super::compare_field::compare_property_optional_pair;
|
||||||
use super::compare_field::compare_property_quoted_string;
|
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;
|
||||||
@@ -13,7 +15,6 @@ use crate::compare::sexp::unquote;
|
|||||||
use crate::types::AffiliatedKeywordValue;
|
use crate::types::AffiliatedKeywordValue;
|
||||||
use crate::types::AstNode;
|
use crate::types::AstNode;
|
||||||
use crate::types::GetAffiliatedKeywords;
|
use crate::types::GetAffiliatedKeywords;
|
||||||
use crate::types::GetStandardProperties;
|
|
||||||
use crate::types::StandardProperties;
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
/// Check if the child string slice is a slice of the parent string slice.
|
/// Check if the child string slice is a slice of the parent string slice.
|
||||||
@@ -28,33 +29,30 @@ 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<'b, 's, S: AsRef<str>>(
|
fn assert_name<S: AsRef<str>>(
|
||||||
emacs: &'b Token<'s>,
|
emacs: &Token<'_>,
|
||||||
name: S,
|
name: S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
@@ -76,25 +74,73 @@ pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
|
|||||||
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
|
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
|
||||||
///
|
///
|
||||||
/// This does **not** handle plain text because plain text is a special case.
|
/// This does **not** handle plain text because plain text is a special case.
|
||||||
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||||
original_document: &'s str,
|
original_document: &'s str,
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
rust: &'b S,
|
rust: &'b S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
||||||
|
|
||||||
|
// Check begin/end
|
||||||
|
{
|
||||||
let (begin, end) = (
|
let (begin, end) = (
|
||||||
standard_properties
|
standard_properties
|
||||||
.begin
|
.begin
|
||||||
.ok_or("Token should have a begin.")?,
|
.ok_or("Token should have a begin.")?,
|
||||||
standard_properties.end.ok_or("Token should have an end.")?,
|
standard_properties.end.ok_or("Token should have an end.")?,
|
||||||
);
|
);
|
||||||
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust); // 0-based
|
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust.get_source()); // 0-based
|
||||||
let rust_begin_char_offset = (&original_document[..rust_begin]).chars().count() + 1; // 1-based
|
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
|
||||||
let rust_end_char_offset =
|
let rust_end_char_offset =
|
||||||
rust_begin_char_offset + (&original_document[rust_begin..rust_end]).chars().count(); // 1-based
|
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
|
||||||
if rust_begin_char_offset != begin || rust_end_char_offset != end {
|
if rust_begin_char_offset != begin || rust_end_char_offset != end {
|
||||||
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
|
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check contents-begin/contents-end
|
||||||
|
{
|
||||||
|
if let Some(rust_contents) = rust.get_contents() {
|
||||||
|
let (begin, end) = (
|
||||||
|
standard_properties
|
||||||
|
.contents_begin
|
||||||
|
.ok_or("Token should have a contents-begin.")?,
|
||||||
|
standard_properties
|
||||||
|
.contents_end
|
||||||
|
.ok_or("Token should have an contents-end.")?,
|
||||||
|
);
|
||||||
|
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust_contents); // 0-based
|
||||||
|
let rust_begin_char_offset = original_document[..rust_begin].chars().count() + 1; // 1-based
|
||||||
|
let rust_end_char_offset =
|
||||||
|
rust_begin_char_offset + original_document[rust_begin..rust_end].chars().count(); // 1-based
|
||||||
|
if rust_begin_char_offset != begin || rust_end_char_offset != end {
|
||||||
|
Err(format!("Rust contents bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs contents bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
|
||||||
|
}
|
||||||
|
} else if standard_properties.contents_begin.is_some()
|
||||||
|
|| standard_properties.contents_end.is_some()
|
||||||
|
{
|
||||||
|
Err(format!("Rust contents is None but emacs contents bounds are ({emacs_begin:?}, {emacs_end:?})", emacs_begin=standard_properties.contents_begin, emacs_end=standard_properties.contents_end))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the post blank matches between emacs and organic.
|
||||||
|
///
|
||||||
|
/// This does **not** handle plain text because plain text is a special case.
|
||||||
|
fn assert_post_blank<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
rust: &'b S,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
||||||
|
let rust_post_blank = rust.get_post_blank();
|
||||||
|
let emacs_post_blank = standard_properties
|
||||||
|
.post_blank
|
||||||
|
.ok_or("Token should have a post-blank.")?;
|
||||||
|
if rust_post_blank as usize != emacs_post_blank {
|
||||||
|
Err(format!("Rust post-blank (in chars) {rust_post_blank} does not match emacs post-blank ({emacs_post_blank})", rust_post_blank = rust_post_blank, emacs_post_blank = emacs_post_blank))?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -112,21 +158,18 @@ struct EmacsStandardProperties {
|
|||||||
post_blank: Option<usize>,
|
post_blank: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_emacs_standard_properties<'b, 's>(
|
fn get_emacs_standard_properties(
|
||||||
emacs: &'b Token<'s>,
|
emacs: &Token<'_>,
|
||||||
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
|
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let attributes_child = children
|
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
|
||||||
.iter()
|
|
||||||
.nth(1)
|
|
||||||
.ok_or("Should have an attributes child.")?;
|
|
||||||
let attributes_map = attributes_child.as_map()?;
|
let attributes_map = attributes_child.as_map()?;
|
||||||
let standard_properties = attributes_map.get(":standard-properties");
|
let standard_properties = attributes_map.get(":standard-properties");
|
||||||
Ok(if standard_properties.is_some() {
|
Ok(if standard_properties.is_some() {
|
||||||
let mut std_props = standard_properties
|
let mut std_props = standard_properties
|
||||||
.expect("if statement proves its Some")
|
.expect("if statement proves its Some")
|
||||||
.as_vector()?
|
.as_vector()?
|
||||||
.into_iter();
|
.iter();
|
||||||
let begin = maybe_token_to_usize(std_props.next())?;
|
let begin = maybe_token_to_usize(std_props.next())?;
|
||||||
let post_affiliated = 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_begin = maybe_token_to_usize(std_props.next())?;
|
||||||
@@ -142,16 +185,13 @@ fn get_emacs_standard_properties<'b, 's>(
|
|||||||
post_blank,
|
post_blank,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let begin = maybe_token_to_usize(attributes_map.get(":begin").map(|token| *token))?;
|
let begin = maybe_token_to_usize(attributes_map.get(":begin").copied())?;
|
||||||
let end = maybe_token_to_usize(attributes_map.get(":end").map(|token| *token))?;
|
let end = maybe_token_to_usize(attributes_map.get(":end").copied())?;
|
||||||
let contents_begin =
|
let contents_begin = maybe_token_to_usize(attributes_map.get(":contents-begin").copied())?;
|
||||||
maybe_token_to_usize(attributes_map.get(":contents-begin").map(|token| *token))?;
|
let contents_end = maybe_token_to_usize(attributes_map.get(":contents-end").copied())?;
|
||||||
let contents_end =
|
let post_blank = maybe_token_to_usize(attributes_map.get(":post-blank").copied())?;
|
||||||
maybe_token_to_usize(attributes_map.get(":contents-end").map(|token| *token))?;
|
|
||||||
let post_blank =
|
|
||||||
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
|
|
||||||
let post_affiliated =
|
let post_affiliated =
|
||||||
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
|
maybe_token_to_usize(attributes_map.get(":post-affiliated").copied())?;
|
||||||
EmacsStandardProperties {
|
EmacsStandardProperties {
|
||||||
begin,
|
begin,
|
||||||
post_affiliated,
|
post_affiliated,
|
||||||
@@ -169,62 +209,57 @@ fn maybe_token_to_usize(
|
|||||||
Ok(token
|
Ok(token
|
||||||
.map(|token| token.as_atom())
|
.map(|token| token.as_atom())
|
||||||
.map_or(Ok(None), |r| r.map(Some))?
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
.map(|val| {
|
.and_then(|val| {
|
||||||
if val == "nil" {
|
if val == "nil" {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(val.parse::<usize>())
|
Some(val.parse::<usize>())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil
|
|
||||||
.map_or(Ok(None), |r| r.map(Some))?)
|
.map_or(Ok(None), |r| r.map(Some))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a named property from the emacs token.
|
/// Get a named property from the emacs token.
|
||||||
///
|
///
|
||||||
/// Returns Ok(None) if value is nil or absent.
|
/// Returns Ok(None) if value is nil or absent.
|
||||||
pub(crate) fn get_property<'b, 's, 'x>(
|
pub(crate) fn get_property<'b, 's>(
|
||||||
emacs: &'b Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
key: &'x str,
|
key: &str,
|
||||||
) -> Result<Option<&'b Token<'s>>, Box<dyn std::error::Error>> {
|
) -> Result<Option<&'b Token<'s>>, Box<dyn std::error::Error>> {
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let attributes_child = children
|
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
|
||||||
.iter()
|
|
||||||
.nth(1)
|
|
||||||
.ok_or("Should have an attributes child.")?;
|
|
||||||
let attributes_map = attributes_child.as_map()?;
|
let attributes_map = attributes_child.as_map()?;
|
||||||
let prop = attributes_map.get(key).map(|token| *token);
|
let prop = attributes_map.get(key).copied();
|
||||||
match prop.map(|token| token.as_atom()) {
|
if let Some(Ok("nil")) = prop.map(Token::as_atom) {
|
||||||
Some(Ok("nil")) => return Ok(None),
|
return Ok(None);
|
||||||
_ => {}
|
}
|
||||||
};
|
|
||||||
Ok(prop)
|
Ok(prop)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a named property containing an unquoted atom from the emacs token.
|
/// Get a named property containing an unquoted atom from the emacs token.
|
||||||
///
|
///
|
||||||
/// Returns None if key is not found.
|
/// Returns None if key is not found.
|
||||||
pub(crate) fn get_property_unquoted_atom<'b, 's, 'x>(
|
pub(crate) fn get_property_unquoted_atom<'s>(
|
||||||
emacs: &'b Token<'s>,
|
emacs: &Token<'s>,
|
||||||
key: &'x str,
|
key: &str,
|
||||||
) -> Result<Option<&'s str>, Box<dyn std::error::Error>> {
|
) -> Result<Option<&'s str>, Box<dyn std::error::Error>> {
|
||||||
Ok(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))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<'b, 's, 'x>(
|
pub(crate) fn get_property_quoted_string<'s>(
|
||||||
emacs: &'b Token<'s>,
|
emacs: &Token<'s>,
|
||||||
key: &'x str,
|
key: &str,
|
||||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
) -> Result<Option<Cow<'s, str>>, Box<dyn std::error::Error>> {
|
||||||
Ok(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))?
|
||||||
.map(unquote)
|
.map(unquote)
|
||||||
.map_or(Ok(None), |r| r.map(Some))?)
|
.map_or(Ok(None), |r| r.map(Some))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a named property containing an unquoted numeric value.
|
/// Get a named property containing an unquoted numeric value.
|
||||||
@@ -301,8 +336,8 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_no_children<'b, 's>(
|
pub(crate) fn assert_no_children(
|
||||||
emacs: &'b Token<'s>,
|
emacs: &Token<'_>,
|
||||||
this_status: &mut DiffStatus,
|
this_status: &mut DiffStatus,
|
||||||
message: &mut Option<String>,
|
message: &mut Option<String>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@@ -331,7 +366,7 @@ where
|
|||||||
let rust_key = rust_key.as_ref();
|
let rust_key = rust_key.as_ref();
|
||||||
let rust_value = rust_value.as_ref();
|
let rust_value = rust_value.as_ref();
|
||||||
let emacs_value = get_property_quoted_string(emacs, rust_key)?;
|
let emacs_value = get_property_quoted_string(emacs, rust_key)?;
|
||||||
if Some(rust_value) != emacs_value.as_ref().map(String::as_str) {
|
if Some(rust_value) != emacs_value.as_deref() {
|
||||||
let this_status = DiffStatus::Bad;
|
let this_status = DiffStatus::Bad;
|
||||||
let message = Some(format!(
|
let message = Some(format!(
|
||||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||||
@@ -376,13 +411,23 @@ where
|
|||||||
)?;
|
)?;
|
||||||
ret.push(diff);
|
ret.push(diff);
|
||||||
}
|
}
|
||||||
AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => {
|
AffiliatedKeywordValue::OptionalPair { optval, val } => {
|
||||||
let diff = compare_property_list_of_list_of_list_of_ast_nodes(
|
let diff = compare_property_optional_pair(
|
||||||
source,
|
source,
|
||||||
emacs,
|
emacs,
|
||||||
rust,
|
rust,
|
||||||
emacs_property_name.as_str(),
|
emacs_property_name.as_str(),
|
||||||
|_| Some(rust_value),
|
|_| Some((*optval, *val)),
|
||||||
|
)?;
|
||||||
|
ret.push(diff);
|
||||||
|
}
|
||||||
|
AffiliatedKeywordValue::ObjectTree(rust_value) => {
|
||||||
|
let diff = compare_property_object_tree(
|
||||||
|
source,
|
||||||
|
emacs,
|
||||||
|
rust,
|
||||||
|
emacs_property_name.as_str(),
|
||||||
|
|_| Some(rust_value.iter()),
|
||||||
)?;
|
)?;
|
||||||
ret.push(diff);
|
ret.push(diff);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
use super::global_settings::EntityDefinition;
|
use super::global_settings::EntityDefinition;
|
||||||
|
|
||||||
pub(crate) const DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS: [&'static str; 1] = ["CAPTION"];
|
/// Keywords that contain the standard set of objects (excluding footnote references).
|
||||||
|
///
|
||||||
|
/// Corresponds to org-element-parsed-keywords elisp variable.
|
||||||
|
pub(crate) const ORG_ELEMENT_PARSED_KEYWORDS: [&str; 1] = ["CAPTION"];
|
||||||
|
|
||||||
pub(crate) const DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["CAPTION", "RESULTS"];
|
/// Keywords that can have a secondary value in square brackets.
|
||||||
|
///
|
||||||
|
/// Corresponds to org-element-dual-keywords elisp variable.
|
||||||
|
pub(crate) const ORG_ELEMENT_DUAL_KEYWORDS: [&str; 2] = ["CAPTION", "RESULTS"];
|
||||||
|
|
||||||
pub(crate) const DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
|
/// Keywords that can be affiliated with an element.
|
||||||
|
///
|
||||||
|
/// Corresponds to org-element-affiliated-keywords elisp variable.
|
||||||
|
pub(crate) const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&str; 13] = [
|
||||||
"CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT",
|
"CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT",
|
||||||
"RESULTS", "SOURCE", "SRCNAME", "TBLNAME",
|
"RESULTS", "SOURCE", "SRCNAME", "TBLNAME",
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&'static str, &'static str); 8] = [
|
/// Mapping of keyword names.
|
||||||
|
///
|
||||||
|
/// Corresponds to org-element-keyword-translation-alist elisp variable.
|
||||||
|
pub(crate) const ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&str, &str); 8] = [
|
||||||
("DATA", "NAME"),
|
("DATA", "NAME"),
|
||||||
("LABEL", "NAME"),
|
("LABEL", "NAME"),
|
||||||
("RESNAME", "NAME"),
|
("RESNAME", "NAME"),
|
||||||
@@ -20,7 +32,7 @@ pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&'static str,
|
|||||||
("HEADERS", "HEADER"),
|
("HEADERS", "HEADER"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&'static str; 23] = [
|
pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&str; 23] = [
|
||||||
"id",
|
"id",
|
||||||
"eww",
|
"eww",
|
||||||
"rmail",
|
"rmail",
|
||||||
|
|||||||
@@ -9,11 +9,9 @@ use super::list::List;
|
|||||||
use super::DynContextMatcher;
|
use super::DynContextMatcher;
|
||||||
use super::RefContext;
|
use super::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::OrgSource;
|
use crate::parser::OrgSource;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum ContextElement<'r, 's> {
|
pub(crate) enum ContextElement<'r, 's> {
|
||||||
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
|
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
|
||||||
ExitMatcherNode(ExitMatcherNode<'r>),
|
ExitMatcherNode(ExitMatcherNode<'r>),
|
||||||
@@ -35,15 +33,6 @@ pub(crate) struct ExitMatcherNode<'r> {
|
|||||||
pub(crate) class: ExitClass,
|
pub(crate) class: ExitClass,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut formatter = f.debug_struct("ExitMatcherNode");
|
|
||||||
formatter.field("class", &self.class.to_string());
|
|
||||||
formatter.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Context<'g, 'r, 's> {
|
pub(crate) struct Context<'g, 'r, 's> {
|
||||||
global_settings: &'g GlobalSettings<'g, 's>,
|
global_settings: &'g GlobalSettings<'g, 's>,
|
||||||
tree: List<'r, &'r ContextElement<'r, 's>>,
|
tree: List<'r, &'r ContextElement<'r, 's>>,
|
||||||
@@ -108,12 +97,11 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
|||||||
pub(crate) fn check_exit_matcher(
|
pub(crate) fn check_exit_matcher(
|
||||||
&'r self,
|
&'r self,
|
||||||
i: OrgSource<'s>,
|
i: OrgSource<'s>,
|
||||||
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
|
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError> {
|
||||||
let mut current_class_filter = ExitClass::Gamma;
|
let mut current_class_filter = ExitClass::Gamma;
|
||||||
for current_node in self.iter_context() {
|
for current_node in self.iter_context() {
|
||||||
let context_element = current_node.get_data();
|
let context_element = current_node.get_data();
|
||||||
match context_element {
|
if let ContextElement::ExitMatcherNode(exit_matcher) = context_element {
|
||||||
ContextElement::ExitMatcherNode(exit_matcher) => {
|
|
||||||
if exit_matcher.class as u32 <= current_class_filter as u32 {
|
if exit_matcher.class as u32 <= current_class_filter as u32 {
|
||||||
current_class_filter = exit_matcher.class;
|
current_class_filter = exit_matcher.class;
|
||||||
let local_result = (exit_matcher.exit_matcher)(¤t_node, i);
|
let local_result = (exit_matcher.exit_matcher)(¤t_node, i);
|
||||||
@@ -122,13 +110,9 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
// TODO: Make this a specific error instead of just a generic MyError
|
// TODO: Make this a specific error instead of just a generic MyError
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("NoExit")));
|
||||||
"NoExit".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates if elements should consume the whitespace after them.
|
/// Indicates if elements should consume the whitespace after them.
|
||||||
@@ -140,12 +124,9 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
|||||||
|
|
||||||
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
|
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
|
||||||
for current_node in self.iter() {
|
for current_node in self.iter() {
|
||||||
match current_node {
|
if let ContextElement::ConsumeTrailingWhitespace(should) = current_node {
|
||||||
ContextElement::ConsumeTrailingWhitespace(should) => {
|
|
||||||
return Some(*should);
|
return Some(*should);
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub(crate) enum ExitClass {
|
pub(crate) enum ExitClass {
|
||||||
Document = 1,
|
Document = 1,
|
||||||
Alpha = 2,
|
Alpha = 2,
|
||||||
Beta = 3,
|
Beta = 3,
|
||||||
Gamma = 4,
|
Gamma = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for ExitClass {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
use std::fmt::Debug;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[cfg(any(feature = "compare", feature = "foreign_document_test"))]
|
#[cfg(any(feature = "compare", feature = "foreign_document_test"))]
|
||||||
pub trait FileAccessInterface: Sync + Debug {
|
pub trait FileAccessInterface: Sync {
|
||||||
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
|
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))]
|
#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))]
|
||||||
pub trait FileAccessInterface: Debug {
|
pub trait FileAccessInterface {
|
||||||
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
|
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LocalFileAccessInterface {
|
pub struct LocalFileAccessInterface {
|
||||||
pub working_directory: Option<PathBuf>,
|
pub working_directory: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
@@ -20,10 +19,9 @@ impl FileAccessInterface for LocalFileAccessInterface {
|
|||||||
fn read_file(&self, path: &str) -> Result<String, std::io::Error> {
|
fn read_file(&self, path: &str) -> Result<String, std::io::Error> {
|
||||||
let final_path = self
|
let final_path = self
|
||||||
.working_directory
|
.working_directory
|
||||||
.as_ref()
|
.as_deref()
|
||||||
.map(PathBuf::as_path)
|
|
||||||
.map(|pb| pb.join(path))
|
.map(|pb| pb.join(path))
|
||||||
.unwrap_or_else(|| PathBuf::from(path));
|
.unwrap_or_else(|| PathBuf::from(path));
|
||||||
Ok(std::fs::read_to_string(final_path)?)
|
std::fs::read_to_string(final_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,16 +5,12 @@ use super::constants::DEFAULT_ORG_ENTITIES;
|
|||||||
use super::constants::DEFAULT_ORG_LINK_PARAMETERS;
|
use super::constants::DEFAULT_ORG_LINK_PARAMETERS;
|
||||||
use super::FileAccessInterface;
|
use super::FileAccessInterface;
|
||||||
use super::LocalFileAccessInterface;
|
use super::LocalFileAccessInterface;
|
||||||
use crate::context::constants::DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS;
|
|
||||||
use crate::context::constants::DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS;
|
|
||||||
use crate::context::constants::DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
|
|
||||||
use crate::context::constants::DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS;
|
|
||||||
use crate::types::IndentationLevel;
|
use crate::types::IndentationLevel;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
|
|
||||||
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
|
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GlobalSettings<'g, 's> {
|
pub struct GlobalSettings<'g, 's> {
|
||||||
pub radio_targets: Vec<&'g Vec<Object<'s>>>,
|
pub radio_targets: Vec<&'g Vec<Object<'s>>>,
|
||||||
pub file_access: &'g dyn FileAccessInterface,
|
pub file_access: &'g dyn FileAccessInterface,
|
||||||
@@ -58,26 +54,6 @@ pub struct GlobalSettings<'g, 's> {
|
|||||||
///
|
///
|
||||||
/// Corresponds to org-entities elisp variable.
|
/// Corresponds to org-entities elisp variable.
|
||||||
pub entities: &'g [EntityDefinition<'s>],
|
pub entities: &'g [EntityDefinition<'s>],
|
||||||
|
|
||||||
/// Keywords that contain the standard set of objects (excluding footnote references).
|
|
||||||
///
|
|
||||||
/// Corresponds to org-element-parsed-keywords elisp variable.
|
|
||||||
pub element_parsed_keywords: &'g [&'s str],
|
|
||||||
|
|
||||||
/// Keywords that can have a secondary value in square brackets.
|
|
||||||
///
|
|
||||||
/// Corresponds to org-element-dual-keywords elisp variable.
|
|
||||||
pub element_dual_keywords: &'g [&'s str],
|
|
||||||
|
|
||||||
/// Keywords that can be affiliated with an element.
|
|
||||||
///
|
|
||||||
/// Corresponds to org-element-affiliated-keywords elisp variable.
|
|
||||||
pub element_affiliated_keywords: &'g [&'s str],
|
|
||||||
|
|
||||||
/// Mapping of keyword names.
|
|
||||||
///
|
|
||||||
/// Corresponds to org-element-keyword-translation-alist elisp variable.
|
|
||||||
pub element_keyword_translation_alist: &'g [(&'s str, &'s str)],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
|
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
|
||||||
@@ -112,10 +88,6 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
|||||||
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
|
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
|
||||||
link_templates: BTreeMap::new(),
|
link_templates: BTreeMap::new(),
|
||||||
entities: &DEFAULT_ORG_ENTITIES,
|
entities: &DEFAULT_ORG_ENTITIES,
|
||||||
element_parsed_keywords: &DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS,
|
|
||||||
element_dual_keywords: &DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS,
|
|
||||||
element_affiliated_keywords: &DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS,
|
|
||||||
element_keyword_translation_alist: &DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,14 +98,10 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Clone, PartialEq, Default)]
|
||||||
pub enum HeadlineLevelFilter {
|
pub enum HeadlineLevelFilter {
|
||||||
Odd,
|
Odd,
|
||||||
|
|
||||||
|
#[default]
|
||||||
OddEven,
|
OddEven,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HeadlineLevelFilter {
|
|
||||||
fn default() -> Self {
|
|
||||||
HeadlineLevelFilter::OddEven
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ impl<'a, T> Iterator for Iter<'a, T> {
|
|||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let ret = self.next.map(|link| link.get_data());
|
let ret = self.next.map(|link| link.get_data());
|
||||||
self.next = self.next.map(|link| link.get_parent()).flatten();
|
self.next = self.next.and_then(|link| link.get_parent());
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ impl<'a, T> Iterator for IterList<'a, T> {
|
|||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let ret = self.next;
|
let ret = self.next;
|
||||||
self.next = self.next.map(|this| this.get_parent()).flatten();
|
self.next = self.next.and_then(|link| link.get_parent());
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::OrgSource;
|
use crate::parser::OrgSource;
|
||||||
|
|
||||||
mod constants;
|
pub(crate) mod constants;
|
||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod context;
|
mod context;
|
||||||
mod exiting;
|
mod exiting;
|
||||||
mod file_access_interface;
|
mod file_access_interface;
|
||||||
@@ -30,4 +31,5 @@ pub use global_settings::GlobalSettings;
|
|||||||
pub use global_settings::HeadlineLevelFilter;
|
pub use global_settings::HeadlineLevelFilter;
|
||||||
pub use global_settings::DEFAULT_TAB_WIDTH;
|
pub use global_settings::DEFAULT_TAB_WIDTH;
|
||||||
pub(crate) use list::List;
|
pub(crate) use list::List;
|
||||||
|
pub(crate) use parser_with_context::bind_context;
|
||||||
pub(crate) use parser_with_context::parser_with_context;
|
pub(crate) use parser_with_context::parser_with_context;
|
||||||
|
|||||||
@@ -4,3 +4,10 @@ macro_rules! parser_with_context {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
pub(crate) use parser_with_context;
|
pub(crate) use parser_with_context;
|
||||||
|
|
||||||
|
macro_rules! bind_context {
|
||||||
|
($target:expr, $context:expr) => {
|
||||||
|
|i| $target($context, i)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub(crate) use bind_context;
|
||||||
|
|||||||
@@ -2,22 +2,18 @@ use nom::error::ErrorKind;
|
|||||||
use nom::error::ParseError;
|
use nom::error::ParseError;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
|
|
||||||
pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>;
|
pub(crate) type Res<T, U> = IResult<T, U, CustomError>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CustomError<I> {
|
pub enum CustomError {
|
||||||
MyError(MyError<&'static str>),
|
Static(&'static str),
|
||||||
Nom(I, ErrorKind),
|
|
||||||
IO(std::io::Error),
|
IO(std::io::Error),
|
||||||
BoxedError(Box<dyn std::error::Error>),
|
Parser(ErrorKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<I: std::fmt::Debug> ParseError<I> for CustomError {
|
||||||
pub struct MyError<I>(pub(crate) I);
|
fn from_error_kind(_input: I, kind: ErrorKind) -> Self {
|
||||||
|
CustomError::Parser(kind)
|
||||||
impl<I> ParseError<I> for CustomError<I> {
|
|
||||||
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
|
|
||||||
CustomError::Nom(input, kind)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self {
|
fn append(_input: I, _kind: ErrorKind, /*mut*/ other: Self) -> Self {
|
||||||
@@ -26,20 +22,14 @@ impl<I> ParseError<I> for CustomError<I> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I> From<std::io::Error> for CustomError<I> {
|
impl From<std::io::Error> for CustomError {
|
||||||
fn from(value: std::io::Error) -> Self {
|
fn from(value: std::io::Error) -> Self {
|
||||||
CustomError::IO(value)
|
CustomError::IO(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I> From<&'static str> for CustomError<I> {
|
impl From<&'static str> for CustomError {
|
||||||
fn from(value: &'static str) -> Self {
|
fn from(value: &'static str) -> Self {
|
||||||
CustomError::MyError(MyError(value))
|
CustomError::Static(value)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
|
|
||||||
fn from(value: Box<dyn std::error::Error>) -> Self {
|
|
||||||
CustomError::BoxedError(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod error;
|
mod error;
|
||||||
pub(crate) use error::CustomError;
|
pub(crate) use error::CustomError;
|
||||||
pub(crate) use error::MyError;
|
|
||||||
pub(crate) use error::Res;
|
pub(crate) use error::Res;
|
||||||
|
|||||||
43
src/event_count/database.rs
Normal file
43
src/event_count/database.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use super::EventType;
|
||||||
|
use crate::parser::OrgSource;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
struct EventKey {
|
||||||
|
event_type: EventType,
|
||||||
|
byte_offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type EventCount = usize;
|
||||||
|
|
||||||
|
static GLOBAL_DATA: Mutex<Option<HashMap<EventKey, EventCount>>> = Mutex::new(None);
|
||||||
|
|
||||||
|
pub(crate) fn record_event(event_type: EventType, input: OrgSource<'_>) {
|
||||||
|
let mut db = GLOBAL_DATA.lock().unwrap();
|
||||||
|
let db = db.get_or_insert_with(HashMap::new);
|
||||||
|
let key = EventKey {
|
||||||
|
event_type,
|
||||||
|
byte_offset: input.get_byte_offset(),
|
||||||
|
};
|
||||||
|
*db.entry(key).or_insert(0) += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn report(original_document: &str) {
|
||||||
|
let mut db = GLOBAL_DATA.lock().unwrap();
|
||||||
|
let db = db.get_or_insert_with(HashMap::new);
|
||||||
|
let mut results: Vec<_> = db.iter().map(|(k, v)| (k, v)).collect();
|
||||||
|
results.sort_by_key(|(_k, v)| *v);
|
||||||
|
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
|
||||||
|
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));
|
||||||
|
for (key, count) in results {
|
||||||
|
println!(
|
||||||
|
"{:?} {} character offset: {} byte offset: {}",
|
||||||
|
key.event_type,
|
||||||
|
count,
|
||||||
|
original_document[..key.byte_offset].chars().count() + 1,
|
||||||
|
key.byte_offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/event_count/event_type.rs
Normal file
4
src/event_count/event_type.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub(crate) enum EventType {
|
||||||
|
ElementStart,
|
||||||
|
}
|
||||||
6
src/event_count/mod.rs
Normal file
6
src/event_count/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
mod database;
|
||||||
|
mod event_type;
|
||||||
|
|
||||||
|
pub(crate) use database::record_event;
|
||||||
|
pub use database::report;
|
||||||
|
pub(crate) use event_type::EventType;
|
||||||
@@ -5,7 +5,7 @@ use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
|
|||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
|
||||||
const SERVICE_NAME: &'static str = "organic";
|
const SERVICE_NAME: &str = "organic";
|
||||||
|
|
||||||
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.
|
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.
|
||||||
|
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -2,13 +2,19 @@
|
|||||||
#![feature(trait_alias)]
|
#![feature(trait_alias)]
|
||||||
#![feature(path_file_prefix)]
|
#![feature(path_file_prefix)]
|
||||||
#![feature(is_sorted)]
|
#![feature(is_sorted)]
|
||||||
|
#![feature(test)]
|
||||||
// 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.
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
pub mod compare;
|
pub mod compare;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
mod error;
|
mod error;
|
||||||
|
#[cfg(feature = "event_count")]
|
||||||
|
pub mod event_count;
|
||||||
mod iter;
|
mod iter;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|||||||
19
src/main.rs
19
src/main.rs
@@ -23,13 +23,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let rt = tokio::runtime::Runtime::new()?;
|
let rt = tokio::runtime::Runtime::new()?;
|
||||||
let result = rt.block_on(async {
|
rt.block_on(async {
|
||||||
init_telemetry()?;
|
init_telemetry()?;
|
||||||
let main_body_result = main_body();
|
let main_body_result = main_body();
|
||||||
shutdown_telemetry()?;
|
shutdown_telemetry()?;
|
||||||
main_body_result
|
main_body_result
|
||||||
});
|
})
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -55,8 +54,11 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
|
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let rust_parsed = parse(org_contents.as_ref())?;
|
let org_contents = org_contents.as_ref();
|
||||||
|
let rust_parsed = parse(org_contents)?;
|
||||||
println!("{:#?}", rust_parsed);
|
println!("{:#?}", rust_parsed);
|
||||||
|
#[cfg(feature = "event_count")]
|
||||||
|
organic::event_count::report(org_contents);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,12 +72,13 @@ fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::err
|
|||||||
let file_access_interface = LocalFileAccessInterface {
|
let file_access_interface = LocalFileAccessInterface {
|
||||||
working_directory: Some(parent_directory.to_path_buf()),
|
working_directory: Some(parent_directory.to_path_buf()),
|
||||||
};
|
};
|
||||||
let global_settings = {
|
let global_settings = GlobalSettings {
|
||||||
let mut global_settings = GlobalSettings::default();
|
file_access: &file_access_interface,
|
||||||
global_settings.file_access = &file_access_interface;
|
..Default::default()
|
||||||
global_settings
|
|
||||||
};
|
};
|
||||||
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
|
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
|
||||||
println!("{:#?}", rust_parsed);
|
println!("{:#?}", rust_parsed);
|
||||||
|
#[cfg(feature = "event_count")]
|
||||||
|
organic::event_count::report(org_contents);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,17 +14,46 @@ use nom::multi::many0;
|
|||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::keyword::affiliated_keyword;
|
||||||
use super::object_parser::standard_set_object;
|
use super::object_parser::standard_set_object;
|
||||||
use super::util::confine_context;
|
use super::util::confine_context;
|
||||||
use crate::context::parser_with_context;
|
use super::OrgSource;
|
||||||
|
use crate::context::bind_context;
|
||||||
|
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
|
||||||
|
use crate::context::constants::ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
|
||||||
|
use crate::context::constants::ORG_ELEMENT_PARSED_KEYWORDS;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
|
use crate::error::Res;
|
||||||
use crate::types::AffiliatedKeywordValue;
|
use crate::types::AffiliatedKeywordValue;
|
||||||
use crate::types::AffiliatedKeywords;
|
use crate::types::AffiliatedKeywords;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
pub(crate) fn affiliated_keywords<'s>(
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
let mut remaining = input;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let result = affiliated_keyword(remaining);
|
||||||
|
match result {
|
||||||
|
Ok((remain, kw)) => {
|
||||||
|
remaining = remain;
|
||||||
|
ret.push(kw);
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((remaining, ret))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
|
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
|
||||||
global_settings: &'g GlobalSettings<'g, 's>,
|
global_settings: &'g GlobalSettings<'g, 's>,
|
||||||
input: AK,
|
input: AK,
|
||||||
@@ -34,29 +63,47 @@ where
|
|||||||
{
|
{
|
||||||
let mut ret = BTreeMap::new();
|
let mut ret = BTreeMap::new();
|
||||||
for kw in input {
|
for kw in input {
|
||||||
let translated_name = translate_name(global_settings, kw.key);
|
let translated_name = translate_name(kw.key);
|
||||||
if is_single_string_keyword(global_settings, translated_name.as_str()) {
|
let keyword_type = identify_keyword_type(translated_name.as_str());
|
||||||
|
match keyword_type {
|
||||||
|
AffiliatedKeywordType::SingleString => {
|
||||||
ret.insert(
|
ret.insert(
|
||||||
translated_name,
|
translated_name,
|
||||||
AffiliatedKeywordValue::SingleString(kw.value),
|
AffiliatedKeywordValue::SingleString(kw.value),
|
||||||
);
|
);
|
||||||
} else if is_list_of_single_string_keyword(global_settings, translated_name.as_str()) {
|
|
||||||
let list_of_strings = ret
|
|
||||||
.entry(translated_name)
|
|
||||||
.or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
|
|
||||||
match list_of_strings {
|
|
||||||
AffiliatedKeywordValue::ListOfStrings(list_of_strings)
|
|
||||||
if list_of_strings.is_empty() =>
|
|
||||||
{
|
|
||||||
list_of_strings.push(kw.value);
|
|
||||||
}
|
}
|
||||||
|
AffiliatedKeywordType::ListOfStrings => {
|
||||||
|
let list_of_strings = ret.entry(translated_name).or_insert_with(|| {
|
||||||
|
AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1))
|
||||||
|
});
|
||||||
|
match list_of_strings {
|
||||||
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
|
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
|
||||||
list_of_strings.clear();
|
|
||||||
list_of_strings.push(kw.value);
|
list_of_strings.push(kw.value);
|
||||||
}
|
}
|
||||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
||||||
}
|
}
|
||||||
} else if is_list_of_objects_keyword(global_settings, translated_name.as_str()) {
|
}
|
||||||
|
AffiliatedKeywordType::OptionalPair => {
|
||||||
|
let (_remaining, optional_string) = opt(all_consuming(map(
|
||||||
|
tuple((
|
||||||
|
take_until::<_, &str, nom::error::Error<_>>("["),
|
||||||
|
tag("["),
|
||||||
|
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
|
||||||
|
tag("]"),
|
||||||
|
eof,
|
||||||
|
)),
|
||||||
|
|(_, _, objects, _, _)| objects,
|
||||||
|
)))(kw.key)
|
||||||
|
.expect("Parser should always succeed.");
|
||||||
|
ret.insert(
|
||||||
|
translated_name,
|
||||||
|
AffiliatedKeywordValue::OptionalPair {
|
||||||
|
optval: optional_string,
|
||||||
|
val: kw.value,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
AffiliatedKeywordType::ObjectTree => {
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
||||||
|
|
||||||
@@ -67,8 +114,9 @@ where
|
|||||||
map_parser(
|
map_parser(
|
||||||
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
|
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
|
||||||
confine_context(|i| {
|
confine_context(|i| {
|
||||||
all_consuming(many0(parser_with_context!(standard_set_object)(
|
all_consuming(many0(bind_context!(
|
||||||
&initial_context,
|
standard_set_object,
|
||||||
|
&initial_context
|
||||||
)))(i)
|
)))(i)
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -80,40 +128,33 @@ where
|
|||||||
.expect("Object parser should always succeed.");
|
.expect("Object parser should always succeed.");
|
||||||
|
|
||||||
// TODO: This should be omitting footnote references
|
// TODO: This should be omitting footnote references
|
||||||
let (_remaining, objects) = all_consuming(many0(parser_with_context!(
|
let (_remaining, objects) = all_consuming(many0(bind_context!(
|
||||||
standard_set_object
|
standard_set_object,
|
||||||
)(&initial_context)))(kw.value.into())
|
&initial_context
|
||||||
|
)))(kw.value.into())
|
||||||
.expect("Object parser should always succeed.");
|
.expect("Object parser should always succeed.");
|
||||||
let list_of_lists = ret.entry(translated_name).or_insert_with(|| {
|
|
||||||
AffiliatedKeywordValue::ListOfListsOfObjects(Vec::with_capacity(1))
|
let entry_per_keyword_list = ret
|
||||||
});
|
|
||||||
match list_of_lists {
|
|
||||||
AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => {
|
|
||||||
list_of_lists.push((optional_objects, objects));
|
|
||||||
}
|
|
||||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let list_of_strings = ret
|
|
||||||
.entry(translated_name)
|
.entry(translated_name)
|
||||||
.or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
|
.or_insert_with(|| AffiliatedKeywordValue::ObjectTree(Vec::with_capacity(1)));
|
||||||
match list_of_strings {
|
match entry_per_keyword_list {
|
||||||
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
|
AffiliatedKeywordValue::ObjectTree(entry_per_keyword_list) => {
|
||||||
list_of_strings.push(kw.value);
|
entry_per_keyword_list.push((optional_objects, objects));
|
||||||
}
|
}
|
||||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
AffiliatedKeywords { keywords: ret }
|
AffiliatedKeywords { keywords: ret }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String {
|
fn translate_name(name: &str) -> String {
|
||||||
let name_until_optval = name
|
let name_until_optval = name
|
||||||
.split_once("[")
|
.split_once('[')
|
||||||
.map(|(before, _after)| before)
|
.map(|(before, _after)| before)
|
||||||
.unwrap_or(name);
|
.unwrap_or(name);
|
||||||
for (src, dst) in global_settings.element_keyword_translation_alist {
|
for (src, dst) in ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST {
|
||||||
if name_until_optval.eq_ignore_ascii_case(src) {
|
if name_until_optval.eq_ignore_ascii_case(src) {
|
||||||
return dst.to_lowercase();
|
return dst.to_lowercase();
|
||||||
}
|
}
|
||||||
@@ -121,40 +162,32 @@ fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s
|
|||||||
name_until_optval.to_lowercase()
|
name_until_optval.to_lowercase()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_single_string_keyword<'g, 's>(
|
enum AffiliatedKeywordType {
|
||||||
_global_settings: &'g GlobalSettings<'g, 's>,
|
SingleString,
|
||||||
name: &'s str,
|
ListOfStrings,
|
||||||
) -> bool {
|
OptionalPair,
|
||||||
// TODO: Is this defined by an elisp variable?
|
ObjectTree,
|
||||||
for single_string_name in ["plot", "name"] {
|
|
||||||
if name.eq_ignore_ascii_case(single_string_name) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_list_of_single_string_keyword<'g, 's>(
|
fn identify_keyword_type(name: &str) -> AffiliatedKeywordType {
|
||||||
_global_settings: &'g GlobalSettings<'g, 's>,
|
let is_multiple = ["CAPTION", "HEADER"]
|
||||||
name: &'s str,
|
.into_iter()
|
||||||
) -> bool {
|
.any(|candidate| name.eq_ignore_ascii_case(candidate))
|
||||||
// TODO: Is this defined by an elisp variable?
|
|| name.to_lowercase().starts_with("attr_");
|
||||||
for single_string_name in ["results"] {
|
let is_parsed = ORG_ELEMENT_PARSED_KEYWORDS
|
||||||
if name.eq_ignore_ascii_case(single_string_name) {
|
.iter()
|
||||||
return true;
|
.any(|candidate| name.eq_ignore_ascii_case(candidate));
|
||||||
|
let can_have_optval = ORG_ELEMENT_DUAL_KEYWORDS
|
||||||
|
.iter()
|
||||||
|
.any(|candidate| name.eq_ignore_ascii_case(candidate));
|
||||||
|
match (is_multiple, is_parsed, can_have_optval) {
|
||||||
|
(true, true, true) => AffiliatedKeywordType::ObjectTree,
|
||||||
|
(true, true, false) => unreachable!("Nothing like this exists in upstream org-mode."),
|
||||||
|
(true, false, true) => unreachable!("Nothing like this exists in upstream org-mode."),
|
||||||
|
(true, false, false) => AffiliatedKeywordType::ListOfStrings,
|
||||||
|
(false, true, true) => unreachable!("Nothing like this exists in upstream org-mode."),
|
||||||
|
(false, true, false) => unreachable!("Nothing like this exists in upstream org-mode."),
|
||||||
|
(false, false, true) => AffiliatedKeywordType::OptionalPair,
|
||||||
|
(false, false, false) => AffiliatedKeywordType::SingleString,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_list_of_objects_keyword<'g, 's>(
|
|
||||||
global_settings: &'g GlobalSettings<'g, 's>,
|
|
||||||
name: &'s str,
|
|
||||||
) -> bool {
|
|
||||||
for parsed_keyword in global_settings.element_parsed_keywords {
|
|
||||||
if name.eq_ignore_ascii_case(parsed_keyword) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use super::OrgSource;
|
|||||||
use crate::context::Matcher;
|
use crate::context::Matcher;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::org_line_ending;
|
use crate::parser::util::org_line_ending;
|
||||||
@@ -66,8 +65,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (remaining, _ws) = space0(remaining)?;
|
let (remaining, _ws) = space0(remaining)?;
|
||||||
let (remaining, (value, (call, inside_header, arguments, end_header))) =
|
let (remaining, (value, babel_call_value)) = consumed(babel_call_value)(remaining)?;
|
||||||
consumed(babel_call_value)(remaining)?;
|
|
||||||
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
@@ -83,33 +81,36 @@ where
|
|||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
value: Into::<&str>::into(value).trim_end(),
|
value: Into::<&str>::into(value).trim_end(),
|
||||||
call: call.map(Into::<&str>::into),
|
call: babel_call_value.call.map(Into::<&str>::into),
|
||||||
inside_header: inside_header.map(Into::<&str>::into),
|
inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
|
||||||
arguments: arguments.map(Into::<&str>::into),
|
arguments: babel_call_value.arguments.map(Into::<&str>::into),
|
||||||
end_header: end_header.map(Into::<&str>::into),
|
end_header: babel_call_value.end_header.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct BabelCallValue<'s> {
|
||||||
|
call: Option<OrgSource<'s>>,
|
||||||
|
inside_header: Option<OrgSource<'s>>,
|
||||||
|
arguments: Option<OrgSource<'s>>,
|
||||||
|
end_header: Option<OrgSource<'s>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn babel_call_value<'s>(
|
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
|
||||||
input: OrgSource<'s>,
|
|
||||||
) -> Res<
|
|
||||||
OrgSource<'s>,
|
|
||||||
(
|
|
||||||
Option<OrgSource<'s>>,
|
|
||||||
Option<OrgSource<'s>>,
|
|
||||||
Option<OrgSource<'s>>,
|
|
||||||
Option<OrgSource<'s>>,
|
|
||||||
),
|
|
||||||
> {
|
|
||||||
let (remaining, call) = opt(babel_call_call)(input)?;
|
let (remaining, call) = opt(babel_call_call)(input)?;
|
||||||
let (remaining, inside_header) = opt(inside_header)(remaining)?;
|
let (remaining, inside_header) = opt(inside_header)(remaining)?;
|
||||||
let (remaining, arguments) = opt(arguments)(remaining)?;
|
let (remaining, arguments) = opt(arguments)(remaining)?;
|
||||||
let (remaining, end_header) = opt(end_header)(remaining)?;
|
let (remaining, end_header) = opt(end_header)(remaining)?;
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
(call, inside_header, arguments.flatten(), end_header),
|
BabelCallValue {
|
||||||
|
call,
|
||||||
|
inside_header,
|
||||||
|
arguments: arguments.flatten(),
|
||||||
|
end_header,
|
||||||
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,9 +216,7 @@ fn impl_balanced_bracket<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if fail_parser(remaining).is_ok() {
|
if fail_parser(remaining).is_ok() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("Fail parser matched.")));
|
||||||
"Fail parser matched.",
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let (remain, _) = anychar(remaining)?;
|
let (remain, _) = anychar(remaining)?;
|
||||||
@@ -226,7 +225,7 @@ fn impl_balanced_bracket<
|
|||||||
let contents_end = remaining;
|
let contents_end = remaining;
|
||||||
|
|
||||||
let (remaining, _) = end_parser(remaining)?;
|
let (remaining, _) = end_parser(remaining)?;
|
||||||
let contents = if contents_start != contents_end {
|
let contents = if Into::<&str>::into(contents_start) != Into::<&str>::into(contents_end) {
|
||||||
Some(contents_start.get_until(contents_end))
|
Some(contents_start.get_until(contents_end))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -245,7 +244,7 @@ mod tests {
|
|||||||
let input = OrgSource::new("()");
|
let input = OrgSource::new("()");
|
||||||
let (remaining, call) = opt(babel_call_call)(input)?;
|
let (remaining, call) = opt(babel_call_call)(input)?;
|
||||||
assert_eq!(Into::<&str>::into(remaining), "()");
|
assert_eq!(Into::<&str>::into(remaining), "()");
|
||||||
assert_eq!(call, None);
|
assert!(call.is_none());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
157
src/parser/bullshitium.rs
Normal file
157
src/parser/bullshitium.rs
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::space0;
|
||||||
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::paragraph::paragraph;
|
||||||
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
|
use super::util::org_line_ending;
|
||||||
|
use super::util::start_of_line;
|
||||||
|
use super::OrgSource;
|
||||||
|
use crate::context::bind_context;
|
||||||
|
use crate::context::RefContext;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::Res;
|
||||||
|
use crate::parser::macros::element;
|
||||||
|
use crate::types::Object;
|
||||||
|
use crate::types::Paragraph;
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn bullshitium<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Paragraph<'s>> {
|
||||||
|
alt((
|
||||||
|
bind_context!(broken_end, context),
|
||||||
|
bind_context!(broken_dynamic_block, context),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn detect_bullshitium<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
|
element!(detect_broken_end, context, input);
|
||||||
|
element!(detect_broken_dynamic_block, context, input);
|
||||||
|
Err(nom::Err::Error(CustomError::Static("No bullshitium.")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn broken_end<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Paragraph<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
|
let (remaining, _) = space0(input)?;
|
||||||
|
let (remaining, _) = tag_no_case(":end:")(remaining)?;
|
||||||
|
let (lead_in_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
if let Ok((remaining, mut paragraph)) =
|
||||||
|
paragraph(std::iter::empty(), lead_in_remaining, context, input)
|
||||||
|
{
|
||||||
|
match paragraph.children.first_mut() {
|
||||||
|
Some(Object::PlainText(plain_text)) => {
|
||||||
|
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
|
||||||
|
}
|
||||||
|
Some(obj) => {
|
||||||
|
panic!("Unhandled first object type inside bullshitium {:?}", obj);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
unreachable!("Paragraph must have children.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((remaining, paragraph))
|
||||||
|
} else {
|
||||||
|
let (remaining, _trailing_ws) =
|
||||||
|
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Paragraph::of_text(
|
||||||
|
input.get_until(remaining).into(),
|
||||||
|
input.get_until(lead_in_remaining).into(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(_context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn detect_broken_end<'b, 'g, 'r, 's>(
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
|
start_of_line(input)?;
|
||||||
|
let (remaining, _) = space0(input)?;
|
||||||
|
let (remaining, _) = tag_no_case(":end:")(remaining)?;
|
||||||
|
let (_remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
Ok((input, ()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Paragraph<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
|
let (remaining, _) = space0(input)?;
|
||||||
|
let (remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
|
||||||
|
let (lead_in_remaining, _) = many_till(anychar, org_line_ending)(remaining)?;
|
||||||
|
if let Ok((remaining, mut paragraph)) =
|
||||||
|
paragraph(std::iter::empty(), lead_in_remaining, context, input)
|
||||||
|
{
|
||||||
|
match paragraph.children.first_mut() {
|
||||||
|
Some(Object::PlainText(plain_text)) => {
|
||||||
|
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
|
||||||
|
}
|
||||||
|
Some(obj) => {
|
||||||
|
panic!("Unhandled first object type inside bullshitium {:?}", obj);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
unreachable!("Paragraph must have children.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok((remaining, paragraph))
|
||||||
|
} else {
|
||||||
|
let (remaining, _trailing_ws) =
|
||||||
|
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Paragraph::of_text(
|
||||||
|
input.get_until(remaining).into(),
|
||||||
|
input.get_until(lead_in_remaining).into(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(_context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn detect_broken_dynamic_block<'b, 'g, 'r, 's>(
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
|
start_of_line(input)?;
|
||||||
|
let (remaining, _) = space0(input)?;
|
||||||
|
let (_remaining, _) = tag_no_case("#+BEGIN:")(remaining)?;
|
||||||
|
Ok((input, ()))
|
||||||
|
}
|
||||||
@@ -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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -137,7 +145,7 @@ fn _global_prefix_end<'b, 'g, 'r, 's>(
|
|||||||
unreachable!("Exceeded citation global prefix bracket depth.")
|
unreachable!("Exceeded citation global prefix bracket depth.")
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -191,7 +199,7 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
|
|||||||
unreachable!("Exceeded citation global suffix bracket depth.")
|
unreachable!("Exceeded citation global suffix bracket depth.")
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -205,49 +213,54 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::context::bind_context;
|
||||||
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::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();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
let paragraph_matcher = bind_context!(element(true), &initial_context);
|
||||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||||
let first_paragraph = match first_paragraph {
|
let first_paragraph = match first_paragraph {
|
||||||
Element::Paragraph(paragraph) => paragraph,
|
Element::Paragraph(paragraph) => paragraph,
|
||||||
_ => 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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::object_parser::minimal_set_object;
|
use crate::parser::object_parser::minimal_set_object;
|
||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
@@ -151,7 +150,7 @@ fn _key_prefix_end<'b, 'g, 'r, 's>(
|
|||||||
unreachable!("Exceeded citation key prefix bracket depth.")
|
unreachable!("Exceeded citation key prefix bracket depth.")
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -181,7 +180,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
|
|||||||
unreachable!("Exceeded citation key suffix bracket depth.")
|
unreachable!("Exceeded citation key suffix bracket depth.")
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -199,9 +198,7 @@ where
|
|||||||
let pre_bracket_depth = input.get_bracket_depth();
|
let pre_bracket_depth = input.get_bracket_depth();
|
||||||
let (remaining, output) = inner(input)?;
|
let (remaining, output) = inner(input)?;
|
||||||
if remaining.get_bracket_depth() - pre_bracket_depth != 0 {
|
if remaining.get_bracket_depth() - pre_bracket_depth != 0 {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("UnbalancedBrackets")));
|
||||||
"UnbalancedBrackets".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
Ok((remaining, output))
|
Ok((remaining, output))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ use crate::context::parser_with_context;
|
|||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
use crate::parser::util::immediate_in_section;
|
use crate::parser::util::immediate_in_section;
|
||||||
@@ -35,9 +34,9 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Comment<'s>> {
|
) -> Res<OrgSource<'s>, Comment<'s>> {
|
||||||
if immediate_in_section(context, "comment") {
|
if immediate_in_section(context, "comment") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
let parser_context = ContextElement::Context("comment");
|
let parser_context = ContextElement::Context("comment");
|
||||||
let parser_context = context.with_additional_node(&parser_context);
|
let parser_context = context.with_additional_node(&parser_context);
|
||||||
@@ -47,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);
|
||||||
@@ -68,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -104,6 +104,7 @@ pub(crate) fn detect_comment<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()>
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::context::bind_context;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
@@ -119,7 +120,7 @@ mod tests {
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let comment_matcher = parser_with_context!(comment)(&initial_context);
|
let comment_matcher = bind_context!(comment, &initial_context);
|
||||||
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
|
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Into::<&str>::into(remaining),
|
Into::<&str>::into(remaining),
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
use nom::bytes::complete::is_not;
|
use nom::bytes::complete::is_not;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many0;
|
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
use super::keyword::affiliated_keyword;
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
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 super::util::org_line_ending;
|
||||||
@@ -49,9 +47,19 @@ where
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(
|
||||||
pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
feature = "tracing",
|
||||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
|
||||||
tuple((start_of_line, tag("%%(")))(input)?;
|
)]
|
||||||
|
pub(crate) fn detect_diary_sexp<'b, 'g, 'r, 's, AK>(
|
||||||
|
_affiliated_keywords: AK,
|
||||||
|
remaining: OrgSource<'s>,
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()>
|
||||||
|
where
|
||||||
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
|
{
|
||||||
|
tuple((start_of_line, tag("%%(")))(remaining)?;
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use super::in_buffer_settings::scan_for_in_buffer_settings;
|
|||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::section::zeroth_section;
|
use super::section::zeroth_section;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
|
use crate::context::bind_context;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
@@ -18,9 +19,7 @@ use crate::context::GlobalSettings;
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::org_source::convert_error;
|
|
||||||
use crate::parser::util::blank_line;
|
use crate::parser::util::blank_line;
|
||||||
use crate::types::AstNode;
|
use crate::types::AstNode;
|
||||||
use crate::types::Document;
|
use crate::types::Document;
|
||||||
@@ -30,7 +29,7 @@ use crate::types::Object;
|
|||||||
///
|
///
|
||||||
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document without an underlying file attached.
|
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document without an underlying file attached.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
pub fn parse(input: &str) -> Result<Document<'_>, Box<dyn std::error::Error>> {
|
||||||
parse_file_with_settings::<&Path>(input, &GlobalSettings::default(), None)
|
parse_file_with_settings::<&Path>(input, &GlobalSettings::default(), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +39,10 @@ pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Err
|
|||||||
///
|
///
|
||||||
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
|
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse_file<'s, P: AsRef<Path>>(
|
pub fn parse_file<P: AsRef<Path>>(
|
||||||
input: &'s str,
|
input: &str,
|
||||||
file_path: Option<P>,
|
file_path: Option<P>,
|
||||||
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
) -> Result<Document<'_>, Box<dyn std::error::Error>> {
|
||||||
parse_file_with_settings(input, &GlobalSettings::default(), file_path)
|
parse_file_with_settings(input, &GlobalSettings::default(), file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
|
|||||||
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
||||||
let wrapped_input = OrgSource::new(input);
|
let wrapped_input = OrgSource::new(input);
|
||||||
let mut doc =
|
let mut doc =
|
||||||
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
|
all_consuming(bind_context!(document_org_source, &initial_context))(wrapped_input)
|
||||||
.map_err(|err| err.to_string())
|
.map_err(|err| err.to_string())
|
||||||
.map(|(_remaining, parsed_document)| parsed_document)?;
|
.map(|(_remaining, parsed_document)| parsed_document)?;
|
||||||
if let Some(file_path) = file_path {
|
if let Some(file_path) = file_path {
|
||||||
@@ -101,11 +100,8 @@ pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
|
|||||||
///
|
///
|
||||||
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn document<'b, 'g, 'r, 's>(
|
fn document<'s>(context: RefContext<'_, '_, '_, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
let (remaining, doc) = document_org_source(context, input.into())?;
|
||||||
input: &'s str,
|
|
||||||
) -> Res<&'s str, Document<'s>> {
|
|
||||||
let (remaining, doc) = document_org_source(context, input.into()).map_err(convert_error)?;
|
|
||||||
Ok((Into::<&str>::into(remaining), doc))
|
Ok((Into::<&str>::into(remaining), doc))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,29 +125,16 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
|||||||
.get_global_settings()
|
.get_global_settings()
|
||||||
.file_access
|
.file_access
|
||||||
.read_file(setup_file)
|
.read_file(setup_file)
|
||||||
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))
|
.map_err(|err| nom::Err::<CustomError>::Failure(err.into()))
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
for setup_file in setup_files.iter().map(String::as_str) {
|
for setup_file in setup_files.iter().map(String::as_str) {
|
||||||
let (_, setup_file_settings) =
|
let (_, setup_file_settings) = scan_for_in_buffer_settings(setup_file.into())?;
|
||||||
scan_for_in_buffer_settings(setup_file.into()).map_err(|err| {
|
|
||||||
eprintln!("{}", err);
|
|
||||||
nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"TODO: make this take an owned string so I can dump err.to_string() into it."
|
|
||||||
.into(),
|
|
||||||
)))
|
|
||||||
})?;
|
|
||||||
final_settings.extend(setup_file_settings);
|
final_settings.extend(setup_file_settings);
|
||||||
}
|
}
|
||||||
final_settings.extend(document_settings);
|
final_settings.extend(document_settings);
|
||||||
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
|
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
|
||||||
.map_err(|err| {
|
.map_err(nom::Err::Error)?;
|
||||||
eprintln!("{}", err);
|
|
||||||
nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"TODO: make this take an owned string so I can dump err.to_string() into it."
|
|
||||||
.into(),
|
|
||||||
)))
|
|
||||||
})?;
|
|
||||||
let new_context = context.with_global_settings(&new_settings);
|
let new_context = context.with_global_settings(&new_settings);
|
||||||
let context = &new_context;
|
let context = &new_context;
|
||||||
|
|
||||||
@@ -160,7 +143,7 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
|||||||
{
|
{
|
||||||
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
||||||
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
|
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
|
||||||
.into_iter()
|
.iter_all_ast_nodes()
|
||||||
.filter_map(|ast_node| {
|
.filter_map(|ast_node| {
|
||||||
if let AstNode::RadioTarget(ast_node) = ast_node {
|
if let AstNode::RadioTarget(ast_node) = ast_node {
|
||||||
Some(ast_node)
|
Some(ast_node)
|
||||||
@@ -176,15 +159,13 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
|||||||
let parser_context = context.with_global_settings(&new_global_settings);
|
let parser_context = context.with_global_settings(&new_global_settings);
|
||||||
let (remaining, mut document) = _document(&parser_context, input)
|
let (remaining, mut document) = _document(&parser_context, input)
|
||||||
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
||||||
apply_post_parse_in_buffer_settings(&mut document)
|
apply_post_parse_in_buffer_settings(&mut document);
|
||||||
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
|
||||||
return Ok((remaining.into(), document));
|
return Ok((remaining.into(), document));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find final in-buffer settings that do not impact parsing
|
// Find final in-buffer settings that do not impact parsing
|
||||||
apply_post_parse_in_buffer_settings(&mut document)
|
apply_post_parse_in_buffer_settings(&mut document);
|
||||||
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
|
||||||
|
|
||||||
Ok((remaining.into(), document))
|
Ok((remaining.into(), document))
|
||||||
}
|
}
|
||||||
@@ -214,3 +195,17 @@ fn _document<'b, 'g, 'r, 's>(
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_full_document(b: &mut Bencher) {
|
||||||
|
let input = include_str!("../../org_mode_samples/element_container_priority/README.org");
|
||||||
|
|
||||||
|
b.iter(|| assert!(parse(input).is_ok()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
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::blank_line;
|
||||||
@@ -32,7 +31,6 @@ use crate::types::Drawer;
|
|||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::types::Paragraph;
|
use crate::types::Paragraph;
|
||||||
use crate::types::SetSource;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
@@ -48,9 +46,9 @@ where
|
|||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
if immediate_in_section(context, "drawer") {
|
if immediate_in_section(context, "drawer") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
start_of_line(remaining)?;
|
start_of_line(remaining)?;
|
||||||
let (remaining, _leading_whitespace) = space0(remaining)?;
|
let (remaining, _leading_whitespace) = space0(remaining)?;
|
||||||
@@ -82,9 +80,8 @@ where
|
|||||||
))(remaining)
|
))(remaining)
|
||||||
{
|
{
|
||||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
|
||||||
let source = get_consumed(remaining, remain);
|
let source = get_consumed(remaining, remain);
|
||||||
element.set_source(source.into());
|
let element = Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()));
|
||||||
(remain, vec![element])
|
(remain, vec![element])
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
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::blank_line;
|
||||||
@@ -38,7 +37,6 @@ use crate::types::DynamicBlock;
|
|||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::types::Paragraph;
|
use crate::types::Paragraph;
|
||||||
use crate::types::SetSource;
|
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
@@ -54,9 +52,9 @@ where
|
|||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
if immediate_in_section(context, "dynamic block") {
|
if immediate_in_section(context, "dynamic block") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
start_of_line(remaining)?;
|
start_of_line(remaining)?;
|
||||||
@@ -79,10 +77,7 @@ where
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
let parameters = match parameters {
|
let parameters = parameters.map(|(_ws, parameters)| parameters);
|
||||||
Some((_ws, parameters)) => Some(parameters),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
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)?;
|
||||||
@@ -92,9 +87,7 @@ where
|
|||||||
))))(remaining)?;
|
))))(remaining)?;
|
||||||
let leading_blank_lines =
|
let leading_blank_lines =
|
||||||
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
|
||||||
element.set_source(source.into());
|
|
||||||
element
|
|
||||||
});
|
});
|
||||||
let (remaining, (mut children, _exit_contents)) =
|
let (remaining, (mut children, _exit_contents)) =
|
||||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
use nom::branch::alt;
|
|
||||||
use nom::multi::many0;
|
|
||||||
|
|
||||||
use super::babel_call::babel_call;
|
use super::babel_call::babel_call;
|
||||||
use super::clock::clock;
|
use super::clock::clock;
|
||||||
use super::comment::comment;
|
use super::comment::comment;
|
||||||
@@ -15,7 +12,6 @@ use super::footnote_definition::detect_footnote_definition;
|
|||||||
use super::footnote_definition::footnote_definition;
|
use super::footnote_definition::footnote_definition;
|
||||||
use super::greater_block::greater_block;
|
use super::greater_block::greater_block;
|
||||||
use super::horizontal_rule::horizontal_rule;
|
use super::horizontal_rule::horizontal_rule;
|
||||||
use super::keyword::affiliated_keyword;
|
|
||||||
use super::keyword::keyword;
|
use super::keyword::keyword;
|
||||||
use super::latex_environment::latex_environment;
|
use super::latex_environment::latex_environment;
|
||||||
use super::lesser_block::comment_block;
|
use super::lesser_block::comment_block;
|
||||||
@@ -28,11 +24,16 @@ use super::paragraph::paragraph;
|
|||||||
use super::plain_list::detect_plain_list;
|
use super::plain_list::detect_plain_list;
|
||||||
use super::plain_list::plain_list;
|
use super::plain_list::plain_list;
|
||||||
use super::table::detect_table;
|
use super::table::detect_table;
|
||||||
use crate::context::parser_with_context;
|
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
|
#[cfg(feature = "event_count")]
|
||||||
|
use crate::event_count::record_event;
|
||||||
|
#[cfg(feature = "event_count")]
|
||||||
|
use crate::event_count::EventType;
|
||||||
|
use crate::parser::affiliated_keyword::affiliated_keywords;
|
||||||
|
use crate::parser::bullshitium::bullshitium;
|
||||||
|
use crate::parser::bullshitium::detect_bullshitium;
|
||||||
use crate::parser::macros::ak_element;
|
use crate::parser::macros::ak_element;
|
||||||
use crate::parser::macros::element;
|
use crate::parser::macros::element;
|
||||||
use crate::parser::table::org_mode_table;
|
use crate::parser::table::org_mode_table;
|
||||||
@@ -56,7 +57,9 @@ fn _element<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
can_be_paragraph: bool,
|
can_be_paragraph: bool,
|
||||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||||
let (post_affiliated_keywords_input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
#[cfg(feature = "event_count")]
|
||||||
|
record_event(EventType::ElementStart, input);
|
||||||
|
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
|
||||||
|
|
||||||
let mut affiliated_keywords = affiliated_keywords.into_iter();
|
let mut affiliated_keywords = affiliated_keywords.into_iter();
|
||||||
|
|
||||||
@@ -240,6 +243,9 @@ fn _element<'b, 'g, 'r, 's>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if can_be_paragraph {
|
if can_be_paragraph {
|
||||||
|
// Fake paragraphs
|
||||||
|
element!(bullshitium, context, input, Element::Paragraph);
|
||||||
|
|
||||||
// Paragraph without affiliated keyword
|
// Paragraph without affiliated keyword
|
||||||
ak_element!(
|
ak_element!(
|
||||||
paragraph,
|
paragraph,
|
||||||
@@ -251,9 +257,7 @@ fn _element<'b, 'g, 'r, 's>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("No element.")))
|
||||||
"No element.",
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) const fn detect_element(
|
pub(crate) const fn detect_element(
|
||||||
@@ -272,22 +276,60 @@ fn _detect_element<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
can_be_paragraph: bool,
|
can_be_paragraph: bool,
|
||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
if alt((
|
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
|
||||||
parser_with_context!(detect_plain_list)(context),
|
|
||||||
|
let mut affiliated_keywords = affiliated_keywords.into_iter();
|
||||||
|
|
||||||
|
ak_element!(
|
||||||
|
detect_plain_list,
|
||||||
|
&mut affiliated_keywords,
|
||||||
|
post_affiliated_keywords_input,
|
||||||
|
context,
|
||||||
|
input
|
||||||
|
);
|
||||||
|
|
||||||
|
ak_element!(
|
||||||
detect_footnote_definition,
|
detect_footnote_definition,
|
||||||
|
&mut affiliated_keywords,
|
||||||
|
post_affiliated_keywords_input,
|
||||||
|
context,
|
||||||
|
input
|
||||||
|
);
|
||||||
|
|
||||||
|
ak_element!(
|
||||||
detect_diary_sexp,
|
detect_diary_sexp,
|
||||||
detect_comment,
|
&mut affiliated_keywords,
|
||||||
|
post_affiliated_keywords_input,
|
||||||
|
context,
|
||||||
|
input
|
||||||
|
);
|
||||||
|
|
||||||
|
element!(detect_comment, input);
|
||||||
|
|
||||||
|
ak_element!(
|
||||||
detect_fixed_width_area,
|
detect_fixed_width_area,
|
||||||
|
&mut affiliated_keywords,
|
||||||
|
post_affiliated_keywords_input,
|
||||||
|
context,
|
||||||
|
input
|
||||||
|
);
|
||||||
|
|
||||||
|
ak_element!(
|
||||||
detect_table,
|
detect_table,
|
||||||
))(input)
|
&mut affiliated_keywords,
|
||||||
.is_ok()
|
post_affiliated_keywords_input,
|
||||||
{
|
context,
|
||||||
return Ok((input, ()));
|
input
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fake paragraphs
|
||||||
|
if !can_be_paragraph {
|
||||||
|
element!(detect_bullshitium, context, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
if _element(context, input, can_be_paragraph).is_ok() {
|
if _element(context, input, can_be_paragraph).is_ok() {
|
||||||
return Ok((input, ()));
|
return Ok((input, ()));
|
||||||
}
|
}
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"No element detected.".into(),
|
Err(nom::Err::Error(CustomError::Static("No element detected.")))
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::satisfy;
|
use nom::character::complete::satisfy;
|
||||||
|
use nom::combinator::cond;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
@@ -13,7 +13,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
|||||||
use crate::context::EntityDefinition;
|
use crate::context::EntityDefinition;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::types::Entity;
|
use crate::types::Entity;
|
||||||
@@ -29,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);
|
||||||
@@ -44,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -58,23 +58,21 @@ fn name<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> {
|
) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> {
|
||||||
for entity in context.get_global_settings().entities {
|
for entity in context.get_global_settings().entities {
|
||||||
let result = tuple((
|
let result = tuple((
|
||||||
tag::<_, _, CustomError<_>>(entity.name),
|
tag::<_, _, CustomError>(entity.name),
|
||||||
|
cond(
|
||||||
|
!entity.name.ends_with(' '),
|
||||||
alt((
|
alt((
|
||||||
verify(map(tag("{}"), |_| true), |_| !entity.name.ends_with(" ")),
|
map(tag("{}"), |_| true),
|
||||||
map(peek(recognize(entity_end)), |_| false),
|
map(peek(recognize(entity_end)), |_| false),
|
||||||
)),
|
)),
|
||||||
|
),
|
||||||
))(input);
|
))(input);
|
||||||
match result {
|
if let Ok((remaining, (ent, use_brackets))) = result {
|
||||||
Ok((remaining, (ent, use_brackets))) => {
|
return Ok((remaining, (entity, ent, use_brackets.unwrap_or(false))));
|
||||||
return Ok((remaining, (entity, ent, use_brackets)));
|
|
||||||
}
|
|
||||||
Err(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("NoEntity")))
|
||||||
"NoEntity".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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::keyword::affiliated_keyword;
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
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 super::util::org_line_ending;
|
||||||
@@ -89,14 +88,24 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
|
|||||||
Ok((remaining, value))
|
Ok((remaining, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(
|
||||||
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
feature = "tracing",
|
||||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
|
||||||
|
)]
|
||||||
|
pub(crate) fn detect_fixed_width_area<'b, 'g, 'r, 's, AK>(
|
||||||
|
_affiliated_keywords: AK,
|
||||||
|
remaining: OrgSource<'s>,
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()>
|
||||||
|
where
|
||||||
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
|
{
|
||||||
tuple((
|
tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
space0,
|
space0,
|
||||||
tag(":"),
|
tag(":"),
|
||||||
alt((tag(" "), org_line_ending)),
|
alt((tag(" "), org_line_ending)),
|
||||||
))(input)?;
|
))(remaining)?;
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ use nom::multi::many_till;
|
|||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
use super::keyword::affiliated_keyword;
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::include_input;
|
use super::util::include_input;
|
||||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
@@ -23,7 +22,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
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::blank_line;
|
||||||
@@ -49,9 +47,9 @@ where
|
|||||||
AK: IntoIterator<Item = Keyword<'s>>,
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
{
|
{
|
||||||
if immediate_in_section(context, "footnote definition") {
|
if immediate_in_section(context, "footnote definition") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
start_of_line(remaining)?;
|
start_of_line(remaining)?;
|
||||||
// Cannot be indented.
|
// Cannot be indented.
|
||||||
@@ -77,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)?;
|
||||||
|
|
||||||
@@ -92,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,
|
||||||
@@ -125,7 +127,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, source) = alt((
|
let (remaining, source) = alt((
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
parser_with_context!(maybe_consume_trailing_whitespace)(context),
|
parser_with_context!(maybe_consume_trailing_whitespace)(context),
|
||||||
detect_footnote_definition,
|
|i| detect_footnote_definition(std::iter::empty(), i, context, i),
|
||||||
))),
|
))),
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
@@ -138,20 +140,31 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
|
|||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(
|
||||||
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
feature = "tracing",
|
||||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
|
||||||
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
|
)]
|
||||||
|
pub(crate) fn detect_footnote_definition<'b, 'g, 'r, 's, AK>(
|
||||||
|
_affiliated_keywords: AK,
|
||||||
|
remaining: OrgSource<'s>,
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()>
|
||||||
|
where
|
||||||
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
|
{
|
||||||
|
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(remaining)?;
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::context::bind_context;
|
||||||
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() {
|
||||||
@@ -165,24 +178,20 @@ line footnote.",
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
|
let footnote_definition_matcher = bind_context!(element(true), &initial_context);
|
||||||
let (remaining, first_footnote_definition) =
|
let (remaining, first_footnote_definition) =
|
||||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||||
let (remaining, second_footnote_definition) =
|
let (remaining, second_footnote_definition) =
|
||||||
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
||||||
assert_eq!(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."
|
||||||
@@ -202,14 +211,12 @@ not in the footnote.",
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
|
let footnote_definition_matcher = bind_context!(element(true), &initial_context);
|
||||||
let (remaining, first_footnote_definition) =
|
let (remaining, first_footnote_definition) =
|
||||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||||
assert_eq!(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;
|
||||||
@@ -20,7 +21,6 @@ use crate::context::ExitMatcherNode;
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::footnote_definition::label;
|
use crate::parser::footnote_definition::label;
|
||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
@@ -60,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,
|
||||||
@@ -70,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,
|
||||||
},
|
},
|
||||||
@@ -107,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,
|
||||||
@@ -117,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,
|
||||||
},
|
},
|
||||||
@@ -145,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),
|
||||||
},
|
},
|
||||||
@@ -176,9 +182,9 @@ fn _footnote_definition_end<'b, 'g, 'r, 's>(
|
|||||||
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
if current_depth > 0 {
|
if current_depth > 0 {
|
||||||
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
|
// Its impossible for the next character to end the footnote reference definition if we're any amount of brackets deep
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"NoFootnoteReferenceDefinitionEnd".into(),
|
"NoFootnoteReferenceDefinitionEnd",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
if current_depth < 0 {
|
if current_depth < 0 {
|
||||||
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
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::blank_line;
|
||||||
@@ -40,7 +39,6 @@ use crate::types::Element;
|
|||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::types::Paragraph;
|
use crate::types::Paragraph;
|
||||||
use crate::types::QuoteBlock;
|
use crate::types::QuoteBlock;
|
||||||
use crate::types::SetSource;
|
|
||||||
use crate::types::SpecialBlock;
|
use crate::types::SpecialBlock;
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -61,10 +59,10 @@ where
|
|||||||
let (remaining, (_begin, name)) = tuple((
|
let (remaining, (_begin, name)) = tuple((
|
||||||
tag_no_case("#+begin_"),
|
tag_no_case("#+begin_"),
|
||||||
verify(name, |name: &OrgSource<'_>| {
|
verify(name, |name: &OrgSource<'_>| {
|
||||||
match Into::<&str>::into(name).to_lowercase().as_str() {
|
!matches!(
|
||||||
"comment" | "example" | "export" | "src" | "verse" => false,
|
Into::<&str>::into(name).to_lowercase().as_str(),
|
||||||
_ => true,
|
"comment" | "example" | "export" | "src" | "verse",
|
||||||
}
|
)
|
||||||
}),
|
}),
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let name = Into::<&str>::into(name);
|
let name = Into::<&str>::into(name);
|
||||||
@@ -232,9 +230,9 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
|||||||
context_name: &'c str,
|
context_name: &'c str,
|
||||||
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
|
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
|
||||||
if in_section(context, context_name) {
|
if in_section(context, context_name) {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
let exit_with_name = greater_block_end(name);
|
let exit_with_name = greater_block_end(name);
|
||||||
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
|
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
|
||||||
@@ -258,9 +256,7 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
|||||||
))))(remaining)?;
|
))))(remaining)?;
|
||||||
let leading_blank_lines =
|
let leading_blank_lines =
|
||||||
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
|
||||||
element.set_source(source.into());
|
|
||||||
element
|
|
||||||
});
|
});
|
||||||
let (remaining, (mut children, _exit_contents)) =
|
let (remaining, (mut children, _exit_contents)) =
|
||||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||||
@@ -288,7 +284,7 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
|
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
|
fn greater_block_end(name: &str) -> impl ContextMatcher + '_ {
|
||||||
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
|
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,18 +18,18 @@ use nom::sequence::tuple;
|
|||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::section::section;
|
use super::section::section;
|
||||||
|
use super::util::exit_matcher_parser;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
use super::util::org_line_ending;
|
use super::util::org_line_ending;
|
||||||
use super::util::org_space;
|
use super::util::org_space;
|
||||||
use super::util::org_space_or_line_ending;
|
use super::util::org_space_or_line_ending;
|
||||||
use super::util::start_of_line;
|
use super::util::start_of_line;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::bind_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::ExitClass;
|
use crate::context::ExitClass;
|
||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
use crate::parser::util::blank_line;
|
use crate::parser::util::blank_line;
|
||||||
@@ -62,10 +62,10 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
let mut scheduled = None;
|
let mut scheduled = None;
|
||||||
let mut deadline = None;
|
let mut deadline = None;
|
||||||
let mut closed = None;
|
let mut closed = None;
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
not(bind_context!(exit_matcher_parser, context))(input)?;
|
||||||
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
|
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
|
||||||
let section_matcher = parser_with_context!(section)(context);
|
let section_matcher = bind_context!(section, context);
|
||||||
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
|
let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
|
||||||
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)?;
|
||||||
@@ -155,7 +155,7 @@ fn headline<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
|
let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
verify(
|
verify(
|
||||||
parser_with_context!(headline_level)(&parser_context),
|
bind_context!(headline_level, &parser_context),
|
||||||
|(_, count, _)| *count > parent_star_count,
|
|(_, count, _)| *count > parent_star_count,
|
||||||
),
|
),
|
||||||
peek(org_space),
|
peek(org_space),
|
||||||
@@ -163,7 +163,7 @@ fn headline<'b, 'g, 'r, 's>(
|
|||||||
|
|
||||||
let (remaining, maybe_todo_keyword) = opt(tuple((
|
let (remaining, maybe_todo_keyword) = opt(tuple((
|
||||||
space1,
|
space1,
|
||||||
parser_with_context!(heading_keyword)(&parser_context),
|
bind_context!(heading_keyword, &parser_context),
|
||||||
peek(org_space_or_line_ending),
|
peek(org_space_or_line_ending),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
|
|
||||||
@@ -177,9 +177,7 @@ fn headline<'b, 'g, 'r, 's>(
|
|||||||
|
|
||||||
let (remaining, maybe_title) = opt(tuple((
|
let (remaining, maybe_title) = opt(tuple((
|
||||||
space1,
|
space1,
|
||||||
consumed(many1(parser_with_context!(standard_set_object)(
|
consumed(many1(bind_context!(standard_set_object, &parser_context))),
|
||||||
&parser_context,
|
|
||||||
))),
|
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
|
|
||||||
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
|
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
|
||||||
@@ -206,11 +204,7 @@ fn headline<'b, 'g, 'r, 's>(
|
|||||||
.map(|(_, (_, title))| title)
|
.map(|(_, (_, title))| title)
|
||||||
.unwrap_or(Vec::new()),
|
.unwrap_or(Vec::new()),
|
||||||
tags: maybe_tags
|
tags: maybe_tags
|
||||||
.map(|(_ws, tags)| {
|
.map(|(_ws, tags)| tags.into_iter().map(Into::<&str>::into).collect())
|
||||||
tags.into_iter()
|
|
||||||
.map(|single_tag| Into::<&str>::into(single_tag))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or(Vec::new()),
|
.unwrap_or(Vec::new()),
|
||||||
is_footnote_section,
|
is_footnote_section,
|
||||||
},
|
},
|
||||||
@@ -264,43 +258,35 @@ fn heading_keyword<'b, 'g, 'r, 's>(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
{
|
{
|
||||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
let result = tag::<_, _, CustomError>(todo_keyword)(input);
|
||||||
match result {
|
if let Ok((remaining, ent)) = result {
|
||||||
Ok((remaining, ent)) => {
|
|
||||||
return Ok((remaining, (TodoKeywordType::Todo, ent)));
|
return Ok((remaining, (TodoKeywordType::Todo, ent)));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for todo_keyword in global_settings
|
for todo_keyword in global_settings
|
||||||
.complete_todo_keywords
|
.complete_todo_keywords
|
||||||
.iter()
|
.iter()
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
{
|
{
|
||||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
let result = tag::<_, _, CustomError>(todo_keyword)(input);
|
||||||
match result {
|
if let Ok((remaining, ent)) = result {
|
||||||
Ok((remaining, ent)) => {
|
|
||||||
return Ok((remaining, (TodoKeywordType::Done, ent)));
|
return Ok((remaining, (TodoKeywordType::Done, ent)));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
}
|
}
|
||||||
}
|
Err(nom::Err::Error(CustomError::Static("NoTodoKeyword")))
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"NoTodoKeyword".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCookie> {
|
fn priority_cookie(input: OrgSource<'_>) -> Res<OrgSource<'_>, PriorityCookie> {
|
||||||
let (remaining, (_, priority_character, _)) = tuple((
|
let (remaining, (_, priority_character, _)) = tuple((
|
||||||
tag("[#"),
|
tag("[#"),
|
||||||
verify(anychar, |c| c.is_alphanumeric()),
|
verify(anychar, |c| c.is_alphanumeric()),
|
||||||
tag("]"),
|
tag("]"),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
|
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
|
||||||
nom::Err::Error(CustomError::MyError(MyError(
|
nom::Err::Error(CustomError::Static(
|
||||||
"Failed to cast priority cookie to number.".into(),
|
"Failed to cast priority cookie to number.",
|
||||||
)))
|
))
|
||||||
})?;
|
})?;
|
||||||
Ok((remaining, cookie))
|
Ok((remaining, cookie))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
|
|||||||
let mut remaining = input;
|
let mut remaining = input;
|
||||||
loop {
|
loop {
|
||||||
// Skip text until possible in_buffer_setting
|
// Skip text until possible in_buffer_setting
|
||||||
let start_of_pound = take_until::<_, _, CustomError<_>>("#+")(remaining);
|
let start_of_pound = take_until::<_, _, CustomError>("#+")(remaining);
|
||||||
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
|
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
|
||||||
start_of_pound
|
start_of_pound
|
||||||
} else {
|
} else {
|
||||||
@@ -47,7 +47,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
|
|||||||
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
|
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
|
||||||
Ok((remain, kw)) => (remain, Some(kw)),
|
Ok((remain, kw)) => (remain, Some(kw)),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let end_of_line = take_until::<_, _, CustomError<_>>("\n")(start_of_pound);
|
let end_of_line = take_until::<_, _, CustomError>("\n")(start_of_pound);
|
||||||
if let Ok((end_of_line, _)) = end_of_line {
|
if let Ok((end_of_line, _)) = end_of_line {
|
||||||
(end_of_line, None)
|
(end_of_line, None)
|
||||||
} else {
|
} else {
|
||||||
@@ -84,11 +84,14 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
|
|||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug"))]
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(level = "debug", skip(original_settings))
|
||||||
|
)]
|
||||||
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||||
keywords: Vec<Keyword<'sf>>,
|
keywords: Vec<Keyword<'sf>>,
|
||||||
original_settings: &'g GlobalSettings<'g, 's>,
|
original_settings: &'g GlobalSettings<'g, 's>,
|
||||||
) -> Result<GlobalSettings<'g, 's>, String> {
|
) -> Result<GlobalSettings<'g, 's>, CustomError> {
|
||||||
let mut new_settings = original_settings.clone();
|
let mut new_settings = original_settings.clone();
|
||||||
|
|
||||||
// Todo Keywords
|
// Todo Keywords
|
||||||
@@ -98,7 +101,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
|| kw.key.eq_ignore_ascii_case("typ_todo")
|
|| kw.key.eq_ignore_ascii_case("typ_todo")
|
||||||
}) {
|
}) {
|
||||||
let (_, (in_progress_words, complete_words)) =
|
let (_, (in_progress_words, complete_words)) =
|
||||||
todo_keywords(kw.value).map_err(|err| err.to_string())?;
|
todo_keywords(kw.value).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,
|
||||||
|
})?;
|
||||||
new_settings
|
new_settings
|
||||||
.in_progress_todo_keywords
|
.in_progress_todo_keywords
|
||||||
.extend(in_progress_words.into_iter().map(str::to_string));
|
.extend(in_progress_words.into_iter().map(str::to_string));
|
||||||
@@ -112,9 +119,14 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
|
.filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
|
||||||
{
|
{
|
||||||
let (_remaining, settings) =
|
let (_remaining, settings) = separated_list0(space1::<&str, CustomError>, is_not(" \t"))(
|
||||||
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
|
kw.value,
|
||||||
.map_err(|err: nom::Err<_>| err.to_string())?;
|
)
|
||||||
|
.map_err(|err: nom::Err<_>| match err {
|
||||||
|
nom::Err::Incomplete(_) => panic!("This parser does not support streaming."),
|
||||||
|
nom::Err::Error(e) => e,
|
||||||
|
nom::Err::Failure(e) => e,
|
||||||
|
})?;
|
||||||
if settings.contains(&"odd") {
|
if settings.contains(&"odd") {
|
||||||
new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
|
new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
|
||||||
}
|
}
|
||||||
@@ -128,7 +140,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
.iter()
|
.iter()
|
||||||
.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(|e| e.to_string())?;
|
let (_, (link_key, link_value)) = link_template(kw.value).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,
|
||||||
|
})?;
|
||||||
new_settings
|
new_settings
|
||||||
.link_templates
|
.link_templates
|
||||||
.insert(link_key.to_owned(), link_value.to_owned());
|
.insert(link_key.to_owned(), link_value.to_owned());
|
||||||
@@ -139,11 +155,9 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
|
|
||||||
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
|
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
|
||||||
#[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>(
|
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
|
||||||
document: &mut Document<'s>,
|
|
||||||
) -> Result<(), &'static str> {
|
|
||||||
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") {
|
||||||
@@ -154,7 +168,6 @@ pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
})
|
})
|
||||||
.last()
|
.last()
|
||||||
.map(|kw| kw.value.to_owned());
|
.map(|kw| kw.value.to_owned());
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
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;
|
||||||
@@ -39,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((
|
||||||
@@ -55,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -131,9 +131,7 @@ fn _header_end<'b, 'g, 'r, 's>(
|
|||||||
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
if current_depth > 0 {
|
if current_depth > 0 {
|
||||||
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
|
||||||
"NoHeaderEnd".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
if current_depth < 0 {
|
if current_depth < 0 {
|
||||||
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
||||||
@@ -183,9 +181,7 @@ fn _argument_end<'b, 'g, 'r, 's>(
|
|||||||
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
|
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||||
if current_depth > 0 {
|
if current_depth > 0 {
|
||||||
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep
|
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("NoArgumentEnd")));
|
||||||
"NoArgumentEnd".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
if current_depth < 0 {
|
if current_depth < 0 {
|
||||||
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.
|
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the argument.
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
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;
|
||||||
@@ -39,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((
|
||||||
@@ -49,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -125,9 +125,7 @@ fn _header_end<'b, 'g, 'r, 's>(
|
|||||||
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
let current_depth = input.get_bracket_depth() - starting_bracket_depth;
|
||||||
if current_depth > 0 {
|
if current_depth > 0 {
|
||||||
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
|
||||||
"NoHeaderEnd".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
if current_depth < 0 {
|
if current_depth < 0 {
|
||||||
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the header.
|
||||||
@@ -187,9 +185,7 @@ fn _body_end<'b, 'g, 'r, 's>(
|
|||||||
let current_depth = input.get_brace_depth() - starting_brace_depth;
|
let current_depth = input.get_brace_depth() - starting_brace_depth;
|
||||||
if current_depth > 0 {
|
if current_depth > 0 {
|
||||||
// Its impossible for the next character to end the body if we're any amount of brace deep
|
// Its impossible for the next character to end the body if we're any amount of brace deep
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("NoBodyEnd")));
|
||||||
"NoBodyEnd".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
if current_depth < 0 {
|
if current_depth < 0 {
|
||||||
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.
|
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ use nom::bytes::complete::tag;
|
|||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::bytes::complete::take_while1;
|
use nom::bytes::complete::take_while1;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::combinator::consumed;
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
@@ -21,24 +20,20 @@ use super::org_source::BracketDepth;
|
|||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
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 crate::context::Matcher;
|
use super::util::org_line_ending;
|
||||||
|
use crate::context::constants::ORG_ELEMENT_AFFILIATED_KEYWORDS;
|
||||||
|
use crate::context::constants::ORG_ELEMENT_DUAL_KEYWORDS;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
|
use crate::parser::macros::element;
|
||||||
use crate::parser::util::start_of_line;
|
use crate::parser::util::start_of_line;
|
||||||
use crate::types::AffiliatedKeywords;
|
use crate::types::AffiliatedKeywords;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
|
|
||||||
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
|
pub(crate) fn filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>(
|
||||||
"caption", "data", "headers", "header", "label", "name", "plot", "resname", "results",
|
|
||||||
"result", "source", "srcname", "tblname",
|
|
||||||
];
|
|
||||||
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
|
|
||||||
|
|
||||||
pub(crate) fn filtered_keyword<F: Matcher>(
|
|
||||||
key_parser: F,
|
key_parser: F,
|
||||||
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
|
) -> impl Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||||
move |input| _filtered_keyword(&key_parser, input)
|
move |input| _filtered_keyword(&key_parser, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +41,7 @@ pub(crate) fn filtered_keyword<F: Matcher>(
|
|||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(key_parser))
|
tracing::instrument(ret, level = "debug", skip(key_parser))
|
||||||
)]
|
)]
|
||||||
fn _filtered_keyword<'s, F: Matcher>(
|
fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>(
|
||||||
key_parser: F,
|
key_parser: F,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||||
@@ -54,30 +49,22 @@ fn _filtered_keyword<'s, F: Matcher>(
|
|||||||
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
|
// TODO: When key is a member of org-element-parsed-keywords, value can contain the standard set objects, excluding footnote references.
|
||||||
let (remaining, (consumed_input, (_, _, parsed_key, _))) =
|
let (remaining, (consumed_input, (_, _, parsed_key, _))) =
|
||||||
consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?;
|
consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?;
|
||||||
match tuple((
|
let (remaining, _ws) = space0(remaining)?;
|
||||||
space0::<OrgSource<'_>, CustomError<OrgSource<'_>>>,
|
if let Ok((remaining, _)) = org_line_ending(remaining) {
|
||||||
alt((line_ending, eof)),
|
|
||||||
))(remaining)
|
|
||||||
{
|
|
||||||
Ok((remaining, _)) => {
|
|
||||||
return Ok((
|
return Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Keyword {
|
Keyword {
|
||||||
source: consumed_input.into(),
|
source: consumed_input.into(),
|
||||||
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: "".into(),
|
value: "",
|
||||||
|
post_blank: None,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
let (remaining, parsed_value) =
|
||||||
};
|
recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?;
|
||||||
let (remaining, _ws) = space0(remaining)?;
|
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
let (remaining, parsed_value) = recognize(many_till(
|
|
||||||
anychar,
|
|
||||||
peek(tuple((space0, alt((line_ending, eof))))),
|
|
||||||
))(remaining)?;
|
|
||||||
let (remaining, _ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Keyword {
|
Keyword {
|
||||||
@@ -85,6 +72,7 @@ fn _filtered_keyword<'s, F: Matcher>(
|
|||||||
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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -103,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,45 +139,43 @@ fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
element!(dual_affiliated_key, input);
|
||||||
recognize(tuple((dual_affiliated_key, tag("["), optval, tag("]")))),
|
element!(plain_affiliated_key, input);
|
||||||
plain_affiliated_key,
|
element!(export_keyword, input);
|
||||||
export_keyword,
|
Err(nom::Err::Error(CustomError::Static("No affiliated key.")))
|
||||||
))(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
|
for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
|
||||||
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
|
let result = map(
|
||||||
match result {
|
tuple((tag_no_case::<_, _, CustomError>(keyword), peek(tag(":")))),
|
||||||
Ok((remaining, ent)) => {
|
|(key, _)| key,
|
||||||
|
)(input);
|
||||||
|
if let Ok((remaining, ent)) = result {
|
||||||
return Ok((remaining, ent));
|
return Ok((remaining, ent));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
|
||||||
"NoKeywordKey".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
|
for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
|
||||||
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
|
let result = recognize(tuple((
|
||||||
match result {
|
tag_no_case::<_, _, CustomError>(keyword),
|
||||||
Ok((remaining, ent)) => {
|
tag("["),
|
||||||
|
optval,
|
||||||
|
tag("]"),
|
||||||
|
peek(tag(":")),
|
||||||
|
)))(input);
|
||||||
|
if let Ok((remaining, ent)) = result {
|
||||||
return Ok((remaining, ent));
|
return Ok((remaining, ent));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
|
||||||
"NoKeywordKey".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -216,7 +203,7 @@ fn _optval_end<'s>(
|
|||||||
unreachable!("Exceeded optval bracket depth.")
|
unreachable!("Exceeded optval bracket depth.")
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_bracket = tag::<_, _, CustomError<_>>("]")(input);
|
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||||
if close_bracket.is_ok() {
|
if close_bracket.is_ok() {
|
||||||
return close_bracket;
|
return close_bracket;
|
||||||
}
|
}
|
||||||
@@ -231,3 +218,18 @@ fn export_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>
|
|||||||
take_while1(|c: char| c.is_alphanumeric() || "-_".contains(c)),
|
take_while1(|c: char| c.is_alphanumeric() || "-_".contains(c)),
|
||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::parser::OrgSource;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_affiliated_keyword(b: &mut Bencher) {
|
||||||
|
let input = OrgSource::new("#+CAPTION[*foo*]: bar *baz*");
|
||||||
|
|
||||||
|
b.iter(|| assert!(affiliated_keyword(input).is_ok()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::error::Res;
|
|||||||
/// Parses the text in the value of a #+TODO keyword.
|
/// Parses the text in the value of a #+TODO keyword.
|
||||||
///
|
///
|
||||||
/// Example input: "foo bar baz | lorem ipsum"
|
/// Example input: "foo bar baz | lorem ipsum"
|
||||||
pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
|
pub(crate) fn todo_keywords(input: &str) -> Res<&str, (Vec<&str>, Vec<&str>)> {
|
||||||
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
|
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
|
||||||
let (remaining, after_pipe_words) = opt(tuple((
|
let (remaining, after_pipe_words) = opt(tuple((
|
||||||
tuple((space0, tag("|"), space0)),
|
tuple((space0, tag("|"), space0)),
|
||||||
@@ -30,12 +30,9 @@ pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, V
|
|||||||
Ok((remaining, (before_pipe_words, after_pipe_words)))
|
Ok((remaining, (before_pipe_words, after_pipe_words)))
|
||||||
} else if !before_pipe_words.is_empty() {
|
} else if !before_pipe_words.is_empty() {
|
||||||
// If there was no pipe, then the last word becomes a completion state instead.
|
// If there was no pipe, then the last word becomes a completion state instead.
|
||||||
let mut after_pipe_words = Vec::with_capacity(1);
|
let after_pipe_words = vec![before_pipe_words
|
||||||
after_pipe_words.push(
|
|
||||||
before_pipe_words
|
|
||||||
.pop()
|
.pop()
|
||||||
.expect("If-statement proves this is Some."),
|
.expect("If-statement proves this is Some.")];
|
||||||
);
|
|
||||||
Ok((remaining, (before_pipe_words, after_pipe_words)))
|
Ok((remaining, (before_pipe_words, after_pipe_words)))
|
||||||
} else {
|
} else {
|
||||||
// No words founds
|
// No words founds
|
||||||
@@ -43,7 +40,7 @@ pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, V
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn todo_keyword_word(input: &str) -> Res<&str, &str> {
|
||||||
let (remaining, keyword) = verify(take_till(|c| "( \t\r\n|".contains(c)), |result: &str| {
|
let (remaining, keyword) = verify(take_till(|c| "( \t\r\n|".contains(c)), |result: &str| {
|
||||||
!result.is_empty()
|
!result.is_empty()
|
||||||
})(input)?;
|
})(input)?;
|
||||||
|
|||||||
@@ -43,13 +43,8 @@ where
|
|||||||
let value_start = remaining;
|
let value_start = remaining;
|
||||||
start_of_line(remaining)?;
|
start_of_line(remaining)?;
|
||||||
let (remaining, _leading_whitespace) = space0(remaining)?;
|
let (remaining, _leading_whitespace) = space0(remaining)?;
|
||||||
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
|
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) =
|
||||||
tag_no_case(r#"\begin{"#),
|
tuple((tag_no_case(r"\begin{"), name, tag("}"), space0, line_ending))(remaining)?;
|
||||||
name,
|
|
||||||
tag("}"),
|
|
||||||
space0,
|
|
||||||
line_ending,
|
|
||||||
))(remaining)?;
|
|
||||||
|
|
||||||
let latex_environment_end_specialized = latex_environment_end(name.into());
|
let latex_environment_end_specialized = latex_environment_end(name.into());
|
||||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
@@ -62,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);
|
||||||
@@ -75,6 +70,7 @@ where
|
|||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -127,7 +123,7 @@ fn _latex_environment_end<'b, 'g, 'r, 's, 'c>(
|
|||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, _name, _close_brace, _ws, _line_ending)) = tuple((
|
let (remaining, (_begin, _name, _close_brace, _ws, _line_ending)) = tuple((
|
||||||
tag_no_case(r#"\end{"#),
|
tag_no_case(r"\end{"),
|
||||||
tag_no_case(current_name_lower),
|
tag_no_case(current_name_lower),
|
||||||
tag("}"),
|
tag("}"),
|
||||||
space0,
|
space0,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
|||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
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;
|
||||||
@@ -40,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((
|
||||||
@@ -48,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -209,9 +209,9 @@ fn pre<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
let preceding_character = input.get_preceding_character();
|
let preceding_character = input.get_preceding_character();
|
||||||
if let Some('$') = preceding_character {
|
if let Some('$') = preceding_character {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Not a valid pre character for dollar char fragment.".into(),
|
"Not a valid pre character for dollar char fragment.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
@@ -283,9 +283,9 @@ fn close_border<'b, 'g, 'r, 's>(
|
|||||||
match preceding_character {
|
match preceding_character {
|
||||||
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
|
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Not a valid pre character for dollar char fragment.".into(),
|
"Not a valid pre character for dollar char fragment.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
use crate::parser::util::blank_line;
|
use crate::parser::util::blank_line;
|
||||||
@@ -76,10 +75,7 @@ where
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
let parameters = match parameters {
|
let parameters = parameters.map(|(_ws, parameters)| parameters);
|
||||||
Some((_ws, parameters)) => Some(parameters),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -110,7 +106,7 @@ where
|
|||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
data: parameters.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -206,7 +202,7 @@ where
|
|||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
|
|
||||||
let (remaining, contents) = content(&parser_context, remaining)?;
|
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
@@ -240,7 +236,7 @@ where
|
|||||||
retain_labels,
|
retain_labels,
|
||||||
use_labels,
|
use_labels,
|
||||||
label_format,
|
label_format,
|
||||||
contents,
|
value: Into::<&str>::into(contents),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -280,7 +276,7 @@ where
|
|||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
|
|
||||||
let (remaining, contents) = content(&parser_context, remaining)?;
|
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
@@ -295,8 +291,8 @@ where
|
|||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
),
|
),
|
||||||
export_type: export_type.map(Into::<&str>::into),
|
export_type: export_type.map(Into::<&str>::into),
|
||||||
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
data: parameters.map(Into::<&str>::into),
|
||||||
contents,
|
value: Into::<&str>::into(contents),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -335,7 +331,7 @@ where
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
let (remaining, contents) = content(&parser_context, remaining)?;
|
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
|
||||||
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
@@ -344,7 +340,7 @@ where
|
|||||||
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
|
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
|
||||||
if let Some(switches) = switches {
|
if let Some(switches) = switches {
|
||||||
(
|
(
|
||||||
if switches.source.len() == 0 {
|
if switches.source.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(switches.source)
|
Some(switches.source)
|
||||||
@@ -375,7 +371,7 @@ where
|
|||||||
retain_labels,
|
retain_labels,
|
||||||
use_labels,
|
use_labels,
|
||||||
label_format,
|
label_format,
|
||||||
contents,
|
value: Into::<&str>::into(contents),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -390,7 +386,7 @@ fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
is_not("\r\n")(input)
|
is_not("\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lesser_block_end<'c>(current_name: &'c str) -> impl ContextMatcher + 'c {
|
fn lesser_block_end(current_name: &str) -> impl ContextMatcher + '_ {
|
||||||
// Since the lesser block names are statically defined in code, we can simply assert that the name is lowercase instead of causing an allocation by converting to lowercase.
|
// Since the lesser block names are statically defined in code, we can simply assert that the name is lowercase instead of causing an allocation by converting to lowercase.
|
||||||
debug_assert!(current_name == current_name.to_lowercase());
|
debug_assert!(current_name == current_name.to_lowercase());
|
||||||
move |context, input: OrgSource<'_>| _lesser_block_end(context, input, current_name)
|
move |context, input: OrgSource<'_>| _lesser_block_end(context, input, current_name)
|
||||||
@@ -420,7 +416,7 @@ fn _lesser_block_end<'b, 'g, 'r, 's, 'c>(
|
|||||||
/// Parser for the beginning of a lesser block
|
/// Parser for the beginning of a lesser block
|
||||||
///
|
///
|
||||||
/// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
|
/// current_name MUST be lowercase. We do not do the conversion ourselves because it is not allowed in a const fn.
|
||||||
const fn lesser_block_begin<'c>(current_name: &'c str) -> impl ContextMatcher + 'c {
|
const fn lesser_block_begin(current_name: &str) -> impl ContextMatcher + '_ {
|
||||||
// TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
|
// TODO: Since this is a const fn, is there ANY way to "generate" functions at compile time?
|
||||||
move |context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
|
move |context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
|
||||||
}
|
}
|
||||||
@@ -531,12 +527,9 @@ fn _example_src_switches<'s>(
|
|||||||
(SwitchState::Normal, "-r") => {
|
(SwitchState::Normal, "-r") => {
|
||||||
saw_r = true;
|
saw_r = true;
|
||||||
use_labels = false;
|
use_labels = false;
|
||||||
match retain_labels {
|
if let RetainLabels::Yes = retain_labels {
|
||||||
RetainLabels::Yes => {
|
|
||||||
retain_labels = RetainLabels::No;
|
retain_labels = RetainLabels::No;
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(SwitchState::Normal, "-l") => {
|
(SwitchState::Normal, "-l") => {
|
||||||
state = SwitchState::LabelFormat;
|
state = SwitchState::LabelFormat;
|
||||||
@@ -610,7 +603,7 @@ fn _example_src_switches<'s>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !matched_a_word {
|
if !matched_a_word {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError("No words."))));
|
return Err(nom::Err::Error(CustomError::Static("No words.")));
|
||||||
}
|
}
|
||||||
let remaining = last_match_remaining;
|
let remaining = last_match_remaining;
|
||||||
|
|
||||||
@@ -657,49 +650,3 @@ fn switch_word<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
is_not(" \t\r\n"),
|
is_not(" \t\r\n"),
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "tracing",
|
|
||||||
tracing::instrument(ret, level = "debug", skip(context))
|
|
||||||
)]
|
|
||||||
pub(crate) fn content<'b, 'g, 'r, 's>(
|
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
|
||||||
input: OrgSource<'s>,
|
|
||||||
) -> Res<OrgSource<'s>, String> {
|
|
||||||
let mut ret = String::new();
|
|
||||||
let mut remaining = input;
|
|
||||||
let exit_matcher_parser = parser_with_context!(exit_matcher_parser)(context);
|
|
||||||
loop {
|
|
||||||
if exit_matcher_parser(remaining).is_ok() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
|
|
||||||
pre_escape_whitespace.map(|val| 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)))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use nom::multi::many0;
|
|||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::types::LineBreak;
|
use crate::types::LineBreak;
|
||||||
@@ -21,7 +20,7 @@ pub(crate) fn line_break<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, LineBreak<'s>> {
|
) -> Res<OrgSource<'s>, LineBreak<'s>> {
|
||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, _) = tag(r#"\\"#)(remaining)?;
|
let (remaining, _) = tag(r"\\")(remaining)?;
|
||||||
let (remaining, _) = recognize(many0(one_of(" \t")))(remaining)?;
|
let (remaining, _) = recognize(many0(one_of(" \t")))(remaining)?;
|
||||||
let (remaining, _) = line_ending(remaining)?;
|
let (remaining, _) = line_ending(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -45,9 +44,9 @@ fn pre<'b, 'g, 'r, 's>(
|
|||||||
match preceding_character {
|
match preceding_character {
|
||||||
// If None, we are at the start of the file
|
// If None, we are at the start of the file
|
||||||
None | Some('\\') => {
|
None | Some('\\') => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Not a valid pre character for line break.".into(),
|
"Not a valid pre character for line break.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
@@ -55,9 +54,9 @@ fn pre<'b, 'g, 'r, 's>(
|
|||||||
let current_line = input.text_since_line_break();
|
let current_line = input.text_since_line_break();
|
||||||
let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
|
let is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
|
||||||
if !is_non_empty_line {
|
if !is_non_empty_line {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Not a valid pre line for line break.".into(),
|
"Not a valid pre line for line break.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// Parse an element that has affiliated keywords.
|
/// Parse an element that has affiliated keywords.
|
||||||
macro_rules! ak_element {
|
macro_rules! ak_element {
|
||||||
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => {
|
($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => {
|
||||||
if let Ok((remaining, ele)) = $parser(
|
if let Ok((remaining, ele)) = $parser(
|
||||||
$affiliated_keywords,
|
$affiliated_keywords,
|
||||||
$post_affiliated_keywords_input,
|
$post_affiliated_keywords_input,
|
||||||
@@ -10,7 +10,7 @@ macro_rules! ak_element {
|
|||||||
return Ok((remaining, $wrapper(ele)));
|
return Ok((remaining, $wrapper(ele)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => {
|
($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => {
|
||||||
if let Ok((remaining, ele)) = $parser(
|
if let Ok((remaining, ele)) = $parser(
|
||||||
$affiliated_keywords,
|
$affiliated_keywords,
|
||||||
$post_affiliated_keywords_input,
|
$post_affiliated_keywords_input,
|
||||||
@@ -25,16 +25,21 @@ macro_rules! ak_element {
|
|||||||
pub(crate) use ak_element;
|
pub(crate) use ak_element;
|
||||||
|
|
||||||
macro_rules! element {
|
macro_rules! element {
|
||||||
($parser:ident, $context: expr, $input: expr, $wrapper: expr) => {
|
($parser:expr, $context: expr, $input: expr, $wrapper: expr) => {
|
||||||
if let Ok((remaining, ele)) = $parser($context, $input) {
|
if let Ok((remaining, ele)) = $parser($context, $input) {
|
||||||
return Ok((remaining, $wrapper(ele)));
|
return Ok((remaining, $wrapper(ele)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
($parser:ident, $context: expr, $input: expr) => {
|
($parser:expr, $context: expr, $input: expr) => {
|
||||||
if let Ok((remaining, ele)) = $parser($context, $input) {
|
if let Ok((remaining, ele)) = $parser($context, $input) {
|
||||||
return Ok((remaining, ele));
|
return Ok((remaining, ele));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
($parser:expr, $input: expr) => {
|
||||||
|
if let Ok((remaining, ele)) = $parser($input) {
|
||||||
|
return Ok((remaining, ele));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use element;
|
pub(crate) use element;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod affiliated_keyword;
|
mod affiliated_keyword;
|
||||||
mod angle_link;
|
mod angle_link;
|
||||||
mod babel_call;
|
mod babel_call;
|
||||||
|
mod bullshitium;
|
||||||
mod citation;
|
mod citation;
|
||||||
mod citation_reference;
|
mod citation_reference;
|
||||||
mod clock;
|
mod clock;
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
use nom::branch::alt;
|
|
||||||
use nom::combinator::map;
|
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::plain_text::plain_text;
|
use super::plain_text::plain_text;
|
||||||
use super::regular_link::regular_link;
|
use super::regular_link::regular_link;
|
||||||
use super::subscript_and_superscript::detect_subscript_or_superscript;
|
use super::subscript_and_superscript::detect_subscript_or_superscript;
|
||||||
use crate::context::parser_with_context;
|
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::angle_link::angle_link;
|
use crate::parser::angle_link::angle_link;
|
||||||
use crate::parser::citation::citation;
|
use crate::parser::citation::citation;
|
||||||
@@ -19,6 +14,7 @@ use crate::parser::inline_babel_call::inline_babel_call;
|
|||||||
use crate::parser::inline_source_block::inline_source_block;
|
use crate::parser::inline_source_block::inline_source_block;
|
||||||
use crate::parser::latex_fragment::latex_fragment;
|
use crate::parser::latex_fragment::latex_fragment;
|
||||||
use crate::parser::line_break::line_break;
|
use crate::parser::line_break::line_break;
|
||||||
|
use crate::parser::macros::element;
|
||||||
use crate::parser::org_macro::org_macro;
|
use crate::parser::org_macro::org_macro;
|
||||||
use crate::parser::plain_link::plain_link;
|
use crate::parser::plain_link::plain_link;
|
||||||
use crate::parser::radio_link::radio_link;
|
use crate::parser::radio_link::radio_link;
|
||||||
@@ -39,14 +35,14 @@ pub(crate) fn standard_set_object<'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>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, object) = alt((
|
element!(standard_set_object_sans_plain_text, context, input);
|
||||||
parser_with_context!(standard_set_object_sans_plain_text)(context),
|
element!(
|
||||||
map(
|
plain_text(detect_standard_set_object_sans_plain_text),
|
||||||
parser_with_context!(plain_text(detect_standard_set_object_sans_plain_text))(context),
|
context,
|
||||||
Object::PlainText,
|
input,
|
||||||
),
|
Object::PlainText
|
||||||
))(input)?;
|
);
|
||||||
Ok((remaining, object))
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -57,14 +53,14 @@ pub(crate) fn minimal_set_object<'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>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, object) = alt((
|
element!(minimal_set_object_sans_plain_text, context, input);
|
||||||
parser_with_context!(minimal_set_object_sans_plain_text)(context),
|
element!(
|
||||||
map(
|
plain_text(detect_minimal_set_object_sans_plain_text),
|
||||||
parser_with_context!(plain_text(detect_minimal_set_object_sans_plain_text))(context),
|
context,
|
||||||
Object::PlainText,
|
input,
|
||||||
),
|
Object::PlainText
|
||||||
))(input)?;
|
);
|
||||||
Ok((remaining, object))
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -75,56 +71,38 @@ fn standard_set_object_sans_plain_text<'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>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, object) = alt((
|
element!(timestamp, context, input, Object::Timestamp);
|
||||||
map(parser_with_context!(timestamp)(context), Object::Timestamp),
|
element!(subscript, context, input, Object::Subscript);
|
||||||
map(parser_with_context!(subscript)(context), Object::Subscript),
|
element!(superscript, context, input, Object::Superscript);
|
||||||
map(
|
element!(statistics_cookie, context, input, Object::StatisticsCookie);
|
||||||
parser_with_context!(superscript)(context),
|
element!(target, context, input, Object::Target);
|
||||||
Object::Superscript,
|
element!(line_break, context, input, Object::LineBreak);
|
||||||
),
|
element!(
|
||||||
map(
|
inline_source_block,
|
||||||
parser_with_context!(statistics_cookie)(context),
|
context,
|
||||||
Object::StatisticsCookie,
|
input,
|
||||||
),
|
Object::InlineSourceBlock
|
||||||
map(parser_with_context!(target)(context), Object::Target),
|
);
|
||||||
map(parser_with_context!(line_break)(context), Object::LineBreak),
|
element!(inline_babel_call, context, input, Object::InlineBabelCall);
|
||||||
map(
|
element!(citation, context, input, Object::Citation);
|
||||||
parser_with_context!(inline_source_block)(context),
|
element!(
|
||||||
Object::InlineSourceBlock,
|
footnote_reference,
|
||||||
),
|
context,
|
||||||
map(
|
input,
|
||||||
parser_with_context!(inline_babel_call)(context),
|
Object::FootnoteReference
|
||||||
Object::InlineBabelCall,
|
);
|
||||||
),
|
element!(export_snippet, context, input, Object::ExportSnippet);
|
||||||
map(parser_with_context!(citation)(context), Object::Citation),
|
element!(entity, context, input, Object::Entity);
|
||||||
map(
|
element!(latex_fragment, context, input, Object::LatexFragment);
|
||||||
parser_with_context!(footnote_reference)(context),
|
element!(radio_link, context, input, Object::RadioLink);
|
||||||
Object::FootnoteReference,
|
element!(radio_target, context, input, Object::RadioTarget);
|
||||||
),
|
element!(text_markup, context, input);
|
||||||
map(
|
element!(regular_link, context, input, Object::RegularLink);
|
||||||
parser_with_context!(export_snippet)(context),
|
element!(plain_link, context, input, Object::PlainLink);
|
||||||
Object::ExportSnippet,
|
element!(angle_link, context, input, Object::AngleLink);
|
||||||
),
|
element!(org_macro, context, input, Object::OrgMacro);
|
||||||
map(parser_with_context!(entity)(context), Object::Entity),
|
|
||||||
map(
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
parser_with_context!(latex_fragment)(context),
|
|
||||||
Object::LatexFragment,
|
|
||||||
),
|
|
||||||
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
|
||||||
map(
|
|
||||||
parser_with_context!(radio_target)(context),
|
|
||||||
Object::RadioTarget,
|
|
||||||
),
|
|
||||||
parser_with_context!(text_markup)(context),
|
|
||||||
map(
|
|
||||||
parser_with_context!(regular_link)(context),
|
|
||||||
Object::RegularLink,
|
|
||||||
),
|
|
||||||
map(parser_with_context!(plain_link)(context), Object::PlainLink),
|
|
||||||
map(parser_with_context!(angle_link)(context), Object::AngleLink),
|
|
||||||
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
|
|
||||||
))(input)?;
|
|
||||||
Ok((remaining, object))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -135,20 +113,12 @@ fn minimal_set_object_sans_plain_text<'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>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, object) = alt((
|
element!(subscript, context, input, Object::Subscript);
|
||||||
map(parser_with_context!(subscript)(context), Object::Subscript),
|
element!(superscript, context, input, Object::Superscript);
|
||||||
map(
|
element!(entity, context, input, Object::Entity);
|
||||||
parser_with_context!(superscript)(context),
|
element!(latex_fragment, context, input, Object::LatexFragment);
|
||||||
Object::Superscript,
|
element!(text_markup, context, input);
|
||||||
),
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
map(parser_with_context!(entity)(context), Object::Entity),
|
|
||||||
map(
|
|
||||||
parser_with_context!(latex_fragment)(context),
|
|
||||||
Object::LatexFragment,
|
|
||||||
),
|
|
||||||
parser_with_context!(text_markup)(context),
|
|
||||||
))(input)?;
|
|
||||||
Ok((remaining, object))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -166,9 +136,7 @@ pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
|||||||
return Ok((input, ()));
|
return Ok((input, ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
|
||||||
"No object detected.".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -186,9 +154,7 @@ fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
|||||||
return Ok((input, ()));
|
return Ok((input, ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
|
||||||
"No object detected.".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -200,16 +166,18 @@ pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
|
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
|
||||||
let (remaining, object) = alt((
|
element!(
|
||||||
parser_with_context!(regular_link_description_set_object_sans_plain_text)(context),
|
regular_link_description_set_object_sans_plain_text,
|
||||||
map(
|
context,
|
||||||
parser_with_context!(plain_text(
|
input
|
||||||
detect_regular_link_description_set_object_sans_plain_text
|
);
|
||||||
))(context),
|
element!(
|
||||||
Object::PlainText,
|
plain_text(detect_regular_link_description_set_object_sans_plain_text),
|
||||||
),
|
context,
|
||||||
))(input)?;
|
input,
|
||||||
Ok((remaining, object))
|
Object::PlainText
|
||||||
|
);
|
||||||
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -221,27 +189,18 @@ fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
|
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
|
||||||
let (remaining, object) = alt((
|
element!(export_snippet, context, input, Object::ExportSnippet);
|
||||||
map(
|
element!(statistics_cookie, context, input, Object::StatisticsCookie);
|
||||||
parser_with_context!(export_snippet)(context),
|
element!(
|
||||||
Object::ExportSnippet,
|
inline_source_block,
|
||||||
),
|
context,
|
||||||
map(
|
input,
|
||||||
parser_with_context!(statistics_cookie)(context),
|
Object::InlineSourceBlock
|
||||||
Object::StatisticsCookie,
|
);
|
||||||
),
|
element!(inline_babel_call, context, input, Object::InlineBabelCall);
|
||||||
map(
|
element!(org_macro, context, input, Object::OrgMacro);
|
||||||
parser_with_context!(inline_source_block)(context),
|
element!(minimal_set_object_sans_plain_text, context, input);
|
||||||
Object::InlineSourceBlock,
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
),
|
|
||||||
map(
|
|
||||||
parser_with_context!(inline_babel_call)(context),
|
|
||||||
Object::InlineBabelCall,
|
|
||||||
),
|
|
||||||
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
|
|
||||||
parser_with_context!(minimal_set_object_sans_plain_text)(context),
|
|
||||||
))(input)?;
|
|
||||||
Ok((remaining, object))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -259,9 +218,7 @@ fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
|||||||
return Ok((input, ()));
|
return Ok((input, ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("No object detected.")))
|
||||||
"No object detected.".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -272,14 +229,14 @@ pub(crate) fn table_cell_set_object<'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>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, object) = alt((
|
element!(table_cell_set_object_sans_plain_text, context, input);
|
||||||
parser_with_context!(table_cell_set_object_sans_plain_text)(context),
|
element!(
|
||||||
map(
|
plain_text(detect_table_cell_set_object_sans_plain_text),
|
||||||
parser_with_context!(plain_text(detect_table_cell_set_object_sans_plain_text))(context),
|
context,
|
||||||
Object::PlainText,
|
input,
|
||||||
),
|
Object::PlainText
|
||||||
))(input)?;
|
);
|
||||||
Ok((remaining, object))
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -290,33 +247,24 @@ fn table_cell_set_object_sans_plain_text<'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>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
let (remaining, object) = alt((
|
element!(citation, context, input, Object::Citation);
|
||||||
map(parser_with_context!(citation)(context), Object::Citation),
|
element!(export_snippet, context, input, Object::ExportSnippet);
|
||||||
map(
|
element!(
|
||||||
parser_with_context!(export_snippet)(context),
|
footnote_reference,
|
||||||
Object::ExportSnippet,
|
context,
|
||||||
),
|
input,
|
||||||
map(
|
Object::FootnoteReference
|
||||||
parser_with_context!(footnote_reference)(context),
|
);
|
||||||
Object::FootnoteReference,
|
element!(radio_link, context, input, Object::RadioLink);
|
||||||
),
|
element!(regular_link, context, input, Object::RegularLink);
|
||||||
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
element!(plain_link, context, input, Object::PlainLink);
|
||||||
map(
|
element!(angle_link, context, input, Object::AngleLink);
|
||||||
parser_with_context!(regular_link)(context),
|
element!(org_macro, context, input, Object::OrgMacro);
|
||||||
Object::RegularLink,
|
element!(radio_target, context, input, Object::RadioTarget);
|
||||||
),
|
element!(target, context, input, Object::Target);
|
||||||
map(parser_with_context!(plain_link)(context), Object::PlainLink),
|
element!(timestamp, context, input, Object::Timestamp);
|
||||||
map(parser_with_context!(angle_link)(context), Object::AngleLink),
|
element!(minimal_set_object_sans_plain_text, context, input);
|
||||||
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
|
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||||
map(
|
|
||||||
parser_with_context!(radio_target)(context),
|
|
||||||
Object::RadioTarget,
|
|
||||||
),
|
|
||||||
map(parser_with_context!(target)(context), Object::Target),
|
|
||||||
map(parser_with_context!(timestamp)(context), Object::Timestamp),
|
|
||||||
parser_with_context!(minimal_set_object_sans_plain_text)(context),
|
|
||||||
))(input)?;
|
|
||||||
Ok((remaining, object))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -334,7 +282,5 @@ fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
|||||||
return Ok((input, ()));
|
return Ok((input, ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
|
||||||
"No object detected.".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
|
|||||||
loop {
|
loop {
|
||||||
not(parser_with_context!(exit_matcher_parser)(context))(remaining)?;
|
not(parser_with_context!(exit_matcher_parser)(context))(remaining)?;
|
||||||
not(peek(tag("}}}")))(remaining)?;
|
not(peek(tag("}}}")))(remaining)?;
|
||||||
if peek(tuple((space0::<OrgSource<'_>, CustomError<_>>, tag(")"))))(remaining).is_ok() {
|
if peek(tuple((space0::<_, CustomError>, tag(")"))))(remaining).is_ok() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
if next_char == '\\' {
|
if next_char == '\\' {
|
||||||
escaping = true;
|
escaping = true;
|
||||||
if peek(tag::<_, _, CustomError<_>>(")"))(new_remaining).is_ok() {
|
if peek(tag::<_, _, CustomError>(")"))(new_remaining).is_ok() {
|
||||||
// Special case for backslash at the end of a macro
|
// Special case for backslash at the end of a macro
|
||||||
remaining = new_remaining;
|
remaining = new_remaining;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -10,12 +10,9 @@ use nom::InputTakeAtPosition;
|
|||||||
use nom::Offset;
|
use nom::Offset;
|
||||||
use nom::Slice;
|
use nom::Slice;
|
||||||
|
|
||||||
use crate::error::CustomError;
|
|
||||||
use crate::error::MyError;
|
|
||||||
|
|
||||||
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,
|
||||||
@@ -85,6 +82,15 @@ impl<'s> OrgSource<'s> {
|
|||||||
self.slice(..(other.end - self.start))
|
self.slice(..(other.end - self.start))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_until_end_of_str(&self, other: &'s str) -> OrgSource<'s> {
|
||||||
|
let full_source_start = self.full_source.as_ptr() as usize;
|
||||||
|
let other_start = other.as_ptr() as usize - full_source_start;
|
||||||
|
let other_end = other_start + other.len();
|
||||||
|
debug_assert!(other_start >= self.start);
|
||||||
|
debug_assert!(other_end <= self.end);
|
||||||
|
self.slice(..(other_end - self.start))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> {
|
pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> {
|
||||||
let skipped_text = self.text_since_line_break();
|
let skipped_text = self.text_since_line_break();
|
||||||
let mut bracket_depth = self.bracket_depth;
|
let mut bracket_depth = self.bracket_depth;
|
||||||
@@ -219,7 +225,7 @@ where
|
|||||||
panic!("Attempted to extend past the end of the WrappedInput.")
|
panic!("Attempted to extend past the end of the WrappedInput.")
|
||||||
}
|
}
|
||||||
if new_start == self.start && new_end == self.end {
|
if new_start == self.start && new_end == self.end {
|
||||||
return self.clone();
|
return *self;
|
||||||
}
|
}
|
||||||
|
|
||||||
let skipped_text = &self.full_source[self.start..new_start];
|
let skipped_text = &self.full_source[self.start..new_start];
|
||||||
@@ -337,7 +343,7 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
|
|||||||
P: Fn(Self::Item) -> bool,
|
P: Fn(Self::Item) -> bool,
|
||||||
{
|
{
|
||||||
match Into::<&str>::into(self).position(predicate) {
|
match Into::<&str>::into(self).position(predicate) {
|
||||||
Some(0) => Err(nom::Err::Error(E::from_error_kind(self.clone(), e))),
|
Some(0) => Err(nom::Err::Error(E::from_error_kind(*self, e))),
|
||||||
Some(idx) => Ok(self.take_split(idx)),
|
Some(idx) => Ok(self.take_split(idx)),
|
||||||
None => Err(nom::Err::Incomplete(nom::Needed::new(1))),
|
None => Err(nom::Err::Incomplete(nom::Needed::new(1))),
|
||||||
}
|
}
|
||||||
@@ -366,11 +372,11 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
|
|||||||
{
|
{
|
||||||
let window = Into::<&str>::into(self);
|
let window = Into::<&str>::into(self);
|
||||||
match window.position(predicate) {
|
match window.position(predicate) {
|
||||||
Some(0) => Err(nom::Err::Error(E::from_error_kind(self.clone(), e))),
|
Some(0) => Err(nom::Err::Error(E::from_error_kind(*self, e))),
|
||||||
Some(n) => Ok(self.take_split(n)),
|
Some(n) => Ok(self.take_split(n)),
|
||||||
None => {
|
None => {
|
||||||
if window.input_len() == 0 {
|
if window.input_len() == 0 {
|
||||||
Err(nom::Err::Error(E::from_error_kind(self.clone(), e)))
|
Err(nom::Err::Error(E::from_error_kind(*self, e)))
|
||||||
} else {
|
} else {
|
||||||
Ok(self.take_split(self.input_len()))
|
Ok(self.take_split(self.input_len()))
|
||||||
}
|
}
|
||||||
@@ -385,33 +391,6 @@ impl<'n, 's> FindSubstring<&'n str> for OrgSource<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
|
|
||||||
err: nom::Err<I>,
|
|
||||||
) -> nom::Err<CustomError<&'a str>> {
|
|
||||||
match err {
|
|
||||||
nom::Err::Incomplete(needed) => nom::Err::Incomplete(needed),
|
|
||||||
nom::Err::Error(err) => nom::Err::Error(err.into()),
|
|
||||||
nom::Err::Failure(err) => nom::Err::Failure(err.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
|
|
||||||
fn from(value: CustomError<OrgSource<'s>>) -> Self {
|
|
||||||
match value {
|
|
||||||
CustomError::MyError(err) => CustomError::MyError(err.into()),
|
|
||||||
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
|
|
||||||
CustomError::IO(err) => CustomError::IO(err),
|
|
||||||
CustomError::BoxedError(err) => CustomError::BoxedError(err),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> From<MyError<OrgSource<'s>>> for MyError<&'s str> {
|
|
||||||
fn from(value: MyError<OrgSource<'s>>) -> Self {
|
|
||||||
MyError(value.0.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
@@ -45,14 +46,14 @@ where
|
|||||||
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
|
|
||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
|
||||||
many_till(standard_set_object_matcher, exit_matcher),
|
many_till(standard_set_object_matcher, exit_matcher),
|
||||||
|(children, _exit_contents)| !children.is_empty(),
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
|
// Not checking parent exit matcher because if there are any children matched then we have a valid paragraph.
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
@@ -60,6 +61,8 @@ where
|
|||||||
remaining,
|
remaining,
|
||||||
Paragraph {
|
Paragraph {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
affiliated_keywords: parse_affiliated_keywords(
|
affiliated_keywords: parse_affiliated_keywords(
|
||||||
context.get_global_settings(),
|
context.get_global_settings(),
|
||||||
affiliated_keywords,
|
affiliated_keywords,
|
||||||
@@ -89,14 +92,14 @@ fn paragraph_end<'b, 'g, 'r, 's>(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::context::parser_with_context;
|
use crate::context::bind_context;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
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::parser::org_source::OrgSource;
|
use crate::parser::org_source::OrgSource;
|
||||||
use crate::types::GetStandardProperties;
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_paragraphs() {
|
fn two_paragraphs() {
|
||||||
@@ -104,18 +107,12 @@ mod tests {
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
let paragraph_matcher = bind_context!(element(true), &initial_context);
|
||||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||||
let (remaining, second_paragraph) =
|
let (remaining, second_paragraph) =
|
||||||
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
|
||||||
first_paragraph.get_standard_properties().get_source(),
|
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
|
||||||
"foo bar baz\n\n"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
second_paragraph.get_standard_properties().get_source(),
|
|
||||||
"lorem ipsum"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ use crate::context::ExitMatcherNode;
|
|||||||
use crate::context::Matcher;
|
use crate::context::Matcher;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
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;
|
||||||
@@ -55,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((
|
||||||
@@ -67,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -95,9 +95,9 @@ fn pre<'b, 'g, 'r, 's>(
|
|||||||
Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {}
|
Some(x) if !WORD_CONSTITUENT_CHARACTERS.contains(x) => {}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
// Not at start of line, cannot be a heading
|
// Not at start of line, cannot be a heading
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Not a valid pre character for plain link.".into(),
|
"Not a valid pre character for plain link.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
@@ -262,18 +262,13 @@ pub(crate) fn protocol<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
for link_parameter in context.get_global_settings().link_parameters {
|
for link_parameter in context.get_global_settings().link_parameters {
|
||||||
let result = tag_no_case::<_, _, CustomError<_>>(*link_parameter)(input);
|
let result = tag_no_case::<_, _, CustomError>(*link_parameter)(input);
|
||||||
match result {
|
if let Ok((remaining, ent)) = result {
|
||||||
Ok((remaining, ent)) => {
|
|
||||||
return Ok((remaining, ent));
|
return Ok((remaining, ent));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("NoLinkProtocol")))
|
||||||
"NoLinkProtocol".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -330,8 +325,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_parenthesis =
|
let close_parenthesis = tag::<_, _, CustomError>(")")(remaining);
|
||||||
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
|
|
||||||
if close_parenthesis.is_ok() {
|
if close_parenthesis.is_ok() {
|
||||||
return close_parenthesis;
|
return close_parenthesis;
|
||||||
}
|
}
|
||||||
@@ -345,9 +339,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("No path plain end")))
|
||||||
"No path plain end".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -423,18 +415,18 @@ fn _path_plain_parenthesis_end<'s>(
|
|||||||
unreachable!("Exceeded plain link parenthesis depth.")
|
unreachable!("Exceeded plain link parenthesis depth.")
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
let close_parenthesis = tag::<_, _, CustomError>(")")(input);
|
||||||
if close_parenthesis.is_ok() {
|
if close_parenthesis.is_ok() {
|
||||||
return close_parenthesis;
|
return close_parenthesis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if current_depth == 1 {
|
if current_depth == 1 {
|
||||||
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input);
|
let open_parenthesis = tag::<_, _, CustomError>("(")(input);
|
||||||
if open_parenthesis.is_ok() {
|
if open_parenthesis.is_ok() {
|
||||||
return open_parenthesis;
|
return open_parenthesis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static(
|
||||||
"No closing parenthesis".into(),
|
"No closing parenthesis",
|
||||||
))))
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
|
|||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::digit1;
|
use nom::character::complete::digit1;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
|
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;
|
||||||
@@ -17,15 +18,16 @@ use nom::multi::many0;
|
|||||||
use nom::multi::many1;
|
use nom::multi::many1;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
use nom::InputTake;
|
||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
use super::element_parser::element;
|
use super::element_parser::element;
|
||||||
use super::keyword::affiliated_keyword;
|
|
||||||
use super::object_parser::standard_set_object;
|
use super::object_parser::standard_set_object;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::include_input;
|
use super::util::include_input;
|
||||||
use super::util::indentation_level;
|
use super::util::indentation_level;
|
||||||
use super::util::non_whitespace_character;
|
use super::util::non_whitespace_character;
|
||||||
|
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;
|
||||||
@@ -33,7 +35,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::blank_line;
|
use crate::parser::util::blank_line;
|
||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
@@ -53,13 +54,17 @@ use crate::types::PlainListType;
|
|||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(context))
|
tracing::instrument(ret, level = "debug", skip(context, _affiliated_keywords))
|
||||||
)]
|
)]
|
||||||
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's, AK>(
|
||||||
|
_affiliated_keywords: AK,
|
||||||
|
remaining: OrgSource<'s>,
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()>
|
||||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
where
|
||||||
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
|
{
|
||||||
if verify(
|
if verify(
|
||||||
tuple((
|
tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
@@ -68,16 +73,54 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
|||||||
alt((space1, line_ending, eof)),
|
alt((space1, line_ending, eof)),
|
||||||
)),
|
)),
|
||||||
|(_start, (indent_level, _), (_bullet_type, bull), _after_whitespace)| {
|
|(_start, (indent_level, _), (_bullet_type, bull), _after_whitespace)| {
|
||||||
!Into::<&str>::into(bull).starts_with("*") || *indent_level > 0
|
!Into::<&str>::into(bull).starts_with('*') || *indent_level > 0
|
||||||
},
|
},
|
||||||
)(input)
|
)(remaining)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
return Ok((input, ()));
|
return Ok((input, ()));
|
||||||
}
|
}
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static("No element detected.")));
|
||||||
"No element detected.".into(),
|
}
|
||||||
))));
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn detect_not_plain_list_item_indent<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, (u16, OrgSource<'s>)> {
|
||||||
|
if let Ok((_remaining, (_, indent, _))) = tuple((
|
||||||
|
start_of_line,
|
||||||
|
parser_with_context!(indentation_level)(context),
|
||||||
|
not(tuple((
|
||||||
|
parser_with_context!(bullet)(context),
|
||||||
|
alt((space1, line_ending, eof)),
|
||||||
|
))),
|
||||||
|
))(input)
|
||||||
|
{
|
||||||
|
return Ok((input, indent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headlines are not plain list items.
|
||||||
|
if let Ok((_remaining, (_, indent, _))) = verify(
|
||||||
|
tuple((
|
||||||
|
start_of_line,
|
||||||
|
parser_with_context!(indentation_level)(context),
|
||||||
|
tuple((
|
||||||
|
parser_with_context!(bullet)(context),
|
||||||
|
alt((space1, line_ending, eof)),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
|(_, (depth, _), ((_, bullet), _))| {
|
||||||
|
*depth == 0 && Into::<&str>::into(bullet).starts_with('*')
|
||||||
|
},
|
||||||
|
)(input)
|
||||||
|
{
|
||||||
|
return Ok((input, indent));
|
||||||
|
}
|
||||||
|
return Err(nom::Err::Error(CustomError::Static("No element detected.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -120,7 +163,7 @@ where
|
|||||||
// While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed.
|
// While #3 is the most slow, it also seems to cleanest and involves the least manual mutation of already-parsed objects so I am going with #3 for now, but we should revisit #1 or #2 when the parser is more developed.
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining);
|
let list_item = plain_list_item(&parser_context, remaining);
|
||||||
match (&first_item_list_type, &list_item) {
|
match (&first_item_list_type, &list_item) {
|
||||||
(None, Ok((_remain, (list_type, _item)))) => {
|
(None, Ok((_remain, (list_type, _item)))) => {
|
||||||
let _ = first_item_list_type.insert(*list_type);
|
let _ = first_item_list_type.insert(*list_type);
|
||||||
@@ -140,25 +183,17 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let maybe_exit = parser_with_context!(exit_matcher_parser)(&parser_context)(remaining);
|
let maybe_exit = exit_matcher_parser(&parser_context, remaining);
|
||||||
if maybe_exit.is_ok() {
|
if maybe_exit.is_ok() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (final_child_start, _final_item_first_parse) = match children.pop() {
|
if children.is_empty() {
|
||||||
Some(final_child) => final_child,
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
None => {
|
"Plain lists require at least one element.",
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
)));
|
||||||
"Plain lists require at least one element.".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
|
|
||||||
let final_item_context = parser_context.with_additional_node(&final_item_context);
|
|
||||||
let (remaining, (_, reparsed_final_item)) =
|
|
||||||
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
|
|
||||||
children.push((final_child_start, reparsed_final_item));
|
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
@@ -187,10 +222,10 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
|
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
|
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
|
||||||
let (remaining, (bullet_type, bull)) = verify(
|
let (remaining, (bullet_type, bull)) =
|
||||||
parser_with_context!(bullet)(context),
|
verify(bind_context!(bullet, context), |(_bullet_type, bull)| {
|
||||||
|(_bullet_type, bull)| !Into::<&str>::into(bull).starts_with("*") || indent_level > 0,
|
!Into::<&str>::into(bull).starts_with('*') || indent_level > 0
|
||||||
)(remaining)?;
|
})(remaining)?;
|
||||||
|
|
||||||
let (remaining, maybe_counter_set) =
|
let (remaining, maybe_counter_set) =
|
||||||
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
|
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
|
||||||
@@ -199,7 +234,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
|
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
|
||||||
|
|
||||||
let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type {
|
let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type {
|
||||||
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?
|
opt(tuple((space1, bind_context!(item_tag, context))))(remaining)?
|
||||||
} else {
|
} else {
|
||||||
(remaining, None)
|
(remaining, None)
|
||||||
};
|
};
|
||||||
@@ -211,6 +246,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let exit_matcher = plain_list_item_end(indent_level);
|
let exit_matcher = plain_list_item_end(indent_level);
|
||||||
|
let final_item_whitespace_cutoff = final_item_whitespace_cutoff(indent_level);
|
||||||
|
let final_whitespace_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
|
class: ExitClass::Beta,
|
||||||
|
exit_matcher: &final_item_whitespace_cutoff,
|
||||||
|
});
|
||||||
|
let final_whitespace_context = context.with_additional_node(&final_whitespace_context);
|
||||||
let contexts = [
|
let contexts = [
|
||||||
ContextElement::ConsumeTrailingWhitespace(true),
|
ContextElement::ConsumeTrailingWhitespace(true),
|
||||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
@@ -218,18 +259,21 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
exit_matcher: &exit_matcher,
|
exit_matcher: &exit_matcher,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = final_whitespace_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 maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
|
let maybe_contentless_item: Res<OrgSource<'_>, ()> =
|
||||||
detect_contentless_item_contents
|
detect_contentless_item_contents(&parser_context, remaining);
|
||||||
)(&parser_context))(remaining);
|
if let Ok((_rem, _ws)) = maybe_contentless_item {
|
||||||
match maybe_contentless_item {
|
let (remaining, _trailing_ws) = if tuple((
|
||||||
Ok((_rem, _ws)) => {
|
blank_line,
|
||||||
let (remaining, _trailing_ws) = if context.should_consume_trailing_whitespace() {
|
bind_context!(final_item_whitespace_cutoff, context),
|
||||||
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
|
))(remaining)
|
||||||
} else {
|
.is_ok()
|
||||||
|
{
|
||||||
recognize(alt((blank_line, eof)))(remaining)?
|
recognize(alt((blank_line, eof)))(remaining)?
|
||||||
|
} else {
|
||||||
|
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
|
||||||
};
|
};
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
return Ok((
|
return Ok((
|
||||||
@@ -251,34 +295,20 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
};
|
|
||||||
let (remaining, pre_blank) = item_tag_post_gap(&parser_context, remaining)?;
|
let (remaining, pre_blank) = item_tag_post_gap(&parser_context, remaining)?;
|
||||||
let pre_blank = Into::<&str>::into(pre_blank)
|
let pre_blank = Into::<&str>::into(pre_blank)
|
||||||
.bytes()
|
.bytes()
|
||||||
.filter(|b| *b == b'\n')
|
.filter(|b| *b == b'\n')
|
||||||
.count();
|
.count();
|
||||||
|
|
||||||
let (mut remaining, (mut children, _exit_contents)) = many_till(
|
let (remaining, (children, _exit_contents)) = many_till(
|
||||||
include_input(parser_with_context!(element(true))(&parser_context)),
|
include_input(bind_context!(element(true), &parser_context)),
|
||||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
bind_context!(exit_matcher_parser, &parser_context),
|
||||||
)(remaining)?;
|
)(remaining)?;
|
||||||
|
|
||||||
if !children.is_empty() && !context.should_consume_trailing_whitespace() {
|
// We have to use the parser_context here to include the whitespace cut-off
|
||||||
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
|
|
||||||
let final_item_context = parser_context.with_additional_node(&final_item_context);
|
|
||||||
let (final_child_start, _original_final_child) = children
|
|
||||||
.pop()
|
|
||||||
.expect("if-statement already checked that children was non-empty.");
|
|
||||||
let (remain, reparsed_final_element) = include_input(parser_with_context!(element(true))(
|
|
||||||
&final_item_context,
|
|
||||||
))(final_child_start)?;
|
|
||||||
remaining = remain;
|
|
||||||
children.push(reparsed_final_element);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
return Ok((
|
return Ok((
|
||||||
@@ -325,7 +355,7 @@ fn bullet<'b, 'g, 'r, 's>(
|
|||||||
map(tag("+"), |bull| (BulletType::Unordered, bull)),
|
map(tag("+"), |bull| (BulletType::Unordered, bull)),
|
||||||
map(
|
map(
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
parser_with_context!(counter)(context),
|
bind_context!(counter, context),
|
||||||
alt((tag("."), tag(")"))),
|
alt((tag("."), tag(")"))),
|
||||||
))),
|
))),
|
||||||
|bull| (BulletType::Ordered, bull),
|
|bull| (BulletType::Ordered, bull),
|
||||||
@@ -380,6 +410,52 @@ fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PlainListIt
|
|||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn final_item_whitespace_cutoff(indent_level: IndentationLevel) -> impl ContextMatcher {
|
||||||
|
move |context, input: OrgSource<'_>| {
|
||||||
|
impl_final_item_whitespace_cutoff(context, input, indent_level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
fn impl_final_item_whitespace_cutoff<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
indent_level: IndentationLevel,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
start_of_line(input)?;
|
||||||
|
// element!(plain_list_end, context, input);
|
||||||
|
|
||||||
|
if let Ok((_remaining, _)) = verify(
|
||||||
|
tuple((
|
||||||
|
opt(blank_line),
|
||||||
|
bind_context!(indentation_level, context),
|
||||||
|
not(multispace1),
|
||||||
|
)),
|
||||||
|
|(_, (depth, _stars), _not_whitespace)| *depth < indent_level,
|
||||||
|
)(input)
|
||||||
|
{
|
||||||
|
return Ok((input, input.take(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok((_remaining, _)) = tuple((
|
||||||
|
opt(blank_line),
|
||||||
|
verify(
|
||||||
|
bind_context!(detect_not_plain_list_item_indent, context),
|
||||||
|
|(depth, _)| *depth == indent_level,
|
||||||
|
),
|
||||||
|
))(input)
|
||||||
|
{
|
||||||
|
return Ok((input, input.take(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(nom::Err::Error(CustomError::Static(
|
||||||
|
"No whitespace cut-off.",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "tracing",
|
feature = "tracing",
|
||||||
tracing::instrument(ret, level = "debug", skip(_context))
|
tracing::instrument(ret, level = "debug", skip(_context))
|
||||||
@@ -415,7 +491,7 @@ fn _plain_list_item_end<'b, 'g, 'r, 's>(
|
|||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
opt(blank_line),
|
opt(blank_line),
|
||||||
parser_with_context!(line_indented_lte_matcher)(context),
|
bind_context!(line_indented_lte_matcher, context),
|
||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -434,7 +510,7 @@ fn _line_indented_lte<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let matched = recognize(verify(
|
let matched = recognize(verify(
|
||||||
tuple((
|
tuple((
|
||||||
parser_with_context!(indentation_level)(context),
|
bind_context!(indentation_level, context),
|
||||||
non_whitespace_character,
|
non_whitespace_character,
|
||||||
)),
|
)),
|
||||||
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
||||||
@@ -460,8 +536,8 @@ fn item_tag<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(
|
many_till(
|
||||||
// TODO: Should this be using a different set like the minimal set?
|
// TODO: Should this be using a different set like the minimal set?
|
||||||
parser_with_context!(standard_set_object)(&parser_context),
|
bind_context!(standard_set_object, &parser_context),
|
||||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
bind_context!(exit_matcher_parser, &parser_context),
|
||||||
),
|
),
|
||||||
|(children, _exit_contents)| !children.is_empty(),
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
)(input)?;
|
)(input)?;
|
||||||
@@ -511,7 +587,7 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
|
|||||||
alt((
|
alt((
|
||||||
peek(recognize(not(blank_line))),
|
peek(recognize(not(blank_line))),
|
||||||
peek(recognize(tuple((many0(blank_line), eof)))),
|
peek(recognize(tuple((many0(blank_line), eof)))),
|
||||||
parser_with_context!(exit_matcher_parser)(context),
|
bind_context!(exit_matcher_parser, context),
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
))),
|
))),
|
||||||
@@ -541,7 +617,7 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
let (remaining, _) = recognize(many_till(
|
let (remaining, _) = recognize(many_till(
|
||||||
blank_line,
|
blank_line,
|
||||||
parser_with_context!(exit_matcher_parser)(context),
|
bind_context!(exit_matcher_parser, context),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
Ok((remaining, ()))
|
Ok((remaining, ()))
|
||||||
}
|
}
|
||||||
@@ -549,10 +625,11 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::context::bind_context;
|
||||||
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() {
|
||||||
@@ -560,10 +637,10 @@ mod tests {
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_list_item_matcher = parser_with_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]
|
||||||
@@ -572,10 +649,10 @@ mod tests {
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_list_item_matcher = parser_with_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]
|
||||||
@@ -587,7 +664,7 @@ mod tests {
|
|||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
assert_eq!(result.get_source(), "1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -599,7 +676,7 @@ mod tests {
|
|||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
assert_eq!(result.get_source(), "1. foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -639,12 +716,12 @@ mod tests {
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
|
let plain_list_matcher = bind_context!(element(true), &initial_context);
|
||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(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
|
||||||
@@ -667,12 +744,12 @@ baz"#,
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
|
let plain_list_matcher = bind_context!(element(true), &initial_context);
|
||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(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
|
||||||
|
|
||||||
@@ -700,12 +777,12 @@ dolar"#,
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_list_matcher = parser_with_context!(element(true))(&initial_context);
|
let plain_list_matcher = bind_context!(element(true), &initial_context);
|
||||||
let (remaining, result) =
|
let (remaining, result) =
|
||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(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
|
||||||
@@ -730,7 +807,7 @@ dolar"#,
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let result = detect_plain_list(&initial_context, input);
|
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -740,7 +817,7 @@ dolar"#,
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let result = detect_plain_list(&initial_context, input);
|
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,7 +827,7 @@ dolar"#,
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let result = detect_plain_list(&initial_context, input);
|
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
|
||||||
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
@@ -761,7 +838,7 @@ dolar"#,
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let result = detect_plain_list(&initial_context, input);
|
let result = detect_plain_list(std::iter::empty(), input, &initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use super::util::org_space_or_line_ending;
|
|||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
use crate::types::PlainText;
|
use crate::types::PlainText;
|
||||||
@@ -92,9 +91,8 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
|
let is_not_whitespace = is_not::<_, _, CustomError>(" \t\r\n")(goal);
|
||||||
match is_not_whitespace {
|
if let Ok((new_goal, payload)) = is_not_whitespace {
|
||||||
Ok((new_goal, payload)) => {
|
|
||||||
let (new_remaining, _) = tuple((
|
let (new_remaining, _) = tuple((
|
||||||
tag_no_case(payload),
|
tag_no_case(payload),
|
||||||
// TODO: Test to see what the REAL condition is. Checking for not-alphabetic works fine for now, but the real criteria might be something like the plain text exit matcher.
|
// TODO: Test to see what the REAL condition is. Checking for not-alphabetic works fine for now, but the real criteria might be something like the plain text exit matcher.
|
||||||
@@ -107,26 +105,21 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
|
|||||||
goal = new_goal;
|
goal = new_goal;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
let is_whitespace = recognize(many1(alt((
|
let is_whitespace = recognize(many1(alt((
|
||||||
recognize(one_of::<&str, &str, CustomError<_>>(" \t")),
|
recognize(one_of::<_, _, CustomError>(" \t")),
|
||||||
line_ending,
|
line_ending,
|
||||||
))))(goal);
|
))))(goal);
|
||||||
match is_whitespace {
|
if let Ok((new_goal, _)) = is_whitespace {
|
||||||
Ok((new_goal, _)) => {
|
|
||||||
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
|
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
|
||||||
remaining = new_remaining;
|
remaining = new_remaining;
|
||||||
goal = new_goal;
|
goal = new_goal;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Target does not match.".into(),
|
"Target does not match.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -144,12 +137,13 @@ mod tests {
|
|||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::context::bind_context;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
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() {
|
||||||
@@ -157,14 +151,15 @@ mod tests {
|
|||||||
let global_settings = GlobalSettings::default();
|
let global_settings = GlobalSettings::default();
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_text_matcher = parser_with_context!(plain_text(
|
let (remaining, result) = map(
|
||||||
detect_standard_set_object_sans_plain_text
|
bind_context!(
|
||||||
))(&initial_context);
|
plain_text(detect_standard_set_object_sans_plain_text),
|
||||||
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
|
&initial_context
|
||||||
|
),
|
||||||
|
Object::PlainText,
|
||||||
|
)(input)
|
||||||
|
.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)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::immediate_in_section;
|
use crate::parser::util::immediate_in_section;
|
||||||
@@ -39,9 +38,9 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
|
) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
|
||||||
if immediate_in_section(context, "property-drawer") {
|
if immediate_in_section(context, "property-drawer") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
let (
|
let (
|
||||||
remaining,
|
remaining,
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use crate::context::ExitMatcherNode;
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
@@ -40,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,
|
||||||
@@ -48,13 +47,16 @@ 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
|
||||||
|
},
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("NoRadioLink")))
|
||||||
"NoRadioLink".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -98,9 +100,9 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
|
|||||||
new_matches.push(new_match);
|
new_matches.push(new_match);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"OnlyMinimalSetObjectsAllowed".into(),
|
"OnlyMinimalSetObjectsAllowed",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -137,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((
|
||||||
@@ -145,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -178,85 +181,96 @@ 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 = {
|
let global_settings = GlobalSettings {
|
||||||
let mut global_settings = GlobalSettings::default();
|
radio_targets: vec![&radio_target_match],
|
||||||
global_settings.radio_targets = vec![&radio_target_match];
|
..Default::default()
|
||||||
global_settings
|
|
||||||
};
|
};
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
let (remaining, first_paragraph) =
|
||||||
let (remaining, first_paragraph) = paragraph_matcher(input).expect("Parse first paragraph");
|
element(true)(&initial_context, input).expect("Parse first paragraph");
|
||||||
let first_paragraph = match first_paragraph {
|
let first_paragraph = match first_paragraph {
|
||||||
Element::Paragraph(paragraph) => paragraph,
|
Element::Paragraph(paragraph) => paragraph,
|
||||||
_ => 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".into()
|
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 = {
|
let global_settings = GlobalSettings {
|
||||||
let mut global_settings = GlobalSettings::default();
|
radio_targets: vec![&radio_target_match],
|
||||||
global_settings.radio_targets = vec![&radio_target_match];
|
..Default::default()
|
||||||
global_settings
|
|
||||||
};
|
};
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let paragraph_matcher = parser_with_context!(element(true))(&initial_context);
|
|
||||||
let (remaining, first_paragraph) =
|
let (remaining, first_paragraph) =
|
||||||
paragraph_matcher(input.into()).expect("Parse first paragraph");
|
element(true)(&initial_context, input).expect("Parse first paragraph");
|
||||||
let first_paragraph = match first_paragraph {
|
let first_paragraph = match first_paragraph {
|
||||||
Element::Paragraph(paragraph) => paragraph,
|
Element::Paragraph(paragraph) => paragraph,
|
||||||
_ => 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* ".into()
|
.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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ use crate::context::ExitMatcherNode;
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::types::LinkType;
|
use crate::types::LinkType;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
@@ -74,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((
|
||||||
@@ -85,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,
|
||||||
},
|
},
|
||||||
@@ -102,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((
|
||||||
@@ -115,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,
|
||||||
},
|
},
|
||||||
@@ -139,14 +143,7 @@ fn pathreg<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, PathReg<'s>> {
|
) -> Res<OrgSource<'s>, PathReg<'s>> {
|
||||||
let (remaining, path) = map_parser(
|
let (remaining, path) = map_parser(
|
||||||
escaped(
|
escaped(take_till1(|c| matches!(c, '\\' | '[' | ']')), '\\', anychar),
|
||||||
take_till1(|c| match c {
|
|
||||||
'\\' | '[' | ']' => true,
|
|
||||||
_ => false,
|
|
||||||
}),
|
|
||||||
'\\',
|
|
||||||
anychar,
|
|
||||||
),
|
|
||||||
parser_with_context!(parse_path_reg)(context),
|
parser_with_context!(parse_path_reg)(context),
|
||||||
)(input)?;
|
)(input)?;
|
||||||
Ok((remaining, path))
|
Ok((remaining, path))
|
||||||
@@ -170,11 +167,7 @@ fn parse_path_reg<'b, 'g, 'r, 's>(
|
|||||||
parser_with_context!(protocol_path_reg)(context),
|
parser_with_context!(protocol_path_reg)(context),
|
||||||
fuzzy_path_reg,
|
fuzzy_path_reg,
|
||||||
))(replaced_input)
|
))(replaced_input)
|
||||||
.map_err(|_| {
|
.map_err(|_| nom::Err::Error(CustomError::Static("No pathreg match after replacement.")))?;
|
||||||
nom::Err::Error(CustomError::MyError(MyError(
|
|
||||||
"No pathreg match after replacement.",
|
|
||||||
)))
|
|
||||||
})?;
|
|
||||||
let remaining = input.take(input.len());
|
let remaining = input.take(input.len());
|
||||||
let link_type = match link.link_type {
|
let link_type = match link.link_type {
|
||||||
LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()),
|
LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()),
|
||||||
@@ -262,12 +255,9 @@ fn apply_link_templates<'b, 'g, 'r, 's>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Handle lingering state
|
// Handle lingering state
|
||||||
match state {
|
if let ParserState::Percent = state {
|
||||||
ParserState::Percent => {
|
|
||||||
ret.push('%');
|
ret.push('%');
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
if !injected_value {
|
if !injected_value {
|
||||||
ret.push_str(inject_value);
|
ret.push_str(inject_value);
|
||||||
}
|
}
|
||||||
@@ -493,7 +483,5 @@ fn impl_path_reg_end<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("No path reg end")))
|
||||||
"No path reg end".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
@@ -58,21 +59,21 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
|
|||||||
many0(blank_line),
|
many0(blank_line),
|
||||||
)))(input)?;
|
)))(input)?;
|
||||||
|
|
||||||
let (remaining, (mut children, _exit_contents)) = verify(
|
let (remaining, (contents, (mut children, _exit_contents))) = consumed(verify(
|
||||||
many_till(element_matcher, exit_matcher),
|
many_till(element_matcher, exit_matcher),
|
||||||
|(children, _exit_contents)| {
|
|(children, _exit_contents)| {
|
||||||
!children.is_empty() || comment_and_property_drawer_element.is_some()
|
!children.is_empty() || comment_and_property_drawer_element.is_some()
|
||||||
},
|
},
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
|
|
||||||
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
|
if let Some((comment, property_drawer, _ws)) = comment_and_property_drawer_element {
|
||||||
children.insert(0, Element::PropertyDrawer(property_drawer));
|
children.insert(0, Element::PropertyDrawer(property_drawer));
|
||||||
comment
|
if let Some(ele) = comment.map(Element::Comment) {
|
||||||
.map(Element::Comment)
|
children.insert(0, ele);
|
||||||
.map(|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);
|
||||||
@@ -80,6 +81,8 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Section {
|
Section {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -115,20 +118,20 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
|
|||||||
remaining = remain;
|
remaining = remain;
|
||||||
input = remain;
|
input = remain;
|
||||||
}
|
}
|
||||||
let (remaining, (mut children, _exit_contents)) = verify(
|
let (remaining, (contents, (mut children, _exit_contents))) = consumed(verify(
|
||||||
many_till(element_matcher, exit_matcher),
|
many_till(element_matcher, exit_matcher),
|
||||||
|(children, _exit_contents)| {
|
|(children, _exit_contents)| {
|
||||||
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
|
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
|
||||||
},
|
},
|
||||||
)(remaining)?;
|
))(remaining)?;
|
||||||
property_drawer_element
|
if let Some(ele) = property_drawer_element.map(Element::PropertyDrawer) {
|
||||||
.map(Element::PropertyDrawer)
|
children.insert(0, ele);
|
||||||
.map(|ele| children.insert(0, ele));
|
}
|
||||||
planning_element
|
if let Some(ele) = planning_element.map(Element::Planning) {
|
||||||
.map(Element::Planning)
|
children.insert(0, ele)
|
||||||
.map(|ele| children.insert(0, ele));
|
}
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, post_blank) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -136,6 +139,8 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Section {
|
Section {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
contents: Some(contents.into()),
|
||||||
|
post_blank: post_blank.map(Into::<&str>::into),
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -26,7 +27,6 @@ use crate::context::ExitMatcherNode;
|
|||||||
use crate::context::Matcher;
|
use crate::context::Matcher;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
@@ -39,7 +39,7 @@ pub(crate) fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<O
|
|||||||
// This does not have to detect all valid subscript/superscript but all that it detects must be valid.
|
// This does not have to detect all valid subscript/superscript but all that it detects must be valid.
|
||||||
let (remaining, _) = one_of("_^")(input)?;
|
let (remaining, _) = one_of("_^")(input)?;
|
||||||
pre(input)?;
|
pre(input)?;
|
||||||
if tag::<_, _, CustomError<_>>("*")(remaining).is_ok() {
|
if tag::<_, _, CustomError>("*")(remaining).is_ok() {
|
||||||
return Ok((input, ()));
|
return Ok((input, ()));
|
||||||
}
|
}
|
||||||
let (remaining, _) = opt(one_of("+-"))(remaining)?;
|
let (remaining, _) = opt(one_of("+-"))(remaining)?;
|
||||||
@@ -55,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((
|
||||||
@@ -73,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -90,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((
|
||||||
@@ -104,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -118,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(
|
||||||
@@ -136,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.into())
|
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()),
|
||||||
@@ -175,7 +187,7 @@ fn script_alphanum<'b, 'g, 'r, 's>(
|
|||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(verify(anychar, |c| {
|
recognize(verify(anychar, |c| {
|
||||||
c.is_alphanumeric() || r#",.\"#.contains(*c)
|
c.is_alphanumeric() || r",.\".contains(*c)
|
||||||
}))(input)
|
}))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +195,7 @@ fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Org
|
|||||||
fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
|
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
|
||||||
peek(tuple((
|
peek(tuple((
|
||||||
take_while(|c| r#",.\"#.contains(c)),
|
take_while(|c| r",.\".contains(c)),
|
||||||
not(script_alphanum_character),
|
not(script_alphanum_character),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
Ok((remaining, final_char))
|
Ok((remaining, final_char))
|
||||||
@@ -196,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 {
|
||||||
@@ -205,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 {
|
||||||
@@ -232,9 +244,9 @@ fn _script_with_braces_end<'b, 'g, 'r, 's>(
|
|||||||
let current_depth = input.get_brace_depth() - starting_brace_depth;
|
let current_depth = input.get_brace_depth() - starting_brace_depth;
|
||||||
if current_depth > 0 {
|
if current_depth > 0 {
|
||||||
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
|
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Not a valid end for subscript or superscript.".into(),
|
"Not a valid end for subscript or superscript.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
if current_depth < 0 {
|
if current_depth < 0 {
|
||||||
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
|
// This shouldn't be possible because if depth is 0 then a closing brace should end the subscript or superscript.
|
||||||
@@ -282,12 +294,12 @@ fn _script_with_parenthesis_end<'s>(
|
|||||||
unreachable!("Exceeded citation key suffix bracket depth.")
|
unreachable!("Exceeded citation key suffix bracket depth.")
|
||||||
}
|
}
|
||||||
if current_depth == 0 {
|
if current_depth == 0 {
|
||||||
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
let close_parenthesis = tag::<_, _, CustomError>(")")(input);
|
||||||
if close_parenthesis.is_ok() {
|
if close_parenthesis.is_ok() {
|
||||||
return close_parenthesis;
|
return close_parenthesis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static(
|
||||||
"No script parenthesis end.".into(),
|
"No script parenthesis end.",
|
||||||
))))
|
)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use nom::multi::many_till;
|
|||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||||
use super::keyword::affiliated_keyword;
|
|
||||||
use super::keyword::table_formula_keyword;
|
use super::keyword::table_formula_keyword;
|
||||||
use super::object_parser::table_cell_set_object;
|
use super::object_parser::table_cell_set_object;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
@@ -92,10 +91,20 @@ where
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(
|
||||||
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
feature = "tracing",
|
||||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
|
||||||
tuple((start_of_line, space0, tag("|")))(input)?;
|
)]
|
||||||
|
pub(crate) fn detect_table<'b, 'g, 'r, 's, AK>(
|
||||||
|
_affiliated_keywords: AK,
|
||||||
|
remaining: OrgSource<'s>,
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()>
|
||||||
|
where
|
||||||
|
AK: IntoIterator<Item = Keyword<'s>>,
|
||||||
|
{
|
||||||
|
tuple((start_of_line, space0, tag("|")))(remaining)?;
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +197,7 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, (children, _exit_contents)) = verify(
|
let (remaining, (children, _exit_contents)) = verify(
|
||||||
many_till(table_cell_set_object_matcher, exit_matcher),
|
many_till(table_cell_set_object_matcher, exit_matcher),
|
||||||
|(children, exit_contents)| {
|
|(children, exit_contents)| {
|
||||||
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with("|")
|
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
|
||||||
},
|
},
|
||||||
)(remaining)?;
|
)(remaining)?;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use crate::context::ExitClass;
|
|||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::types::Target;
|
use crate::types::Target;
|
||||||
@@ -42,9 +41,9 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
|
|||||||
.get_preceding_character()
|
.get_preceding_character()
|
||||||
.expect("We cannot be at the start of the file because we are inside a target.");
|
.expect("We cannot be at the start of the file because we are inside a target.");
|
||||||
if preceding_character.is_whitespace() {
|
if preceding_character.is_whitespace() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Targets cannot end with whitespace.".into(),
|
"Targets cannot end with whitespace.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
let (remaining, _) = tag(">>")(remaining)?;
|
let (remaining, _) = tag(">>")(remaining)?;
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, _trailing_whitespace) =
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -34,7 +36,6 @@ use crate::context::ExitMatcherNode;
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::radio_link::rematch_target;
|
use crate::parser::radio_link::rematch_target;
|
||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
@@ -77,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -96,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -115,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -134,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -153,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -172,24 +182,27 @@ 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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_markup_object<'c>(
|
fn text_markup_object(
|
||||||
marker_symbol: &'c str,
|
marker_symbol: &str,
|
||||||
) -> 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<
|
||||||
+ 'c {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,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) =
|
||||||
@@ -216,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,
|
||||||
@@ -226,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")]
|
||||||
@@ -234,25 +247,25 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
|||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
if exit_matcher_parser(context, remaining).is_ok() {
|
if exit_matcher_parser(context, remaining).is_ok() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Parent exit matcher is triggering.".into(),
|
"Parent exit matcher is triggering.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<'c>(
|
fn text_markup_string(
|
||||||
marker_symbol: &'c str,
|
marker_symbol: &str,
|
||||||
) -> 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>>)>
|
||||||
+ 'c {
|
+ '_ {
|
||||||
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
|
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,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) =
|
||||||
@@ -290,16 +303,16 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
|
|||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
if exit_matcher_parser(context, remaining).is_ok() {
|
if exit_matcher_parser(context, remaining).is_ok() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Parent exit matcher is triggering.".into(),
|
"Parent exit matcher is triggering.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
@@ -321,9 +334,9 @@ fn pre<'b, 'g, 'r, 's>(
|
|||||||
// If None, we are at the start of the file which is technically the beginning of a line.
|
// If None, we are at the start of the file which is technically the beginning of a line.
|
||||||
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
|
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Not a valid pre character for text markup.".into(),
|
"Not a valid pre character for text markup.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above.
|
None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above.
|
||||||
};
|
};
|
||||||
@@ -343,10 +356,7 @@ fn post<'b, 'g, 'r, 's>(
|
|||||||
Ok((remaining, ()))
|
Ok((remaining, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_markup_end<'c>(
|
fn text_markup_end(marker_symbol: &str, contents_start_offset: usize) -> impl ContextMatcher + '_ {
|
||||||
marker_symbol: &'c str,
|
|
||||||
contents_start_offset: usize,
|
|
||||||
) -> impl ContextMatcher + 'c {
|
|
||||||
move |context, input: OrgSource<'_>| {
|
move |context, input: OrgSource<'_>| {
|
||||||
_text_markup_end(context, input, marker_symbol, contents_start_offset)
|
_text_markup_end(context, input, marker_symbol, contents_start_offset)
|
||||||
}
|
}
|
||||||
@@ -363,9 +373,9 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
|
|||||||
contents_start_offset: usize,
|
contents_start_offset: usize,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
if input.get_byte_offset() == contents_start_offset {
|
if input.get_byte_offset() == contents_start_offset {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Text markup cannot be empty".into(),
|
"Text markup cannot be empty",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
not(preceded_by_whitespace(false))(input)?;
|
not(preceded_by_whitespace(false))(input)?;
|
||||||
let (remaining, _marker) = terminated(
|
let (remaining, _marker) = terminated(
|
||||||
@@ -386,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,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -409,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,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -432,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,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -455,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,
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
@@ -477,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)?;
|
||||||
@@ -488,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)?;
|
||||||
@@ -498,13 +517,15 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
|||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
if exit_matcher_parser(context, remaining).is_ok() {
|
if exit_matcher_parser(context, remaining).is_ok() {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Parent exit matcher is triggering.".into(),
|
"Parent exit matcher is triggering.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -159,7 +160,7 @@ 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) =
|
||||||
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);
|
||||||
|
|
||||||
@@ -175,6 +176,7 @@ 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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -213,7 +215,7 @@ 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) =
|
||||||
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);
|
||||||
|
|
||||||
@@ -229,6 +231,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -246,7 +249,7 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _separator) = tag("--")(remaining)?;
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
let (remaining, second_timestamp) = active_timestamp(context, remaining)?;
|
let (remaining, second_timestamp) = active_timestamp(context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _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 +267,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 +306,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 +322,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -335,7 +340,7 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _separator) = tag("--")(remaining)?;
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?;
|
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _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 +359,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 +398,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 +414,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),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -486,7 +493,7 @@ fn dayname_end<'b, 'g, 'r, 's>(
|
|||||||
}))(input)
|
}))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn time<'c>(
|
const fn time(
|
||||||
allow_rest: bool,
|
allow_rest: bool,
|
||||||
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Time<'s>>
|
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Time<'s>>
|
||||||
{
|
{
|
||||||
@@ -590,6 +597,7 @@ fn time_range_rest_end<'b, 'g, 'r, 's>(
|
|||||||
tag("-"),
|
tag("-"),
|
||||||
parser_with_context!(time(true))(&parent_node),
|
parser_with_context!(time(true))(&parent_node),
|
||||||
)))(input);
|
)))(input);
|
||||||
|
#[allow(clippy::let_and_return)] // otherwise parent_node does not live long enough.
|
||||||
exit_contents
|
exit_contents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ use crate::context::parser_with_context;
|
|||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::types::IndentationLevel;
|
use crate::types::IndentationLevel;
|
||||||
|
|
||||||
@@ -28,10 +27,7 @@ pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
|
|||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
|
||||||
/// Check if we are below a section of the given section type regardless of depth
|
/// Check if we are below a section of the given section type regardless of depth
|
||||||
pub(crate) fn in_section<'b, 'g, 'r, 's, 'x>(
|
pub(crate) fn in_section(context: RefContext<'_, '_, '_, '_>, section_name: &str) -> bool {
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
|
||||||
section_name: &'x str,
|
|
||||||
) -> bool {
|
|
||||||
for thing in context.iter() {
|
for thing in context.iter() {
|
||||||
match thing {
|
match thing {
|
||||||
ContextElement::Context(name) if *name == section_name => return true,
|
ContextElement::Context(name) if *name == section_name => return true,
|
||||||
@@ -42,9 +38,9 @@ pub(crate) fn in_section<'b, 'g, 'r, 's, 'x>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if we are currently an immediate child of the given section type
|
/// Checks if we are currently an immediate child of the given section type
|
||||||
pub(crate) fn immediate_in_section<'b, 'g, 'r, 's, 'x>(
|
pub(crate) fn immediate_in_section(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'_, '_, '_, '_>,
|
||||||
section_name: &'x str,
|
section_name: &str,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
for thing in context.iter() {
|
for thing in context.iter() {
|
||||||
match thing {
|
match thing {
|
||||||
@@ -85,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(
|
||||||
@@ -132,9 +135,7 @@ pub(crate) fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()>
|
|||||||
if input.is_at_start_of_line() {
|
if input.is_at_start_of_line() {
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
} else {
|
} else {
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("Not at start of line")))
|
||||||
"Not at start of line".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,9 +156,9 @@ fn _preceded_by_whitespace<'s>(
|
|||||||
.map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space
|
.map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space
|
||||||
.unwrap_or(allow_start_of_file)
|
.unwrap_or(allow_start_of_file)
|
||||||
{
|
{
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::Static(
|
||||||
"Must be preceded by a whitespace character.".into(),
|
"Must be preceded by a whitespace character.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
@@ -198,9 +199,7 @@ pub(crate) fn text_until_exit<'b, 'g, 'r, 's>(
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
|
fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("Not implemented yet.")))
|
||||||
"Not implemented yet.".into(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -208,9 +207,7 @@ fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
|
|||||||
/// Text from the current point until the next line break or end of file
|
/// Text from the current point until the next line break or end of file
|
||||||
///
|
///
|
||||||
/// Useful for debugging.
|
/// Useful for debugging.
|
||||||
fn text_until_eol<'r, 's>(
|
fn text_until_eol<'r, 's>(input: OrgSource<'s>) -> Result<&'s str, nom::Err<CustomError>> {
|
||||||
input: OrgSource<'s>,
|
|
||||||
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
|
|
||||||
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
|
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
|
||||||
.map(|(_remaining, line)| Into::<&str>::into(line))?;
|
.map(|(_remaining, line)| Into::<&str>::into(line))?;
|
||||||
Ok(line.trim())
|
Ok(line.trim())
|
||||||
@@ -234,29 +231,31 @@ where
|
|||||||
/// Match single space or tab.
|
/// Match single space or tab.
|
||||||
///
|
///
|
||||||
/// In org-mode syntax, spaces and tabs are often (but not always!) interchangeable.
|
/// In org-mode syntax, spaces and tabs are often (but not always!) interchangeable.
|
||||||
pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> {
|
pub(crate) fn org_space(input: OrgSource<'_>) -> Res<OrgSource<'_>, char> {
|
||||||
one_of(" \t")(input)
|
one_of(" \t")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Matches a single space, tab, line ending, or end of file.
|
/// Matches a single space, tab, line ending, or end of file.
|
||||||
///
|
///
|
||||||
/// In org-mode syntax there are often delimiters that could be any whitespace at all or the end of file.
|
/// In org-mode syntax there are often delimiters that could be any whitespace at all or the end of file.
|
||||||
pub(crate) fn org_space_or_line_ending<'s>(
|
pub(crate) fn org_space_or_line_ending(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> {
|
||||||
input: OrgSource<'s>,
|
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
||||||
alt((recognize(org_space), org_line_ending))(input)
|
alt((recognize(org_space), org_line_ending))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Match a line break or the end of the file.
|
/// Match a line break or the end of the file.
|
||||||
///
|
///
|
||||||
/// In org-mode syntax, the end of the file can serve the same purpose as a line break syntactically.
|
/// In org-mode syntax, the end of the file can serve the same purpose as a line break syntactically.
|
||||||
pub(crate) fn org_line_ending<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
pub(crate) fn org_line_ending(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> {
|
||||||
alt((line_ending, eof))(input)
|
alt((line_ending, eof))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Match the whitespace at the beginning of a line and give it an indentation level.
|
/// Match the whitespace at the beginning of a line and give it an indentation level.
|
||||||
pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
|
#[cfg_attr(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
feature = "tracing",
|
||||||
|
tracing::instrument(ret, level = "debug", skip(context))
|
||||||
|
)]
|
||||||
|
pub(crate) fn indentation_level<'s>(
|
||||||
|
context: RefContext<'_, '_, '_, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, (IndentationLevel, OrgSource<'s>)> {
|
) -> Res<OrgSource<'s>, (IndentationLevel, OrgSource<'s>)> {
|
||||||
let (remaining, leading_whitespace) = space0(input)?;
|
let (remaining, leading_whitespace) = space0(input)?;
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ use super::Object;
|
|||||||
pub enum AffiliatedKeywordValue<'s> {
|
pub enum AffiliatedKeywordValue<'s> {
|
||||||
SingleString(&'s str),
|
SingleString(&'s str),
|
||||||
ListOfStrings(Vec<&'s str>),
|
ListOfStrings(Vec<&'s str>),
|
||||||
ListOfListsOfObjects(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
|
OptionalPair {
|
||||||
|
optval: Option<&'s str>,
|
||||||
|
val: &'s str,
|
||||||
|
},
|
||||||
|
ObjectTree(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -15,7 +19,7 @@ pub struct AffiliatedKeyword<'s> {
|
|||||||
pub value: AffiliatedKeywordValue<'s>,
|
pub value: AffiliatedKeywordValue<'s>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct AffiliatedKeywords<'s> {
|
pub struct AffiliatedKeywords<'s> {
|
||||||
pub(crate) keywords: BTreeMap<String, AffiliatedKeywordValue<'s>>,
|
pub(crate) keywords: BTreeMap<String, AffiliatedKeywordValue<'s>>,
|
||||||
}
|
}
|
||||||
@@ -23,11 +27,3 @@ pub struct AffiliatedKeywords<'s> {
|
|||||||
pub trait GetAffiliatedKeywords<'s> {
|
pub trait GetAffiliatedKeywords<'s> {
|
||||||
fn get_affiliated_keywords<'a>(&'a self) -> &'a AffiliatedKeywords<'s>;
|
fn get_affiliated_keywords<'a>(&'a self) -> &'a AffiliatedKeywords<'s>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Default for AffiliatedKeywords<'s> {
|
|
||||||
fn default() -> Self {
|
|
||||||
AffiliatedKeywords {
|
|
||||||
keywords: BTreeMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,10 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use super::remove_trailing::RemoveTrailing;
|
||||||
use super::Element;
|
use super::Element;
|
||||||
use super::GetStandardProperties;
|
|
||||||
use super::NodeProperty;
|
use super::NodeProperty;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
|
use super::PostBlank;
|
||||||
use super::StandardProperties;
|
use super::StandardProperties;
|
||||||
use super::Timestamp;
|
use super::Timestamp;
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ pub struct Heading<'s> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Section<'s> {
|
pub struct Section<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,41 +57,108 @@ pub enum TodoKeywordType {
|
|||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
|
|
||||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
|
||||||
match self {
|
|
||||||
DocumentElement::Heading(inner) => inner,
|
|
||||||
DocumentElement::Section(inner) => inner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Document<'s> {
|
impl<'s> StandardProperties<'s> for Document<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
let post_blank = self.get_post_blank();
|
||||||
|
let mut content_lines = self
|
||||||
|
.source
|
||||||
|
.split_inclusive('\n')
|
||||||
|
.remove_trailing(post_blank);
|
||||||
|
let first_line = content_lines.next();
|
||||||
|
let last_line = content_lines.last().or(first_line);
|
||||||
|
match (first_line, last_line) {
|
||||||
|
(None, None) => None,
|
||||||
|
(None, Some(_)) | (Some(_), None) => unreachable!(),
|
||||||
|
(Some(first_line), Some(last_line)) => {
|
||||||
|
let first_line_offset = first_line.as_ptr() as usize;
|
||||||
|
let last_line_offset = last_line.as_ptr() as usize + last_line.len();
|
||||||
|
let source_offset = self.source.as_ptr() as usize;
|
||||||
|
debug_assert!(super::lesser_element::is_slice_of(self.source, first_line));
|
||||||
|
debug_assert!(super::lesser_element::is_slice_of(self.source, last_line));
|
||||||
|
Some(
|
||||||
|
&self.source[(first_line_offset - source_offset)
|
||||||
|
..(last_line_offset - first_line_offset)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.into_iter()
|
||||||
|
.last()
|
||||||
|
.map(|child| child.get_post_blank())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Section<'s> {
|
impl<'s> StandardProperties<'s> for Section<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Heading<'s> {
|
impl<'s> StandardProperties<'s> for Heading<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
let first_child = self.children.first();
|
||||||
|
let last_child = self.children.last();
|
||||||
|
match (first_child, last_child) {
|
||||||
|
(None, None) => None,
|
||||||
|
(None, Some(_)) | (Some(_), None) => unreachable!(),
|
||||||
|
(Some(first_child), Some(last_child)) => {
|
||||||
|
let first_child_offset = first_child.get_source().as_ptr() as usize;
|
||||||
|
let last_child_offset = {
|
||||||
|
let last_child_source = last_child.get_source();
|
||||||
|
last_child_source.as_ptr() as usize + last_child_source.len()
|
||||||
|
};
|
||||||
|
let source_offset = self.source.as_ptr() as usize;
|
||||||
|
debug_assert!(super::lesser_element::is_slice_of(
|
||||||
|
self.source,
|
||||||
|
first_child.get_source()
|
||||||
|
));
|
||||||
|
debug_assert!(super::lesser_element::is_slice_of(
|
||||||
|
self.source,
|
||||||
|
last_child.get_source()
|
||||||
|
));
|
||||||
|
Some(
|
||||||
|
&self.source[(first_child_offset - source_offset)
|
||||||
|
..(last_child_offset - first_child_offset)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.children
|
||||||
|
.last()
|
||||||
|
.map(|child| child.get_post_blank())
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Heading<'s> {
|
impl<'s> Heading<'s> {
|
||||||
pub fn get_raw_value(&self) -> String {
|
pub fn get_raw_value(&self) -> String {
|
||||||
// TODO: I think this could just return a string slice instead of an owned string.
|
// TODO: I think this could just return a string slice instead of an owned string.
|
||||||
let title_source: String = self
|
let title_source: String = self.title.iter().map(|obj| obj.get_source()).collect();
|
||||||
.title
|
|
||||||
.iter()
|
|
||||||
.map(|obj| obj.get_standard_properties().get_source())
|
|
||||||
.collect();
|
|
||||||
title_source
|
title_source
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,11 +171,14 @@ impl<'s> Heading<'s> {
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.flat_map(|section| section.children.iter())
|
.flat_map(|section| section.children.iter())
|
||||||
.take(1)
|
.take_while(|element| {
|
||||||
.filter_map(|element| match element {
|
matches!(element, Element::Planning(_) | Element::PropertyDrawer(_))
|
||||||
|
})
|
||||||
|
.find_map(|element| match element {
|
||||||
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
|
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
|
.into_iter()
|
||||||
.flat_map(|property_drawer| property_drawer.children.iter())
|
.flat_map(|property_drawer| property_drawer.children.iter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,10 +190,8 @@ impl<'s> Document<'s> {
|
|||||||
.iter()
|
.iter()
|
||||||
.flat_map(|zeroth_section| zeroth_section.children.iter());
|
.flat_map(|zeroth_section| zeroth_section.children.iter());
|
||||||
let property_drawer = zeroth_section_children
|
let property_drawer = zeroth_section_children
|
||||||
.take_while(|element| match element {
|
.take_while(|element| {
|
||||||
Element::Comment(_) => true,
|
matches!(element, Element::Comment(_) | Element::PropertyDrawer(_))
|
||||||
Element::PropertyDrawer(_) => true,
|
|
||||||
_ => false,
|
|
||||||
})
|
})
|
||||||
.find_map(|element| match element {
|
.find_map(|element| match element {
|
||||||
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
|
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
|
||||||
@@ -131,3 +202,26 @@ impl<'s> Document<'s> {
|
|||||||
.flat_map(|property_drawer| property_drawer.children.iter())
|
.flat_map(|property_drawer| property_drawer.children.iter())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'s> StandardProperties<'s> for DocumentElement<'s> {
|
||||||
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
|
match self {
|
||||||
|
DocumentElement::Heading(inner) => inner.get_source(),
|
||||||
|
DocumentElement::Section(inner) => inner.get_source(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
match self {
|
||||||
|
DocumentElement::Heading(inner) => inner.get_contents(),
|
||||||
|
DocumentElement::Section(inner) => inner.get_contents(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
match self {
|
||||||
|
DocumentElement::Heading(inner) => inner.get_post_blank(),
|
||||||
|
DocumentElement::Section(inner) => inner.get_post_blank(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ 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;
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Element<'s> {
|
pub enum Element<'s> {
|
||||||
Paragraph(Paragraph<'s>),
|
Paragraph(Paragraph<'s>),
|
||||||
@@ -54,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)]
|
||||||
@@ -81,6 +82,8 @@ pub struct DynamicBlock<'s> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FootnoteDefinition<'s> {
|
pub struct FootnoteDefinition<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub contents: Option<&'s str>,
|
||||||
|
pub post_blank: Option<&'s str>,
|
||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub label: &'s str,
|
pub label: &'s str,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
@@ -131,72 +134,172 @@ impl<'s> StandardProperties<'s> for PlainList<'s> {
|
|||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for PlainListItem<'s> {
|
impl<'s> StandardProperties<'s> for PlainListItem<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for CenterBlock<'s> {
|
impl<'s> StandardProperties<'s> for CenterBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
|
impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
|
impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
|
impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
|
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Drawer<'s> {
|
impl<'s> StandardProperties<'s> for Drawer<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
|
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for NodeProperty<'s> {
|
impl<'s> StandardProperties<'s> for NodeProperty<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Table<'s> {
|
impl<'s> StandardProperties<'s> for Table<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for TableRow<'s> {
|
impl<'s> StandardProperties<'s> for TableRow<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> PlainListItem<'s> {
|
impl<'s> PlainListItem<'s> {
|
||||||
|
|||||||
@@ -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,6 +35,7 @@ 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)]
|
||||||
@@ -59,7 +79,7 @@ pub struct ExampleBlock<'s> {
|
|||||||
pub retain_labels: RetainLabels,
|
pub retain_labels: RetainLabels,
|
||||||
pub use_labels: bool,
|
pub use_labels: bool,
|
||||||
pub label_format: Option<&'s str>,
|
pub label_format: Option<&'s str>,
|
||||||
pub contents: String,
|
pub value: &'s str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -68,7 +88,7 @@ pub struct ExportBlock<'s> {
|
|||||||
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
pub affiliated_keywords: AffiliatedKeywords<'s>,
|
||||||
pub export_type: Option<&'s str>,
|
pub export_type: Option<&'s str>,
|
||||||
pub data: Option<&'s str>,
|
pub data: Option<&'s str>,
|
||||||
pub contents: String,
|
pub value: &'s str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -83,7 +103,7 @@ pub struct SrcBlock<'s> {
|
|||||||
pub retain_labels: RetainLabels,
|
pub retain_labels: RetainLabels,
|
||||||
pub use_labels: bool,
|
pub use_labels: bool,
|
||||||
pub label_format: Option<&'s str>,
|
pub label_format: Option<&'s str>,
|
||||||
pub contents: String,
|
pub value: &'s str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -134,6 +154,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)]
|
||||||
@@ -152,6 +173,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,13 +191,13 @@ impl<'s> Paragraph<'s> {
|
|||||||
/// Generate a paragraph of the passed in text with no additional properties.
|
/// Generate a paragraph of the passed in text with no additional properties.
|
||||||
///
|
///
|
||||||
/// This is used for elements that support an "empty" content like greater blocks.
|
/// This is used for elements that support an "empty" content like greater blocks.
|
||||||
pub(crate) fn of_text(input: &'s str) -> Self {
|
pub(crate) fn of_text(source: &'s str, body: &'s str) -> Self {
|
||||||
let mut objects = Vec::with_capacity(1);
|
|
||||||
objects.push(Object::PlainText(PlainText { source: input }));
|
|
||||||
Paragraph {
|
Paragraph {
|
||||||
source: input,
|
source,
|
||||||
|
contents: None, // TODO
|
||||||
|
post_blank: None, // TODO
|
||||||
affiliated_keywords: AffiliatedKeywords::default(),
|
affiliated_keywords: AffiliatedKeywords::default(),
|
||||||
children: objects,
|
children: vec![Object::PlainText(PlainText { source: body })],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,92 +206,236 @@ impl<'s> StandardProperties<'s> for Paragraph<'s> {
|
|||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
self.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
self.post_blank
|
||||||
|
.map(|text| text.lines().count())
|
||||||
|
.unwrap_or(0)
|
||||||
|
.try_into()
|
||||||
|
.expect("Too much post-blank to fit into a PostBlank.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for TableCell<'s> {
|
impl<'s> StandardProperties<'s> for TableCell<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Comment<'s> {
|
impl<'s> StandardProperties<'s> for Comment<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
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> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
|
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
|
impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for ExportBlock<'s> {
|
impl<'s> StandardProperties<'s> for ExportBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl<'s> StandardProperties<'s> for SrcBlock<'s> {
|
impl<'s> StandardProperties<'s> for SrcBlock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Clock<'s> {
|
impl<'s> StandardProperties<'s> for Clock<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for DiarySexp<'s> {
|
impl<'s> StandardProperties<'s> for DiarySexp<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Planning<'s> {
|
impl<'s> StandardProperties<'s> for Planning<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
|
impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
|
impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for Keyword<'s> {
|
impl<'s> StandardProperties<'s> for Keyword<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
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> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_post_blank(&self) -> PostBlank {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
|
impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
|
||||||
fn get_source<'b>(&'b self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_contents<'b>(&'b self) -> Option<&'s str> {
|
||||||
|
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> {
|
||||||
@@ -296,6 +462,13 @@ impl<'s> FixedWidthArea<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'s> ExampleBlock<'s> {
|
||||||
|
/// Gets the contents of the lesser block, handling the escaping of lines with leading commas.
|
||||||
|
pub fn get_value(&self) -> Cow<'s, str> {
|
||||||
|
lesser_block_content(self.value).expect("This parser should never fail.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'s> ExportBlock<'s> {
|
impl<'s> ExportBlock<'s> {
|
||||||
/// Gets the export type capitalized.
|
/// Gets the export type capitalized.
|
||||||
///
|
///
|
||||||
@@ -303,6 +476,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> {
|
||||||
@@ -376,3 +561,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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user