Compare commits
90 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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"
|
||||||
|
|||||||
12
Makefile
12
Makefile
@@ -37,6 +37,18 @@ 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: clippyfix
|
||||||
|
clippyfix:
|
||||||
|
> cargo clippy --fix --lib -p organic --all-features
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||||
|
|||||||
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 cargo-cache
|
||||||
|
|
||||||
|
# NOTE: This target will write to folders underneath the git-root
|
||||||
|
.PHONY: run
|
||||||
|
run: build
|
||||||
|
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
|
shell: build
|
||||||
|
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry $(IMAGE_NAME)
|
||||||
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
|
||||||
@@ -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
|
||||||
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"))]
|
||||||
@@ -108,10 +106,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 +192,6 @@ struct ResultLayer {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SingleFileResult {
|
struct SingleFileResult {
|
||||||
name: String,
|
name: String,
|
||||||
file_path: PathBuf,
|
|
||||||
status: TestStatus,
|
status: TestStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +221,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 {
|
||||||
|
|||||||
@@ -16,7 +16,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 +56,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 +69,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 +97,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 +104,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 +177,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 +185,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)?
|
||||||
@@ -272,10 +267,7 @@ pub(crate) fn compare_property_set_of_quoted_string<
|
|||||||
.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.as_str()).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 +280,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 +468,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 +497,133 @@ 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) {
|
||||||
|
let kw_e = kw_e.as_list()?;
|
||||||
// Iterate the outer lists
|
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
|
||||||
for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) {
|
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
|
||||||
let mut middle_value = value.as_list()?.iter();
|
if let Some(or) = &kw_r.0 {
|
||||||
// First element of middle list is the mandatory value (the value past the colon).
|
// if optional value
|
||||||
let mandatory_value = middle_value.next();
|
let mut kw_e = kw_e.iter();
|
||||||
let mandatory_value = match mandatory_value {
|
// First element is a list representing the mandatory value.
|
||||||
Some(mandatory_value) => mandatory_value,
|
if let Some(val_e) = kw_e.next() {
|
||||||
None => {
|
let el = val_e.as_list()?;
|
||||||
|
if el.len() != kw_r.1.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, kw_e, kw_r
|
||||||
));
|
));
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||||
}
|
}
|
||||||
};
|
for (e, r) in el.iter().zip(kw_r.1.iter()) {
|
||||||
|
|
||||||
// Compare optional value
|
|
||||||
if let Some(rust_optional) = rust_optional {
|
|
||||||
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
|
|
||||||
if rust_optional.len() != middle_value.len() {
|
|
||||||
let this_status = DiffStatus::Bad;
|
|
||||||
let message = Some(format!(
|
|
||||||
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}",
|
|
||||||
emacs_field,
|
|
||||||
middle_value.len(),
|
|
||||||
rust_optional.len(),
|
|
||||||
rust_optional
|
|
||||||
));
|
|
||||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
|
||||||
}
|
|
||||||
for (e, r) in middle_value.zip(rust_optional) {
|
|
||||||
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() {
|
} else {
|
||||||
let diff_scope = artificial_diff_scope("optional value", child_status)?;
|
|
||||||
full_status.push(diff_scope);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
||||||
|
|||||||
@@ -227,7 +227,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 +330,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 {
|
||||||
@@ -426,15 +426,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 +490,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
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[allow(clippy::module_inception)]
|
||||||
mod compare;
|
mod compare;
|
||||||
mod compare_field;
|
mod compare_field;
|
||||||
mod diff;
|
mod diff;
|
||||||
|
|||||||
@@ -113,24 +113,21 @@ 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>> {
|
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let mut out: Vec<u8> = Vec::with_capacity(text.len());
|
let mut out: Vec<u8> = Vec::with_capacity(text.len());
|
||||||
if !text.starts_with(r#"""#) {
|
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 = ParseState::Normal;
|
||||||
for current_char in interior_text.bytes().into_iter() {
|
for current_char in interior_text.bytes() {
|
||||||
// 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), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => {
|
||||||
@@ -229,11 +226,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 +259,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 +340,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 +353,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,8 @@
|
|||||||
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;
|
||||||
@@ -53,8 +54,8 @@ pub(crate) fn compare_standard_properties<
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
|
pub(crate) 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();
|
||||||
@@ -89,9 +90,9 @@ pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
|||||||
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); // 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))?;
|
||||||
}
|
}
|
||||||
@@ -112,21 +113,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 +140,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 +164,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(
|
||||||
emacs: &'b Token<'s>,
|
emacs: &Token<'_>,
|
||||||
key: &'x str,
|
key: &str,
|
||||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
) -> Result<Option<String>, 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 +291,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 +321,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 +366,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,20 @@ 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>),
|
#[allow(dead_code)]
|
||||||
Nom(I, ErrorKind),
|
Text(String),
|
||||||
|
Static(&'static str),
|
||||||
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 +24,20 @@ 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> {
|
impl From<String> for CustomError {
|
||||||
fn from(value: Box<dyn std::error::Error>) -> Self {
|
fn from(value: String) -> Self {
|
||||||
CustomError::BoxedError(value)
|
CustomError::Text(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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
165
src/parser/bullshitium.rs
Normal file
165
src/parser/bullshitium.rs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
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::AffiliatedKeywords;
|
||||||
|
use crate::types::Object;
|
||||||
|
use crate::types::Paragraph;
|
||||||
|
use crate::types::PlainText;
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
source: input.get_until(remaining).into(),
|
||||||
|
affiliated_keywords: AffiliatedKeywords::default(),
|
||||||
|
children: vec![Object::PlainText(PlainText {
|
||||||
|
source: 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 {
|
||||||
|
source: input.get_until(remaining).into(),
|
||||||
|
affiliated_keywords: AffiliatedKeywords::default(),
|
||||||
|
children: vec![Object::PlainText(PlainText {
|
||||||
|
source: 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, ()))
|
||||||
|
}
|
||||||
@@ -137,7 +137,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 +191,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,6 +205,7 @@ 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;
|
||||||
@@ -219,7 +220,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 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,
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -104,6 +103,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 +119,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;
|
||||||
|
|
||||||
@@ -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;
|
||||||
@@ -48,9 +47,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)?;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -54,9 +53,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 +78,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)?;
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -58,23 +57,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"))]
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -125,7 +123,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,16 +136,27 @@ 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;
|
||||||
@@ -165,7 +174,7 @@ 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) =
|
||||||
@@ -202,7 +211,7 @@ 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.");
|
||||||
|
|||||||
@@ -20,7 +20,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;
|
||||||
@@ -176,9 +175,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;
|
||||||
@@ -61,10 +60,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 +231,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)?;
|
||||||
@@ -288,7 +287,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(_) => CustomError::Text(err.to_string()),
|
||||||
|
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(_) => CustomError::Text(err.to_string()),
|
||||||
|
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(_) => CustomError::Text(err.to_string()),
|
||||||
|
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,9 +155,7 @@ 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()
|
.into_iter()
|
||||||
.filter_map(|ast_node| {
|
.filter_map(|ast_node| {
|
||||||
@@ -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;
|
||||||
@@ -131,9 +130,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 +180,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;
|
||||||
@@ -125,9 +124,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 +184,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,21 @@ 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: "",
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
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 {
|
||||||
@@ -150,45 +136,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 +200,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 +215,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 {
|
||||||
@@ -127,7 +122,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;
|
||||||
@@ -209,9 +208,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 +282,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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -295,7 +291,7 @@ 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,
|
contents,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -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)
|
||||||
@@ -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;
|
||||||
|
|
||||||
@@ -675,7 +668,9 @@ pub(crate) fn content<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
|
let (remain, (pre_escape_whitespace, line)) = content_line(remaining)?;
|
||||||
pre_escape_whitespace.map(|val| ret.push_str(Into::<&str>::into(val)));
|
if let Some(val) = pre_escape_whitespace {
|
||||||
|
ret.push_str(Into::<&str>::into(val));
|
||||||
|
}
|
||||||
ret.push_str(line.into());
|
ret.push_str(line.into());
|
||||||
remaining = remain;
|
remaining = remain;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
|
||||||
))));
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,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 +108,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,9 +10,6 @@ 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, PartialEq)]
|
||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ 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;
|
||||||
@@ -104,7 +104,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 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.");
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -95,9 +94,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 +261,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 +324,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 +338,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 +414,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,6 +625,7 @@ 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;
|
||||||
@@ -560,7 +637,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 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_standard_properties().get_source(), "1.");
|
||||||
@@ -572,7 +649,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 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_standard_properties().get_source(), "1. foo");
|
||||||
@@ -639,7 +716,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 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");
|
||||||
@@ -667,7 +744,7 @@ 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");
|
||||||
@@ -700,7 +777,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 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");
|
||||||
@@ -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,6 +137,7 @@ 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;
|
||||||
@@ -157,10 +151,14 @@ 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_standard_properties().get_source(),
|
result.get_standard_properties().get_source(),
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -52,9 +51,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
Err(nom::Err::Error(CustomError::Static("NoRadioLink")))
|
||||||
"NoRadioLink".into(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
@@ -98,9 +95,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",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -185,15 +182,14 @@ mod tests {
|
|||||||
fn plain_text_radio_target() {
|
fn plain_text_radio_target() {
|
||||||
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!"),
|
||||||
@@ -212,7 +208,7 @@ mod tests {
|
|||||||
&Object::RadioLink(RadioLink {
|
&Object::RadioLink(RadioLink {
|
||||||
source: "bar ",
|
source: "bar ",
|
||||||
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
||||||
path: "bar".into()
|
path: "bar"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -224,16 +220,15 @@ mod tests {
|
|||||||
source: "*bar*",
|
source: "*bar*",
|
||||||
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!"),
|
||||||
@@ -255,7 +250,7 @@ mod tests {
|
|||||||
source: "*bar* ",
|
source: "*bar* ",
|
||||||
children: vec![Object::PlainText(PlainText { source: "bar" })]
|
children: vec![Object::PlainText(PlainText { source: "bar" })]
|
||||||
})],
|
})],
|
||||||
path: "*bar* ".into()
|
path: "*bar* "
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -139,14 +138,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 +162,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 +250,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 +478,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(),
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,12 +65,12 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
|
|||||||
},
|
},
|
||||||
)(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, _trailing_ws) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
@@ -121,12 +121,12 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
|
|||||||
!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, _trailing_ws) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
|
|||||||
@@ -26,7 +26,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 +38,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)?;
|
||||||
@@ -137,7 +136,7 @@ fn script_body<'b, 'g, 'r, 's>(
|
|||||||
ScriptBody::Braceless(body.into())
|
ScriptBody::Braceless(body.into())
|
||||||
}),
|
}),
|
||||||
map(parser_with_context!(script_with_braces)(context), |body| {
|
map(parser_with_context!(script_with_braces)(context), |body| {
|
||||||
ScriptBody::WithBraces(body.into())
|
ScriptBody::WithBraces(body)
|
||||||
}),
|
}),
|
||||||
map(
|
map(
|
||||||
parser_with_context!(script_with_parenthesis)(context),
|
parser_with_context!(script_with_parenthesis)(context),
|
||||||
@@ -175,7 +174,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 +182,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))
|
||||||
@@ -232,9 +231,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 +281,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) =
|
||||||
|
|||||||
@@ -34,7 +34,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;
|
||||||
@@ -183,13 +182,13 @@ fn code<'b, 'g, 'r, 's>(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
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<OrgSource<'s>, Vec<Object<'s>>>
|
||||||
+ 'c {
|
+ '_ {
|
||||||
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
|
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,9 +233,9 @@ 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.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,13 +245,13 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
|||||||
Ok((remaining, children))
|
Ok((remaining, children))
|
||||||
}
|
}
|
||||||
|
|
||||||
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>>
|
||||||
+ 'c {
|
+ '_ {
|
||||||
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
|
move |context, input: OrgSource<'_>| _text_markup_string(context, input, marker_symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,9 +289,9 @@ 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.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,9 +320,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 +342,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 +359,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(
|
||||||
@@ -498,9 +494,9 @@ 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.",
|
||||||
))));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -486,7 +486,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 +590,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 {
|
||||||
@@ -132,9 +128,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 +149,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 +192,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 +200,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 +224,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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -101,11 +101,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 +120,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),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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>),
|
||||||
|
|||||||
@@ -170,12 +170,10 @@ impl<'s> Paragraph<'s> {
|
|||||||
///
|
///
|
||||||
/// 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(input: &'s str) -> Self {
|
||||||
let mut objects = Vec::with_capacity(1);
|
|
||||||
objects.push(Object::PlainText(PlainText { source: input }));
|
|
||||||
Paragraph {
|
Paragraph {
|
||||||
source: input,
|
source: input,
|
||||||
affiliated_keywords: AffiliatedKeywords::default(),
|
affiliated_keywords: AffiliatedKeywords::default(),
|
||||||
children: objects,
|
children: vec![Object::PlainText(PlainText { source: input })],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ pub struct Superscript<'s> {
|
|||||||
pub children: Vec<Object<'s>>,
|
pub children: Vec<Object<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Perhaps there is an optimization of converting to unix time we can do to shrink this struct. (ref: clippy::large_enum_variant on Element)
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Timestamp<'s> {
|
pub struct Timestamp<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
@@ -315,7 +316,7 @@ pub struct Minute(MinuteInner);
|
|||||||
|
|
||||||
impl Year {
|
impl Year {
|
||||||
// TODO: Make a real error type instead of a boxed any error.
|
// TODO: Make a real error type instead of a boxed any error.
|
||||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(source: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let year = source.parse::<YearInner>()?;
|
let year = source.parse::<YearInner>()?;
|
||||||
Ok(Year(year))
|
Ok(Year(year))
|
||||||
}
|
}
|
||||||
@@ -327,9 +328,9 @@ impl Year {
|
|||||||
|
|
||||||
impl Month {
|
impl Month {
|
||||||
// TODO: Make a real error type instead of a boxed any error.
|
// TODO: Make a real error type instead of a boxed any error.
|
||||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(source: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let month = source.parse::<MonthInner>()?;
|
let month = source.parse::<MonthInner>()?;
|
||||||
if month < 1 || month > 12 {
|
if !(1..=12).contains(&month) {
|
||||||
Err("Month exceeds possible range.")?;
|
Err("Month exceeds possible range.")?;
|
||||||
}
|
}
|
||||||
Ok(Month(month))
|
Ok(Month(month))
|
||||||
@@ -342,9 +343,9 @@ impl Month {
|
|||||||
|
|
||||||
impl DayOfMonth {
|
impl DayOfMonth {
|
||||||
// TODO: Make a real error type instead of a boxed any error.
|
// TODO: Make a real error type instead of a boxed any error.
|
||||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(source: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let day_of_month = source.parse::<DayOfMonthInner>()?;
|
let day_of_month = source.parse::<DayOfMonthInner>()?;
|
||||||
if day_of_month < 1 || day_of_month > 31 {
|
if !(1..=31).contains(&day_of_month) {
|
||||||
Err("Day of month exceeds possible range.")?;
|
Err("Day of month exceeds possible range.")?;
|
||||||
}
|
}
|
||||||
Ok(DayOfMonth(day_of_month))
|
Ok(DayOfMonth(day_of_month))
|
||||||
@@ -357,7 +358,7 @@ impl DayOfMonth {
|
|||||||
|
|
||||||
impl Hour {
|
impl Hour {
|
||||||
// TODO: Make a real error type instead of a boxed any error.
|
// TODO: Make a real error type instead of a boxed any error.
|
||||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(source: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let hour = source.parse::<HourInner>()?;
|
let hour = source.parse::<HourInner>()?;
|
||||||
if hour > 23 {
|
if hour > 23 {
|
||||||
Err("Hour exceeds possible range.")?;
|
Err("Hour exceeds possible range.")?;
|
||||||
@@ -372,7 +373,7 @@ impl Hour {
|
|||||||
|
|
||||||
impl Minute {
|
impl Minute {
|
||||||
// TODO: Make a real error type instead of a boxed any error.
|
// TODO: Make a real error type instead of a boxed any error.
|
||||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new(source: &str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let minute = source.parse::<MinuteInner>()?;
|
let minute = source.parse::<MinuteInner>()?;
|
||||||
if minute > 59 {
|
if minute > 59 {
|
||||||
Err("Minute exceeds possible range.")?;
|
Err("Minute exceeds possible range.")?;
|
||||||
@@ -731,21 +732,21 @@ impl<'s> RegularLink<'s> {
|
|||||||
/// Coalesce whitespace if the raw_link contains line breaks.
|
/// Coalesce whitespace if the raw_link contains line breaks.
|
||||||
///
|
///
|
||||||
/// This corresponds to the output you would get from the upstream emacs org-mode AST.
|
/// This corresponds to the output you would get from the upstream emacs org-mode AST.
|
||||||
pub fn get_raw_link<'b>(&'b self) -> Cow<'b, str> {
|
pub fn get_raw_link(&self) -> Cow<'_, str> {
|
||||||
coalesce_whitespace_if_line_break(&self.raw_link)
|
coalesce_whitespace_if_line_break(&self.raw_link)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Coalesce whitespace if the path contains line breaks.
|
/// Coalesce whitespace if the path contains line breaks.
|
||||||
///
|
///
|
||||||
/// This corresponds to the output you would get from the upstream emacs org-mode AST.
|
/// This corresponds to the output you would get from the upstream emacs org-mode AST.
|
||||||
pub fn get_path<'b>(&'b self) -> Cow<'b, str> {
|
pub fn get_path(&self) -> Cow<'_, str> {
|
||||||
coalesce_whitespace_if_line_break(&self.path)
|
coalesce_whitespace_if_line_break(&self.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Coalesce whitespace if the search_option contains line breaks.
|
/// Coalesce whitespace if the search_option contains line breaks.
|
||||||
///
|
///
|
||||||
/// This corresponds to the output you would get from the upstream emacs org-mode AST.
|
/// This corresponds to the output you would get from the upstream emacs org-mode AST.
|
||||||
pub fn get_search_option<'b>(&'b self) -> Option<Cow<'b, str>> {
|
pub fn get_search_option(&self) -> Option<Cow<'_, str>> {
|
||||||
self.search_option
|
self.search_option
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|search_option| coalesce_whitespace_if_line_break(search_option.borrow()))
|
.map(|search_option| coalesce_whitespace_if_line_break(search_option.borrow()))
|
||||||
@@ -782,7 +783,7 @@ impl<'s> OrgMacro<'s> {
|
|||||||
pub fn get_args<'b>(&'b self) -> impl Iterator<Item = Cow<'s, str>> + 'b {
|
pub fn get_args<'b>(&'b self) -> impl Iterator<Item = Cow<'s, str>> + 'b {
|
||||||
self.args
|
self.args
|
||||||
.iter()
|
.iter()
|
||||||
.map(|arg| coalesce_whitespace_escaped('\\', |c| ",".contains(c))(*arg))
|
.map(|arg| coalesce_whitespace_escaped('\\', |c| ",".contains(c))(arg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::borrow::Cow;
|
|||||||
/// Removes all whitespace from a string if any line breaks are present.
|
/// Removes all whitespace from a string if any line breaks are present.
|
||||||
///
|
///
|
||||||
/// Example: "foo bar" => "foo bar" but "foo \n bar" => "foobar".
|
/// Example: "foo bar" => "foo bar" but "foo \n bar" => "foobar".
|
||||||
pub(crate) fn remove_whitespace_if_line_break<'s>(input: &'s str) -> Cow<'s, str> {
|
pub(crate) fn remove_whitespace_if_line_break(input: &str) -> Cow<'_, str> {
|
||||||
let mut state = RemoveWhitespaceIfLineBreakState::Normal;
|
let mut state = RemoveWhitespaceIfLineBreakState::Normal;
|
||||||
for (offset, c) in input.char_indices() {
|
for (offset, c) in input.char_indices() {
|
||||||
match (&mut state, c) {
|
match (&mut state, c) {
|
||||||
@@ -49,7 +49,7 @@ enum RemoveWhitespaceIfLineBreakState {
|
|||||||
/// Removes all line breaks from a string
|
/// Removes all line breaks from a string
|
||||||
///
|
///
|
||||||
/// Example: "foo bar" => "foo bar" but "foo \n bar" => "foo bar".
|
/// Example: "foo bar" => "foo bar" but "foo \n bar" => "foo bar".
|
||||||
pub(crate) fn remove_line_break<'s>(input: &'s str) -> Cow<'s, str> {
|
pub(crate) fn remove_line_break(input: &str) -> Cow<'_, str> {
|
||||||
let mut state = RemoveLineBreakState::Normal;
|
let mut state = RemoveLineBreakState::Normal;
|
||||||
for (offset, c) in input.char_indices() {
|
for (offset, c) in input.char_indices() {
|
||||||
match (&mut state, c) {
|
match (&mut state, c) {
|
||||||
@@ -79,7 +79,7 @@ enum RemoveLineBreakState {
|
|||||||
/// Removes all whitespace from a string if any line breaks are present.
|
/// Removes all whitespace from a string if any line breaks are present.
|
||||||
///
|
///
|
||||||
/// Example: "foo bar" => "foo bar" but "foo \n bar" => "foo bar".
|
/// Example: "foo bar" => "foo bar" but "foo \n bar" => "foo bar".
|
||||||
pub(crate) fn coalesce_whitespace_if_line_break<'s>(input: &'s str) -> Cow<'s, str> {
|
pub(crate) fn coalesce_whitespace_if_line_break(input: &str) -> Cow<'_, str> {
|
||||||
let mut state = CoalesceWhitespaceIfLineBreakState::Normal;
|
let mut state = CoalesceWhitespaceIfLineBreakState::Normal;
|
||||||
for (offset, c) in input.char_indices() {
|
for (offset, c) in input.char_indices() {
|
||||||
match (&mut state, c) {
|
match (&mut state, c) {
|
||||||
@@ -120,8 +120,7 @@ pub(crate) fn coalesce_whitespace_if_line_break<'s>(input: &'s str) -> Cow<'s, s
|
|||||||
ret.push(c);
|
ret.push(c);
|
||||||
}
|
}
|
||||||
// Do nothing if preceding character was whitespace and this character also is whitespace.
|
// Do nothing if preceding character was whitespace and this character also is whitespace.
|
||||||
} else {
|
} else if c.is_ascii_whitespace() {
|
||||||
if c.is_ascii_whitespace() {
|
|
||||||
// Preceding character was not whitespace but this is.
|
// Preceding character was not whitespace but this is.
|
||||||
sub_loop_in_whitespace = true;
|
sub_loop_in_whitespace = true;
|
||||||
ret.push(' ');
|
ret.push(' ');
|
||||||
@@ -130,7 +129,6 @@ pub(crate) fn coalesce_whitespace_if_line_break<'s>(input: &'s str) -> Cow<'s, s
|
|||||||
ret.push(c);
|
ret.push(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !*in_whitespace {
|
if !*in_whitespace {
|
||||||
// If this line break was the start of whitespace then we need to inject a space character for it.
|
// If this line break was the start of whitespace then we need to inject a space character for it.
|
||||||
ret.push(' ');
|
ret.push(' ');
|
||||||
@@ -202,7 +200,7 @@ enum CoalesceWhitespaceIfLineBreakState {
|
|||||||
///
|
///
|
||||||
/// Example: "foo bar" => "foobar" and "foo \n bar" => "foobar".
|
/// Example: "foo bar" => "foobar" and "foo \n bar" => "foobar".
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn coalesce_whitespace<'s>(input: &'s str) -> Cow<'s, str> {
|
pub(crate) fn coalesce_whitespace(input: &str) -> Cow<'_, str> {
|
||||||
let mut state = CoalesceWhitespace::Normal;
|
let mut state = CoalesceWhitespace::Normal;
|
||||||
for (offset, c) in input.char_indices() {
|
for (offset, c) in input.char_indices() {
|
||||||
match (&mut state, c) {
|
match (&mut state, c) {
|
||||||
@@ -248,18 +246,18 @@ enum CoalesceWhitespace {
|
|||||||
/// Removes all whitespace from a string and handle escaping characters.
|
/// Removes all whitespace from a string and handle escaping characters.
|
||||||
///
|
///
|
||||||
/// Example: "foo bar" => "foobar" and "foo \n bar" => "foobar" but if the escape character is backslash and comma is an escapable character than "foo\,bar" becomes "foo,bar".
|
/// Example: "foo bar" => "foobar" and "foo \n bar" => "foobar" but if the escape character is backslash and comma is an escapable character than "foo\,bar" becomes "foo,bar".
|
||||||
pub(crate) fn coalesce_whitespace_escaped<'c, C: Fn(char) -> bool>(
|
pub(crate) fn coalesce_whitespace_escaped<C: Fn(char) -> bool>(
|
||||||
escape_character: char,
|
escape_character: char,
|
||||||
escapable_characters: C,
|
escapable_characters: C,
|
||||||
) -> impl for<'s> Fn(&'s str) -> Cow<'s, str> {
|
) -> impl for<'s> Fn(&'s str) -> Cow<'s, str> {
|
||||||
move |input| impl_coalesce_whitespace_escaped(input, escape_character, &escapable_characters)
|
move |input| impl_coalesce_whitespace_escaped(input, escape_character, &escapable_characters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_coalesce_whitespace_escaped<'s, C: Fn(char) -> bool>(
|
fn impl_coalesce_whitespace_escaped<C: Fn(char) -> bool>(
|
||||||
input: &'s str,
|
input: &str,
|
||||||
escape_character: char,
|
escape_character: char,
|
||||||
escapable_characters: C,
|
escapable_characters: C,
|
||||||
) -> Cow<'s, str> {
|
) -> Cow<'_, str> {
|
||||||
let mut state = CoalesceWhitespaceEscaped::Normal {
|
let mut state = CoalesceWhitespaceEscaped::Normal {
|
||||||
in_whitespace: false,
|
in_whitespace: false,
|
||||||
};
|
};
|
||||||
@@ -451,7 +449,7 @@ enum CoalesceWhitespaceEscaped {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_lowercase<'s>(input: &'s str) -> Cow<'s, str> {
|
pub(crate) fn to_lowercase(input: &str) -> Cow<'_, str> {
|
||||||
if input.chars().any(|c| !c.is_lowercase()) {
|
if input.chars().any(|c| !c.is_lowercase()) {
|
||||||
Cow::Owned(input.to_lowercase())
|
Cow::Owned(input.to_lowercase())
|
||||||
} else {
|
} else {
|
||||||
@@ -517,4 +515,25 @@ mod tests {
|
|||||||
assert!(matches!(output, Cow::Borrowed(_)));
|
assert!(matches!(output, Cow::Borrowed(_)));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_coalesce_whitespace_if_line_break() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
assert_eq!(coalesce_whitespace_if_line_break("foo bar"), "foo bar");
|
||||||
|
assert_eq!(coalesce_whitespace_if_line_break("foo \n bar"), "foo bar");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_whitespace_if_line_break() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
assert_eq!(remove_whitespace_if_line_break("foo bar"), "foo bar");
|
||||||
|
assert_eq!(remove_whitespace_if_line_break("foo \n bar"), "foobar");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_remove_line_break() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
assert_eq!(remove_line_break("foo bar"), "foo bar");
|
||||||
|
assert_eq!(remove_line_break("foo \n bar"), "foo bar");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#![cfg(feature = "compare")]
|
#![cfg(feature = "compare")]
|
||||||
|
|
||||||
#[feature(exit_status_error)]
|
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
||||||
let org_path = "{path}";
|
let org_path = "{path}";
|
||||||
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
||||||
organic::compare::run_anonymous_compare(org_contents.as_str()).await?;
|
assert!(organic::compare::run_anonymous_compare(org_contents.as_str()).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -14,12 +14,11 @@ async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
|||||||
async fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
async fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
||||||
let org_path = "{path}";
|
let org_path = "{path}";
|
||||||
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
||||||
let global_settings = {{
|
let global_settings = organic::settings::GlobalSettings {{
|
||||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
list_allow_alphabetical: true,
|
||||||
global_settings.list_allow_alphabetical = true;
|
..Default::default()
|
||||||
global_settings
|
|
||||||
}};
|
}};
|
||||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
|
assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -28,12 +27,11 @@ async fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
|||||||
async fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
async fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
||||||
let org_path = "{path}";
|
let org_path = "{path}";
|
||||||
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
||||||
let global_settings = {{
|
let global_settings = organic::settings::GlobalSettings {{
|
||||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
tab_width: 1,
|
||||||
global_settings.tab_width = 1;
|
..Default::default()
|
||||||
global_settings
|
|
||||||
}};
|
}};
|
||||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
|
assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -42,12 +40,11 @@ async fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
|||||||
async fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
async fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
||||||
let org_path = "{path}";
|
let org_path = "{path}";
|
||||||
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
||||||
let global_settings = {{
|
let global_settings = organic::settings::GlobalSettings {{
|
||||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
tab_width: 16,
|
||||||
global_settings.tab_width = 16;
|
..Default::default()
|
||||||
global_settings
|
|
||||||
}};
|
}};
|
||||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
|
assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}}
|
}}
|
||||||
|
|
||||||
@@ -56,11 +53,10 @@ async fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
|||||||
async fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
async fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
|
||||||
let org_path = "{path}";
|
let org_path = "{path}";
|
||||||
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
|
||||||
let global_settings = {{
|
let global_settings = organic::settings::GlobalSettings {{
|
||||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
odd_levels_only: organic::settings::HeadlineLevelFilter::Odd,
|
||||||
global_settings.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd;
|
..Default::default()
|
||||||
global_settings
|
|
||||||
}};
|
}};
|
||||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
|
assert!(organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?);
|
||||||
Ok(())
|
Ok(())
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user