Compare commits
152 Commits
dc9188dffc
...
v0.1.12
| 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 | ||
|
|
4b54f95087 | ||
|
|
17c2e9fefe | ||
|
|
d2d8c1ffcf | ||
|
|
b9c638c280 | ||
|
|
8ac8f9fe6e | ||
|
|
ddb3144e66 | ||
|
|
ad5efc4b0f | ||
|
|
2de33b8150 | ||
|
|
e1fde88a60 | ||
|
|
123da9cca3 | ||
|
|
c20e7b5f2f | ||
|
|
74a3512038 | ||
|
|
ff04c4a131 | ||
|
|
00611e05c2 | ||
|
|
3e7e54a1bd | ||
|
|
aa35d1dc03 | ||
|
|
92afdc0ea6 | ||
|
|
f43920fc7c | ||
|
|
dde4bc7920 | ||
|
|
3d68e1fd00 | ||
|
|
8271f6b44a | ||
|
|
8a26965e14 | ||
|
|
3927889e66 | ||
|
|
5ecd7b8bef | ||
|
|
b0b795d13b | ||
|
|
182c2737cd | ||
|
|
5f93cabff5 | ||
|
|
a1f8cbe079 | ||
|
|
d7e870cba1 | ||
|
|
591b5ed382 | ||
|
|
fd141762f0 | ||
|
|
d59bbfa7d2 | ||
|
|
1d9f91cdd2 | ||
|
|
68f8e04ee8 | ||
|
|
c039e0d62c | ||
|
|
e6e3783ec6 | ||
|
|
5ae19e455d | ||
|
|
f475754a71 | ||
|
|
767f44f94d | ||
|
|
1411aca7b5 | ||
|
|
9ccdcaac24 | ||
|
|
d1223dcdb7 | ||
|
|
59448a4f2c | ||
|
|
5136880532 | ||
|
|
8654cf5507 | ||
|
|
6ca4dc8ffc | ||
|
|
a6f36ba679 | ||
|
|
176e37874e | ||
|
|
3208a04f7a | ||
|
|
0d579263cb | ||
|
|
c022b30110 | ||
|
|
7da4e4a29b | ||
|
|
8bc942a26f | ||
|
|
dff7550038 | ||
|
|
c4edcb8c24 | ||
|
|
4a44d88461 | ||
|
|
efc6bd11d9 | ||
|
|
51429e3155 | ||
|
|
b1a0fa4acf | ||
|
|
aeb2b6fe68 | ||
|
|
6679db98a8 | ||
|
|
5363324bbf |
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
|
||||
@@ -137,7 +137,7 @@ spec:
|
||||
value: []
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
- name: run-image-all
|
||||
- name: run-image-tracing-compare
|
||||
taskRef:
|
||||
name: run-docker-image
|
||||
workspaces:
|
||||
@@ -152,6 +152,46 @@ spec:
|
||||
value: ["--no-default-features", "--features", "tracing,compare"]
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
- name: run-image-compare-foreign
|
||||
taskRef:
|
||||
name: run-docker-image
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: git-source
|
||||
- name: cargo-cache
|
||||
workspace: cargo-cache
|
||||
runAfter:
|
||||
- run-image-tracing-compare
|
||||
params:
|
||||
- name: args
|
||||
value:
|
||||
[
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
"compare,foreign_document_test",
|
||||
]
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
- name: run-image-all
|
||||
taskRef:
|
||||
name: run-docker-image
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: git-source
|
||||
- name: cargo-cache
|
||||
workspace: cargo-cache
|
||||
runAfter:
|
||||
- run-image-compare-foreign
|
||||
params:
|
||||
- name: args
|
||||
value:
|
||||
[
|
||||
"--no-default-features",
|
||||
"--features",
|
||||
"tracing,compare,foreign_document_test",
|
||||
]
|
||||
- name: docker-image
|
||||
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
|
||||
finally:
|
||||
- name: report-success
|
||||
when:
|
||||
|
||||
@@ -30,3 +30,10 @@ spec:
|
||||
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]+$"
|
||||
- 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]+$"
|
||||
|
||||
23
Cargo.toml
23
Cargo.toml
@@ -1,6 +1,8 @@
|
||||
# cargo-features = ["profile-rustflags"]
|
||||
|
||||
[package]
|
||||
name = "organic"
|
||||
version = "0.1.9"
|
||||
version = "0.1.12"
|
||||
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||
description = "An org-mode parser."
|
||||
edition = "2021"
|
||||
@@ -31,7 +33,14 @@ path = "src/lib.rs"
|
||||
path = "src/bin_compare.rs"
|
||||
required-features = ["compare"]
|
||||
|
||||
[[bin]]
|
||||
# This bin exists for development purposes only. The real target of this crate is the library.
|
||||
name = "foreign_document_test"
|
||||
path = "src/bin_foreign_document_test.rs"
|
||||
required-features = ["foreign_document_test"]
|
||||
|
||||
[dependencies]
|
||||
futures = { version = "0.3.28", optional = true }
|
||||
nom = "7.1.1"
|
||||
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
|
||||
opentelemetry-otlp = { version = "0.13.0", optional = true }
|
||||
@@ -40,14 +49,17 @@ tokio = { version = "1.30.0", optional = true, default-features = false, feature
|
||||
tracing = { version = "0.1.37", optional = true }
|
||||
tracing-opentelemetry = { version = "0.20.0", optional = true }
|
||||
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
|
||||
walkdir = { version = "2.3.3", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
walkdir = "2.3.3"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
compare = []
|
||||
compare = ["tokio/process", "tokio/macros"]
|
||||
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"]
|
||||
event_count = []
|
||||
|
||||
# Optimized build for any sort of release.
|
||||
[profile.release-lto]
|
||||
@@ -55,6 +67,13 @@ inherits = "release"
|
||||
lto = true
|
||||
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.perf]
|
||||
inherits = "release"
|
||||
|
||||
14
Makefile
14
Makefile
@@ -37,6 +37,18 @@ clean:
|
||||
format:
|
||||
> $(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
|
||||
test:
|
||||
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
@@ -52,6 +64,8 @@ buildtest:
|
||||
> cargo build --no-default-features --features compare
|
||||
> cargo build --no-default-features --features tracing
|
||||
> cargo build --no-default-features --features compare,tracing
|
||||
> cargo build --no-default-features --features compare,foreign_document_test
|
||||
> cargo build --no-default-features --features compare,tracing,foreign_document_test
|
||||
|
||||
.PHONY: foreign_document_test
|
||||
foreign_document_test:
|
||||
|
||||
@@ -6,14 +6,13 @@ Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) p
|
||||
|
||||
This project is still under HEAVY development. While the version remains v0.1.x the API will be changing often. Once we hit v0.2.x we will start following semver.
|
||||
|
||||
Currently, the parser is able to correctly identify the start/end bounds of all the org-mode objects and elements (except table.el tables, org-mode tables are supported) but many of the interior properties are not yet populated.
|
||||
Currently, Organic parses most documents the same as the official org-mode parser. Most of the development right now is finding documents where the parsers differ and fixing those issues.
|
||||
|
||||
### Project Goals
|
||||
- We aim to provide perfect parity with the emacs org-mode parser. In that regard, any document that parses differently between Emacs and Organic is considered a bug.
|
||||
- The parser should be fast. We're not doing anything special, but since this is written in Rust and natively compiled we should be able to beat the existing parsers.
|
||||
- The parser should have minimal dependencies. This should reduce effort w.r.t.: security audits, legal compliance, portability.
|
||||
- The parser should be usable everywhere. In the interest of getting org-mode used in as many places as possible, this parser should be usable by everyone everywhere. This means:
|
||||
- It must have a permissive license for use in proprietary code bases.
|
||||
- It must have a permissive license.
|
||||
- We will investigate compiling to WASM. This is an important goal of the project and will definitely happen, but only after the parser has a more stable API.
|
||||
- We will investigate compiling to a C library for native linking to other code. This is more of a maybe-goal for the project.
|
||||
### Project Non-Goals
|
||||
|
||||
12
build.rs
12
build.rs
@@ -14,7 +14,7 @@ use walkdir::WalkDir;
|
||||
fn main() {
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
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
|
||||
println!("cargo:rerun-if-changed=org_mode_samples");
|
||||
@@ -51,7 +51,7 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
|
||||
.to_lowercase()
|
||||
.strip_suffix(".org")
|
||||
.expect("Should have .org extension")
|
||||
.replace("/", "_");
|
||||
.replace('/', "_");
|
||||
|
||||
write!(
|
||||
test_file,
|
||||
@@ -66,10 +66,6 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
|
||||
}
|
||||
|
||||
#[cfg(feature = "compare")]
|
||||
fn is_expect_fail(name: &str) -> Option<&str> {
|
||||
match name {
|
||||
"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,
|
||||
}
|
||||
fn is_expect_fail(_name: &str) -> Option<&str> {
|
||||
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)
|
||||
@@ -14,7 +14,7 @@ RUN make DESTDIR="/root/dist" install
|
||||
|
||||
|
||||
FROM build AS build-org-mode
|
||||
ARG ORG_VERSION=f32f5982a76217629dad8b8fd82d53212576aee6
|
||||
ARG ORG_VERSION=abf5156096c06ee5aa05795c3dc5a065f76ada97
|
||||
COPY --from=build-emacs /root/dist/ /
|
||||
RUN mkdir /root/dist
|
||||
# Savannah does not allow fetching specific revisions, so we're going to have to put unnecessary load on their server by cloning main and then checking out the revision we want.
|
||||
@@ -102,6 +102,4 @@ COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_docume
|
||||
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
|
||||
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
|
||||
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
|
||||
COPY foreign_document_test_entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Run the Organic compare script against a series of documents sourced from exterior places.
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||
|
||||
function log {
|
||||
(>&2 echo "${@}")
|
||||
}
|
||||
|
||||
function die {
|
||||
local status_code="$1"
|
||||
shift
|
||||
(>&2 echo "${@}")
|
||||
exit "$status_code"
|
||||
}
|
||||
|
||||
function main {
|
||||
cargo build --no-default-features --features compare --profile release-lto
|
||||
if [ "${CARGO_TARGET_DIR:-}" = "" ]; then
|
||||
CARGO_TARGET_DIR=$(realpath target/)
|
||||
fi
|
||||
PARSE="${CARGO_TARGET_DIR}/release-lto/compare"
|
||||
|
||||
local all_status=0
|
||||
set +e
|
||||
|
||||
(run_compare_function "org-mode" compare_all_org_document "/foreign_documents/org-mode")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "worg" compare_all_org_document "/foreign_documents/worg")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "howard_abrams" compare_howard_abrams)
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
|
||||
set -e
|
||||
if [ "$all_status" -ne 0 ]; then
|
||||
red_text "Some tests failed."
|
||||
else
|
||||
green_text "All tests passed."
|
||||
fi
|
||||
return "$all_status"
|
||||
}
|
||||
|
||||
function green_text {
|
||||
(IFS=' '; printf '\x1b[38;2;0;255;0m%s\x1b[0m' "${*}")
|
||||
}
|
||||
|
||||
function red_text {
|
||||
(IFS=' '; printf '\x1b[38;2;255;0;0m%s\x1b[0m' "${*}")
|
||||
}
|
||||
|
||||
function yellow_text {
|
||||
(IFS=' '; printf '\x1b[38;2;255;255;0m%s\x1b[0m' "${*}")
|
||||
}
|
||||
|
||||
function indent {
|
||||
local depth="$1"
|
||||
local scaled_depth=$((depth * 2))
|
||||
shift 1
|
||||
local prefix
|
||||
prefix=$(printf -- "%${scaled_depth}s")
|
||||
while read -r l; do
|
||||
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
|
||||
done
|
||||
}
|
||||
|
||||
function run_compare_function {
|
||||
local name="$1"
|
||||
local stdoutput
|
||||
shift 1
|
||||
set +e
|
||||
stdoutput=$("${@}")
|
||||
local status=$?
|
||||
set -e
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "$(green_text "GOOD") $name"
|
||||
indent 1 <<<"$stdoutput"
|
||||
else
|
||||
echo "$(red_text "FAIL") $name"
|
||||
indent 1 <<<"$stdoutput"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function compare_all_org_document {
|
||||
local root_dir="$1"
|
||||
local target_document
|
||||
local all_status=0
|
||||
while read target_document; do
|
||||
local relative_path
|
||||
relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
|
||||
set +e
|
||||
(run_compare "$relative_path" "$target_document")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
set -e
|
||||
done<<<"$(find "$root_dir" -type f -iname '*.org' | sort)"
|
||||
return "$all_status"
|
||||
}
|
||||
|
||||
function run_compare {
|
||||
local name="$1"
|
||||
local target_document="$2"
|
||||
set +e
|
||||
($PARSE "$target_document" &> /dev/null)
|
||||
local status=$?
|
||||
set -e
|
||||
if [ "$status" -eq 0 ]; then
|
||||
echo "$(green_text "GOOD") $name"
|
||||
else
|
||||
echo "$(red_text "FAIL") $name"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function compare_howard_abrams {
|
||||
local all_status=0
|
||||
set +e
|
||||
|
||||
(run_compare_function "dot-files" compare_all_org_document "/foreign_documents/howardabrams/dot-files")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "hamacs" compare_all_org_document "/foreign_documents/howardabrams/hamacs")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "demo-it" compare_all_org_document "/foreign_documents/howardabrams/demo-it")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "magit-demo" compare_all_org_document "/foreign_documents/howardabrams/magit-demo")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "pdx-emacs-hackers" compare_all_org_document "/foreign_documents/howardabrams/pdx-emacs-hackers")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "flora-simulator" compare_all_org_document "/foreign_documents/howardabrams/flora-simulator")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "literate-devops-demo" compare_all_org_document "/foreign_documents/howardabrams/literate-devops-demo")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "clojure-yesql-xp" compare_all_org_document "/foreign_documents/howardabrams/clojure-yesql-xp")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
(run_compare_function "veep" compare_all_org_document "/foreign_documents/howardabrams/veep")
|
||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||
|
||||
set -e
|
||||
return "$all_status"
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
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
|
||||
8
org_mode_samples/affiliated_keyword/optional_value.org
Normal file
8
org_mode_samples/affiliated_keyword/optional_value.org
Normal file
@@ -0,0 +1,8 @@
|
||||
#+CAPTION[foo]: *bar*
|
||||
#+CAPTION[*lorem* ipsum]: dolar
|
||||
1. baz
|
||||
|
||||
|
||||
#+CAPTION[foo]: *bar*
|
||||
#+CAPTION[*lorem* ipsum]: dolar
|
||||
# Comments cannot have affiliated keywords so those become regular keywords.
|
||||
@@ -0,0 +1,3 @@
|
||||
foo
|
||||
:end:
|
||||
bar
|
||||
@@ -0,0 +1,2 @@
|
||||
foo
|
||||
:end:
|
||||
@@ -0,0 +1,3 @@
|
||||
1. foo
|
||||
#+NAME: bar
|
||||
2. baz
|
||||
@@ -0,0 +1,7 @@
|
||||
** Foo
|
||||
DEADLINE: <2023-10-16 Mon>
|
||||
:PROPERTIES:
|
||||
:foo: *a*
|
||||
:Bar: *b*
|
||||
:BAZ: *c*
|
||||
:END:
|
||||
@@ -0,0 +1,5 @@
|
||||
* Overwrite
|
||||
#+NAME: foo
|
||||
:PROPERTIES:
|
||||
:header-args: :var foo="lorem"
|
||||
:END:
|
||||
3
org_mode_samples/greater_element/table/row/name.org
Normal file
3
org_mode_samples/greater_element/table/row/name.org
Normal file
@@ -0,0 +1,3 @@
|
||||
|foo|
|
||||
#+NAME: bar
|
||||
|baz |
|
||||
@@ -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
|
||||
3
org_mode_samples/sections_and_headings/name.org
Normal file
3
org_mode_samples/sections_and_headings/name.org
Normal file
@@ -0,0 +1,3 @@
|
||||
#+NAME: foo
|
||||
* bar
|
||||
#+NAME: baz
|
||||
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")
|
||||
fi
|
||||
(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
|
||||
# flags to consider --show-info
|
||||
|
||||
@@ -14,30 +14,40 @@ mod init_tracing;
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
main_body()
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async {
|
||||
let main_body_result = main_body().await;
|
||||
main_body_result
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
let result = rt.block_on(async {
|
||||
rt.block_on(async {
|
||||
init_telemetry()?;
|
||||
let main_body_result = main_body();
|
||||
let main_body_result = main_body().await;
|
||||
shutdown_telemetry()?;
|
||||
main_body_result
|
||||
});
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn main_body() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let args = std::env::args().skip(1);
|
||||
if args.is_empty() {
|
||||
let org_contents = read_stdin_to_string()?;
|
||||
run_anonymous_compare(org_contents)
|
||||
if run_anonymous_compare(org_contents).await? {
|
||||
} else {
|
||||
Err("Diff results do not match.")?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
for arg in args {
|
||||
run_compare_on_file(arg)?
|
||||
if run_compare_on_file(arg).await? {
|
||||
} else {
|
||||
Err("Diff results do not match.")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
394
src/bin_foreign_document_test.rs
Normal file
394
src/bin_foreign_document_test.rs
Normal file
@@ -0,0 +1,394 @@
|
||||
#![feature(round_char_boundary)]
|
||||
#![feature(exact_size_is_empty)]
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::future::FutureExt;
|
||||
use organic::compare::silent_compare_on_file;
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::task::JoinError;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use crate::init_tracing::init_telemetry;
|
||||
#[cfg(feature = "tracing")]
|
||||
use crate::init_tracing::shutdown_telemetry;
|
||||
#[cfg(feature = "tracing")]
|
||||
mod init_tracing;
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async {
|
||||
let main_body_result = main_body().await;
|
||||
main_body_result
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async {
|
||||
init_telemetry()?;
|
||||
let main_body_result = main_body().await;
|
||||
shutdown_telemetry()?;
|
||||
main_body_result
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
|
||||
let layer = compare_group("org-mode", || {
|
||||
compare_all_org_document("/foreign_documents/org-mode")
|
||||
});
|
||||
let layer = layer.chain(compare_group("emacs", || {
|
||||
compare_all_org_document("/foreign_documents/emacs")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("worg", || {
|
||||
compare_all_org_document("/foreign_documents/worg")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("howard_abrams", compare_howard_abrams));
|
||||
let layer = layer.chain(compare_group("doomemacs", || {
|
||||
compare_all_org_document("/foreign_documents/doomemacs")
|
||||
}));
|
||||
|
||||
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
|
||||
let mut any_failed = false;
|
||||
for test in running_tests.into_iter() {
|
||||
let test_result = test.await??;
|
||||
if test_result.is_immediately_bad() || test_result.has_bad_children() {
|
||||
any_failed = true;
|
||||
}
|
||||
test_result.print();
|
||||
}
|
||||
|
||||
if any_failed {
|
||||
println!(
|
||||
"{color}Some tests failed.{reset}",
|
||||
color = TestResult::foreground_color(255, 0, 0),
|
||||
reset = TestResult::reset_color(),
|
||||
);
|
||||
Ok(ExitCode::FAILURE)
|
||||
} else {
|
||||
println!(
|
||||
"{color}All tests passed.{reset}",
|
||||
color = TestResult::foreground_color(0, 255, 0),
|
||||
reset = TestResult::reset_color(),
|
||||
);
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
}
|
||||
|
||||
fn compare_howard_abrams() -> impl Iterator<Item = TestConfig> {
|
||||
let layer = compare_group("dot-files", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/dot-files")
|
||||
});
|
||||
let layer = layer.chain(compare_group("hamacs", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/hamacs")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("demo-it", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/demo-it")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("magit-demo", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/magit-demo")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("pdx-emacs-hackers", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/pdx-emacs-hackers")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("flora-simulator", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/flora-simulator")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("literate-devops-demo", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/literate-devops-demo")
|
||||
}));
|
||||
let layer = layer.chain(compare_group("clojure-yesql-xp", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/clojure-yesql-xp")
|
||||
}));
|
||||
layer.chain(compare_group("veep", || {
|
||||
compare_all_org_document("/foreign_documents/howardabrams/veep")
|
||||
}))
|
||||
}
|
||||
|
||||
fn compare_group<N: Into<String>, F: Fn() -> I, I: Iterator<Item = TestConfig>>(
|
||||
name: N,
|
||||
inner: F,
|
||||
) -> impl Iterator<Item = TestConfig> {
|
||||
std::iter::once(TestConfig::TestLayer(TestLayer {
|
||||
name: name.into(),
|
||||
children: inner().collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn compare_all_org_document<P: AsRef<Path>>(root_dir: P) -> impl Iterator<Item = TestConfig> {
|
||||
let root_dir = root_dir.as_ref();
|
||||
let mut test_files = WalkDir::new(root_dir)
|
||||
.into_iter()
|
||||
.filter(|e| match e {
|
||||
Ok(dir_entry) => {
|
||||
dir_entry.file_type().is_file()
|
||||
&& Path::new(dir_entry.file_name())
|
||||
.extension()
|
||||
.map(|ext| ext.to_ascii_lowercase() == "org")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
Err(_) => true,
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap();
|
||||
test_files.sort_by_cached_key(|test_file| PathBuf::from(test_file.path()));
|
||||
let test_configs: Vec<_> = test_files
|
||||
.into_iter()
|
||||
.map(|test_file| {
|
||||
let name = test_file
|
||||
.path()
|
||||
.strip_prefix(root_dir)
|
||||
.expect("Result is from walkdir so it must be below the root directory.")
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
TestConfig::SingleFile(SingleFile {
|
||||
name,
|
||||
file_path: test_file.into_path(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
test_configs.into_iter()
|
||||
}
|
||||
|
||||
static TEST_PERMITS: Semaphore = Semaphore::const_new(8);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TestConfig {
|
||||
TestLayer(TestLayer),
|
||||
SingleFile(SingleFile),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestLayer {
|
||||
name: String,
|
||||
children: Vec<TestConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SingleFile {
|
||||
name: String,
|
||||
file_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TestResult {
|
||||
ResultLayer(ResultLayer),
|
||||
SingleFileResult(SingleFileResult),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ResultLayer {
|
||||
name: String,
|
||||
children: Vec<TestResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SingleFileResult {
|
||||
name: String,
|
||||
status: TestStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum TestStatus {
|
||||
Pass,
|
||||
Fail,
|
||||
}
|
||||
|
||||
impl TestConfig {
|
||||
fn run_test(self) -> BoxFuture<'static, Result<TestResult, JoinError>> {
|
||||
async move {
|
||||
match self {
|
||||
TestConfig::TestLayer(test) => Ok(TestResult::ResultLayer(test.run_test().await?)),
|
||||
TestConfig::SingleFile(test) => {
|
||||
Ok(TestResult::SingleFileResult(test.run_test().await?))
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl SingleFile {
|
||||
async fn run_test(self) -> Result<SingleFileResult, JoinError> {
|
||||
let _permit = TEST_PERMITS.acquire().await.unwrap();
|
||||
let result = silent_compare_on_file(&self.file_path).await;
|
||||
Ok(SingleFileResult {
|
||||
name: self.name,
|
||||
status: if let Ok(true) = result {
|
||||
TestStatus::Pass
|
||||
} else {
|
||||
TestStatus::Fail
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TestLayer {
|
||||
async fn run_test(self) -> Result<ResultLayer, JoinError> {
|
||||
let running_children: Vec<_> = self
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|c| tokio::spawn(c.run_test()))
|
||||
.collect();
|
||||
let mut children = Vec::with_capacity(running_children.len());
|
||||
for c in running_children {
|
||||
children.push(c.await??);
|
||||
}
|
||||
Ok(ResultLayer {
|
||||
name: self.name,
|
||||
children,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TestResult {
|
||||
pub fn print(&self) {
|
||||
self.print_indented(0);
|
||||
}
|
||||
|
||||
fn print_indented(&self, indentation: usize) {
|
||||
match self {
|
||||
TestResult::ResultLayer(result) => result.print_indented(indentation),
|
||||
TestResult::SingleFileResult(result) => result.print_indented(indentation),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_bad_children(&self) -> bool {
|
||||
match self {
|
||||
TestResult::ResultLayer(result) => result.has_bad_children(),
|
||||
TestResult::SingleFileResult(result) => result.has_bad_children(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_immediately_bad(&self) -> bool {
|
||||
match self {
|
||||
TestResult::ResultLayer(result) => result.is_immediately_bad(),
|
||||
TestResult::SingleFileResult(result) => result.is_immediately_bad(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn foreground_color(red: u8, green: u8, blue: u8) -> String {
|
||||
if TestResult::should_use_color() {
|
||||
format!(
|
||||
"\x1b[38;2;{red};{green};{blue}m",
|
||||
red = red,
|
||||
green = green,
|
||||
blue = blue
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn background_color(red: u8, green: u8, blue: u8) -> String {
|
||||
if TestResult::should_use_color() {
|
||||
format!(
|
||||
"\x1b[48;2;{red};{green};{blue}m",
|
||||
red = red,
|
||||
green = green,
|
||||
blue = blue
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reset_color() -> &'static str {
|
||||
if TestResult::should_use_color() {
|
||||
"\x1b[0m"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
fn should_use_color() -> bool {
|
||||
!std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl SingleFileResult {
|
||||
fn print_indented(&self, indentation: usize) {
|
||||
match self.status {
|
||||
TestStatus::Pass => {
|
||||
println!(
|
||||
"{indentation}{color}PASS{reset} {name}",
|
||||
indentation = " ".repeat(indentation),
|
||||
color = TestResult::foreground_color(0, 255, 0),
|
||||
reset = TestResult::reset_color(),
|
||||
name = self.name
|
||||
);
|
||||
}
|
||||
TestStatus::Fail => {
|
||||
println!(
|
||||
"{indentation}{color}FAIL{reset} {name}",
|
||||
indentation = " ".repeat(indentation),
|
||||
color = TestResult::foreground_color(255, 0, 0),
|
||||
reset = TestResult::reset_color(),
|
||||
name = self.name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_bad_children(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_immediately_bad(&self) -> bool {
|
||||
match self.status {
|
||||
TestStatus::Pass => false,
|
||||
TestStatus::Fail => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResultLayer {
|
||||
fn print_indented(&self, indentation: usize) {
|
||||
if self.is_immediately_bad() {
|
||||
println!(
|
||||
"{indentation}{color}FAIL{reset} {name}",
|
||||
indentation = " ".repeat(indentation),
|
||||
color = TestResult::foreground_color(255, 0, 0),
|
||||
reset = TestResult::reset_color(),
|
||||
name = self.name
|
||||
);
|
||||
} else if self.has_bad_children() {
|
||||
println!(
|
||||
"{indentation}{color}BADCHILD{reset} {name}",
|
||||
indentation = " ".repeat(indentation),
|
||||
color = TestResult::foreground_color(255, 255, 0),
|
||||
reset = TestResult::reset_color(),
|
||||
name = self.name
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"{indentation}{color}PASS{reset} {name}",
|
||||
indentation = " ".repeat(indentation),
|
||||
color = TestResult::foreground_color(0, 255, 0),
|
||||
reset = TestResult::reset_color(),
|
||||
name = self.name
|
||||
);
|
||||
}
|
||||
self.children
|
||||
.iter()
|
||||
.for_each(|result| result.print_indented(indentation + 1));
|
||||
}
|
||||
|
||||
fn has_bad_children(&self) -> bool {
|
||||
self.children
|
||||
.iter()
|
||||
.any(|result| result.is_immediately_bad() || result.has_bad_children())
|
||||
}
|
||||
|
||||
fn is_immediately_bad(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -12,39 +12,60 @@ use crate::context::LocalFileAccessInterface;
|
||||
use crate::parser::parse_file_with_settings;
|
||||
use crate::parser::parse_with_settings;
|
||||
|
||||
pub fn run_anonymous_compare<P: AsRef<str>>(
|
||||
pub async fn run_anonymous_compare<P: AsRef<str>>(
|
||||
org_contents: P,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default())
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), false).await
|
||||
}
|
||||
|
||||
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_compare_on_file_with_settings(org_path, &GlobalSettings::default())
|
||||
pub async fn run_compare_on_file<P: AsRef<Path>>(
|
||||
org_path: P,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), false).await
|
||||
}
|
||||
|
||||
pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
|
||||
pub async fn silent_anonymous_compare<P: AsRef<str>>(
|
||||
org_contents: P,
|
||||
global_settings: &GlobalSettings,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), true).await
|
||||
}
|
||||
|
||||
pub async fn silent_compare_on_file<P: AsRef<Path>>(
|
||||
org_path: P,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), true).await
|
||||
}
|
||||
|
||||
pub async fn run_anonymous_compare_with_settings<'g, 's, P: AsRef<str>>(
|
||||
org_contents: P,
|
||||
global_settings: &GlobalSettings<'g, 's>,
|
||||
silent: bool,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
// TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings.
|
||||
let org_contents = org_contents.as_ref().replace("\r\n", "\n");
|
||||
let org_contents = org_contents.as_str();
|
||||
print_versions()?;
|
||||
if !silent {
|
||||
print_versions().await?;
|
||||
}
|
||||
let rust_parsed = parse_with_settings(org_contents, global_settings)?;
|
||||
let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings)?;
|
||||
let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings).await?;
|
||||
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
||||
|
||||
println!("{}\n\n\n", org_contents);
|
||||
println!("{}", org_sexp);
|
||||
println!("{:#?}", rust_parsed);
|
||||
if !silent {
|
||||
println!("{}\n\n\n", org_contents);
|
||||
println!("{}", org_sexp);
|
||||
println!("{:#?}", rust_parsed);
|
||||
}
|
||||
|
||||
// We do the diffing after printing out both parsed forms in case the diffing panics
|
||||
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
|
||||
diff_result.print(org_contents)?;
|
||||
if !silent {
|
||||
diff_result.print(org_contents)?;
|
||||
}
|
||||
|
||||
if diff_result.is_bad() {
|
||||
Err("Diff results do not match.")?;
|
||||
} else {
|
||||
return Ok(false);
|
||||
} else if !silent {
|
||||
println!(
|
||||
"{color}Entire document passes.{reset}",
|
||||
color = DiffResult::foreground_color(0, 255, 0),
|
||||
@@ -52,15 +73,18 @@ pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
|
||||
pub async fn run_compare_on_file_with_settings<'g, 's, P: AsRef<Path>>(
|
||||
org_path: P,
|
||||
global_settings: &GlobalSettings,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
global_settings: &GlobalSettings<'g, 's>,
|
||||
silent: bool,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
let org_path = org_path.as_ref();
|
||||
print_versions()?;
|
||||
if !silent {
|
||||
print_versions().await?;
|
||||
}
|
||||
let parent_directory = org_path
|
||||
.parent()
|
||||
.ok_or("Should be contained inside a directory.")?;
|
||||
@@ -77,20 +101,24 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
|
||||
global_settings
|
||||
};
|
||||
let rust_parsed = parse_file_with_settings(org_contents, &global_settings, Some(org_path))?;
|
||||
let org_sexp = emacs_parse_file_org_document(org_path, &global_settings)?;
|
||||
let org_sexp = emacs_parse_file_org_document(org_path, &global_settings).await?;
|
||||
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
||||
|
||||
println!("{}\n\n\n", org_contents);
|
||||
println!("{}", org_sexp);
|
||||
println!("{:#?}", rust_parsed);
|
||||
if !silent {
|
||||
println!("{}\n\n\n", org_contents);
|
||||
println!("{}", org_sexp);
|
||||
println!("{:#?}", rust_parsed);
|
||||
}
|
||||
|
||||
// We do the diffing after printing out both parsed forms in case the diffing panics
|
||||
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
|
||||
diff_result.print(org_contents)?;
|
||||
if !silent {
|
||||
diff_result.print(org_contents)?;
|
||||
}
|
||||
|
||||
if diff_result.is_bad() {
|
||||
Err("Diff results do not match.")?;
|
||||
} else {
|
||||
return Ok(false);
|
||||
} else if !silent {
|
||||
println!(
|
||||
"{color}Entire document passes.{reset}",
|
||||
color = DiffResult::foreground_color(0, 255, 0),
|
||||
@@ -98,11 +126,14 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
|
||||
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
||||
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
||||
async fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
|
||||
eprintln!("Using emacs version: {}", get_emacs_version().await?.trim());
|
||||
eprintln!(
|
||||
"Using org-mode version: {}",
|
||||
get_org_mode_version().await?.trim()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ use super::util::get_property_unquoted_atom;
|
||||
use crate::types::AstNode;
|
||||
use crate::types::CharOffsetInLine;
|
||||
use crate::types::LineNumber;
|
||||
use crate::types::Object;
|
||||
use crate::types::RetainLabels;
|
||||
use crate::types::SwitchNumberLines;
|
||||
|
||||
@@ -57,11 +56,11 @@ impl<'b, 's> ComparePropertiesResult<'b, 's> {
|
||||
/// 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.
|
||||
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
|
||||
pub(crate) fn compare_noop<'b, 's, R, RG>(
|
||||
_source: &'s str,
|
||||
_emacs: &'b Token<'s>,
|
||||
_rust_node: R,
|
||||
_emacs_field: &'x str,
|
||||
_emacs_field: &str,
|
||||
_rust_value_getter: RG,
|
||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||
Ok(ComparePropertiesResult::NoChange)
|
||||
@@ -70,18 +69,16 @@ pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
|
||||
/// 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.
|
||||
pub(crate) fn compare_identity() -> () {
|
||||
()
|
||||
}
|
||||
pub(crate) fn compare_identity() {}
|
||||
|
||||
/// 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.
|
||||
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,
|
||||
emacs: &'b Token<'s>,
|
||||
_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)?;
|
||||
@@ -100,7 +97,6 @@ pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
|
||||
pub(crate) fn compare_property_quoted_string<
|
||||
'b,
|
||||
's,
|
||||
'x,
|
||||
R,
|
||||
RV: AsRef<str> + std::fmt::Debug,
|
||||
RG: Fn(R) -> Option<RV>,
|
||||
@@ -108,12 +104,12 @@ pub(crate) fn compare_property_quoted_string<
|
||||
_source: &'s str,
|
||||
emacs: &'b Token<'s>,
|
||||
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_quoted_string(emacs, emacs_field)?;
|
||||
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 message = Some(format!(
|
||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||
@@ -181,7 +177,6 @@ where
|
||||
pub(crate) fn compare_property_list_of_quoted_string<
|
||||
'b,
|
||||
's,
|
||||
'x,
|
||||
R,
|
||||
RV: AsRef<str> + std::fmt::Debug,
|
||||
RI: Iterator<Item = RV>,
|
||||
@@ -190,7 +185,7 @@ pub(crate) fn compare_property_list_of_quoted_string<
|
||||
_source: &'s str,
|
||||
emacs: &'b Token<'s>,
|
||||
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)?
|
||||
@@ -272,10 +267,7 @@ pub(crate) fn compare_property_set_of_quoted_string<
|
||||
.map(unquote)
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let value: BTreeSet<&str> = value.iter().map(|e| e.as_str()).collect();
|
||||
let mismatched: Vec<_> = value
|
||||
.symmetric_difference(&rust_value)
|
||||
.map(|val| *val)
|
||||
.collect();
|
||||
let mismatched: Vec<_> = value.symmetric_difference(&rust_value).copied().collect();
|
||||
if !mismatched.is_empty() {
|
||||
let this_status = DiffStatus::Bad;
|
||||
let message = Some(format!(
|
||||
@@ -288,11 +280,111 @@ pub(crate) fn compare_property_set_of_quoted_string<
|
||||
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,
|
||||
emacs: &'b Token<'s>,
|
||||
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,
|
||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||
// get_property already converts nil to None.
|
||||
@@ -376,14 +468,7 @@ where
|
||||
match (value, rust_value) {
|
||||
(None, None) => {}
|
||||
(Some(el), None)
|
||||
if el.len() == 1
|
||||
&& el.into_iter().all(|t| {
|
||||
if let Ok(r#""""#) = t.as_atom() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) => {}
|
||||
if el.len() == 1 && el.iter().all(|t| matches!(t.as_atom(), Ok(r#""""#))) => {}
|
||||
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
|
||||
let this_status = DiffStatus::Bad;
|
||||
let message = Some(format!(
|
||||
@@ -412,79 +497,139 @@ where
|
||||
Ok(ComparePropertiesResult::NoChange)
|
||||
}
|
||||
|
||||
/// Special compare used for affiliate keywords that are parsed as objects.
|
||||
///
|
||||
/// 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 that seems to always have 1 element.
|
||||
/// - Inner list of the objects from each #+caption keyword (or other parsed keyword).
|
||||
pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
|
||||
pub(crate) fn compare_property_object_tree<
|
||||
'b,
|
||||
's,
|
||||
'x,
|
||||
R,
|
||||
RG: Fn(R) -> Option<&'b Vec<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,
|
||||
emacs: &'b Token<'s>,
|
||||
rust_node: R,
|
||||
emacs_field: &'x str,
|
||||
rust_value_getter: RG,
|
||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
|
||||
// TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier.
|
||||
let rust_value = rust_value_getter(rust_node);
|
||||
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>>
|
||||
where
|
||||
AstNode<'b, 's>: From<&'b RV>,
|
||||
AstNode<'b, 's>: From<&'b ROV>,
|
||||
{
|
||||
let value = get_property(emacs, emacs_field)?
|
||||
.map(Token::as_list)
|
||||
.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) => {
|
||||
return Ok(ComparePropertiesResult::NoChange);
|
||||
}
|
||||
(None, Some(_)) | (Some(_), None) => {
|
||||
(None, rv @ Some(_)) | (Some(_), rv @ None) => {
|
||||
let this_status = DiffStatus::Bad;
|
||||
let message = Some(format!(
|
||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||
emacs_field, value, rust_value
|
||||
emacs_field, value, rv
|
||||
));
|
||||
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 message = Some(format!(
|
||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||
emacs_field, value, rust_value
|
||||
emacs_field, el, rl
|
||||
));
|
||||
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());
|
||||
|
||||
// Iterate the outer lists
|
||||
for (value, rust_value) in value.iter().zip(rust_value.iter()) {
|
||||
// Assert the middle list is a length of 1 because I've never seen it any other way.
|
||||
let value = value.as_list()?;
|
||||
if value.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));
|
||||
for (kw_e, kw_r) in outer_emacs_list.iter().zip(outer_rust_list) {
|
||||
let kw_e = kw_e.as_list()?;
|
||||
let child_status_length = kw_r.1.len() + kw_r.0.as_ref().map(|opt| opt.len()).unwrap_or(0);
|
||||
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(child_status_length);
|
||||
if let Some(or) = &kw_r.0 {
|
||||
// if optional value
|
||||
let mut kw_e = kw_e.iter();
|
||||
// First element is a list representing the mandatory value.
|
||||
if let Some(val_e) = kw_e.next() {
|
||||
let el = val_e.as_list()?;
|
||||
if el.len() != kw_r.1.len() {
|
||||
let this_status = DiffStatus::Bad;
|
||||
let message = Some(format!(
|
||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||
emacs_field, kw_e, kw_r
|
||||
));
|
||||
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
|
||||
}
|
||||
for (e, r) in el.iter().zip(kw_r.1.iter()) {
|
||||
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||
}
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
// 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())?);
|
||||
}
|
||||
} 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())?);
|
||||
}
|
||||
}
|
||||
// Drill past the middle list to the inner list.
|
||||
let value = value
|
||||
.first()
|
||||
.expect("The above if-statement asserts this exists.");
|
||||
let value = value.as_list()?;
|
||||
// Compare inner lists
|
||||
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
|
||||
for (e, r) in value.iter().zip(rust_value) {
|
||||
child_status.push(compare_ast_node(source, e, r.into())?);
|
||||
if !child_status.is_empty() {
|
||||
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
|
||||
full_status.push(diff_scope);
|
||||
}
|
||||
let diff_scope = artificial_owned_diff_scope(emacs_field, child_status)?;
|
||||
return Ok(ComparePropertiesResult::DiffEntry(diff_scope));
|
||||
}
|
||||
Ok(ComparePropertiesResult::NoChange)
|
||||
|
||||
if full_status.is_empty() {
|
||||
Ok(ComparePropertiesResult::NoChange)
|
||||
} else {
|
||||
let diff_scope = artificial_owned_diff_scope(emacs_field, full_status)?;
|
||||
Ok(ComparePropertiesResult::DiffEntry(diff_scope))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compare_property_number_lines<
|
||||
|
||||
@@ -227,7 +227,7 @@ impl<'b, 's> DiffResult<'b, 's> {
|
||||
status_text = status_text,
|
||||
name = self.name,
|
||||
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() {
|
||||
child.print_indented(indentation + 1, original_document)?;
|
||||
@@ -330,8 +330,8 @@ pub(crate) fn artificial_diff_scope<'b, 's>(
|
||||
.into())
|
||||
}
|
||||
|
||||
pub(crate) fn artificial_owned_diff_scope<'b, 's, 'x>(
|
||||
name: &'x str,
|
||||
pub(crate) fn artificial_owned_diff_scope<'b, 's>(
|
||||
name: &str,
|
||||
children: Vec<DiffEntry<'b, 's>>,
|
||||
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
|
||||
Ok(DiffLayer {
|
||||
@@ -426,14 +426,9 @@ 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.
|
||||
if let AstNode::PlainText(_) = rust {
|
||||
} else {
|
||||
match compare_standard_properties(source, emacs, &rust) {
|
||||
Err(err) => {
|
||||
compare_result.status = DiffStatus::Bad;
|
||||
compare_result.message = Some(err.to_string())
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
} else if let Err(err) = compare_standard_properties(source, emacs, &rust) {
|
||||
compare_result.status = DiffStatus::Bad;
|
||||
compare_result.message = Some(err.to_string())
|
||||
}
|
||||
|
||||
Ok(compare_result.into())
|
||||
@@ -495,7 +490,7 @@ fn _compare_document<'b, 's>(
|
||||
.map(EmacsField::Required),
|
||||
(
|
||||
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
|
||||
),
|
||||
(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod compare;
|
||||
mod compare_field;
|
||||
mod diff;
|
||||
@@ -10,3 +11,5 @@ pub use compare::run_anonymous_compare;
|
||||
pub use compare::run_anonymous_compare_with_settings;
|
||||
pub use compare::run_compare_on_file;
|
||||
pub use compare::run_compare_on_file_with_settings;
|
||||
pub use compare::silent_anonymous_compare;
|
||||
pub use compare::silent_compare_on_file;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::context::HeadlineLevelFilter;
|
||||
use crate::settings::GlobalSettings;
|
||||
@@ -25,9 +26,9 @@ fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn emacs_parse_anonymous_org_document<C>(
|
||||
pub(crate) async fn emacs_parse_anonymous_org_document<'g, 's, C>(
|
||||
file_contents: C,
|
||||
global_settings: &GlobalSettings,
|
||||
global_settings: &GlobalSettings<'g, 's>,
|
||||
) -> Result<String, Box<dyn std::error::Error>>
|
||||
where
|
||||
C: AsRef<str>,
|
||||
@@ -54,7 +55,7 @@ where
|
||||
.arg("--batch")
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
let out = cmd.output()?;
|
||||
let out = cmd.output().await?;
|
||||
let status = out.status.exit_ok();
|
||||
if status.is_err() {
|
||||
eprintln!(
|
||||
@@ -69,9 +70,9 @@ where
|
||||
Ok(String::from_utf8(org_sexp)?)
|
||||
}
|
||||
|
||||
pub(crate) fn emacs_parse_file_org_document<P>(
|
||||
pub(crate) async fn emacs_parse_file_org_document<'g, 's, P>(
|
||||
file_path: P,
|
||||
global_settings: &GlobalSettings,
|
||||
global_settings: &GlobalSettings<'g, 's>,
|
||||
) -> Result<String, Box<dyn std::error::Error>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
@@ -106,7 +107,7 @@ where
|
||||
.arg("--batch")
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
let out = cmd.output()?;
|
||||
let out = cmd.output().await?;
|
||||
let status = out.status.exit_ok();
|
||||
if status.is_err() {
|
||||
eprintln!(
|
||||
@@ -143,7 +144,7 @@ where
|
||||
output
|
||||
}
|
||||
|
||||
pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
pub async fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let elisp_script = r#"(progn
|
||||
(message "%s" (version))
|
||||
)"#;
|
||||
@@ -156,12 +157,12 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
|
||||
let out = cmd.output()?;
|
||||
let out = cmd.output().await?;
|
||||
out.status.exit_ok()?;
|
||||
Ok(String::from_utf8(out.stderr)?)
|
||||
}
|
||||
|
||||
pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
pub async fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let elisp_script = r#"(progn
|
||||
(org-mode)
|
||||
(message "%s" (org-version nil t nil))
|
||||
@@ -175,7 +176,7 @@ pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
|
||||
let out = cmd.output()?;
|
||||
let out = cmd.output().await?;
|
||||
out.status.exit_ok()?;
|
||||
Ok(String::from_utf8(out.stderr)?)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||
debug_assert!(is_slice_of(input, remaining));
|
||||
let source = {
|
||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
||||
&input[..offset]
|
||||
};
|
||||
source.into()
|
||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
||||
&input[..offset]
|
||||
}
|
||||
|
||||
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
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());
|
||||
}
|
||||
if !text.ends_with(r#"""#) {
|
||||
if !text.ends_with('"') {
|
||||
return Err("Quoted text does not end with quote.".into());
|
||||
}
|
||||
let interior_text = &text[1..(text.len() - 1)];
|
||||
let mut state = ParseState::Normal;
|
||||
for current_char in interior_text.bytes().into_iter() {
|
||||
for current_char in interior_text.bytes() {
|
||||
// Check to see if octal finished
|
||||
state = match (state, current_char) {
|
||||
(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"))]
|
||||
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, body) = take_till1(|c| match c {
|
||||
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
|
||||
_ => false,
|
||||
})(input)?;
|
||||
Ok((remaining, Token::Atom(body.into())))
|
||||
let (remaining, body) =
|
||||
take_till1(|c| matches!(c, ' ' | '\t' | '\r' | '\n' | ')' | ']'))(input)?;
|
||||
Ok((remaining, Token::Atom(body)))
|
||||
}
|
||||
|
||||
#[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 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"))]
|
||||
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = tag("#<")(input)?;
|
||||
let (remaining, _body) = take_till1(|c| match c {
|
||||
'>' => true,
|
||||
_ => false,
|
||||
})(remaining)?;
|
||||
let (remaining, _body) = take_till1(|c| matches!(c, '>'))(remaining)?;
|
||||
let (remaining, _) = tag(">")(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, (text, props)) = delimited(
|
||||
multispace0,
|
||||
@@ -348,10 +340,7 @@ mod tests {
|
||||
let input = r#" (foo "b(a)r" baz ) "#;
|
||||
let (remaining, parsed) = sexp(input).expect("Parse the input");
|
||||
assert_eq!(remaining, "");
|
||||
assert!(match parsed {
|
||||
Token::List(_) => true,
|
||||
_ => false,
|
||||
});
|
||||
assert!(matches!(parsed, Token::List(_)));
|
||||
let children = match parsed {
|
||||
Token::List(children) => children,
|
||||
_ => panic!("Should be a list."),
|
||||
@@ -364,14 +353,14 @@ mod tests {
|
||||
r#"foo"#
|
||||
);
|
||||
assert_eq!(
|
||||
match children.iter().nth(1) {
|
||||
match children.get(1) {
|
||||
Some(Token::Atom(body)) => *body,
|
||||
_ => panic!("Second child should be an atom."),
|
||||
},
|
||||
r#""b(a)r""#
|
||||
);
|
||||
assert_eq!(
|
||||
match children.iter().nth(2) {
|
||||
match children.get(2) {
|
||||
Some(Token::Atom(body)) => *body,
|
||||
_ => panic!("Third child should be an atom."),
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
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_object_tree;
|
||||
use super::compare_field::compare_property_optional_pair;
|
||||
use super::compare_field::compare_property_quoted_string;
|
||||
use super::compare_field::ComparePropertiesResult;
|
||||
use super::diff::DiffEntry;
|
||||
@@ -53,8 +54,8 @@ pub(crate) fn compare_standard_properties<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
|
||||
emacs: &'b Token<'s>,
|
||||
pub(crate) fn assert_name<S: AsRef<str>>(
|
||||
emacs: &Token<'_>,
|
||||
name: S,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
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.")?,
|
||||
);
|
||||
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 =
|
||||
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 {
|
||||
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>,
|
||||
}
|
||||
|
||||
fn get_emacs_standard_properties<'b, 's>(
|
||||
emacs: &'b Token<'s>,
|
||||
fn get_emacs_standard_properties(
|
||||
emacs: &Token<'_>,
|
||||
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
|
||||
let children = emacs.as_list()?;
|
||||
let attributes_child = children
|
||||
.iter()
|
||||
.nth(1)
|
||||
.ok_or("Should have an attributes child.")?;
|
||||
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
|
||||
let attributes_map = attributes_child.as_map()?;
|
||||
let standard_properties = attributes_map.get(":standard-properties");
|
||||
Ok(if standard_properties.is_some() {
|
||||
let mut std_props = standard_properties
|
||||
.expect("if statement proves its Some")
|
||||
.as_vector()?
|
||||
.into_iter();
|
||||
.iter();
|
||||
let begin = maybe_token_to_usize(std_props.next())?;
|
||||
let post_affiliated = maybe_token_to_usize(std_props.next())?;
|
||||
let contents_begin = maybe_token_to_usize(std_props.next())?;
|
||||
@@ -142,16 +140,13 @@ fn get_emacs_standard_properties<'b, 's>(
|
||||
post_blank,
|
||||
}
|
||||
} else {
|
||||
let begin = maybe_token_to_usize(attributes_map.get(":begin").map(|token| *token))?;
|
||||
let end = maybe_token_to_usize(attributes_map.get(":end").map(|token| *token))?;
|
||||
let contents_begin =
|
||||
maybe_token_to_usize(attributes_map.get(":contents-begin").map(|token| *token))?;
|
||||
let contents_end =
|
||||
maybe_token_to_usize(attributes_map.get(":contents-end").map(|token| *token))?;
|
||||
let post_blank =
|
||||
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
|
||||
let begin = maybe_token_to_usize(attributes_map.get(":begin").copied())?;
|
||||
let end = maybe_token_to_usize(attributes_map.get(":end").copied())?;
|
||||
let contents_begin = maybe_token_to_usize(attributes_map.get(":contents-begin").copied())?;
|
||||
let contents_end = maybe_token_to_usize(attributes_map.get(":contents-end").copied())?;
|
||||
let post_blank = maybe_token_to_usize(attributes_map.get(":post-blank").copied())?;
|
||||
let post_affiliated =
|
||||
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
|
||||
maybe_token_to_usize(attributes_map.get(":post-affiliated").copied())?;
|
||||
EmacsStandardProperties {
|
||||
begin,
|
||||
post_affiliated,
|
||||
@@ -169,62 +164,57 @@ fn maybe_token_to_usize(
|
||||
Ok(token
|
||||
.map(|token| token.as_atom())
|
||||
.map_or(Ok(None), |r| r.map(Some))?
|
||||
.map(|val| {
|
||||
.and_then(|val| {
|
||||
if val == "nil" {
|
||||
None
|
||||
} else {
|
||||
Some(val.parse::<usize>())
|
||||
}
|
||||
})
|
||||
.flatten() // Outer option is whether or not the param exists, inner option is whether or not it is nil
|
||||
.map_or(Ok(None), |r| r.map(Some))?)
|
||||
}
|
||||
|
||||
/// Get a named property from the emacs token.
|
||||
///
|
||||
/// 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>,
|
||||
key: &'x str,
|
||||
key: &str,
|
||||
) -> Result<Option<&'b Token<'s>>, Box<dyn std::error::Error>> {
|
||||
let children = emacs.as_list()?;
|
||||
let attributes_child = children
|
||||
.iter()
|
||||
.nth(1)
|
||||
.ok_or("Should have an attributes child.")?;
|
||||
let attributes_child = children.get(1).ok_or("Should have an attributes child.")?;
|
||||
let attributes_map = attributes_child.as_map()?;
|
||||
let prop = attributes_map.get(key).map(|token| *token);
|
||||
match prop.map(|token| token.as_atom()) {
|
||||
Some(Ok("nil")) => return Ok(None),
|
||||
_ => {}
|
||||
};
|
||||
let prop = attributes_map.get(key).copied();
|
||||
if let Some(Ok("nil")) = prop.map(Token::as_atom) {
|
||||
return Ok(None);
|
||||
}
|
||||
Ok(prop)
|
||||
}
|
||||
|
||||
/// Get a named property containing an unquoted atom from the emacs token.
|
||||
///
|
||||
/// Returns None if key is not found.
|
||||
pub(crate) fn get_property_unquoted_atom<'b, 's, 'x>(
|
||||
emacs: &'b Token<'s>,
|
||||
key: &'x str,
|
||||
pub(crate) fn get_property_unquoted_atom<'s>(
|
||||
emacs: &Token<'s>,
|
||||
key: &str,
|
||||
) -> Result<Option<&'s str>, Box<dyn std::error::Error>> {
|
||||
Ok(get_property(emacs, key)?
|
||||
get_property(emacs, key)?
|
||||
.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.
|
||||
///
|
||||
/// Returns None if key is not found.
|
||||
pub(crate) fn get_property_quoted_string<'b, 's, 'x>(
|
||||
emacs: &'b Token<'s>,
|
||||
key: &'x str,
|
||||
pub(crate) fn get_property_quoted_string(
|
||||
emacs: &Token<'_>,
|
||||
key: &str,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
Ok(get_property(emacs, key)?
|
||||
get_property(emacs, key)?
|
||||
.map(Token::as_atom)
|
||||
.map_or(Ok(None), |r| r.map(Some))?
|
||||
.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.
|
||||
@@ -301,8 +291,8 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn assert_no_children<'b, 's>(
|
||||
emacs: &'b Token<'s>,
|
||||
pub(crate) fn assert_no_children(
|
||||
emacs: &Token<'_>,
|
||||
this_status: &mut DiffStatus,
|
||||
message: &mut Option<String>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
@@ -331,7 +321,7 @@ where
|
||||
let rust_key = rust_key.as_ref();
|
||||
let rust_value = rust_value.as_ref();
|
||||
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 message = Some(format!(
|
||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||
@@ -376,13 +366,23 @@ where
|
||||
)?;
|
||||
ret.push(diff);
|
||||
}
|
||||
AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => {
|
||||
let diff = compare_property_list_of_list_of_list_of_ast_nodes(
|
||||
AffiliatedKeywordValue::OptionalPair { optval, val } => {
|
||||
let diff = compare_property_optional_pair(
|
||||
source,
|
||||
emacs,
|
||||
rust,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
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",
|
||||
"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"),
|
||||
("LABEL", "NAME"),
|
||||
("RESNAME", "NAME"),
|
||||
@@ -20,7 +32,7 @@ pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&'static str,
|
||||
("HEADERS", "HEADER"),
|
||||
];
|
||||
|
||||
pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&'static str; 23] = [
|
||||
pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&str; 23] = [
|
||||
"id",
|
||||
"eww",
|
||||
"rmail",
|
||||
|
||||
@@ -9,12 +9,9 @@ use super::list::List;
|
||||
use super::DynContextMatcher;
|
||||
use super::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::OrgSource;
|
||||
use crate::types::Keyword;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ContextElement<'r, 's> {
|
||||
/// Stores a parser that indicates that children should exit upon matching an exit matcher.
|
||||
ExitMatcherNode(ExitMatcherNode<'r>),
|
||||
@@ -25,37 +22,17 @@ pub(crate) enum ContextElement<'r, 's> {
|
||||
/// Indicates if elements should consume the whitespace after them.
|
||||
ConsumeTrailingWhitespace(bool),
|
||||
|
||||
/// Indicate that we are parsing a paragraph that already has affiliated keywords.
|
||||
///
|
||||
/// The value stored is the start of the element after the affiliated keywords. In this way, we can ensure that we do not exit an element immediately after the affiliated keyword had been consumed.
|
||||
HasAffiliatedKeyword(HasAffiliatedKeywordInner<'r, 's>),
|
||||
|
||||
/// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement.
|
||||
#[allow(dead_code)]
|
||||
Placeholder(PhantomData<&'s str>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct HasAffiliatedKeywordInner<'r, 's> {
|
||||
pub(crate) start_after_affiliated_keywords: OrgSource<'s>,
|
||||
pub(crate) keywords: &'r Vec<Keyword<'s>>,
|
||||
}
|
||||
|
||||
pub(crate) struct ExitMatcherNode<'r> {
|
||||
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
|
||||
pub(crate) exit_matcher: &'r DynContextMatcher<'r>,
|
||||
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> {
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
tree: List<'r, &'r ContextElement<'r, 's>>,
|
||||
@@ -120,27 +97,22 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
||||
pub(crate) fn check_exit_matcher(
|
||||
&'r self,
|
||||
i: OrgSource<'s>,
|
||||
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
|
||||
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError> {
|
||||
let mut current_class_filter = ExitClass::Gamma;
|
||||
for current_node in self.iter_context() {
|
||||
let context_element = current_node.get_data();
|
||||
match context_element {
|
||||
ContextElement::ExitMatcherNode(exit_matcher) => {
|
||||
if exit_matcher.class as u32 <= current_class_filter as u32 {
|
||||
current_class_filter = exit_matcher.class;
|
||||
let local_result = (exit_matcher.exit_matcher)(¤t_node, i);
|
||||
if local_result.is_ok() {
|
||||
return local_result;
|
||||
}
|
||||
if let ContextElement::ExitMatcherNode(exit_matcher) = context_element {
|
||||
if exit_matcher.class as u32 <= current_class_filter as u32 {
|
||||
current_class_filter = exit_matcher.class;
|
||||
let local_result = (exit_matcher.exit_matcher)(¤t_node, i);
|
||||
if local_result.is_ok() {
|
||||
return local_result;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
// TODO: Make this a specific error instead of just a generic MyError
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoExit".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("NoExit")));
|
||||
}
|
||||
|
||||
/// Indicates if elements should consume the whitespace after them.
|
||||
@@ -152,11 +124,8 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
||||
|
||||
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
|
||||
for current_node in self.iter() {
|
||||
match current_node {
|
||||
ContextElement::ConsumeTrailingWhitespace(should) => {
|
||||
return Some(*should);
|
||||
}
|
||||
_ => {}
|
||||
if let ContextElement::ConsumeTrailingWhitespace(should) = current_node {
|
||||
return Some(*should);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ExitClass {
|
||||
Document = 1,
|
||||
Alpha = 2,
|
||||
Beta = 3,
|
||||
Gamma = 4,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExitClass {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub trait FileAccessInterface: Debug {
|
||||
#[cfg(any(feature = "compare", feature = "foreign_document_test"))]
|
||||
pub trait FileAccessInterface: Sync {
|
||||
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))]
|
||||
pub trait FileAccessInterface {
|
||||
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LocalFileAccessInterface {
|
||||
pub working_directory: Option<PathBuf>,
|
||||
}
|
||||
@@ -14,10 +19,9 @@ impl FileAccessInterface for LocalFileAccessInterface {
|
||||
fn read_file(&self, path: &str) -> Result<String, std::io::Error> {
|
||||
let final_path = self
|
||||
.working_directory
|
||||
.as_ref()
|
||||
.map(PathBuf::as_path)
|
||||
.as_deref()
|
||||
.map(|pb| pb.join(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::FileAccessInterface;
|
||||
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::Object;
|
||||
|
||||
// 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 radio_targets: Vec<&'g Vec<Object<'s>>>,
|
||||
pub file_access: &'g dyn FileAccessInterface,
|
||||
@@ -58,26 +54,6 @@ pub struct GlobalSettings<'g, 's> {
|
||||
///
|
||||
/// Corresponds to org-entities elisp variable.
|
||||
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;
|
||||
@@ -112,10 +88,6 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
||||
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
|
||||
link_templates: BTreeMap::new(),
|
||||
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 {
|
||||
Odd,
|
||||
|
||||
#[default]
|
||||
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> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ impl<'a, T> Iterator for IterList<'a, T> {
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::error::Res;
|
||||
use crate::parser::OrgSource;
|
||||
|
||||
mod constants;
|
||||
pub(crate) mod constants;
|
||||
#[allow(clippy::module_inception)]
|
||||
mod context;
|
||||
mod exiting;
|
||||
mod file_access_interface;
|
||||
@@ -22,7 +23,6 @@ type DynMatcher<'c> = dyn Matcher + 'c;
|
||||
pub(crate) use context::Context;
|
||||
pub(crate) use context::ContextElement;
|
||||
pub(crate) use context::ExitMatcherNode;
|
||||
pub(crate) use context::HasAffiliatedKeywordInner;
|
||||
pub(crate) use exiting::ExitClass;
|
||||
pub use file_access_interface::FileAccessInterface;
|
||||
pub use file_access_interface::LocalFileAccessInterface;
|
||||
@@ -31,4 +31,5 @@ pub use global_settings::GlobalSettings;
|
||||
pub use global_settings::HeadlineLevelFilter;
|
||||
pub use global_settings::DEFAULT_TAB_WIDTH;
|
||||
pub(crate) use list::List;
|
||||
pub(crate) use parser_with_context::bind_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;
|
||||
|
||||
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::IResult;
|
||||
|
||||
pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>;
|
||||
pub(crate) type Res<T, U> = IResult<T, U, CustomError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CustomError<I> {
|
||||
MyError(MyError<&'static str>),
|
||||
Nom(I, ErrorKind),
|
||||
pub enum CustomError {
|
||||
#[allow(dead_code)]
|
||||
Text(String),
|
||||
Static(&'static str),
|
||||
IO(std::io::Error),
|
||||
BoxedError(Box<dyn std::error::Error>),
|
||||
Parser(ErrorKind),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MyError<I>(pub(crate) I);
|
||||
|
||||
impl<I> ParseError<I> for CustomError<I> {
|
||||
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
|
||||
CustomError::Nom(input, kind)
|
||||
impl<I: std::fmt::Debug> ParseError<I> for CustomError {
|
||||
fn from_error_kind(_input: I, kind: ErrorKind) -> Self {
|
||||
CustomError::Parser(kind)
|
||||
}
|
||||
|
||||
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 {
|
||||
CustomError::IO(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> From<&'static str> for CustomError<I> {
|
||||
impl From<&'static str> for CustomError {
|
||||
fn from(value: &'static str) -> Self {
|
||||
CustomError::MyError(MyError(value))
|
||||
CustomError::Static(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
|
||||
fn from(value: Box<dyn std::error::Error>) -> Self {
|
||||
CustomError::BoxedError(value)
|
||||
impl From<String> for CustomError {
|
||||
fn from(value: String) -> Self {
|
||||
CustomError::Text(value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#[allow(clippy::module_inception)]
|
||||
mod error;
|
||||
pub(crate) use error::CustomError;
|
||||
pub(crate) use error::MyError;
|
||||
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")]
|
||||
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.
|
||||
|
||||
|
||||
@@ -2,13 +2,19 @@
|
||||
#![feature(trait_alias)]
|
||||
#![feature(path_file_prefix)]
|
||||
#![feature(is_sorted)]
|
||||
#![feature(test)]
|
||||
// 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")]
|
||||
pub mod compare;
|
||||
|
||||
mod context;
|
||||
mod error;
|
||||
#[cfg(feature = "event_count")]
|
||||
pub mod event_count;
|
||||
mod iter;
|
||||
pub mod parser;
|
||||
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")]
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
let result = rt.block_on(async {
|
||||
rt.block_on(async {
|
||||
init_telemetry()?;
|
||||
let main_body_result = main_body();
|
||||
shutdown_telemetry()?;
|
||||
main_body_result
|
||||
});
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
#[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>> {
|
||||
let rust_parsed = parse(org_contents.as_ref())?;
|
||||
let org_contents = org_contents.as_ref();
|
||||
let rust_parsed = parse(org_contents)?;
|
||||
println!("{:#?}", rust_parsed);
|
||||
#[cfg(feature = "event_count")]
|
||||
organic::event_count::report(org_contents);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
working_directory: Some(parent_directory.to_path_buf()),
|
||||
};
|
||||
let global_settings = {
|
||||
let mut global_settings = GlobalSettings::default();
|
||||
global_settings.file_access = &file_access_interface;
|
||||
global_settings
|
||||
let global_settings = GlobalSettings {
|
||||
file_access: &file_access_interface,
|
||||
..Default::default()
|
||||
};
|
||||
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
|
||||
println!("{:#?}", rust_parsed);
|
||||
#[cfg(feature = "event_count")]
|
||||
organic::event_count::report(org_contents);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,122 +1,193 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::take_until;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::combinator::all_consuming;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::map_parser;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::object_parser::standard_set_object;
|
||||
use crate::context::parser_with_context;
|
||||
use super::util::confine_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::ContextElement;
|
||||
use crate::context::GlobalSettings;
|
||||
use crate::context::List;
|
||||
use crate::error::Res;
|
||||
use crate::types::AffiliatedKeywordValue;
|
||||
use crate::types::AffiliatedKeywords;
|
||||
use crate::types::Keyword;
|
||||
|
||||
pub(crate) fn parse_affiliated_keywords<'g, 's>(
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
input: Vec<Keyword<'s>>,
|
||||
) -> AffiliatedKeywords<'s> {
|
||||
let mut ret = BTreeMap::new();
|
||||
for kw in input.into_iter() {
|
||||
let translated_name = translate_name(global_settings, kw.key);
|
||||
if is_single_string_keyword(global_settings, translated_name.as_str()) {
|
||||
ret.insert(
|
||||
translated_name,
|
||||
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);
|
||||
}
|
||||
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
|
||||
list_of_strings.clear();
|
||||
list_of_strings.push(kw.value);
|
||||
}
|
||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
||||
}
|
||||
} else if is_list_of_objects_keyword(global_settings, translated_name.as_str()) {
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
||||
#[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;
|
||||
|
||||
// TODO: This should be omitting footnote references
|
||||
let (_remaining, objects) = all_consuming(many0(parser_with_context!(
|
||||
standard_set_object
|
||||
)(&initial_context)))(kw.value.into())
|
||||
.expect("Object parser should always succeed.");
|
||||
let list_of_lists = ret.entry(translated_name).or_insert_with(|| {
|
||||
AffiliatedKeywordValue::ListOfListsOfObjects(Vec::with_capacity(1))
|
||||
});
|
||||
match list_of_lists {
|
||||
AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => {
|
||||
list_of_lists.push(objects);
|
||||
}
|
||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
||||
loop {
|
||||
let result = affiliated_keyword(remaining);
|
||||
match result {
|
||||
Ok((remain, kw)) => {
|
||||
remaining = remain;
|
||||
ret.push(kw);
|
||||
}
|
||||
} else {
|
||||
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) => {
|
||||
list_of_strings.push(kw.value);
|
||||
}
|
||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((remaining, ret))
|
||||
}
|
||||
|
||||
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
input: AK,
|
||||
) -> AffiliatedKeywords<'s>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let mut ret = BTreeMap::new();
|
||||
for kw in input {
|
||||
let translated_name = translate_name(kw.key);
|
||||
let keyword_type = identify_keyword_type(translated_name.as_str());
|
||||
match keyword_type {
|
||||
AffiliatedKeywordType::SingleString => {
|
||||
ret.insert(
|
||||
translated_name,
|
||||
AffiliatedKeywordValue::SingleString(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) => {
|
||||
list_of_strings.push(kw.value);
|
||||
}
|
||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
||||
}
|
||||
}
|
||||
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 = Context::new(global_settings, List::new(&initial_context));
|
||||
|
||||
let (_remaining, optional_objects) = opt(all_consuming(map(
|
||||
tuple((
|
||||
take_until("["),
|
||||
tag("["),
|
||||
map_parser(
|
||||
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
|
||||
confine_context(|i| {
|
||||
all_consuming(many0(bind_context!(
|
||||
standard_set_object,
|
||||
&initial_context
|
||||
)))(i)
|
||||
}),
|
||||
),
|
||||
tag("]"),
|
||||
eof,
|
||||
)),
|
||||
|(_, _, objects, _, _)| objects,
|
||||
)))(kw.key.into())
|
||||
.expect("Object parser should always succeed.");
|
||||
|
||||
// TODO: This should be omitting footnote references
|
||||
let (_remaining, objects) = all_consuming(many0(bind_context!(
|
||||
standard_set_object,
|
||||
&initial_context
|
||||
)))(kw.value.into())
|
||||
.expect("Object parser should always succeed.");
|
||||
|
||||
let entry_per_keyword_list = ret
|
||||
.entry(translated_name)
|
||||
.or_insert_with(|| AffiliatedKeywordValue::ObjectTree(Vec::with_capacity(1)));
|
||||
match entry_per_keyword_list {
|
||||
AffiliatedKeywordValue::ObjectTree(entry_per_keyword_list) => {
|
||||
entry_per_keyword_list.push((optional_objects, objects));
|
||||
}
|
||||
_ => panic!("Invalid AffiliatedKeywordValue type."),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
AffiliatedKeywords { keywords: ret }
|
||||
}
|
||||
|
||||
fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String {
|
||||
for (src, dst) in global_settings.element_keyword_translation_alist {
|
||||
if name.eq_ignore_ascii_case(src) {
|
||||
fn translate_name(name: &str) -> String {
|
||||
let name_until_optval = name
|
||||
.split_once('[')
|
||||
.map(|(before, _after)| before)
|
||||
.unwrap_or(name);
|
||||
for (src, dst) in ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST {
|
||||
if name_until_optval.eq_ignore_ascii_case(src) {
|
||||
return dst.to_lowercase();
|
||||
}
|
||||
}
|
||||
name.to_lowercase()
|
||||
name_until_optval.to_lowercase()
|
||||
}
|
||||
|
||||
fn is_single_string_keyword<'g, 's>(
|
||||
_global_settings: &'g GlobalSettings<'g, 's>,
|
||||
name: &'s str,
|
||||
) -> bool {
|
||||
// TODO: Is this defined by an elisp variable?
|
||||
for single_string_name in ["plot", "name"] {
|
||||
if name.eq_ignore_ascii_case(single_string_name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
enum AffiliatedKeywordType {
|
||||
SingleString,
|
||||
ListOfStrings,
|
||||
OptionalPair,
|
||||
ObjectTree,
|
||||
}
|
||||
|
||||
fn is_list_of_single_string_keyword<'g, 's>(
|
||||
_global_settings: &'g GlobalSettings<'g, 's>,
|
||||
name: &'s str,
|
||||
) -> bool {
|
||||
// TODO: Is this defined by an elisp variable?
|
||||
for single_string_name in ["results"] {
|
||||
if name.eq_ignore_ascii_case(single_string_name) {
|
||||
return true;
|
||||
}
|
||||
fn identify_keyword_type(name: &str) -> AffiliatedKeywordType {
|
||||
let is_multiple = ["CAPTION", "HEADER"]
|
||||
.into_iter()
|
||||
.any(|candidate| name.eq_ignore_ascii_case(candidate))
|
||||
|| name.to_lowercase().starts_with("attr_");
|
||||
let is_parsed = ORG_ELEMENT_PARSED_KEYWORDS
|
||||
.iter()
|
||||
.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
|
||||
}
|
||||
|
||||
@@ -9,13 +9,11 @@ use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
use nom::InputTake;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::BracketDepth;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use super::util::start_of_line;
|
||||
@@ -23,22 +21,25 @@ use super::OrgSource;
|
||||
use crate::context::Matcher;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::util::org_line_ending;
|
||||
use crate::types::BabelCall;
|
||||
use crate::types::Keyword;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn babel_call<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn babel_call<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, BabelCall<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
|
||||
) -> Res<OrgSource<'s>, BabelCall<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
start_of_line(remaining)?;
|
||||
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
|
||||
|
||||
@@ -64,8 +65,7 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
let (remaining, _ws) = space0(remaining)?;
|
||||
let (remaining, (value, (call, inside_header, arguments, end_header))) =
|
||||
consumed(babel_call_value)(remaining)?;
|
||||
let (remaining, (value, babel_call_value)) = consumed(babel_call_value)(remaining)?;
|
||||
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||
|
||||
let (remaining, _trailing_ws) =
|
||||
@@ -81,33 +81,36 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>(
|
||||
affiliated_keywords,
|
||||
),
|
||||
value: Into::<&str>::into(value).trim_end(),
|
||||
call: call.map(Into::<&str>::into),
|
||||
inside_header: inside_header.map(Into::<&str>::into),
|
||||
arguments: arguments.map(Into::<&str>::into),
|
||||
end_header: end_header.map(Into::<&str>::into),
|
||||
call: babel_call_value.call.map(Into::<&str>::into),
|
||||
inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
|
||||
arguments: babel_call_value.arguments.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"))]
|
||||
fn babel_call_value<'s>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<
|
||||
OrgSource<'s>,
|
||||
(
|
||||
Option<OrgSource<'s>>,
|
||||
Option<OrgSource<'s>>,
|
||||
Option<OrgSource<'s>>,
|
||||
Option<OrgSource<'s>>,
|
||||
),
|
||||
> {
|
||||
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
|
||||
let (remaining, call) = opt(babel_call_call)(input)?;
|
||||
let (remaining, inside_header) = opt(inside_header)(remaining)?;
|
||||
let (remaining, arguments) = opt(arguments)(remaining)?;
|
||||
let (remaining, end_header) = opt(end_header)(remaining)?;
|
||||
Ok((
|
||||
remaining,
|
||||
(call, inside_header, arguments.flatten(), end_header),
|
||||
BabelCallValue {
|
||||
call,
|
||||
inside_header,
|
||||
arguments: arguments.flatten(),
|
||||
end_header,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -213,9 +216,7 @@ fn impl_balanced_bracket<
|
||||
}
|
||||
|
||||
if fail_parser(remaining).is_ok() {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Fail parser matched.",
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("Fail parser matched.")));
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||
if close_bracket.is_ok() {
|
||||
return close_bracket;
|
||||
}
|
||||
@@ -191,7 +191,7 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
|
||||
unreachable!("Exceeded citation global suffix bracket depth.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||
if close_bracket.is_ok() {
|
||||
return close_bracket;
|
||||
}
|
||||
@@ -205,6 +205,7 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::GlobalSettings;
|
||||
use crate::context::List;
|
||||
@@ -219,7 +220,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let paragraph_matcher = 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 first_paragraph = match first_paragraph {
|
||||
Element::Paragraph(paragraph) => paragraph,
|
||||
|
||||
@@ -20,7 +20,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::object_parser::minimal_set_object;
|
||||
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.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||
if close_bracket.is_ok() {
|
||||
return close_bracket;
|
||||
}
|
||||
@@ -181,7 +180,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
|
||||
unreachable!("Exceeded citation key suffix bracket depth.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("]")(input);
|
||||
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||
if close_bracket.is_ok() {
|
||||
return close_bracket;
|
||||
}
|
||||
@@ -199,9 +198,7 @@ where
|
||||
let pre_bracket_depth = input.get_bracket_depth();
|
||||
let (remaining, output) = inner(input)?;
|
||||
if remaining.get_bracket_depth() - pre_bracket_depth != 0 {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"UnbalancedBrackets".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("UnbalancedBrackets")));
|
||||
}
|
||||
Ok((remaining, output))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::immediate_in_section;
|
||||
@@ -35,9 +34,9 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Comment<'s>> {
|
||||
if immediate_in_section(context, "comment") {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Cannot nest objects of the same element".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Cannot nest objects of the same element",
|
||||
)));
|
||||
}
|
||||
let parser_context = ContextElement::Context("comment");
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::GlobalSettings;
|
||||
@@ -119,7 +119,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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");
|
||||
assert_eq!(
|
||||
Into::<&str>::into(remaining),
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many0;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use super::util::org_line_ending;
|
||||
@@ -14,16 +12,21 @@ use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::DiarySexp;
|
||||
use crate::types::Keyword;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn 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>, DiarySexp<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, DiarySexp<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
start_of_line(remaining)?;
|
||||
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
|
||||
let (remaining, _eol) = org_line_ending(remaining)?;
|
||||
@@ -44,9 +47,19 @@ pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn detect_diary_sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
||||
tuple((start_of_line, tag("%%(")))(input)?;
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
|
||||
)]
|
||||
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, ()))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use super::in_buffer_settings::scan_for_in_buffer_settings;
|
||||
use super::org_source::OrgSource;
|
||||
use super::section::zeroth_section;
|
||||
use super::util::get_consumed;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::ContextElement;
|
||||
@@ -18,9 +19,7 @@ use crate::context::GlobalSettings;
|
||||
use crate::context::List;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::org_source::convert_error;
|
||||
use crate::parser::util::blank_line;
|
||||
use crate::types::AstNode;
|
||||
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.
|
||||
#[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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_file<'s, P: AsRef<Path>>(
|
||||
input: &'s str,
|
||||
pub fn parse_file<P: AsRef<Path>>(
|
||||
input: &str,
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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 wrapped_input = OrgSource::new(input);
|
||||
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(|(_remaining, parsed_document)| parsed_document)?;
|
||||
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".
|
||||
#[allow(dead_code)]
|
||||
fn document<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: &'s str,
|
||||
) -> Res<&'s str, Document<'s>> {
|
||||
let (remaining, doc) = document_org_source(context, input.into()).map_err(convert_error)?;
|
||||
fn document<'s>(context: RefContext<'_, '_, '_, 's>, input: &'s str) -> Res<&'s str, Document<'s>> {
|
||||
let (remaining, doc) = document_org_source(context, input.into())?;
|
||||
Ok((Into::<&str>::into(remaining), doc))
|
||||
}
|
||||
|
||||
@@ -129,29 +125,16 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
||||
.get_global_settings()
|
||||
.file_access
|
||||
.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<_>, _>>()?;
|
||||
for setup_file in setup_files.iter().map(String::as_str) {
|
||||
let (_, setup_file_settings) =
|
||||
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(),
|
||||
)))
|
||||
})?;
|
||||
let (_, setup_file_settings) = scan_for_in_buffer_settings(setup_file.into())?;
|
||||
final_settings.extend(setup_file_settings);
|
||||
}
|
||||
final_settings.extend(document_settings);
|
||||
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
|
||||
.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(),
|
||||
)))
|
||||
})?;
|
||||
.map_err(nom::Err::Error)?;
|
||||
let new_context = context.with_global_settings(&new_settings);
|
||||
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 (remaining, mut document) = _document(&parser_context, input)
|
||||
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
||||
apply_post_parse_in_buffer_settings(&mut document)
|
||||
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
||||
apply_post_parse_in_buffer_settings(&mut document);
|
||||
return Ok((remaining.into(), document));
|
||||
}
|
||||
}
|
||||
|
||||
// Find final in-buffer settings that do not impact parsing
|
||||
apply_post_parse_in_buffer_settings(&mut document)
|
||||
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
||||
apply_post_parse_in_buffer_settings(&mut 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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,10 @@ use nom::character::complete::space0;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use crate::context::parser_with_context;
|
||||
@@ -21,7 +19,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::element_parser::element;
|
||||
use crate::parser::util::blank_line;
|
||||
@@ -32,23 +29,28 @@ use crate::parser::util::start_of_line;
|
||||
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
|
||||
use crate::types::Drawer;
|
||||
use crate::types::Element;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::Paragraph;
|
||||
use crate::types::SetSource;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn drawer<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn drawer<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Drawer<'s>> {
|
||||
) -> Res<OrgSource<'s>, Drawer<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
if immediate_in_section(context, "drawer") {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Cannot nest objects of the same element".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Cannot nest objects of the same element",
|
||||
)));
|
||||
}
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
start_of_line(remaining)?;
|
||||
let (remaining, _leading_whitespace) = space0(remaining)?;
|
||||
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
|
||||
|
||||
@@ -18,7 +18,6 @@ use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use crate::context::parser_with_context;
|
||||
@@ -27,7 +26,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::element_parser::element;
|
||||
use crate::parser::util::blank_line;
|
||||
@@ -37,23 +35,28 @@ use crate::parser::util::immediate_in_section;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::DynamicBlock;
|
||||
use crate::types::Element;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::Paragraph;
|
||||
use crate::types::SetSource;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn dynamic_block<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
|
||||
) -> Res<OrgSource<'s>, DynamicBlock<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
if immediate_in_section(context, "dynamic block") {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Cannot nest objects of the same element".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Cannot nest objects of the same element",
|
||||
)));
|
||||
}
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
|
||||
start_of_line(remaining)?;
|
||||
let (remaining, _leading_whitespace) = space0(remaining)?;
|
||||
@@ -75,10 +78,7 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
||||
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[2]);
|
||||
let parameters = match parameters {
|
||||
Some((_ws, parameters)) => Some(parameters),
|
||||
None => None,
|
||||
};
|
||||
let parameters = parameters.map(|(_ws, parameters)| parameters);
|
||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
not(exit_matcher)(remaining)?;
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
use nom::branch::alt;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::sequence::tuple;
|
||||
#[cfg(feature = "tracing")]
|
||||
use tracing::span;
|
||||
|
||||
use super::babel_call::babel_call;
|
||||
use super::clock::clock;
|
||||
use super::comment::comment;
|
||||
@@ -20,8 +12,6 @@ use super::footnote_definition::detect_footnote_definition;
|
||||
use super::footnote_definition::footnote_definition;
|
||||
use super::greater_block::greater_block;
|
||||
use super::horizontal_rule::horizontal_rule;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::keyword::affiliated_keyword_as_regular_keyword;
|
||||
use super::keyword::keyword;
|
||||
use super::latex_environment::latex_environment;
|
||||
use super::lesser_block::comment_block;
|
||||
@@ -34,11 +24,18 @@ use super::paragraph::paragraph;
|
||||
use super::plain_list::detect_plain_list;
|
||||
use super::plain_list::plain_list;
|
||||
use super::table::detect_table;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
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::element;
|
||||
use crate::parser::table::org_mode_table;
|
||||
use crate::types::Element;
|
||||
|
||||
@@ -60,117 +57,207 @@ fn _element<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
can_be_paragraph: bool,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
let plain_list_matcher = parser_with_context!(plain_list)(context);
|
||||
let greater_block_matcher = parser_with_context!(greater_block)(context);
|
||||
let dynamic_block_matcher = parser_with_context!(dynamic_block)(context);
|
||||
let footnote_definition_matcher = parser_with_context!(footnote_definition)(context);
|
||||
let comment_matcher = parser_with_context!(comment)(context);
|
||||
let drawer_matcher = parser_with_context!(drawer)(context);
|
||||
let table_matcher = parser_with_context!(org_mode_table)(context);
|
||||
let verse_block_matcher = parser_with_context!(verse_block)(context);
|
||||
let comment_block_matcher = parser_with_context!(comment_block)(context);
|
||||
let example_block_matcher = parser_with_context!(example_block)(context);
|
||||
let export_block_matcher = parser_with_context!(export_block)(context);
|
||||
let src_block_matcher = parser_with_context!(src_block)(context);
|
||||
let clock_matcher = parser_with_context!(clock)(context);
|
||||
let diary_sexp_matcher = parser_with_context!(diary_sexp)(context);
|
||||
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
|
||||
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
|
||||
let keyword_matcher = parser_with_context!(keyword)(context);
|
||||
let babel_keyword_matcher = parser_with_context!(babel_call)(context);
|
||||
let paragraph_matcher = parser_with_context!(paragraph)(context);
|
||||
let latex_environment_matcher = parser_with_context!(latex_environment)(context);
|
||||
#[cfg(feature = "event_count")]
|
||||
record_event(EventType::ElementStart, input);
|
||||
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
|
||||
|
||||
let (mut remaining, mut maybe_element) = {
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = span!(tracing::Level::DEBUG, "Main element block");
|
||||
#[cfg(feature = "tracing")]
|
||||
let _enter = span.enter();
|
||||
let mut affiliated_keywords = affiliated_keywords.into_iter();
|
||||
|
||||
opt(alt((
|
||||
map(plain_list_matcher, Element::PlainList),
|
||||
greater_block_matcher,
|
||||
map(dynamic_block_matcher, Element::DynamicBlock),
|
||||
map(footnote_definition_matcher, Element::FootnoteDefinition),
|
||||
map(comment_matcher, Element::Comment),
|
||||
map(drawer_matcher, Element::Drawer),
|
||||
map(table_matcher, Element::Table),
|
||||
map(verse_block_matcher, Element::VerseBlock),
|
||||
map(comment_block_matcher, Element::CommentBlock),
|
||||
map(example_block_matcher, Element::ExampleBlock),
|
||||
map(export_block_matcher, Element::ExportBlock),
|
||||
map(src_block_matcher, Element::SrcBlock),
|
||||
map(clock_matcher, Element::Clock),
|
||||
map(diary_sexp_matcher, Element::DiarySexp),
|
||||
map(fixed_width_area_matcher, Element::FixedWidthArea),
|
||||
map(horizontal_rule_matcher, Element::HorizontalRule),
|
||||
map(latex_environment_matcher, Element::LatexEnvironment),
|
||||
map(babel_keyword_matcher, Element::BabelCall),
|
||||
map(keyword_matcher, Element::Keyword),
|
||||
)))(input)?
|
||||
};
|
||||
ak_element!(
|
||||
plain_list,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::PlainList
|
||||
);
|
||||
|
||||
if maybe_element.is_none() && can_be_paragraph {
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = span!(tracing::Level::DEBUG, "Paragraph with affiliated keyword.");
|
||||
#[cfg(feature = "tracing")]
|
||||
let _enter = span.enter();
|
||||
ak_element!(
|
||||
greater_block,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input
|
||||
);
|
||||
|
||||
let (remain, paragraph_with_affiliated_keyword) = opt(map(
|
||||
tuple((
|
||||
peek(affiliated_keyword),
|
||||
map(paragraph_matcher, Element::Paragraph),
|
||||
)),
|
||||
|(_, paragraph)| paragraph,
|
||||
))(remaining)?;
|
||||
if paragraph_with_affiliated_keyword.is_some() {
|
||||
remaining = remain;
|
||||
maybe_element = paragraph_with_affiliated_keyword;
|
||||
}
|
||||
}
|
||||
ak_element!(
|
||||
dynamic_block,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::DynamicBlock
|
||||
);
|
||||
|
||||
if maybe_element.is_none() {
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = span!(
|
||||
tracing::Level::DEBUG,
|
||||
"Affiliated keyword as regular keyword."
|
||||
ak_element!(
|
||||
footnote_definition,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::FootnoteDefinition
|
||||
);
|
||||
|
||||
element!(comment, context, input, Element::Comment);
|
||||
|
||||
ak_element!(
|
||||
drawer,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::Drawer
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
org_mode_table,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::Table
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
verse_block,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::VerseBlock
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
comment_block,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::CommentBlock
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
example_block,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::ExampleBlock
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
export_block,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::ExportBlock
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
src_block,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::SrcBlock
|
||||
);
|
||||
|
||||
element!(clock, context, input, Element::Clock);
|
||||
|
||||
ak_element!(
|
||||
diary_sexp,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::DiarySexp
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
fixed_width_area,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::FixedWidthArea
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
horizontal_rule,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::HorizontalRule
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
latex_environment,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::LatexEnvironment
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
babel_call,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::BabelCall
|
||||
);
|
||||
|
||||
// Keyword with affiliated keywords
|
||||
ak_element!(
|
||||
keyword,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::Keyword
|
||||
);
|
||||
|
||||
if can_be_paragraph {
|
||||
// Paragraph with affiliated keyword
|
||||
ak_element!(
|
||||
paragraph,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input,
|
||||
Element::Paragraph
|
||||
);
|
||||
#[cfg(feature = "tracing")]
|
||||
let _enter = span.enter();
|
||||
|
||||
let (remain, kw) = opt(map(
|
||||
parser_with_context!(affiliated_keyword_as_regular_keyword)(context),
|
||||
Element::Keyword,
|
||||
))(remaining)?;
|
||||
if kw.is_some() {
|
||||
maybe_element = kw;
|
||||
remaining = remain;
|
||||
}
|
||||
}
|
||||
|
||||
if maybe_element.is_none() && can_be_paragraph {
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = span!(
|
||||
tracing::Level::DEBUG,
|
||||
"Paragraph without affiliated keyword."
|
||||
// Keyword without affiliated keywords
|
||||
ak_element!(
|
||||
keyword,
|
||||
std::iter::empty(),
|
||||
input,
|
||||
context,
|
||||
input,
|
||||
Element::Keyword
|
||||
);
|
||||
|
||||
if can_be_paragraph {
|
||||
// Fake paragraphs
|
||||
element!(bullshitium, context, input, Element::Paragraph);
|
||||
|
||||
// Paragraph without affiliated keyword
|
||||
ak_element!(
|
||||
paragraph,
|
||||
std::iter::empty(),
|
||||
input,
|
||||
context,
|
||||
input,
|
||||
Element::Paragraph
|
||||
);
|
||||
#[cfg(feature = "tracing")]
|
||||
let _enter = span.enter();
|
||||
|
||||
let (remain, paragraph_without_affiliated_keyword) =
|
||||
map(paragraph_matcher, Element::Paragraph)(remaining)?;
|
||||
remaining = remain;
|
||||
maybe_element = Some(paragraph_without_affiliated_keyword);
|
||||
}
|
||||
|
||||
if maybe_element.is_none() {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No element.",
|
||||
))));
|
||||
}
|
||||
let element = maybe_element.expect("The above if-statement ensures this is Some().");
|
||||
|
||||
Ok((remaining, element))
|
||||
Err(nom::Err::Error(CustomError::Static("No element.")))
|
||||
}
|
||||
|
||||
pub(crate) const fn detect_element(
|
||||
@@ -189,22 +276,60 @@ fn _detect_element<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
can_be_paragraph: bool,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
if alt((
|
||||
parser_with_context!(detect_plain_list)(context),
|
||||
let (post_affiliated_keywords_input, affiliated_keywords) = affiliated_keywords(input)?;
|
||||
|
||||
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,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
detect_diary_sexp,
|
||||
detect_comment,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input
|
||||
);
|
||||
|
||||
element!(detect_comment, input);
|
||||
|
||||
ak_element!(
|
||||
detect_fixed_width_area,
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input
|
||||
);
|
||||
|
||||
ak_element!(
|
||||
detect_table,
|
||||
))(input)
|
||||
.is_ok()
|
||||
{
|
||||
return Ok((input, ()));
|
||||
&mut affiliated_keywords,
|
||||
post_affiliated_keywords_input,
|
||||
context,
|
||||
input
|
||||
);
|
||||
|
||||
// Fake paragraphs
|
||||
if !can_be_paragraph {
|
||||
element!(detect_bullshitium, context, input);
|
||||
}
|
||||
|
||||
if _element(context, input, can_be_paragraph).is_ok() {
|
||||
return Ok((input, ()));
|
||||
}
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No element detected.".into(),
|
||||
))));
|
||||
|
||||
Err(nom::Err::Error(CustomError::Static("No element detected.")))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::satisfy;
|
||||
use nom::combinator::cond;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
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::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::types::Entity;
|
||||
@@ -58,23 +57,21 @@ fn name<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, (&'g EntityDefinition<'s>, OrgSource<'s>, bool)> {
|
||||
for entity in context.get_global_settings().entities {
|
||||
let result = tuple((
|
||||
tag::<_, _, CustomError<_>>(entity.name),
|
||||
alt((
|
||||
verify(map(tag("{}"), |_| true), |_| !entity.name.ends_with(" ")),
|
||||
map(peek(recognize(entity_end)), |_| false),
|
||||
)),
|
||||
tag::<_, _, CustomError>(entity.name),
|
||||
cond(
|
||||
!entity.name.ends_with(' '),
|
||||
alt((
|
||||
map(tag("{}"), |_| true),
|
||||
map(peek(recognize(entity_end)), |_| false),
|
||||
)),
|
||||
),
|
||||
))(input);
|
||||
match result {
|
||||
Ok((remaining, (ent, use_brackets))) => {
|
||||
return Ok((remaining, (entity, ent, use_brackets)));
|
||||
}
|
||||
Err(_) => {}
|
||||
if let Ok((remaining, (ent, use_brackets))) = result {
|
||||
return Ok((remaining, (entity, ent, use_brackets.unwrap_or(false))));
|
||||
}
|
||||
}
|
||||
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoEntity".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("NoEntity")))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
|
||||
@@ -10,7 +10,6 @@ use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use super::util::org_line_ending;
|
||||
@@ -21,16 +20,21 @@ use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::FixedWidthArea;
|
||||
use crate::types::Keyword;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn 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>, FixedWidthArea<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, FixedWidthArea<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
|
||||
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;
|
||||
@@ -84,14 +88,24 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
|
||||
Ok((remaining, value))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
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((
|
||||
start_of_line,
|
||||
space0,
|
||||
tag(":"),
|
||||
alt((tag(" "), org_line_ending)),
|
||||
))(input)?;
|
||||
))(remaining)?;
|
||||
Ok((input, ()))
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::include_input;
|
||||
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::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::element_parser::element;
|
||||
use crate::parser::util::blank_line;
|
||||
@@ -33,21 +31,26 @@ use crate::parser::util::immediate_in_section;
|
||||
use crate::parser::util::maybe_consume_trailing_whitespace;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::FootnoteDefinition;
|
||||
use crate::types::Keyword;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn 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>, FootnoteDefinition<'s>> {
|
||||
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
if immediate_in_section(context, "footnote definition") {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Cannot nest objects of the same element".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Cannot nest objects of the same element",
|
||||
)));
|
||||
}
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
start_of_line(remaining)?;
|
||||
// Cannot be indented.
|
||||
let (remaining, (_, lbl, _, _, _)) = tuple((
|
||||
@@ -120,7 +123,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
|
||||
let (remaining, source) = alt((
|
||||
recognize(tuple((
|
||||
parser_with_context!(maybe_consume_trailing_whitespace)(context),
|
||||
detect_footnote_definition,
|
||||
|i| detect_footnote_definition(std::iter::empty(), i, context, i),
|
||||
))),
|
||||
recognize(tuple((
|
||||
start_of_line,
|
||||
@@ -133,16 +136,27 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
|
||||
Ok((remaining, source))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
||||
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
|
||||
)]
|
||||
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, ()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::GlobalSettings;
|
||||
use crate::context::List;
|
||||
@@ -160,7 +174,7 @@ line footnote.",
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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) =
|
||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||
let (remaining, second_footnote_definition) =
|
||||
@@ -197,7 +211,7 @@ not in the footnote.",
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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) =
|
||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||
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::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::footnote_definition::label;
|
||||
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;
|
||||
if current_depth > 0 {
|
||||
// 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(
|
||||
"NoFootnoteReferenceDefinitionEnd".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"NoFootnoteReferenceDefinitionEnd",
|
||||
)));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// This shouldn't be possible because if depth is 0 then a closing bracket should end the footnote definition.
|
||||
|
||||
@@ -18,7 +18,6 @@ use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::in_section;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
@@ -29,7 +28,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::element_parser::element;
|
||||
use crate::parser::util::blank_line;
|
||||
@@ -46,23 +44,26 @@ use crate::types::SpecialBlock;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
pub(crate) fn greater_block<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
let pre_affiliated_keywords_input = input;
|
||||
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
pre_affiliated_keywords_input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
start_of_line(input)?;
|
||||
let (remaining, _leading_whitespace) = space0(input)?;
|
||||
let (remaining, (_begin, name)) = tuple((
|
||||
tag_no_case("#+begin_"),
|
||||
verify(name, |name: &OrgSource<'_>| {
|
||||
match Into::<&str>::into(name).to_lowercase().as_str() {
|
||||
"comment" | "example" | "export" | "src" | "verse" => false,
|
||||
_ => true,
|
||||
}
|
||||
!matches!(
|
||||
Into::<&str>::into(name).to_lowercase().as_str(),
|
||||
"comment" | "example" | "export" | "src" | "verse",
|
||||
)
|
||||
}),
|
||||
))(remaining)?;
|
||||
let name = Into::<&str>::into(name);
|
||||
@@ -91,14 +92,17 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
fn center_block<'b, 'g, 'r, 's>(
|
||||
fn center_block<'b, 'g, 'r, 's, AK>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
pre_affiliated_keywords_input: OrgSource<'s>,
|
||||
affiliated_keywords: Vec<Keyword<'s>>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
affiliated_keywords: AK,
|
||||
) -> Res<OrgSource<'s>, Element<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, (source, children)) = greater_block_body(
|
||||
context,
|
||||
input,
|
||||
@@ -121,14 +125,17 @@ fn center_block<'b, 'g, 'r, 's>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
fn quote_block<'b, 'g, 'r, 's>(
|
||||
fn quote_block<'b, 'g, 'r, 's, AK>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
pre_affiliated_keywords_input: OrgSource<'s>,
|
||||
affiliated_keywords: Vec<Keyword<'s>>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
affiliated_keywords: AK,
|
||||
) -> Res<OrgSource<'s>, Element<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, (source, children)) = greater_block_body(
|
||||
context,
|
||||
input,
|
||||
@@ -149,15 +156,18 @@ fn quote_block<'b, 'g, 'r, 's>(
|
||||
))
|
||||
}
|
||||
|
||||
fn special_block<'s>(
|
||||
fn special_block<'s, AK>(
|
||||
name: &'s str,
|
||||
) -> impl for<'b, 'g, 'r> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
OrgSource<'s>,
|
||||
OrgSource<'s>,
|
||||
Vec<Keyword<'s>>,
|
||||
AK,
|
||||
) -> Res<OrgSource<'s>, Element<'s>>
|
||||
+ 's {
|
||||
+ 's
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let context_name = format!("special block {}", name);
|
||||
move |context, input, pre_affiliated_keywords_input, affiliated_keywords| {
|
||||
_special_block(
|
||||
@@ -173,16 +183,19 @@ fn special_block<'s>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
fn _special_block<'c, 'b, 'g, 'r, 's>(
|
||||
fn _special_block<'c, 'b, 'g, 'r, 's, AK>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
pre_affiliated_keywords_input: OrgSource<'s>,
|
||||
name: &'s str,
|
||||
context_name: &'c str,
|
||||
affiliated_keywords: Vec<Keyword<'s>>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
affiliated_keywords: AK,
|
||||
) -> Res<OrgSource<'s>, Element<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
|
||||
let (remaining, (source, children)) = greater_block_body(
|
||||
context,
|
||||
@@ -218,9 +231,9 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
||||
context_name: &'c str,
|
||||
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
|
||||
if in_section(context, context_name) {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Cannot nest objects of the same element".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Cannot nest objects of the same element",
|
||||
)));
|
||||
}
|
||||
let exit_with_name = greater_block_end(name);
|
||||
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
|
||||
@@ -274,7 +287,7 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,18 +18,18 @@ use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::section::section;
|
||||
use super::util::exit_matcher_parser;
|
||||
use super::util::get_consumed;
|
||||
use super::util::org_line_ending;
|
||||
use super::util::org_space;
|
||||
use super::util::org_space_or_line_ending;
|
||||
use super::util::start_of_line;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::util::blank_line;
|
||||
@@ -62,10 +62,10 @@ fn _heading<'b, 'g, 'r, 's>(
|
||||
let mut scheduled = None;
|
||||
let mut deadline = 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 section_matcher = parser_with_context!(section)(context);
|
||||
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
|
||||
let section_matcher = bind_context!(section, context);
|
||||
let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
|
||||
let (remaining, maybe_section) =
|
||||
opt(map(section_matcher, DocumentElement::Section))(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((
|
||||
start_of_line,
|
||||
verify(
|
||||
parser_with_context!(headline_level)(&parser_context),
|
||||
bind_context!(headline_level, &parser_context),
|
||||
|(_, count, _)| *count > parent_star_count,
|
||||
),
|
||||
peek(org_space),
|
||||
@@ -163,7 +163,7 @@ fn headline<'b, 'g, 'r, 's>(
|
||||
|
||||
let (remaining, maybe_todo_keyword) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(heading_keyword)(&parser_context),
|
||||
bind_context!(heading_keyword, &parser_context),
|
||||
peek(org_space_or_line_ending),
|
||||
)))(remaining)?;
|
||||
|
||||
@@ -177,9 +177,7 @@ fn headline<'b, 'g, 'r, 's>(
|
||||
|
||||
let (remaining, maybe_title) = opt(tuple((
|
||||
space1,
|
||||
consumed(many1(parser_with_context!(standard_set_object)(
|
||||
&parser_context,
|
||||
))),
|
||||
consumed(many1(bind_context!(standard_set_object, &parser_context))),
|
||||
)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
|
||||
@@ -206,11 +204,7 @@ fn headline<'b, 'g, 'r, 's>(
|
||||
.map(|(_, (_, title))| title)
|
||||
.unwrap_or(Vec::new()),
|
||||
tags: maybe_tags
|
||||
.map(|(_ws, tags)| {
|
||||
tags.into_iter()
|
||||
.map(|single_tag| Into::<&str>::into(single_tag))
|
||||
.collect()
|
||||
})
|
||||
.map(|(_ws, tags)| tags.into_iter().map(Into::<&str>::into).collect())
|
||||
.unwrap_or(Vec::new()),
|
||||
is_footnote_section,
|
||||
},
|
||||
@@ -264,12 +258,9 @@ fn heading_keyword<'b, 'g, 'r, 's>(
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
{
|
||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, (TodoKeywordType::Todo, ent)));
|
||||
}
|
||||
Err(_) => {}
|
||||
let result = tag::<_, _, CustomError>(todo_keyword)(input);
|
||||
if let Ok((remaining, ent)) = result {
|
||||
return Ok((remaining, (TodoKeywordType::Todo, ent)));
|
||||
}
|
||||
}
|
||||
for todo_keyword in global_settings
|
||||
@@ -277,30 +268,25 @@ fn heading_keyword<'b, 'g, 'r, 's>(
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
{
|
||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, (TodoKeywordType::Done, ent)));
|
||||
}
|
||||
Err(_) => {}
|
||||
let result = tag::<_, _, CustomError>(todo_keyword)(input);
|
||||
if let Ok((remaining, ent)) = result {
|
||||
return Ok((remaining, (TodoKeywordType::Done, ent)));
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoTodoKeyword".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("NoTodoKeyword")))
|
||||
}
|
||||
}
|
||||
|
||||
fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCookie> {
|
||||
fn priority_cookie(input: OrgSource<'_>) -> Res<OrgSource<'_>, PriorityCookie> {
|
||||
let (remaining, (_, priority_character, _)) = tuple((
|
||||
tag("[#"),
|
||||
verify(anychar, |c| c.is_alphanumeric()),
|
||||
tag("]"),
|
||||
))(input)?;
|
||||
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
|
||||
nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Failed to cast priority cookie to number.".into(),
|
||||
)))
|
||||
nom::Err::Error(CustomError::Static(
|
||||
"Failed to cast priority cookie to number.",
|
||||
))
|
||||
})?;
|
||||
Ok((remaining, cookie))
|
||||
}
|
||||
|
||||
@@ -5,12 +5,10 @@ use nom::character::complete::space0;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many1_count;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::get_consumed;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
@@ -18,16 +16,21 @@ use crate::context::RefContext;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::HorizontalRule;
|
||||
use crate::types::Keyword;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn horizontal_rule<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, HorizontalRule<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
start_of_line(remaining)?;
|
||||
let (remaining, _rule) = recognize(tuple((
|
||||
space0,
|
||||
|
||||
@@ -35,7 +35,7 @@ pub(crate) fn scan_for_in_buffer_settings<'s>(
|
||||
let mut remaining = input;
|
||||
loop {
|
||||
// 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 {
|
||||
start_of_pound
|
||||
} 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) {
|
||||
Ok((remain, kw)) => (remain, Some(kw)),
|
||||
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 {
|
||||
(end_of_line, None)
|
||||
} else {
|
||||
@@ -84,11 +84,14 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
|
||||
))(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>(
|
||||
keywords: Vec<Keyword<'sf>>,
|
||||
original_settings: &'g GlobalSettings<'g, 's>,
|
||||
) -> Result<GlobalSettings<'g, 's>, String> {
|
||||
) -> Result<GlobalSettings<'g, 's>, CustomError> {
|
||||
let mut new_settings = original_settings.clone();
|
||||
|
||||
// Todo Keywords
|
||||
@@ -98,7 +101,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
|| kw.key.eq_ignore_ascii_case("typ_todo")
|
||||
}) {
|
||||
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
|
||||
.in_progress_todo_keywords
|
||||
.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()
|
||||
.filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
|
||||
{
|
||||
let (_remaining, settings) =
|
||||
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
|
||||
.map_err(|err: nom::Err<_>| err.to_string())?;
|
||||
let (_remaining, settings) = separated_list0(space1::<&str, CustomError>, is_not(" \t"))(
|
||||
kw.value,
|
||||
)
|
||||
.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") {
|
||||
new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
|
||||
}
|
||||
@@ -128,7 +140,11 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
.iter()
|
||||
.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
|
||||
.link_templates
|
||||
.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.
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
|
||||
document: &mut Document<'s>,
|
||||
) -> Result<(), &'static str> {
|
||||
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(document: &mut Document<'s>) {
|
||||
document.category = Into::<AstNode>::into(&*document)
|
||||
.into_iter()
|
||||
.filter_map(|ast_node| {
|
||||
@@ -154,7 +168,6 @@ pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
|
||||
})
|
||||
.last()
|
||||
.map(|kw| kw.value.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
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;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoHeaderEnd".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// 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;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the argument if we're any amount of parenthesis deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoArgumentEnd".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("NoArgumentEnd")));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// 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::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
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;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the header if we're any amount of bracket deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoHeaderEnd".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("NoHeaderEnd")));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// 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;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the body if we're any amount of brace deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoBodyEnd".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("NoBodyEnd")));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// This shouldn't be possible because if depth is 0 then a closing brace should end the body.
|
||||
|
||||
@@ -4,16 +4,14 @@ use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::bytes::complete::take_while1;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::character::complete::space0;
|
||||
use nom::combinator::consumed;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
@@ -22,24 +20,20 @@ use super::org_source::BracketDepth;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::get_consumed;
|
||||
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::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::macros::element;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::AffiliatedKeywords;
|
||||
use crate::types::Keyword;
|
||||
|
||||
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
|
||||
"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>(
|
||||
pub(crate) fn filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>>(
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -47,7 +41,7 @@ pub(crate) fn filtered_keyword<F: Matcher>(
|
||||
feature = "tracing",
|
||||
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,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
@@ -55,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.
|
||||
let (remaining, (consumed_input, (_, _, parsed_key, _))) =
|
||||
consumed(tuple((space0, tag("#+"), key_parser, tag(":"))))(input)?;
|
||||
match tuple((
|
||||
space0::<OrgSource<'_>, CustomError<OrgSource<'_>>>,
|
||||
alt((line_ending, eof)),
|
||||
))(remaining)
|
||||
{
|
||||
Ok((remaining, _)) => {
|
||||
return Ok((
|
||||
remaining,
|
||||
Keyword {
|
||||
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.
|
||||
key: parsed_key.into(),
|
||||
value: "".into(),
|
||||
},
|
||||
));
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
let (remaining, _ws) = space0(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)?;
|
||||
if let Ok((remaining, _)) = org_line_ending(remaining) {
|
||||
return Ok((
|
||||
remaining,
|
||||
Keyword {
|
||||
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.
|
||||
key: parsed_key.into(),
|
||||
value: "",
|
||||
},
|
||||
));
|
||||
}
|
||||
let (remaining, parsed_value) =
|
||||
recognize(many_till(anychar, peek(tuple((space0, org_line_ending)))))(remaining)?;
|
||||
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||
Ok((
|
||||
remaining,
|
||||
Keyword {
|
||||
@@ -92,13 +77,17 @@ fn _filtered_keyword<'s, F: Matcher>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn keyword<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn keyword<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
|
||||
let (remaining, _trailing_ws) =
|
||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||
@@ -109,22 +98,6 @@ pub(crate) fn keyword<'b, 'g, 'r, 's>(
|
||||
Ok((remaining, kw))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
)]
|
||||
pub(crate) fn affiliated_keyword_as_regular_keyword<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
let (remaining, mut kw) = affiliated_keyword(input)?;
|
||||
let (remaining, _trailing_ws) =
|
||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
kw.source = Into::<&str>::into(source);
|
||||
Ok((remaining, kw))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
filtered_keyword(affiliated_key)(input)
|
||||
@@ -163,45 +136,43 @@ fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
alt((
|
||||
recognize(tuple((dual_affiliated_key, tag("["), optval, tag("]")))),
|
||||
plain_affiliated_key,
|
||||
export_keyword,
|
||||
))(input)
|
||||
element!(dual_affiliated_key, input);
|
||||
element!(plain_affiliated_key, input);
|
||||
element!(export_keyword, input);
|
||||
Err(nom::Err::Error(CustomError::Static("No affiliated key.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn plain_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
for keyword in ORG_ELEMENT_AFFILIATED_KEYWORDS {
|
||||
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, ent));
|
||||
}
|
||||
Err(_) => {}
|
||||
let result = map(
|
||||
tuple((tag_no_case::<_, _, CustomError>(keyword), peek(tag(":")))),
|
||||
|(key, _)| key,
|
||||
)(input);
|
||||
if let Ok((remaining, ent)) = result {
|
||||
return Ok((remaining, ent));
|
||||
}
|
||||
}
|
||||
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoKeywordKey".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn dual_affiliated_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
for keyword in ORG_ELEMENT_DUAL_KEYWORDS {
|
||||
let result = tag_no_case::<_, _, CustomError<_>>(keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, ent));
|
||||
}
|
||||
Err(_) => {}
|
||||
let result = recognize(tuple((
|
||||
tag_no_case::<_, _, CustomError>(keyword),
|
||||
tag("["),
|
||||
optval,
|
||||
tag("]"),
|
||||
peek(tag(":")),
|
||||
)))(input);
|
||||
if let Ok((remaining, ent)) = result {
|
||||
return Ok((remaining, ent));
|
||||
}
|
||||
}
|
||||
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoKeywordKey".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("NoKeywordKey")))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -229,7 +200,7 @@ fn _optval_end<'s>(
|
||||
unreachable!("Exceeded optval bracket depth.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_bracket = tag::<_, _, CustomError<_>>("]")(input);
|
||||
let close_bracket = tag::<_, _, CustomError>("]")(input);
|
||||
if close_bracket.is_ok() {
|
||||
return close_bracket;
|
||||
}
|
||||
@@ -244,3 +215,18 @@ fn export_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>
|
||||
take_while1(|c: char| c.is_alphanumeric() || "-_".contains(c)),
|
||||
)))(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.
|
||||
///
|
||||
/// 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, after_pipe_words) = opt(tuple((
|
||||
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)))
|
||||
} else if !before_pipe_words.is_empty() {
|
||||
// If there was no pipe, then the last word becomes a completion state instead.
|
||||
let mut after_pipe_words = Vec::with_capacity(1);
|
||||
after_pipe_words.push(
|
||||
before_pipe_words
|
||||
.pop()
|
||||
.expect("If-statement proves this is Some."),
|
||||
);
|
||||
let after_pipe_words = vec![before_pipe_words
|
||||
.pop()
|
||||
.expect("If-statement proves this is Some.")];
|
||||
Ok((remaining, (before_pipe_words, after_pipe_words)))
|
||||
} else {
|
||||
// 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| {
|
||||
!result.is_empty()
|
||||
})(input)?;
|
||||
|
||||
@@ -8,12 +8,10 @@ use nom::character::complete::space0;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::get_consumed;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
@@ -26,27 +24,27 @@ use crate::context::RefContext;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::LatexEnvironment;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn latex_environment<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, LatexEnvironment<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let value_start = remaining;
|
||||
start_of_line(remaining)?;
|
||||
let (remaining, _leading_whitespace) = space0(remaining)?;
|
||||
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) = tuple((
|
||||
tag_no_case(r#"\begin{"#),
|
||||
name,
|
||||
tag("}"),
|
||||
space0,
|
||||
line_ending,
|
||||
))(remaining)?;
|
||||
let (remaining, (_opening, name, _open_close_brace, _ws, _line_ending)) =
|
||||
tuple((tag_no_case(r"\begin{"), name, tag("}"), space0, line_ending))(remaining)?;
|
||||
|
||||
let latex_environment_end_specialized = latex_environment_end(name.into());
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
@@ -124,7 +122,7 @@ fn _latex_environment_end<'b, 'g, 'r, 's, 'c>(
|
||||
start_of_line(input)?;
|
||||
let (remaining, _leading_whitespace) = space0(input)?;
|
||||
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("}"),
|
||||
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::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::get_consumed;
|
||||
@@ -209,9 +208,9 @@ fn pre<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
let preceding_character = input.get_preceding_character();
|
||||
if let Some('$') = preceding_character {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid pre character for dollar char fragment.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Not a valid pre character for dollar char fragment.",
|
||||
)));
|
||||
}
|
||||
Ok((input, ()))
|
||||
}
|
||||
@@ -283,9 +282,9 @@ fn close_border<'b, 'g, 'r, 's>(
|
||||
match preceding_character {
|
||||
Some(c) if !c.is_whitespace() && !".,;$".contains(c) => Ok((input, ())),
|
||||
_ => {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid pre character for dollar char fragment.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Not a valid pre character for dollar char fragment.",
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,11 @@ use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
use nom::InputTake;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use crate::context::parser_with_context;
|
||||
@@ -29,7 +27,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::util::blank_line;
|
||||
@@ -41,6 +38,7 @@ use crate::types::CharOffsetInLine;
|
||||
use crate::types::CommentBlock;
|
||||
use crate::types::ExampleBlock;
|
||||
use crate::types::ExportBlock;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::LineNumber;
|
||||
use crate::types::Object;
|
||||
use crate::types::PlainText;
|
||||
@@ -51,13 +49,17 @@ use crate::types::VerseBlock;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn verse_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn verse_block<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, VerseBlock<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, VerseBlock<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, _) = lesser_block_begin("verse")(context, remaining)?;
|
||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
|
||||
@@ -73,10 +75,7 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
|
||||
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[2]);
|
||||
let parameters = match parameters {
|
||||
Some((_ws, parameters)) => Some(parameters),
|
||||
None => None,
|
||||
};
|
||||
let parameters = parameters.map(|(_ws, parameters)| parameters);
|
||||
|
||||
let object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
@@ -107,7 +106,7 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
|
||||
context.get_global_settings(),
|
||||
affiliated_keywords,
|
||||
),
|
||||
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
||||
data: parameters.map(Into::<&str>::into),
|
||||
children,
|
||||
},
|
||||
))
|
||||
@@ -115,13 +114,17 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn comment_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn comment_block<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, CommentBlock<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, CommentBlock<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, _) = lesser_block_begin("comment")(context, remaining)?;
|
||||
let (remaining, _parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
|
||||
@@ -159,13 +162,17 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn example_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn example_block<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, ExampleBlock<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, _) = lesser_block_begin("example")(context, remaining)?;
|
||||
let (remaining, parameters) = opt(alt((
|
||||
map(tuple((space1, example_switches)), |(_, switches)| switches),
|
||||
@@ -236,13 +243,17 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn export_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn export_block<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ExportBlock<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, ExportBlock<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, _) = lesser_block_begin("export")(context, remaining)?;
|
||||
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
|
||||
let (remaining, export_type) = opt(map(
|
||||
@@ -280,7 +291,7 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
|
||||
affiliated_keywords,
|
||||
),
|
||||
export_type: export_type.map(Into::<&str>::into),
|
||||
data: parameters.map(|parameters| Into::<&str>::into(parameters)),
|
||||
data: parameters.map(Into::<&str>::into),
|
||||
contents,
|
||||
},
|
||||
))
|
||||
@@ -288,13 +299,17 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn src_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn src_block<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, SrcBlock<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, SrcBlock<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let (remaining, _) = lesser_block_begin("src")(context, remaining)?;
|
||||
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
|
||||
let (remaining, language) =
|
||||
@@ -325,7 +340,7 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>(
|
||||
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
|
||||
if let Some(switches) = switches {
|
||||
(
|
||||
if switches.source.len() == 0 {
|
||||
if switches.source.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(switches.source)
|
||||
@@ -371,7 +386,7 @@ fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
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.
|
||||
debug_assert!(current_name == current_name.to_lowercase());
|
||||
move |context, input: OrgSource<'_>| _lesser_block_end(context, input, current_name)
|
||||
@@ -401,7 +416,7 @@ fn _lesser_block_end<'b, 'g, 'r, 's, 'c>(
|
||||
/// 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.
|
||||
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?
|
||||
move |context, input: OrgSource<'_>| _lesser_block_begin(context, input, current_name)
|
||||
}
|
||||
@@ -512,11 +527,8 @@ fn _example_src_switches<'s>(
|
||||
(SwitchState::Normal, "-r") => {
|
||||
saw_r = true;
|
||||
use_labels = false;
|
||||
match retain_labels {
|
||||
RetainLabels::Yes => {
|
||||
retain_labels = RetainLabels::No;
|
||||
}
|
||||
_ => {}
|
||||
if let RetainLabels::Yes = retain_labels {
|
||||
retain_labels = RetainLabels::No;
|
||||
}
|
||||
}
|
||||
(SwitchState::Normal, "-l") => {
|
||||
@@ -591,7 +603,7 @@ fn _example_src_switches<'s>(
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
@@ -656,7 +668,9 @@ pub(crate) fn content<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
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());
|
||||
remaining = remain;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use nom::multi::many0;
|
||||
use super::org_source::OrgSource;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::types::LineBreak;
|
||||
@@ -21,7 +20,7 @@ pub(crate) fn line_break<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, LineBreak<'s>> {
|
||||
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, _) = line_ending(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
@@ -45,9 +44,9 @@ fn pre<'b, 'g, 'r, 's>(
|
||||
match preceding_character {
|
||||
// If None, we are at the start of the file
|
||||
None | Some('\\') => {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid pre character for line break.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"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 is_non_empty_line = current_line.chars().any(|c| !c.is_whitespace());
|
||||
if !is_non_empty_line {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid pre line for line break.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Not a valid pre line for line break.",
|
||||
)));
|
||||
}
|
||||
|
||||
Ok((input, ()))
|
||||
|
||||
45
src/parser/macros.rs
Normal file
45
src/parser/macros.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
/// Parse an element that has affiliated keywords.
|
||||
macro_rules! ak_element {
|
||||
($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => {
|
||||
if let Ok((remaining, ele)) = $parser(
|
||||
$affiliated_keywords,
|
||||
$post_affiliated_keywords_input,
|
||||
$context,
|
||||
$input,
|
||||
) {
|
||||
return Ok((remaining, $wrapper(ele)));
|
||||
}
|
||||
};
|
||||
($parser:expr, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => {
|
||||
if let Ok((remaining, ele)) = $parser(
|
||||
$affiliated_keywords,
|
||||
$post_affiliated_keywords_input,
|
||||
$context,
|
||||
$input,
|
||||
) {
|
||||
return Ok((remaining, ele));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use ak_element;
|
||||
|
||||
macro_rules! element {
|
||||
($parser:expr, $context: expr, $input: expr, $wrapper: expr) => {
|
||||
if let Ok((remaining, ele)) = $parser($context, $input) {
|
||||
return Ok((remaining, $wrapper(ele)));
|
||||
}
|
||||
};
|
||||
($parser:expr, $context: expr, $input: expr) => {
|
||||
if let Ok((remaining, ele)) = $parser($context, $input) {
|
||||
return Ok((remaining, ele));
|
||||
}
|
||||
};
|
||||
($parser:expr, $input: expr) => {
|
||||
if let Ok((remaining, ele)) = $parser($input) {
|
||||
return Ok((remaining, ele));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use element;
|
||||
@@ -1,6 +1,7 @@
|
||||
mod affiliated_keyword;
|
||||
mod angle_link;
|
||||
mod babel_call;
|
||||
mod bullshitium;
|
||||
mod citation;
|
||||
mod citation_reference;
|
||||
mod clock;
|
||||
@@ -27,6 +28,7 @@ mod latex_environment;
|
||||
mod latex_fragment;
|
||||
mod lesser_block;
|
||||
mod line_break;
|
||||
mod macros;
|
||||
mod object_parser;
|
||||
mod org_macro;
|
||||
mod org_source;
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use nom::branch::alt;
|
||||
use nom::combinator::map;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::plain_text::plain_text;
|
||||
use super::regular_link::regular_link;
|
||||
use super::subscript_and_superscript::detect_subscript_or_superscript;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::angle_link::angle_link;
|
||||
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::latex_fragment::latex_fragment;
|
||||
use crate::parser::line_break::line_break;
|
||||
use crate::parser::macros::element;
|
||||
use crate::parser::org_macro::org_macro;
|
||||
use crate::parser::plain_link::plain_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>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, object) = alt((
|
||||
parser_with_context!(standard_set_object_sans_plain_text)(context),
|
||||
map(
|
||||
parser_with_context!(plain_text(detect_standard_set_object_sans_plain_text))(context),
|
||||
Object::PlainText,
|
||||
),
|
||||
))(input)?;
|
||||
Ok((remaining, object))
|
||||
element!(standard_set_object_sans_plain_text, context, input);
|
||||
element!(
|
||||
plain_text(detect_standard_set_object_sans_plain_text),
|
||||
context,
|
||||
input,
|
||||
Object::PlainText
|
||||
);
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -57,14 +53,14 @@ pub(crate) fn minimal_set_object<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, object) = alt((
|
||||
parser_with_context!(minimal_set_object_sans_plain_text)(context),
|
||||
map(
|
||||
parser_with_context!(plain_text(detect_minimal_set_object_sans_plain_text))(context),
|
||||
Object::PlainText,
|
||||
),
|
||||
))(input)?;
|
||||
Ok((remaining, object))
|
||||
element!(minimal_set_object_sans_plain_text, context, input);
|
||||
element!(
|
||||
plain_text(detect_minimal_set_object_sans_plain_text),
|
||||
context,
|
||||
input,
|
||||
Object::PlainText
|
||||
);
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -75,56 +71,38 @@ fn standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, object) = alt((
|
||||
map(parser_with_context!(timestamp)(context), Object::Timestamp),
|
||||
map(parser_with_context!(subscript)(context), Object::Subscript),
|
||||
map(
|
||||
parser_with_context!(superscript)(context),
|
||||
Object::Superscript,
|
||||
),
|
||||
map(
|
||||
parser_with_context!(statistics_cookie)(context),
|
||||
Object::StatisticsCookie,
|
||||
),
|
||||
map(parser_with_context!(target)(context), Object::Target),
|
||||
map(parser_with_context!(line_break)(context), Object::LineBreak),
|
||||
map(
|
||||
parser_with_context!(inline_source_block)(context),
|
||||
Object::InlineSourceBlock,
|
||||
),
|
||||
map(
|
||||
parser_with_context!(inline_babel_call)(context),
|
||||
Object::InlineBabelCall,
|
||||
),
|
||||
map(parser_with_context!(citation)(context), Object::Citation),
|
||||
map(
|
||||
parser_with_context!(footnote_reference)(context),
|
||||
Object::FootnoteReference,
|
||||
),
|
||||
map(
|
||||
parser_with_context!(export_snippet)(context),
|
||||
Object::ExportSnippet,
|
||||
),
|
||||
map(parser_with_context!(entity)(context), Object::Entity),
|
||||
map(
|
||||
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))
|
||||
element!(timestamp, context, input, Object::Timestamp);
|
||||
element!(subscript, context, input, Object::Subscript);
|
||||
element!(superscript, context, input, Object::Superscript);
|
||||
element!(statistics_cookie, context, input, Object::StatisticsCookie);
|
||||
element!(target, context, input, Object::Target);
|
||||
element!(line_break, context, input, Object::LineBreak);
|
||||
element!(
|
||||
inline_source_block,
|
||||
context,
|
||||
input,
|
||||
Object::InlineSourceBlock
|
||||
);
|
||||
element!(inline_babel_call, context, input, Object::InlineBabelCall);
|
||||
element!(citation, context, input, Object::Citation);
|
||||
element!(
|
||||
footnote_reference,
|
||||
context,
|
||||
input,
|
||||
Object::FootnoteReference
|
||||
);
|
||||
element!(export_snippet, context, input, Object::ExportSnippet);
|
||||
element!(entity, context, input, Object::Entity);
|
||||
element!(latex_fragment, context, input, Object::LatexFragment);
|
||||
element!(radio_link, context, input, Object::RadioLink);
|
||||
element!(radio_target, context, input, Object::RadioTarget);
|
||||
element!(text_markup, context, input);
|
||||
element!(regular_link, context, input, Object::RegularLink);
|
||||
element!(plain_link, context, input, Object::PlainLink);
|
||||
element!(angle_link, context, input, Object::AngleLink);
|
||||
element!(org_macro, context, input, Object::OrgMacro);
|
||||
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -135,20 +113,12 @@ fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, object) = alt((
|
||||
map(parser_with_context!(subscript)(context), Object::Subscript),
|
||||
map(
|
||||
parser_with_context!(superscript)(context),
|
||||
Object::Superscript,
|
||||
),
|
||||
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))
|
||||
element!(subscript, context, input, Object::Subscript);
|
||||
element!(superscript, context, input, Object::Superscript);
|
||||
element!(entity, context, input, Object::Entity);
|
||||
element!(latex_fragment, context, input, Object::LatexFragment);
|
||||
element!(text_markup, context, input);
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -166,9 +136,7 @@ pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
return Ok((input, ()));
|
||||
}
|
||||
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No object detected.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -186,9 +154,7 @@ fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
return Ok((input, ()));
|
||||
}
|
||||
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No object detected.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -200,16 +166,18 @@ pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'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 ]]
|
||||
let (remaining, object) = alt((
|
||||
parser_with_context!(regular_link_description_set_object_sans_plain_text)(context),
|
||||
map(
|
||||
parser_with_context!(plain_text(
|
||||
detect_regular_link_description_set_object_sans_plain_text
|
||||
))(context),
|
||||
Object::PlainText,
|
||||
),
|
||||
))(input)?;
|
||||
Ok((remaining, object))
|
||||
element!(
|
||||
regular_link_description_set_object_sans_plain_text,
|
||||
context,
|
||||
input
|
||||
);
|
||||
element!(
|
||||
plain_text(detect_regular_link_description_set_object_sans_plain_text),
|
||||
context,
|
||||
input,
|
||||
Object::PlainText
|
||||
);
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -221,27 +189,18 @@ fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'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 ]]
|
||||
let (remaining, object) = alt((
|
||||
map(
|
||||
parser_with_context!(export_snippet)(context),
|
||||
Object::ExportSnippet,
|
||||
),
|
||||
map(
|
||||
parser_with_context!(statistics_cookie)(context),
|
||||
Object::StatisticsCookie,
|
||||
),
|
||||
map(
|
||||
parser_with_context!(inline_source_block)(context),
|
||||
Object::InlineSourceBlock,
|
||||
),
|
||||
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))
|
||||
element!(export_snippet, context, input, Object::ExportSnippet);
|
||||
element!(statistics_cookie, context, input, Object::StatisticsCookie);
|
||||
element!(
|
||||
inline_source_block,
|
||||
context,
|
||||
input,
|
||||
Object::InlineSourceBlock
|
||||
);
|
||||
element!(inline_babel_call, context, input, Object::InlineBabelCall);
|
||||
element!(org_macro, context, input, Object::OrgMacro);
|
||||
element!(minimal_set_object_sans_plain_text, context, input);
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -259,9 +218,7 @@ fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
return Ok((input, ()));
|
||||
}
|
||||
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No object detected.".into(),
|
||||
))));
|
||||
Err(nom::Err::Error(CustomError::Static("No object detected.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -272,14 +229,14 @@ pub(crate) fn table_cell_set_object<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, object) = alt((
|
||||
parser_with_context!(table_cell_set_object_sans_plain_text)(context),
|
||||
map(
|
||||
parser_with_context!(plain_text(detect_table_cell_set_object_sans_plain_text))(context),
|
||||
Object::PlainText,
|
||||
),
|
||||
))(input)?;
|
||||
Ok((remaining, object))
|
||||
element!(table_cell_set_object_sans_plain_text, context, input);
|
||||
element!(
|
||||
plain_text(detect_table_cell_set_object_sans_plain_text),
|
||||
context,
|
||||
input,
|
||||
Object::PlainText
|
||||
);
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -290,33 +247,24 @@ fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||
let (remaining, object) = alt((
|
||||
map(parser_with_context!(citation)(context), Object::Citation),
|
||||
map(
|
||||
parser_with_context!(export_snippet)(context),
|
||||
Object::ExportSnippet,
|
||||
),
|
||||
map(
|
||||
parser_with_context!(footnote_reference)(context),
|
||||
Object::FootnoteReference,
|
||||
),
|
||||
map(parser_with_context!(radio_link)(context), Object::RadioLink),
|
||||
map(
|
||||
parser_with_context!(regular_link)(context),
|
||||
Object::RegularLink,
|
||||
),
|
||||
map(parser_with_context!(plain_link)(context), Object::PlainLink),
|
||||
map(parser_with_context!(angle_link)(context), Object::AngleLink),
|
||||
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
|
||||
map(
|
||||
parser_with_context!(radio_target)(context),
|
||||
Object::RadioTarget,
|
||||
),
|
||||
map(parser_with_context!(target)(context), Object::Target),
|
||||
map(parser_with_context!(timestamp)(context), Object::Timestamp),
|
||||
parser_with_context!(minimal_set_object_sans_plain_text)(context),
|
||||
))(input)?;
|
||||
Ok((remaining, object))
|
||||
element!(citation, context, input, Object::Citation);
|
||||
element!(export_snippet, context, input, Object::ExportSnippet);
|
||||
element!(
|
||||
footnote_reference,
|
||||
context,
|
||||
input,
|
||||
Object::FootnoteReference
|
||||
);
|
||||
element!(radio_link, context, input, Object::RadioLink);
|
||||
element!(regular_link, context, input, Object::RegularLink);
|
||||
element!(plain_link, context, input, Object::PlainLink);
|
||||
element!(angle_link, context, input, Object::AngleLink);
|
||||
element!(org_macro, context, input, Object::OrgMacro);
|
||||
element!(radio_target, context, input, Object::RadioTarget);
|
||||
element!(target, context, input, Object::Target);
|
||||
element!(timestamp, context, input, Object::Timestamp);
|
||||
element!(minimal_set_object_sans_plain_text, context, input);
|
||||
Err(nom::Err::Error(CustomError::Static("No object.")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -334,7 +282,5 @@ fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
|
||||
return Ok((input, ()));
|
||||
}
|
||||
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No object detected.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("No object detected.")));
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
|
||||
loop {
|
||||
not(parser_with_context!(exit_matcher_parser)(context))(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;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ fn org_macro_arg<'b, 'g, 'r, 's>(
|
||||
}
|
||||
if next_char == '\\' {
|
||||
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
|
||||
remaining = new_remaining;
|
||||
break;
|
||||
|
||||
@@ -10,9 +10,6 @@ use nom::InputTakeAtPosition;
|
||||
use nom::Offset;
|
||||
use nom::Slice;
|
||||
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
|
||||
pub(crate) type BracketDepth = i16;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
@@ -85,6 +82,15 @@ impl<'s> OrgSource<'s> {
|
||||
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> {
|
||||
let skipped_text = self.text_since_line_break();
|
||||
let mut bracket_depth = self.bracket_depth;
|
||||
@@ -219,7 +225,7 @@ where
|
||||
panic!("Attempted to extend past the end of the WrappedInput.")
|
||||
}
|
||||
if new_start == self.start && new_end == self.end {
|
||||
return self.clone();
|
||||
return *self;
|
||||
}
|
||||
|
||||
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,
|
||||
{
|
||||
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)),
|
||||
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);
|
||||
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)),
|
||||
None => {
|
||||
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 {
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -2,54 +2,46 @@ use nom::branch::alt;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many1;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::element_parser::detect_element;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::blank_line;
|
||||
use super::util::get_consumed;
|
||||
use super::util::get_has_affiliated_keyword;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::HasAffiliatedKeywordInner;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::Paragraph;
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn paragraph<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn paragraph<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Paragraph<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
let contexts = [
|
||||
ContextElement::HasAffiliatedKeyword(HasAffiliatedKeywordInner {
|
||||
start_after_affiliated_keywords: remaining,
|
||||
keywords: &affiliated_keywords,
|
||||
}),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: ¶graph_end,
|
||||
}),
|
||||
];
|
||||
) -> Res<OrgSource<'s>, Paragraph<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let contexts = [ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: ¶graph_end,
|
||||
})];
|
||||
let parser_context = context.with_additional_node(&contexts[0]);
|
||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
|
||||
@@ -89,15 +81,6 @@ fn paragraph_end<'b, 'g, 'r, 's>(
|
||||
if regular_end.is_ok() {
|
||||
return regular_end;
|
||||
}
|
||||
match get_has_affiliated_keyword(context) {
|
||||
Some(start_post_affiliated_keywords) if input == start_post_affiliated_keywords => {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No exit due to affiliated keywords.",
|
||||
))));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Check to see if input is the start of a HasAffiliatedKeyword
|
||||
alt((
|
||||
recognize(parser_with_context!(detect_element(false))(context)),
|
||||
eof,
|
||||
@@ -106,7 +89,7 @@ fn paragraph_end<'b, 'g, 'r, 's>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::GlobalSettings;
|
||||
@@ -121,7 +104,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let paragraph_matcher = 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, second_paragraph) =
|
||||
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
||||
|
||||
@@ -36,7 +36,6 @@ use crate::context::ExitMatcherNode;
|
||||
use crate::context::Matcher;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
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(_) => {
|
||||
// Not at start of line, cannot be a heading
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid pre character for plain link.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Not a valid pre character for plain link.",
|
||||
)));
|
||||
}
|
||||
};
|
||||
Ok((input, ()))
|
||||
@@ -262,18 +261,13 @@ pub(crate) fn protocol<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
for link_parameter in context.get_global_settings().link_parameters {
|
||||
let result = tag_no_case::<_, _, CustomError<_>>(*link_parameter)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, ent));
|
||||
}
|
||||
Err(_) => {}
|
||||
let result = tag_no_case::<_, _, CustomError>(*link_parameter)(input);
|
||||
if let Ok((remaining, ent)) = result {
|
||||
return Ok((remaining, ent));
|
||||
}
|
||||
}
|
||||
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoLinkProtocol".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("NoLinkProtocol")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -330,8 +324,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
if current_depth == 0 {
|
||||
let close_parenthesis =
|
||||
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
|
||||
let close_parenthesis = tag::<_, _, CustomError>(")")(remaining);
|
||||
if close_parenthesis.is_ok() {
|
||||
return close_parenthesis;
|
||||
}
|
||||
@@ -345,9 +338,7 @@ fn impl_path_plain_end<'b, 'g, 'r, 's>(
|
||||
}
|
||||
}
|
||||
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No path plain end".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("No path plain end")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -423,18 +414,18 @@ fn _path_plain_parenthesis_end<'s>(
|
||||
unreachable!("Exceeded plain link parenthesis depth.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
||||
let close_parenthesis = tag::<_, _, CustomError>(")")(input);
|
||||
if close_parenthesis.is_ok() {
|
||||
return close_parenthesis;
|
||||
}
|
||||
}
|
||||
if current_depth == 1 {
|
||||
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input);
|
||||
let open_parenthesis = tag::<_, _, CustomError>("(")(input);
|
||||
if open_parenthesis.is_ok() {
|
||||
return open_parenthesis;
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No closing parenthesis".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static(
|
||||
"No closing parenthesis",
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::digit1;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::multispace1;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
@@ -17,15 +18,16 @@ use nom::multi::many0;
|
||||
use nom::multi::many1;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
use nom::InputTake;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::element_parser::element;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::object_parser::standard_set_object;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::include_input;
|
||||
use super::util::indentation_level;
|
||||
use super::util::non_whitespace_character;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ContextMatcher;
|
||||
@@ -33,7 +35,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::blank_line;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
@@ -43,6 +44,7 @@ use crate::parser::util::org_space;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::CheckboxType;
|
||||
use crate::types::IndentationLevel;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::Object;
|
||||
use crate::types::PlainList;
|
||||
use crate::types::PlainListItem;
|
||||
@@ -52,13 +54,17 @@ use crate::types::PlainListType;
|
||||
|
||||
#[cfg_attr(
|
||||
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>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, ()>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
if verify(
|
||||
tuple((
|
||||
start_of_line,
|
||||
@@ -67,28 +73,69 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
||||
alt((space1, line_ending, eof)),
|
||||
)),
|
||||
|(_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()
|
||||
{
|
||||
return Ok((input, ()));
|
||||
}
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No element detected.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static("No element detected.")));
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
)]
|
||||
pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
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>, PlainList<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> 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(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn plain_list<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, PlainList<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
let contexts = [
|
||||
ContextElement::Context("plain list"),
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
@@ -116,7 +163,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
// 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 {
|
||||
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) {
|
||||
(None, Ok((_remain, (list_type, _item)))) => {
|
||||
let _ = first_item_list_type.insert(*list_type);
|
||||
@@ -136,25 +183,17 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
}
|
||||
};
|
||||
|
||||
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() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let (final_child_start, _final_item_first_parse) = match children.pop() {
|
||||
Some(final_child) => final_child,
|
||||
None => {
|
||||
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));
|
||||
if children.is_empty() {
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Plain lists require at least one element.",
|
||||
)));
|
||||
}
|
||||
|
||||
let (remaining, _trailing_ws) =
|
||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||
@@ -183,10 +222,10 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
|
||||
let (remaining, (bullet_type, bull)) = verify(
|
||||
parser_with_context!(bullet)(context),
|
||||
|(_bullet_type, bull)| !Into::<&str>::into(bull).starts_with("*") || indent_level > 0,
|
||||
)(remaining)?;
|
||||
let (remaining, (bullet_type, bull)) =
|
||||
verify(bind_context!(bullet, context), |(_bullet_type, bull)| {
|
||||
!Into::<&str>::into(bull).starts_with('*') || indent_level > 0
|
||||
})(remaining)?;
|
||||
|
||||
let (remaining, maybe_counter_set) =
|
||||
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
|
||||
@@ -195,7 +234,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
|
||||
|
||||
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 {
|
||||
(remaining, None)
|
||||
};
|
||||
@@ -207,6 +246,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
};
|
||||
|
||||
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 = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
@@ -214,67 +259,56 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
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 maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
|
||||
detect_contentless_item_contents
|
||||
)(&parser_context))(remaining);
|
||||
match maybe_contentless_item {
|
||||
Ok((_rem, _ws)) => {
|
||||
let (remaining, _trailing_ws) = if context.should_consume_trailing_whitespace() {
|
||||
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
|
||||
} else {
|
||||
recognize(alt((blank_line, eof)))(remaining)?
|
||||
};
|
||||
let source = get_consumed(input, remaining);
|
||||
return Ok((
|
||||
remaining,
|
||||
(
|
||||
list_type,
|
||||
PlainListItem {
|
||||
source: source.into(),
|
||||
indentation: indent_level,
|
||||
bullet: bull.into(),
|
||||
counter: maybe_counter_set,
|
||||
checkbox: None,
|
||||
tag: maybe_tag
|
||||
.map(|(_ws, item_tag)| item_tag)
|
||||
.unwrap_or(Vec::new()),
|
||||
pre_blank: 0,
|
||||
children: Vec::new(),
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
let maybe_contentless_item: Res<OrgSource<'_>, ()> =
|
||||
detect_contentless_item_contents(&parser_context, remaining);
|
||||
if let Ok((_rem, _ws)) = maybe_contentless_item {
|
||||
let (remaining, _trailing_ws) = if tuple((
|
||||
blank_line,
|
||||
bind_context!(final_item_whitespace_cutoff, context),
|
||||
))(remaining)
|
||||
.is_ok()
|
||||
{
|
||||
recognize(alt((blank_line, eof)))(remaining)?
|
||||
} else {
|
||||
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
|
||||
};
|
||||
let source = get_consumed(input, remaining);
|
||||
return Ok((
|
||||
remaining,
|
||||
(
|
||||
list_type,
|
||||
PlainListItem {
|
||||
source: source.into(),
|
||||
indentation: indent_level,
|
||||
bullet: bull.into(),
|
||||
counter: maybe_counter_set,
|
||||
checkbox: None,
|
||||
tag: maybe_tag
|
||||
.map(|(_ws, item_tag)| item_tag)
|
||||
.unwrap_or(Vec::new()),
|
||||
pre_blank: 0,
|
||||
children: Vec::new(),
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
let (remaining, pre_blank) = item_tag_post_gap(&parser_context, remaining)?;
|
||||
let pre_blank = Into::<&str>::into(pre_blank)
|
||||
.bytes()
|
||||
.filter(|b| *b == b'\n')
|
||||
.count();
|
||||
|
||||
let (mut remaining, (mut children, _exit_contents)) = many_till(
|
||||
include_input(parser_with_context!(element(true))(&parser_context)),
|
||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||
let (remaining, (children, _exit_contents)) = many_till(
|
||||
include_input(bind_context!(element(true), &parser_context)),
|
||||
bind_context!(exit_matcher_parser, &parser_context),
|
||||
)(remaining)?;
|
||||
|
||||
if !children.is_empty() && !context.should_consume_trailing_whitespace() {
|
||||
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);
|
||||
}
|
||||
|
||||
// We have to use the parser_context here to include the whitespace cut-off
|
||||
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);
|
||||
return Ok((
|
||||
@@ -321,7 +355,7 @@ fn bullet<'b, 'g, 'r, 's>(
|
||||
map(tag("+"), |bull| (BulletType::Unordered, bull)),
|
||||
map(
|
||||
recognize(tuple((
|
||||
parser_with_context!(counter)(context),
|
||||
bind_context!(counter, context),
|
||||
alt((tag("."), tag(")"))),
|
||||
))),
|
||||
|bull| (BulletType::Ordered, bull),
|
||||
@@ -376,6 +410,52 @@ fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PlainListIt
|
||||
))(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(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(_context))
|
||||
@@ -411,7 +491,7 @@ fn _plain_list_item_end<'b, 'g, 'r, 's>(
|
||||
start_of_line(input)?;
|
||||
recognize(tuple((
|
||||
opt(blank_line),
|
||||
parser_with_context!(line_indented_lte_matcher)(context),
|
||||
bind_context!(line_indented_lte_matcher, context),
|
||||
)))(input)
|
||||
}
|
||||
|
||||
@@ -430,7 +510,7 @@ fn _line_indented_lte<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let matched = recognize(verify(
|
||||
tuple((
|
||||
parser_with_context!(indentation_level)(context),
|
||||
bind_context!(indentation_level, context),
|
||||
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)
|
||||
@@ -456,8 +536,8 @@ fn item_tag<'b, 'g, 'r, 's>(
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(
|
||||
// TODO: Should this be using a different set like the minimal set?
|
||||
parser_with_context!(standard_set_object)(&parser_context),
|
||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||
bind_context!(standard_set_object, &parser_context),
|
||||
bind_context!(exit_matcher_parser, &parser_context),
|
||||
),
|
||||
|(children, _exit_contents)| !children.is_empty(),
|
||||
)(input)?;
|
||||
@@ -507,7 +587,7 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
|
||||
alt((
|
||||
peek(recognize(not(blank_line))),
|
||||
peek(recognize(tuple((many0(blank_line), eof)))),
|
||||
parser_with_context!(exit_matcher_parser)(context),
|
||||
bind_context!(exit_matcher_parser, context),
|
||||
)),
|
||||
),
|
||||
))),
|
||||
@@ -537,7 +617,7 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
let (remaining, _) = recognize(many_till(
|
||||
blank_line,
|
||||
parser_with_context!(exit_matcher_parser)(context),
|
||||
bind_context!(exit_matcher_parser, context),
|
||||
))(input)?;
|
||||
Ok((remaining, ()))
|
||||
}
|
||||
@@ -545,6 +625,7 @@ fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::GlobalSettings;
|
||||
use crate::context::List;
|
||||
@@ -556,7 +637,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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();
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
||||
@@ -568,7 +649,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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();
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
||||
@@ -580,8 +661,8 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||
let (remaining, result) = plain_list_matcher(input).unwrap();
|
||||
let (remaining, result) =
|
||||
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
||||
}
|
||||
@@ -592,8 +673,8 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||
let (remaining, result) = plain_list_matcher(input).unwrap();
|
||||
let (remaining, result) =
|
||||
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
||||
}
|
||||
@@ -605,8 +686,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||
let result = plain_list_matcher(input);
|
||||
let result = plain_list(std::iter::empty(), input, &initial_context, input);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -617,8 +697,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||
let result = plain_list_matcher(input);
|
||||
let result = plain_list(std::iter::empty(), input, &initial_context, input);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
@@ -637,7 +716,7 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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) =
|
||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
|
||||
@@ -665,7 +744,7 @@ baz"#,
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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) =
|
||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||
assert_eq!(Into::<&str>::into(remaining), "baz");
|
||||
@@ -698,7 +777,7 @@ dolar"#,
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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) =
|
||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||
assert_eq!(Into::<&str>::into(remaining), "dolar");
|
||||
@@ -728,7 +807,7 @@ dolar"#,
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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());
|
||||
}
|
||||
|
||||
@@ -738,7 +817,7 @@ dolar"#,
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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());
|
||||
}
|
||||
|
||||
@@ -748,7 +827,7 @@ dolar"#,
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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.
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -759,7 +838,7 @@ dolar"#,
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ use super::util::org_space_or_line_ending;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::types::Object;
|
||||
use crate::types::PlainText;
|
||||
@@ -92,41 +91,35 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
|
||||
break;
|
||||
}
|
||||
|
||||
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
|
||||
match is_not_whitespace {
|
||||
Ok((new_goal, payload)) => {
|
||||
let (new_remaining, _) = tuple((
|
||||
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.
|
||||
peek(alt((
|
||||
recognize(verify(anychar, |c| !c.is_alphanumeric())),
|
||||
eof,
|
||||
))),
|
||||
))(remaining)?;
|
||||
remaining = new_remaining;
|
||||
goal = new_goal;
|
||||
continue;
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
let is_not_whitespace = is_not::<_, _, CustomError>(" \t\r\n")(goal);
|
||||
if let Ok((new_goal, payload)) = is_not_whitespace {
|
||||
let (new_remaining, _) = tuple((
|
||||
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.
|
||||
peek(alt((
|
||||
recognize(verify(anychar, |c| !c.is_alphanumeric())),
|
||||
eof,
|
||||
))),
|
||||
))(remaining)?;
|
||||
remaining = new_remaining;
|
||||
goal = new_goal;
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_whitespace = recognize(many1(alt((
|
||||
recognize(one_of::<&str, &str, CustomError<_>>(" \t")),
|
||||
recognize(one_of::<_, _, CustomError>(" \t")),
|
||||
line_ending,
|
||||
))))(goal);
|
||||
match is_whitespace {
|
||||
Ok((new_goal, _)) => {
|
||||
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
|
||||
remaining = new_remaining;
|
||||
goal = new_goal;
|
||||
continue;
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
if let Ok((new_goal, _)) = is_whitespace {
|
||||
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
|
||||
remaining = new_remaining;
|
||||
goal = new_goal;
|
||||
continue;
|
||||
}
|
||||
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Target does not match.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Target does not match.",
|
||||
)));
|
||||
}
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
@@ -144,6 +137,7 @@ mod tests {
|
||||
use nom::combinator::map;
|
||||
|
||||
use super::*;
|
||||
use crate::context::bind_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::GlobalSettings;
|
||||
@@ -157,10 +151,14 @@ mod tests {
|
||||
let global_settings = GlobalSettings::default();
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let plain_text_matcher = parser_with_context!(plain_text(
|
||||
detect_standard_set_object_sans_plain_text
|
||||
))(&initial_context);
|
||||
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
|
||||
let (remaining, result) = map(
|
||||
bind_context!(
|
||||
plain_text(detect_standard_set_object_sans_plain_text),
|
||||
&initial_context
|
||||
),
|
||||
Object::PlainText,
|
||||
)(input)
|
||||
.unwrap();
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(
|
||||
result.get_standard_properties().get_source(),
|
||||
|
||||
@@ -21,7 +21,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::util::immediate_in_section;
|
||||
@@ -39,9 +38,9 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
|
||||
if immediate_in_section(context, "property-drawer") {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Cannot nest objects of the same element".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Cannot nest objects of the same element",
|
||||
)));
|
||||
}
|
||||
let (
|
||||
remaining,
|
||||
|
||||
@@ -21,7 +21,6 @@ use crate::context::ExitMatcherNode;
|
||||
use crate::context::List;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::types::Object;
|
||||
@@ -52,9 +51,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoRadioLink".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("NoRadioLink")))
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
@@ -98,9 +95,9 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
|
||||
new_matches.push(new_match);
|
||||
}
|
||||
_ => {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"OnlyMinimalSetObjectsAllowed".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"OnlyMinimalSetObjectsAllowed",
|
||||
)));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -185,15 +182,14 @@ mod tests {
|
||||
fn plain_text_radio_target() {
|
||||
let input = OrgSource::new("foo bar baz");
|
||||
let radio_target_match = vec![Object::PlainText(PlainText { source: "bar" })];
|
||||
let global_settings = {
|
||||
let mut global_settings = GlobalSettings::default();
|
||||
global_settings.radio_targets = vec![&radio_target_match];
|
||||
global_settings
|
||||
let global_settings = GlobalSettings {
|
||||
radio_targets: vec![&radio_target_match],
|
||||
..Default::default()
|
||||
};
|
||||
let initial_context = ContextElement::document_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) = paragraph_matcher(input).expect("Parse first paragraph");
|
||||
let (remaining, first_paragraph) =
|
||||
element(true)(&initial_context, input).expect("Parse first paragraph");
|
||||
let first_paragraph = match first_paragraph {
|
||||
Element::Paragraph(paragraph) => paragraph,
|
||||
_ => panic!("Should be a paragraph!"),
|
||||
@@ -212,7 +208,7 @@ mod tests {
|
||||
&Object::RadioLink(RadioLink {
|
||||
source: "bar ",
|
||||
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
||||
path: "bar".into()
|
||||
path: "bar"
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -224,16 +220,15 @@ mod tests {
|
||||
source: "*bar*",
|
||||
children: vec![Object::PlainText(PlainText { source: "bar" })],
|
||||
})];
|
||||
let global_settings = {
|
||||
let mut global_settings = GlobalSettings::default();
|
||||
global_settings.radio_targets = vec![&radio_target_match];
|
||||
global_settings
|
||||
let global_settings = GlobalSettings {
|
||||
radio_targets: vec![&radio_target_match],
|
||||
..Default::default()
|
||||
};
|
||||
let initial_context = ContextElement::document_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) =
|
||||
paragraph_matcher(input.into()).expect("Parse first paragraph");
|
||||
element(true)(&initial_context, input).expect("Parse first paragraph");
|
||||
let first_paragraph = match first_paragraph {
|
||||
Element::Paragraph(paragraph) => paragraph,
|
||||
_ => panic!("Should be a paragraph!"),
|
||||
@@ -255,7 +250,7 @@ mod tests {
|
||||
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::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::types::LinkType;
|
||||
use crate::types::Object;
|
||||
@@ -139,14 +138,7 @@ fn pathreg<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, PathReg<'s>> {
|
||||
let (remaining, path) = map_parser(
|
||||
escaped(
|
||||
take_till1(|c| match c {
|
||||
'\\' | '[' | ']' => true,
|
||||
_ => false,
|
||||
}),
|
||||
'\\',
|
||||
anychar,
|
||||
),
|
||||
escaped(take_till1(|c| matches!(c, '\\' | '[' | ']')), '\\', anychar),
|
||||
parser_with_context!(parse_path_reg)(context),
|
||||
)(input)?;
|
||||
Ok((remaining, path))
|
||||
@@ -170,11 +162,7 @@ fn parse_path_reg<'b, 'g, 'r, 's>(
|
||||
parser_with_context!(protocol_path_reg)(context),
|
||||
fuzzy_path_reg,
|
||||
))(replaced_input)
|
||||
.map_err(|_| {
|
||||
nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No pathreg match after replacement.",
|
||||
)))
|
||||
})?;
|
||||
.map_err(|_| nom::Err::Error(CustomError::Static("No pathreg match after replacement.")))?;
|
||||
let remaining = input.take(input.len());
|
||||
let link_type = match link.link_type {
|
||||
LinkType::Protocol(protocol) => LinkType::Protocol(protocol.into_owned().into()),
|
||||
@@ -262,11 +250,8 @@ fn apply_link_templates<'b, 'g, 'r, 's>(
|
||||
};
|
||||
}
|
||||
// Handle lingering state
|
||||
match state {
|
||||
ParserState::Percent => {
|
||||
ret.push('%');
|
||||
}
|
||||
_ => {}
|
||||
if let ParserState::Percent = state {
|
||||
ret.push('%');
|
||||
}
|
||||
if !injected_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(
|
||||
"No path reg end".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("No path reg end")))
|
||||
}
|
||||
|
||||
@@ -65,12 +65,12 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
|
||||
},
|
||||
)(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));
|
||||
comment
|
||||
.map(Element::Comment)
|
||||
.map(|ele| children.insert(0, ele));
|
||||
});
|
||||
if let Some(ele) = comment.map(Element::Comment) {
|
||||
children.insert(0, ele);
|
||||
}
|
||||
}
|
||||
|
||||
let (remaining, _trailing_ws) =
|
||||
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()
|
||||
},
|
||||
)(remaining)?;
|
||||
property_drawer_element
|
||||
.map(Element::PropertyDrawer)
|
||||
.map(|ele| children.insert(0, ele));
|
||||
planning_element
|
||||
.map(Element::Planning)
|
||||
.map(|ele| children.insert(0, ele));
|
||||
if let Some(ele) = property_drawer_element.map(Element::PropertyDrawer) {
|
||||
children.insert(0, ele);
|
||||
}
|
||||
if let Some(ele) = planning_element.map(Element::Planning) {
|
||||
children.insert(0, ele)
|
||||
}
|
||||
|
||||
let (remaining, _trailing_ws) =
|
||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||
|
||||
@@ -26,7 +26,6 @@ use crate::context::ExitMatcherNode;
|
||||
use crate::context::Matcher;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
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.
|
||||
let (remaining, _) = one_of("_^")(input)?;
|
||||
pre(input)?;
|
||||
if tag::<_, _, CustomError<_>>("*")(remaining).is_ok() {
|
||||
if tag::<_, _, CustomError>("*")(remaining).is_ok() {
|
||||
return Ok((input, ()));
|
||||
}
|
||||
let (remaining, _) = opt(one_of("+-"))(remaining)?;
|
||||
@@ -137,7 +136,7 @@ fn script_body<'b, 'g, 'r, 's>(
|
||||
ScriptBody::Braceless(body.into())
|
||||
}),
|
||||
map(parser_with_context!(script_with_braces)(context), |body| {
|
||||
ScriptBody::WithBraces(body.into())
|
||||
ScriptBody::WithBraces(body)
|
||||
}),
|
||||
map(
|
||||
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"))]
|
||||
fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(verify(anychar, |c| {
|
||||
c.is_alphanumeric() || r#",.\"#.contains(*c)
|
||||
c.is_alphanumeric() || r",.\".contains(*c)
|
||||
}))(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>> {
|
||||
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
|
||||
peek(tuple((
|
||||
take_while(|c| r#",.\"#.contains(c)),
|
||||
take_while(|c| r",.\".contains(c)),
|
||||
not(script_alphanum_character),
|
||||
)))(remaining)?;
|
||||
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;
|
||||
if current_depth > 0 {
|
||||
// Its impossible for the next character to end the subscript or superscript if we're any amount of braces deep
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid end for subscript or superscript.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Not a valid end for subscript or superscript.",
|
||||
)));
|
||||
}
|
||||
if current_depth < 0 {
|
||||
// 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.")
|
||||
}
|
||||
if current_depth == 0 {
|
||||
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
||||
let close_parenthesis = tag::<_, _, CustomError>(")")(input);
|
||||
if close_parenthesis.is_ok() {
|
||||
return close_parenthesis;
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"No script parenthesis end.".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static(
|
||||
"No script parenthesis end.",
|
||||
)))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::affiliated_keyword::parse_affiliated_keywords;
|
||||
use super::keyword::affiliated_keyword;
|
||||
use super::keyword::table_formula_keyword;
|
||||
use super::object_parser::table_cell_set_object;
|
||||
use super::org_source::OrgSource;
|
||||
@@ -29,6 +28,7 @@ use crate::context::RefContext;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::Table;
|
||||
use crate::types::TableCell;
|
||||
use crate::types::TableRow;
|
||||
@@ -38,13 +38,17 @@ use crate::types::TableRow;
|
||||
/// This is not the table.el style.
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
|
||||
)]
|
||||
pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn org_mode_table<'b, 'g, 'r, 's, AK>(
|
||||
affiliated_keywords: AK,
|
||||
remaining: OrgSource<'s>,
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Table<'s>> {
|
||||
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
|
||||
) -> Res<OrgSource<'s>, Table<'s>>
|
||||
where
|
||||
AK: IntoIterator<Item = Keyword<'s>>,
|
||||
{
|
||||
start_of_line(remaining)?;
|
||||
peek(tuple((space0, tag("|"))))(remaining)?;
|
||||
|
||||
@@ -87,10 +91,20 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||
let (input, _) = many0(affiliated_keyword)(input)?;
|
||||
tuple((start_of_line, space0, tag("|")))(input)?;
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(_context, _affiliated_keywords))
|
||||
)]
|
||||
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, ()))
|
||||
}
|
||||
|
||||
@@ -183,7 +197,7 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
|
||||
let (remaining, (children, _exit_contents)) = verify(
|
||||
many_till(table_cell_set_object_matcher, exit_matcher),
|
||||
|(children, exit_contents)| {
|
||||
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with("|")
|
||||
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
|
||||
},
|
||||
)(remaining)?;
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::types::Target;
|
||||
@@ -42,9 +41,9 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
|
||||
.get_preceding_character()
|
||||
.expect("We cannot be at the start of the file because we are inside a target.");
|
||||
if preceding_character.is_whitespace() {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Targets cannot end with whitespace.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Targets cannot end with whitespace.",
|
||||
)));
|
||||
}
|
||||
let (remaining, _) = tag(">>")(remaining)?;
|
||||
let (remaining, _trailing_whitespace) =
|
||||
|
||||
@@ -34,7 +34,6 @@ use crate::context::ExitMatcherNode;
|
||||
use crate::context::List;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::radio_link::rematch_target;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
@@ -183,13 +182,13 @@ fn code<'b, 'g, 'r, 's>(
|
||||
))
|
||||
}
|
||||
|
||||
fn text_markup_object<'c>(
|
||||
marker_symbol: &'c str,
|
||||
fn text_markup_object(
|
||||
marker_symbol: &str,
|
||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>>
|
||||
+ 'c {
|
||||
+ '_ {
|
||||
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")]
|
||||
let _enter = span.enter();
|
||||
if exit_matcher_parser(context, remaining).is_ok() {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Parent exit matcher is triggering.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Parent exit matcher is triggering.",
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,13 +245,13 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
||||
Ok((remaining, children))
|
||||
}
|
||||
|
||||
fn text_markup_string<'c>(
|
||||
marker_symbol: &'c str,
|
||||
fn text_markup_string(
|
||||
marker_symbol: &str,
|
||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>>
|
||||
+ 'c {
|
||||
+ '_ {
|
||||
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")]
|
||||
let _enter = span.enter();
|
||||
if exit_matcher_parser(context, remaining).is_ok() {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Parent exit matcher is triggering.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"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.
|
||||
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
|
||||
Some(_) => {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not a valid pre character for text markup.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"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.
|
||||
};
|
||||
@@ -343,10 +342,7 @@ fn post<'b, 'g, 'r, 's>(
|
||||
Ok((remaining, ()))
|
||||
}
|
||||
|
||||
fn text_markup_end<'c>(
|
||||
marker_symbol: &'c str,
|
||||
contents_start_offset: usize,
|
||||
) -> impl ContextMatcher + 'c {
|
||||
fn text_markup_end(marker_symbol: &str, contents_start_offset: usize) -> impl ContextMatcher + '_ {
|
||||
move |context, input: OrgSource<'_>| {
|
||||
_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,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
if input.get_byte_offset() == contents_start_offset {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Text markup cannot be empty".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Text markup cannot be empty",
|
||||
)));
|
||||
}
|
||||
not(preceded_by_whitespace(false))(input)?;
|
||||
let (remaining, _marker) = terminated(
|
||||
@@ -498,9 +494,9 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
||||
#[cfg(feature = "tracing")]
|
||||
let _enter = span.enter();
|
||||
if exit_matcher_parser(context, remaining).is_ok() {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Parent exit matcher is triggering.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Parent exit matcher is triggering.",
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -486,7 +486,7 @@ fn dayname_end<'b, 'g, 'r, 's>(
|
||||
}))(input)
|
||||
}
|
||||
|
||||
const fn time<'c>(
|
||||
const fn time(
|
||||
allow_rest: bool,
|
||||
) -> 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("-"),
|
||||
parser_with_context!(time(true))(&parent_node),
|
||||
)))(input);
|
||||
#[allow(clippy::let_and_return)] // otherwise parent_node does not live long enough.
|
||||
exit_contents
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::types::IndentationLevel;
|
||||
|
||||
@@ -28,10 +27,7 @@ pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
/// 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>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
section_name: &'x str,
|
||||
) -> bool {
|
||||
pub(crate) fn in_section(context: RefContext<'_, '_, '_, '_>, section_name: &str) -> bool {
|
||||
for thing in context.iter() {
|
||||
match thing {
|
||||
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
|
||||
pub(crate) fn immediate_in_section<'b, 'g, 'r, 's, 'x>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
section_name: &'x str,
|
||||
pub(crate) fn immediate_in_section(
|
||||
context: RefContext<'_, '_, '_, '_>,
|
||||
section_name: &str,
|
||||
) -> bool {
|
||||
for thing in context.iter() {
|
||||
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() {
|
||||
Ok((input, ()))
|
||||
} else {
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not at start of line".into(),
|
||||
))))
|
||||
Err(nom::Err::Error(CustomError::Static("Not at start of line")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,9 +149,9 @@ fn _preceded_by_whitespace<'s>(
|
||||
.map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space
|
||||
.unwrap_or(allow_start_of_file)
|
||||
{
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Must be preceded by a whitespace character.".into(),
|
||||
))));
|
||||
return Err(nom::Err::Error(CustomError::Static(
|
||||
"Must be preceded by a whitespace character.",
|
||||
)));
|
||||
}
|
||||
Ok((input, ()))
|
||||
}
|
||||
@@ -198,9 +192,7 @@ pub(crate) fn text_until_exit<'b, 'g, 'r, 's>(
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Not implemented yet.".into(),
|
||||
))));
|
||||
Err(nom::Err::Error(CustomError::Static("Not implemented yet.")))
|
||||
}
|
||||
|
||||
#[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
|
||||
///
|
||||
/// Useful for debugging.
|
||||
fn text_until_eol<'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
|
||||
fn text_until_eol<'r, 's>(input: OrgSource<'s>) -> Result<&'s str, nom::Err<CustomError>> {
|
||||
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
|
||||
.map(|(_remaining, line)| Into::<&str>::into(line))?;
|
||||
Ok(line.trim())
|
||||
@@ -234,29 +224,31 @@ where
|
||||
/// Match single space or tab.
|
||||
///
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub(crate) fn org_space_or_line_ending<'s>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
pub(crate) fn org_space_or_line_ending(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> {
|
||||
alt((recognize(org_space), org_line_ending))(input)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
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)
|
||||
}
|
||||
|
||||
/// Match the whitespace at the beginning of a line and give it an indentation level.
|
||||
pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
#[cfg_attr(
|
||||
feature = "tracing",
|
||||
tracing::instrument(ret, level = "debug", skip(context))
|
||||
)]
|
||||
pub(crate) fn indentation_level<'s>(
|
||||
context: RefContext<'_, '_, '_, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, (IndentationLevel, OrgSource<'s>)> {
|
||||
let (remaining, leading_whitespace) = space0(input)?;
|
||||
@@ -271,24 +263,6 @@ pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
|
||||
Ok((remaining, (indentation_level, leading_whitespace)))
|
||||
}
|
||||
|
||||
pub(crate) fn get_has_affiliated_keyword<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
) -> Option<OrgSource<'s>> {
|
||||
for context in context.iter() {
|
||||
match context {
|
||||
ContextElement::HasAffiliatedKeyword(inner) => {
|
||||
if !inner.keywords.is_empty() {
|
||||
return Some(inner.start_after_affiliated_keywords);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Reset the input OrgSource as if it was starting a fresh document.
|
||||
///
|
||||
/// This is important for making start-of-document, end-of-document, and other context-dependent tests succeed.
|
||||
|
||||
@@ -6,7 +6,11 @@ use super::Object;
|
||||
pub enum AffiliatedKeywordValue<'s> {
|
||||
SingleString(&'s str),
|
||||
ListOfStrings(Vec<&'s str>),
|
||||
ListOfListsOfObjects(Vec<Vec<Object<'s>>>),
|
||||
OptionalPair {
|
||||
optval: Option<&'s str>,
|
||||
val: &'s str,
|
||||
},
|
||||
ObjectTree(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -15,7 +19,7 @@ pub struct AffiliatedKeyword<'s> {
|
||||
pub value: AffiliatedKeywordValue<'s>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AffiliatedKeywords<'s> {
|
||||
pub(crate) keywords: BTreeMap<String, AffiliatedKeywordValue<'s>>,
|
||||
}
|
||||
@@ -23,11 +27,3 @@ pub struct AffiliatedKeywords<'s> {
|
||||
pub trait GetAffiliatedKeywords<'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,
|
||||
})
|
||||
.flat_map(|section| section.children.iter())
|
||||
.take(1)
|
||||
.filter_map(|element| match element {
|
||||
.take_while(|element| {
|
||||
matches!(element, Element::Planning(_) | Element::PropertyDrawer(_))
|
||||
})
|
||||
.find_map(|element| match element {
|
||||
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
|
||||
_ => None,
|
||||
})
|
||||
.into_iter()
|
||||
.flat_map(|property_drawer| property_drawer.children.iter())
|
||||
}
|
||||
}
|
||||
@@ -117,10 +120,8 @@ impl<'s> Document<'s> {
|
||||
.iter()
|
||||
.flat_map(|zeroth_section| zeroth_section.children.iter());
|
||||
let property_drawer = zeroth_section_children
|
||||
.take_while(|element| match element {
|
||||
Element::Comment(_) => true,
|
||||
Element::PropertyDrawer(_) => true,
|
||||
_ => false,
|
||||
.take_while(|element| {
|
||||
matches!(element, Element::Comment(_) | Element::PropertyDrawer(_))
|
||||
})
|
||||
.find_map(|element| match element {
|
||||
Element::PropertyDrawer(property_drawer) => Some(property_drawer),
|
||||
|
||||
@@ -26,6 +26,7 @@ use super::SetSource;
|
||||
use super::SpecialBlock;
|
||||
use super::StandardProperties;
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Debug)]
|
||||
pub enum Element<'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.
|
||||
pub(crate) fn of_text(input: &'s str) -> Self {
|
||||
let mut objects = Vec::with_capacity(1);
|
||||
objects.push(Object::PlainText(PlainText { source: input }));
|
||||
Paragraph {
|
||||
source: input,
|
||||
affiliated_keywords: AffiliatedKeywords::default(),
|
||||
children: objects,
|
||||
children: vec![Object::PlainText(PlainText { source: input })],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user