3 Commits

Author SHA1 Message Date
Tom Alexander
fcd63b1231 fixup
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 21:58:41 -04:00
Tom Alexander
743b4c9982 fixup
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
2023-08-31 21:55:20 -04:00
Tom Alexander
7eccf8fccd Remove dependency on run-docker-image task.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
2023-08-31 21:48:06 -04:00
144 changed files with 2489 additions and 5093 deletions

View File

@@ -1,203 +0,0 @@
apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
name: rust-foreign-document-test
spec:
pipelineSpec:
timeouts:
pipeline: "2h0m0s"
tasks: "1h0m40s"
finally: "0h30m0s"
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
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:
- --target=foreign-document-test
- --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: run-image
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)"
- name: cargo-cache-autoclean
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
params:
- name: command
value: [cargo, cache, --autoclean]
- name: args
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
workspaces:
- name: git-source
- name: docker-credentials
- name: cargo-cache
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-test-foreign-document
- name: docker-credentials
secret:
secretName: harbor-plain
serviceAccountName: build-bot
params:
- name: image-name
value: "harbor.fizz.buzz/private/organic-test-foreign-document"
- name: path-to-image-context
value: docker/organic_test/
- name: path-to-dockerfile
value: docker/organic_test/Dockerfile

View File

@@ -83,7 +83,6 @@ spec:
value: "gcr.io/kaniko-project/executor:v1.12.1"
- name: EXTRA_ARGS
value:
- --target=tester
- --cache=true
- --cache-copy-layers
- --cache-repo=harbor.fizz.buzz/kanikocache/cache
@@ -100,18 +99,22 @@ spec:
runAfter:
- fetch-repository
- name: run-image
taskRef:
name: run-docker-image
taskSpec:
metadata: {}
stepTemplate:
name: ""
workingDir: "$(workspaces.source.path)"
workspaces:
- name: source
workspace: git-source
mountPath: /source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- build-image
params:
- name: args
value:
mountPath: /usr/local/cargo/registry
optional: true
steps:
- name: run
image: "$(params.IMAGE)"
command: []
args:
[
--no-default-features,
--features,
@@ -121,8 +124,16 @@ spec:
--test,
test_loader,
]
- name: docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
params:
- name: IMAGE
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
runAfter:
- build-image
finally:
- name: report-success
when:

View File

@@ -16,13 +16,6 @@ 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: rust-foreign-document-test
source: "pipeline-foreign-document-test.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]+$"
- name: rust-build
source: "pipeline-rust-build.yaml"
# Override https-based url from lighthouse events.

View File

@@ -1,6 +1,6 @@
[package]
name = "organic"
version = "0.1.8"
version = "0.1.4"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021"
@@ -20,16 +20,10 @@ include = [
name = "organic"
path = "src/lib.rs"
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the library.
name = "parse"
path = "src/main.rs"
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the library.
name = "compare"
path = "src/bin_compare.rs"
required-features = ["compare"]
path = "src/main.rs"
[dependencies]
nom = "7.1.1"

View File

@@ -33,10 +33,6 @@ release:
clean:
> cargo clean
.PHONY: format
format:
> $(MAKE) -C docker/cargo_fmt run
.PHONY: test
test:
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
@@ -46,10 +42,6 @@ dockertest:
> $(MAKE) -C docker/organic_test
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
.PHONY: foreign_document_test
foreign_document_test:
> $(MAKE) -C docker/organic_test run_foreign_document_test
.PHONY: dockerclean
dockerclean:
# Delete volumes created for running the tests in docker. This does not touch anything related to the jaeger docker container.

View File

@@ -2,63 +2,12 @@
Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) parser.
## Project Status
This project is a personal learning project to grow my experience in [rust](https://www.rust-lang.org/). It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README.
## Using this library
TODO: Add section on using Organic as a library (which is the intended use for this project).
### The parse binary
This program takes org-mode input either streamed in on stdin or as paths to files passed in as arguments. It then parses them using Organic and dumps the result to stdout. This program is intended solely as a development tool. Examples:
```bash
cat /foo/bar.org | cargo run --bin parse
```
```bash
cargo build --profile release-lto
./target/release-lto/parse /foo/bar.org /lorem/ipsum.org
```
### The compare binary
This program takes org-mode input either streamed in on stdin or as paths to files passed in as arguments. It then parses them using Organic and the official Emacs Org-mode parser and compares the parse result. This program is intended solely as a development tool. Since org-mode is a moving target, it is recommended that you run this through docker since we pin the version of org-mode to a specific revision. Examples:
```bash
cat /foo/bar.org | ./scripts/run_docker_compare.bash
```
```bash
./scripts/run_docker_compare.bash /foo/bar.org /lorem/ipsum.org
```
Not recommended since it is not through docker:
```bash
cat /foo/bar.org | cargo run --features compare --bin compare
```
```bash
cargo build --profile release-lto --features compare
./target/release-lto/compare /foo/bar.org /lorem/ipsum.org
```
## Running the tests
There are three levels of tests for this repository: the standard tests, the autogenerated tests, and the foreign document tests.
### The standard tests
These are regular hand-written rust tests. These can be run with:
```bash
make unittest
```
### The auto-generated tests
These tests are automatically generated from the files in the `org_mode_samples` directory and they are still integrated with the rust/cargo testing framework. For each org-mode document in that folder, a test is generated that will parse the document with both Organic and the official Emacs Org-mode parser and then it will compare the parse results. Any deviation is considered a failure. Since org-mode is a moving target, it is recommended that you run these tests inside docker since the `organic-test` docker image is pinned to a specific revision of org-mode. These can be run with:
```bash
make dockertest
```
### The foreign document tests
These tests function the same as the auto-generated tests except they are **not** integrated with the rust/cargo testing framework and they involve comparing the parse of org-mode documents that live outside this repository. This allows us to test against a far greater variety of org-mode input documents without pulling massive sets of org-mode documents into this repository. The recommended way to run these tests is still through docker because it pins org-mode and the test documents to specific git revisions. These can be run with:
```bash
make foreign_document_test
```
## License
This project is released under the public-domain-equivalent [0BSD license](https://www.tldrlegal.com/license/bsd-0-clause-license), however, this project has a couple permissively licensed non-public-domain-equivalent dependencies which require their copyright notices and/or license texts to be included. I am not a lawyer and this is not legal advice but it is my layperson's understanding that if you distribute a binary statically linking this library, you will need to abide by their terms since their code will also be linked in your binary.
This project is released under the public-domain-equivalent [0BSD license](https://www.tldrlegal.com/license/bsd-0-clause-license). This license puts no restrictions on the use of this code (you do not even have to include the copyright notice or license text when using it). HOWEVER, this project has a couple permissively licensed dependencies which do require their copyright notices and/or license texts to be included. I am not a lawyer and this is not legal advice but it is my layperson's understanding that if you distribute a binary with this library linked in, you will need to abide by their terms since their code will also be linked in your binary. I try to keep the dependencies to a minimum and the most restrictive dependency I will ever include is a permissively licensed one.

View File

@@ -16,9 +16,6 @@ fn main() {
let destination = Path::new(&out_dir).join("tests.rs");
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");
write_header(&mut test_file);
let test_files = WalkDir::new("org_mode_samples")
@@ -74,6 +71,10 @@ fn write_header(test_file: &mut File) {
test_file,
r#"
#[feature(exit_status_error)]
use organic::compare_document;
use organic::parser::document;
use organic::emacs_parse_org_document;
use organic::parser::sexp::sexp_with_padding;
"#
)
@@ -85,6 +86,7 @@ fn is_expect_fail(name: &str) -> Option<&str> {
match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
_ => None,
}
}

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile .
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push
push:

View File

@@ -6,7 +6,7 @@ all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile .
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push
push:

View File

@@ -1,5 +1,5 @@
FROM alpine:3.17 AS build
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk libgccjit-dev
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk
FROM build AS build-emacs
@@ -8,13 +8,13 @@ RUN git clone --depth 1 --branch $EMACS_VERSION https://git.savannah.gnu.org/git
WORKDIR /root/emacs
RUN mkdir /root/dist
RUN ./autogen.sh
RUN ./configure --prefix /usr --without-x --without-sound --with-native-compilation=aot
RUN ./configure --prefix /usr --without-x --without-sound
RUN make
RUN make DESTDIR="/root/dist" install
FROM build AS build-org-mode
ARG ORG_VERSION=c703541ffcc14965e3567f928de1683a1c1e33f6
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1
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.
@@ -25,83 +25,11 @@ RUN make compile
RUN make DESTDIR="/root/dist" install
FROM rustlang/rust:nightly-alpine3.17 AS tester
FROM rustlang/rust:nightly-alpine3.17
ENV LANG=en_US.UTF-8
RUN apk add --no-cache musl-dev ncurses gnutls libgccjit
RUN apk add --no-cache musl-dev ncurses gnutls
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
COPY --from=build-emacs /root/dist/ /
COPY --from=build-org-mode /root/dist/ /
ENTRYPOINT ["cargo", "test"]
FROM build as foreign-document-gather
ARG HOWARD_ABRAMS_DOT_FILES_VERSION=1b54fe75d74670dc7bcbb6b01ea560c45528c628
ARG HOWARD_ABRAMS_DOT_FILES_PATH=/foreign_documents/howardabrams/dot-files
ARG HOWARD_ABRAMS_DOT_FILES_REPO=https://github.com/howardabrams/dot-files.git
RUN mkdir /foreign_documents
RUN mkdir -p $HOWARD_ABRAMS_DOT_FILES_PATH && git -C $HOWARD_ABRAMS_DOT_FILES_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DOT_FILES_PATH remote add origin $HOWARD_ABRAMS_DOT_FILES_REPO && git -C $HOWARD_ABRAMS_DOT_FILES_PATH fetch origin $HOWARD_ABRAMS_DOT_FILES_VERSION && git -C $HOWARD_ABRAMS_DOT_FILES_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_HAMACS_VERSION=da51188cc195d41882175d412fe40a8bc5730c5c
ARG HOWARD_ABRAMS_HAMACS_PATH=/foreign_documents/howardabrams/hamacs
ARG HOWARD_ABRAMS_HAMACS_REPO=https://github.com/howardabrams/hamacs.git
RUN mkdir -p $HOWARD_ABRAMS_HAMACS_PATH && git -C $HOWARD_ABRAMS_HAMACS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_HAMACS_PATH remote add origin $HOWARD_ABRAMS_HAMACS_REPO && git -C $HOWARD_ABRAMS_HAMACS_PATH fetch origin $HOWARD_ABRAMS_HAMACS_VERSION && git -C $HOWARD_ABRAMS_HAMACS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_DEMO_IT_VERSION=e399fd7ceb73caeae7cb50b247359bafcaee2a3f
ARG HOWARD_ABRAMS_DEMO_IT_PATH=/foreign_documents/howardabrams/demo-it
ARG HOWARD_ABRAMS_DEMO_IT_REPO=https://github.com/howardabrams/demo-it.git
RUN mkdir -p $HOWARD_ABRAMS_DEMO_IT_PATH && git -C $HOWARD_ABRAMS_DEMO_IT_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DEMO_IT_PATH remote add origin $HOWARD_ABRAMS_DEMO_IT_REPO && git -C $HOWARD_ABRAMS_DEMO_IT_PATH fetch origin $HOWARD_ABRAMS_DEMO_IT_VERSION && git -C $HOWARD_ABRAMS_DEMO_IT_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_MAGIT_DEMO_VERSION=59e82f6bc7c18f550478d86a8f680c3f2da66985
ARG HOWARD_ABRAMS_MAGIT_DEMO_PATH=/foreign_documents/howardabrams/magit-demo
ARG HOWARD_ABRAMS_MAGIT_DEMO_REPO=https://github.com/howardabrams/magit-demo.git
RUN mkdir -p $HOWARD_ABRAMS_MAGIT_DEMO_PATH && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH remote add origin $HOWARD_ABRAMS_MAGIT_DEMO_REPO && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH fetch origin $HOWARD_ABRAMS_MAGIT_DEMO_VERSION && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION=bfb7bd640fdf0ce3def21f9fc591ed35d776b26d
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH=/foreign_documents/howardabrams/pdx-emacs-hackers
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO=https://github.com/howardabrams/pdx-emacs-hackers.git
RUN mkdir -p $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH remote add origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH fetch origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION=50de13068722b9e3878f8598b749b7ccd14e7f8e
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_PATH=/foreign_documents/howardabrams/flora-simulator
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_REPO=https://github.com/howardabrams/flora-simulator.git
RUN mkdir -p $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH remote add origin $HOWARD_ABRAMS_FLORA_SIMULATOR_REPO && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH fetch origin $HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION=2d7a5e41001a1adf7ec24aeb6acc8525a72d7892
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH=/foreign_documents/howardabrams/literate-devops-demo
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO=https://github.com/howardabrams/literate-devops-demo.git
RUN mkdir -p $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH remote add origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH fetch origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION=b651c7f8b47b2710e99fce9652980902bbc1c6c9
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH=/foreign_documents/howardabrams/clojure-yesql-xp
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO=https://github.com/howardabrams/clojure-yesql-xp.git
RUN mkdir -p $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH remote add origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH fetch origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_VEEP_VERSION=e37fcf63a5c4a526255735ee34955528b3b280ae
ARG HOWARD_ABRAMS_VEEP_PATH=/foreign_documents/howardabrams/veep
ARG HOWARD_ABRAMS_VEEP_REPO=https://github.com/howardabrams/veep.git
RUN mkdir -p $HOWARD_ABRAMS_VEEP_PATH && git -C $HOWARD_ABRAMS_VEEP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_VEEP_PATH remote add origin $HOWARD_ABRAMS_VEEP_REPO && git -C $HOWARD_ABRAMS_VEEP_PATH fetch origin $HOWARD_ABRAMS_VEEP_VERSION && git -C $HOWARD_ABRAMS_VEEP_PATH checkout FETCH_HEAD
ARG DOOMEMACS_VERSION=42d5fd83504f8aa80f3248036006fbcd49222943
ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
ARG WORG_VERSION=0c8d5679b536af450b61812246a3e02b8103f4b8
ARG WORG_PATH=/foreign_documents/worg
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
FROM tester as foreign-document-test
RUN apk add --no-cache bash coreutils
RUN mkdir /foreign_documents
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
COPY --from=foreign-document-gather /foreign_documents/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"]

View File

@@ -6,11 +6,7 @@ all: build push
.PHONY: build
build:
docker build -t $(IMAGE_NAME) -f Dockerfile --target tester .
.PHONY: build_foreign_document_test
build_foreign_document_test:
docker build -t $(IMAGE_NAME)-foreign-document -f Dockerfile --target foreign-document-test .
docker build -t $(IMAGE_NAME) -f Dockerfile ../../
.PHONY: push
push:
@@ -38,7 +34,3 @@ run: build
.PHONY: shell
shell: build
docker run --rm -i -t --entrypoint /bin/sh --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)
.PHONY: run_foreign_document_test
run_foreign_document_test: build_foreign_document_test
docker run --rm --init --read-only --mount type=tmpfs,destination=/tmp -v "$$(readlink -f ../../):/source:ro" --workdir=/source --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target $(IMAGE_NAME)-foreign-document

View File

@@ -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 "${@}"

View File

@@ -25,4 +25,3 @@ This could significantly reduce our calls to exit matchers.
I think targets would break this.
The exit matchers are already implicitly building this behavior since they should all exit very early when the starting character is wrong. Putting this logic in a centralized place, far away from where those characters are actually going to be used, is unfortunate for readability.
** Use exit matcher to cut off trailing whitespace instead of re-matching in plain lists.

View File

@@ -1 +0,0 @@
This folder contains org-mode documents that get automatically included as tests using build.rs.

View File

@@ -1,3 +0,0 @@
#+BEGIN: timestamp :format "%Y-%m-%d %H:%M"
#+END

View File

@@ -1,8 +0,0 @@
* Footnotes
[fn:1]
#+BEGIN_EXAMPLE
baz
#+END_EXAMPLE

View File

@@ -1,5 +0,0 @@
#+begin_quote
foo
#+end_quote

View File

@@ -1,3 +0,0 @@
# These are only allowed by configuring org-list-allow-alphabetical which the automated tests are not currently set up to do, so this will parse as a paragraph:
a. foo
b. bar

View File

@@ -1,2 +0,0 @@
3. [@3] foo
4. bar

View File

@@ -1,6 +0,0 @@
- foo ::
- bar ::
baz

View File

@@ -1,2 +1 @@
- {{{foo(bar)}}} :: baz
- =foo= :: bar

View File

@@ -1,3 +0,0 @@
- foo :: bar
- foo :: bar
- foo :: bar

View File

@@ -1,2 +0,0 @@
- =foo :: bar= :: baz
- lorem :: ipsum :: dolar

View File

@@ -1,5 +1,3 @@
1.
2.
3.
* headline

View File

@@ -1,3 +0,0 @@
1. foo
- bar
- lorem :: ipsum

View File

@@ -1,2 +0,0 @@
# Since this is an ordered list, the text before the " :: " is NOT parsed as a tag.
1. foo :: bar

View File

@@ -1,6 +0,0 @@
* Overwrite
:PROPERTIES:
:header-args: :var foo="lorem"
:header-args:emacs-lisp: :var bar="ipsum"
:header-args:emacs-lisp+: :results silent :var baz=7
:END:

View File

@@ -1,6 +0,0 @@
# The STARTUP directive here instructs org-mode to align tables which emacs normally does when opening the file. Since Organic is solely a parser, we have no business editing the org-mode document so Organic does not handle aligning tables, so in order for this test to pass, we have to avoid that behavior in Emacs.
#+STARTUP: align
|foo|bar|
|-
|lorem|ipsum|

View File

@@ -1,6 +0,0 @@
src_elisp{(bar)}
*src_elisp{(bar)}*
| foo *bar* |
| foo src_elisp{(bar)} |
| foo *src_elisp{(bar)}* |

View File

@@ -1,4 +0,0 @@
| Name | Value |
|------+-------|
| foo | bar |
#+tblfm:

View File

@@ -1,8 +0,0 @@
| Name | Price | Quantity | Total |
|------+-------+----------+-------|
| foo | 7 | 4 | 28 |
| bar | 3.5 | 3 | 10.5 |
|------+-------+----------+-------|
| | | 7 | 38.5 |
#+tblfm: $4=$2*$3::@>$4=vsum(@2..@-1)
#+tblfm: @>$3=vsum(@2..@-1)

View File

@@ -1,6 +0,0 @@
%%(foo
)
%%(bar ; baz
lorem

View File

@@ -1,2 +0,0 @@
# Fixed width areas must begin with colon followed by a space, not a tab, so this is not a fixed width area.
: foo

View File

@@ -1,7 +0,0 @@
# This test is to prove that the parser works with affiliated keywords that have both a shorter and longer version.
#+results:
#+result:
#+begin_latex
\foo
#+end_latex

View File

@@ -1 +0,0 @@
#+call: foo(bar="baz")

View File

@@ -1 +0,0 @@
#+title:foo:bar: baz: lorem: ipsum

View File

@@ -1,2 +0,0 @@
#+begin_src
#+end_src

View File

@@ -1,4 +0,0 @@
# There are trailing spaces after the begin and end src lines
#+begin_src
echo "this is a source block."
#+end_src

View File

@@ -1,3 +0,0 @@
*[fn:: /abcdef[fn::ghijklmnopqrstuvw]xyz/ r]*
*[fn:: /abcdef[fn::ghijk *lmnopq* rstuvw]xyz/ r]*

View File

@@ -1,11 +0,0 @@
# Should be a link:
https://en.wikipedia.org/wiki/Shebang_(Unix)
# No closing parenthesis, so link ends at underscore.
https://en.wikipedia.org/wiki/Shebang_(Unix
# Parenthesis only allowed to depth of 2 so link ends at underscore.
https://en.wikipedia.org/wiki/Shebang_(((Unix)))
# Even though they eventually become balanced, we hit negative parenthesis depth so link ends at )
https://en.wikipedia.org/wiki/Shebang)Unix(

View File

@@ -1 +0,0 @@
mailto:foo@bar.baz .

View File

@@ -1,3 +0,0 @@
<<<Foo Bar Baz>>>
foo bar baz

View File

@@ -1,6 +0,0 @@
<<<foo bar baz>>>
foo
bar
baz

View File

@@ -1 +0,0 @@
[[elisp:(local-set-key "\M-\x" 'foo-bar-baz)]]

View File

@@ -1 +0,0 @@
[[https://en.wikipedia.org/wiki/Shebang_(Unix)]]

View File

@@ -1 +0,0 @@
[[[http://foo.bar/baz][lorem]]]

View File

@@ -1,4 +0,0 @@
[/]
[/2]
[3/]
[%]

View File

@@ -1,7 +0,0 @@
# Even though *exporting* honors the setting to require braces for subscript/superscript, the official org-mode parser still parses subscripts and superscripts.
#+OPTIONS: ^:{}
foo_this isn't a subscript when exported due to lack of braces (but its still a subscript during parsing)
bar_{this is a subscript}

View File

@@ -1,13 +0,0 @@
foo_(bar)
foo_(b(ar)
foo_(b{ar)
foo_{b(ar}
foo_(b(a)r)
foo_b(a)r
foo_(b+ar)

View File

@@ -1 +0,0 @@
foo ** bar ** baz

View File

@@ -1 +0,0 @@
foo ~~ bar ~~ baz

View File

@@ -1,4 +0,0 @@
# Since "foos" has an extra "s", this does not match the target.
the foos bar
The <<<foo>>> and stuff.

View File

@@ -1,4 +1 @@
foo ==>bar=.
# This uses a zero-width space to escape the equals signs to make the verbatim not end.
=lorem == ipsum=

View File

@@ -1,2 +0,0 @@
* TODO [#A] COMMENT foo bar
baz

View File

@@ -1,2 +0,0 @@
* DONE
*

View File

@@ -1,6 +0,0 @@
#+TODO: TODO(t) INPROGRESS(i/!) | DONE(d!) CANCELED(c@/!)
# ! : Log changes leading to this state.
# @ : Log changes leading to this state and prompt for a comment to include.
# /! : Log changes leaving this state if and only if to a state that does not log. This can be combined with the above like WAIT(w!/!) or DELAYED(d@/!)
* INPROGRESS
- State "TODO" from "INPROGRESS" [2023-09-14 Thu 02:13]

View File

@@ -1,8 +0,0 @@
#+STARTUP: odd
* Foo
***** Bar
* Baz
*** Lorem
* Ipsum
**** Dolar
***** Cat

View File

@@ -1,4 +0,0 @@
* DONE foo
DEADLINE: <2023-09-08 Fri>
* DONE bar

View File

@@ -4,10 +4,10 @@ set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
(cd "$DIR/../" && RUSTFLAGS="-C opt-level=0" cargo build --no-default-features)
valgrind --tool=callgrind --callgrind-out-file="$DIR/../callgrind.out" "$DIR/../target/debug/parse" "${@}"
RUSTFLAGS="-C opt-level=0" cargo build --no-default-features
valgrind --tool=callgrind --callgrind-out-file=callgrind.out target/debug/compare
echo "You probably want to run:"
echo "callgrind_annotate --auto=yes '$DIR/../callgrind.out'"
echo "callgrind_annotate --auto=yes callgrind.out"

View File

@@ -6,6 +6,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="perf"}
cd "$DIR/../"
function main {
local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
@@ -13,12 +15,12 @@ function main {
else
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" "${@}"
cargo build --no-default-features "${additional_flags[@]}"
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/compare
# Convert to a format firefox will read
# flags to consider --show-info
perf script -F +pid --input "$DIR/../perf.data" > "$DIR/../perf.firefox"
perf script -F +pid --input perf.data > perf.firefox
echo "You probably want to go to https://profiler.firefox.com/"
echo "Either that or run hotspot"

View File

@@ -8,29 +8,15 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${TRACE:="NO"} # or YES to send traces to jaeger
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
: ${NO_COLOR:=""} # Set to anything to disable color output
: ${PROFILE:="debug"}
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make)
############## Setup #########################
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
build_container
launch_container "${@}"
launch_container
}
function build_container {
@@ -39,6 +25,7 @@ function build_container {
function launch_container {
local additional_flags=()
local additional_args=()
local features=(compare)
if [ "$NO_COLOR" != "" ]; then
@@ -52,8 +39,11 @@ function launch_container {
fi
if [ "$SHELL" != "YES" ]; then
local features_joined=$(IFS=","; echo "${features[*]}")
additional_args+=(cargo run --no-default-features --features "$features_joined")
additional_flags+=(--read-only)
else
additional_args+=(/bin/sh)
additional_flags+=(-t)
fi
@@ -61,51 +51,7 @@ function launch_container {
additional_flags+=(--env RUST_BACKTRACE=full)
fi
if [ "$SHELL" = "YES" ]; then
exec docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test /bin/sh
fi
local features_joined
features_joined=$(IFS=","; echo "${features[*]}")
local build_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
PROFILE="debug"
else
build_flags+=(--profile "$PROFILE")
fi
if [ $# -gt 0 ]; then
# If we passed in args, we need to forward them along
for path in "${@}"; do
local full_path
full_path=$($REALPATH "$path")
init_script=$(cat <<EOF
set -euo pipefail
IFS=\$'\n\t'
cargo build --bin compare --no-default-features --features "$features_joined" ${build_flags[@]}
exec /target/${PROFILE}/compare "/input${full_path}"
EOF
)
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
done
else
local current_directory init_script
current_directory=$(pwd)
init_script=$(cat <<EOF
set -euo pipefail
IFS=\$'\n\t'
cargo build --bin compare --no-default-features --features "$features_joined" ${build_flags[@]}
cd /input${current_directory}
exec /target/${PROFILE}/compare
EOF
)
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "/:/input:ro" -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
fi
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}"
}
main "${@}"

View File

@@ -1,72 +0,0 @@
#!/usr/bin/env bash
#
# Bisect parsing a file at various line cut-off points to see which line causes the parse to differ from emacs.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
############## Setup #########################
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function log {
(>&2 echo "${@}")
}
############## Program #########################
function main {
local target_full_path
target_full_path=$($REALPATH "$1")
SOURCE_FOLDER=$(dirname "$target_full_path")
TARGET_DOCUMENT=$(basename "$target_full_path")
local good=0
local bad
bad=$(wc -l "$SOURCE_FOLDER/$TARGET_DOCUMENT" | awk '{print $1}')
set +e
(run_parse "$bad")
local status=$?
set -e
if [ $status -eq 0 ]; then
log "Entire file passes."
exit 0
fi
while [[ "$((bad - good))" -gt 1 ]]; do
local next_line=$((((bad - good) / 2) + good))
log "Testing line $next_line"
set +e
run_parse "$next_line" &> /dev/null
local status=$?
set -e
if [ $status -eq 0 ]; then
good="$next_line"
log "Line $next_line good"
else
bad="$next_line"
log "Line $next_line bad"
fi
done
echo "Bad line: $bad"
}
function run_parse {
local lines="$1"
cd "$SOURCE_FOLDER"
head -n "$lines" "$SOURCE_FOLDER/$TARGET_DOCUMENT" | PROFILE=release-lto "${DIR}/run_docker_compare.bash"
local status=$?
return "$status"
}
main "${@}"

View File

@@ -6,6 +6,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${NO_COLOR:=""} # Set to anything to disable color output
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath)
MAKE=$(command -v gmake || command -v make)
@@ -55,7 +56,7 @@ cargo test --no-default-features --features compare --no-fail-fast --lib --test
EOF
)
docker run "${additional_flags[@]}" --init --rm --read-only --mount type=tmpfs,destination=/tmp -v "$($REALPATH "$DIR/../"):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
docker run "${additional_flags[@]}" --init --rm --read-only --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test sh -c "$init_script"
}

View File

@@ -4,6 +4,7 @@ set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath)
function main {
@@ -11,7 +12,7 @@ function main {
local test
while read test; do
(cd "$DIR/../" && cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output)
cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output
done<<<"$test_names"
}

View File

@@ -7,6 +7,8 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${PROFILE:="release-lto"}
cd "$DIR/../"
function main {
local additional_flags=()
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
@@ -14,8 +16,8 @@ function main {
else
additional_flags+=(--profile "$PROFILE")
fi
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
time "$DIR/../target/${PROFILE}/parse" "${@}"
cargo build --no-default-features "${additional_flags[@]}"
time ./target/${PROFILE}/compare
}
main "${@}"

View File

@@ -1,52 +0,0 @@
#![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
use std::io::Read;
use organic::compare::run_anonymous_compare;
use organic::compare::run_compare_on_file;
#[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry;
#[cfg(feature = "tracing")]
use crate::init_tracing::shutdown_telemetry;
#[cfg(feature = "tracing")]
mod init_tracing;
#[cfg(not(feature = "tracing"))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
main_body()
}
#[cfg(feature = "tracing")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = 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"))]
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)
} else {
for arg in args {
run_compare_on_file(arg)?
}
Ok(())
}
}
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
let mut stdin_contents = String::new();
std::io::stdin()
.lock()
.read_to_string(&mut stdin_contents)?;
Ok(stdin_contents)
}

View File

@@ -1,77 +0,0 @@
use std::path::Path;
use crate::compare::diff::compare_document;
use crate::compare::parse::emacs_parse_anonymous_org_document;
use crate::compare::parse::emacs_parse_file_org_document;
use crate::compare::parse::get_emacs_version;
use crate::compare::parse::get_org_mode_version;
use crate::compare::sexp::sexp;
use crate::parser::parse;
use crate::parser::parse_with_settings;
use crate::GlobalSettings;
use crate::LocalFileAccessInterface;
pub fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P,
) -> Result<(), 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();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let rust_parsed = parse(org_contents)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?;
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);
// 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 diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
// 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.replace("\r\n", "\n");
let org_contents = org_contents.as_str();
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 rust_parsed = parse_with_settings(org_contents, &global_settings)?;
let org_sexp = emacs_parse_file_org_document(org_path)?;
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);
// 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 diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}

View File

@@ -1,73 +1,68 @@
use std::collections::BTreeSet;
use std::collections::HashSet;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::assert_bounds;
use super::util::assert_name;
use super::util::get_property;
use crate::types::AngleLink;
use crate::types::Bold;
use crate::types::CheckboxType;
use crate::types::Citation;
use crate::types::CitationReference;
use crate::types::Clock;
use crate::types::Code;
use crate::types::Comment;
use crate::types::CommentBlock;
use crate::types::DiarySexp;
use crate::types::Document;
use crate::types::DocumentElement;
use crate::types::Drawer;
use crate::types::DynamicBlock;
use crate::types::Element;
use crate::types::Entity;
use crate::types::ExampleBlock;
use crate::types::ExportBlock;
use crate::types::ExportSnippet;
use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::GreaterBlock;
use crate::types::Heading;
use crate::types::HorizontalRule;
use crate::types::InlineBabelCall;
use crate::types::InlineSourceBlock;
use crate::types::Italic;
use crate::types::Keyword;
use crate::types::LatexEnvironment;
use crate::types::LatexFragment;
use crate::types::LineBreak;
use crate::types::NodeProperty;
use crate::types::Object;
use crate::types::OrgMacro;
use crate::types::Paragraph;
use crate::types::PlainLink;
use crate::types::PlainList;
use crate::types::PlainListItem;
use crate::types::PlainText;
use crate::types::Planning;
use crate::types::PriorityCookie;
use crate::types::PropertyDrawer;
use crate::types::RadioLink;
use crate::types::RadioTarget;
use crate::types::RegularLink;
use crate::types::Section;
use crate::types::Source;
use crate::types::SrcBlock;
use crate::types::StatisticsCookie;
use crate::types::StrikeThrough;
use crate::types::Subscript;
use crate::types::Superscript;
use crate::types::Table;
use crate::types::TableCell;
use crate::types::TableRow;
use crate::types::Target;
use crate::types::Timestamp;
use crate::types::TodoKeywordType;
use crate::types::Underline;
use crate::types::Verbatim;
use crate::types::VerseBlock;
use crate::parser::sexp::unquote;
use crate::parser::sexp::Token;
use crate::parser::AngleLink;
use crate::parser::Bold;
use crate::parser::Citation;
use crate::parser::CitationReference;
use crate::parser::Clock;
use crate::parser::Code;
use crate::parser::Comment;
use crate::parser::CommentBlock;
use crate::parser::DiarySexp;
use crate::parser::Document;
use crate::parser::DocumentElement;
use crate::parser::Drawer;
use crate::parser::DynamicBlock;
use crate::parser::Element;
use crate::parser::Entity;
use crate::parser::ExampleBlock;
use crate::parser::ExportBlock;
use crate::parser::ExportSnippet;
use crate::parser::FixedWidthArea;
use crate::parser::FootnoteDefinition;
use crate::parser::FootnoteReference;
use crate::parser::GreaterBlock;
use crate::parser::Heading;
use crate::parser::HorizontalRule;
use crate::parser::InlineBabelCall;
use crate::parser::InlineSourceBlock;
use crate::parser::Italic;
use crate::parser::Keyword;
use crate::parser::LatexEnvironment;
use crate::parser::LatexFragment;
use crate::parser::LineBreak;
use crate::parser::Object;
use crate::parser::OrgMacro;
use crate::parser::Paragraph;
use crate::parser::PlainLink;
use crate::parser::PlainList;
use crate::parser::PlainListItem;
use crate::parser::PlainText;
use crate::parser::Planning;
use crate::parser::PropertyDrawer;
use crate::parser::RadioLink;
use crate::parser::RadioTarget;
use crate::parser::RegularLink;
use crate::parser::Section;
use crate::parser::Source;
use crate::parser::SrcBlock;
use crate::parser::StatisticsCookie;
use crate::parser::StrikeThrough;
use crate::parser::Subscript;
use crate::parser::Superscript;
use crate::parser::Table;
use crate::parser::TableCell;
use crate::parser::TableRow;
use crate::parser::Target;
use crate::parser::Timestamp;
use crate::parser::Underline;
use crate::parser::Verbatim;
use crate::parser::VerseBlock;
#[derive(Debug)]
pub enum DiffEntry<'s> {
@@ -87,7 +82,7 @@ pub struct DiffResult<'s> {
}
#[derive(Debug, PartialEq)]
enum DiffStatus {
pub enum DiffStatus {
Good,
Bad,
}
@@ -310,7 +305,6 @@ fn compare_element<'s>(
Element::FixedWidthArea(obj) => compare_fixed_width_area(source, emacs, obj),
Element::HorizontalRule(obj) => compare_horizontal_rule(source, emacs, obj),
Element::Keyword(obj) => compare_keyword(source, emacs, obj),
Element::BabelCall(obj) => compare_babel_call(source, emacs, obj),
Element::LatexEnvironment(obj) => compare_latex_environment(source, emacs, obj),
};
match compare_result {
@@ -490,11 +484,11 @@ fn compare_heading<'s>(
let level = get_property(emacs, ":level")?
.ok_or("Level should not be nil")?
.as_atom()?;
if rust.level.to_string() != level {
if rust.stars.to_string() != level {
this_status = DiffStatus::Bad;
message = Some(format!(
"Headline level do not match (emacs != rust): {} != {}",
level, rust.level
"Headline level do not much (emacs != rust): {} != {}",
level, rust.stars
))
}
@@ -516,9 +510,9 @@ fn compare_heading<'s>(
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
match (todo_keyword, &rust.todo_keyword, unquote(todo_keyword)) {
match (todo_keyword, rust.todo_keyword, unquote(todo_keyword)) {
("nil", None, _) => {}
(_, Some((_rust_todo_type, rust_todo)), Ok(emacs_todo)) if emacs_todo == *rust_todo => {}
(_, Some(rust_todo), Ok(emacs_todo)) if emacs_todo == rust_todo => {}
(emacs_todo, rust_todo, _) => {
this_status = DiffStatus::Bad;
message = Some(format!(
@@ -527,37 +521,9 @@ fn compare_heading<'s>(
));
}
};
// Compare todo-type
let todo_type = get_property(emacs, ":todo-type")?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
// todo-type is an unquoted string either todo, done, or nil
match (todo_type, &rust.todo_keyword) {
("nil", None) => {}
("todo", Some((TodoKeywordType::Todo, _))) => {}
("done", Some((TodoKeywordType::Done, _))) => {}
(emacs_todo, rust_todo) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"(emacs != rust) {:?} != {:?}",
emacs_todo, rust_todo
));
}
};
// Compare title
let title = get_property(emacs, ":title")?;
match (title, rust.title.len()) {
(None, 0) => {}
(None, _) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Titles do not match (emacs != rust): {:?} != {:?}",
title, rust.title
))
}
(Some(title), _) => {
let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?;
let title_status = title
.as_list()?
.iter()
@@ -565,60 +531,8 @@ fn compare_heading<'s>(
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
.collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
}
};
// Compare priority
let priority = get_property(emacs, ":priority")?;
match (priority, rust.priority_cookie) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
priority, rust.priority_cookie
));
}
(Some(emacs_priority_cookie), Some(rust_priority_cookie)) => {
let emacs_priority_cookie =
emacs_priority_cookie.as_atom()?.parse::<PriorityCookie>()?;
if emacs_priority_cookie != rust_priority_cookie {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
emacs_priority_cookie, rust_priority_cookie
));
}
}
}
// Compare archived
let archived = get_property(emacs, ":archivedp")?;
match (archived, rust.is_archived) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"archived mismatch (emacs != rust) {:?} != {:?}",
archived, rust.is_archived
));
}
(None, false) | (Some(_), true) => {}
}
// Compare commented
let commented = get_property(emacs, ":commentedp")?;
match (commented, rust.is_comment) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"commented mismatch (emacs != rust) {:?} != {:?}",
commented, rust.is_comment
));
}
(None, false) | (Some(_), true) => {}
}
// TODO: Compare :footnote-section-p
// TODO: Compare todo-type, priority, :footnote-section-p, :archivedp, :commentedp
// Compare section
let section_status = children
@@ -732,10 +646,6 @@ fn compare_plain_list<'s>(
Ok(_) => {}
};
// TODO compare :type
//
// :type is an unquoted atom of either descriptive, ordered, or unordered
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_plain_list_item(source, emacs_child, rust_child)?);
}
@@ -804,27 +714,6 @@ fn compare_plain_list_item<'s>(
contents_status,
)?);
// TODO: compare :bullet :counter :pre-blank
// Compare checkbox
let checkbox = get_property(emacs, ":checkbox")?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
match (checkbox, &rust.checkbox) {
("nil", None) => {}
("off", Some((CheckboxType::Off, _))) => {}
("trans", Some((CheckboxType::Trans, _))) => {}
("on", Some((CheckboxType::On, _))) => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Checkbox mismatch (emacs != rust) {:?} != {:?}",
checkbox, rust.checkbox
));
}
};
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
@@ -898,8 +787,6 @@ fn compare_dynamic_block<'s>(
Ok(_) => {}
};
// TODO: Compare :block-name :arguments
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_element(source, emacs_child, rust_child)?);
}
@@ -1027,7 +914,7 @@ fn compare_property_drawer<'s>(
rust: &'s PropertyDrawer<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let mut child_status = Vec::new();
let child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "property-drawer";
@@ -1043,8 +930,9 @@ fn compare_property_drawer<'s>(
Ok(_) => {}
};
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_node_property(source, emacs_child, rust_child)?);
for (_emacs_child, _rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
// TODO: What are node properties and are they the only legal child of property drawers?
// child_status.push(compare_element(source, emacs_child, rust_child)?);
}
Ok(DiffResult {
@@ -1058,40 +946,6 @@ fn compare_property_drawer<'s>(
.into())
}
fn compare_node_property<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s NodeProperty<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "node-property";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
match assert_bounds(source, emacs, rust) {
Err(err) => {
this_status = DiffStatus::Bad;
message = Some(err.to_string())
}
Ok(_) => {}
};
// TODO: Compare :key :value
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message,
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into())
}
fn compare_table<'s>(
source: &'s str,
emacs: &'s Token<'s>,
@@ -1114,44 +968,6 @@ fn compare_table<'s>(
Ok(_) => {}
};
// Compare formulas
//
// :tblfm is either nil or a list () filled with quoted strings containing the value for any tblfm keywords at the end of the table.
let emacs_formulas = get_property(emacs, ":tblfm")?;
if let Some(emacs_formulas) = emacs_formulas {
let emacs_formulas = emacs_formulas.as_list()?;
if emacs_formulas.len() != rust.formulas.len() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
} else {
let atoms = emacs_formulas
.into_iter()
.map(Token::as_atom)
.collect::<Result<Vec<_>, _>>()?;
let unquoted = atoms
.into_iter()
.map(unquote)
.collect::<Result<BTreeSet<_>, _>>()?;
for kw in &rust.formulas {
if !unquoted.contains(kw.value) {
this_status = DiffStatus::Bad;
message = Some(format!("Could not find formula in emacs: {}", kw.value))
}
}
}
} else {
if !rust.formulas.is_empty() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
}
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_row(source, emacs_child, rust_child)?);
}
@@ -1189,10 +1005,6 @@ fn compare_table_row<'s>(
Ok(_) => {}
};
// TODO: Compare :type
//
// :type is an unquoted atom of either standard or rule
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_cell(source, emacs_child, rust_child)?);
}
@@ -1391,8 +1203,6 @@ fn compare_src_block<'s>(
Ok(_) => {}
};
// TODO: Compare :language :switches :parameters :number-lines :preserve-indent :retain-labels :use-labels :label-fmt :value
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
@@ -1582,77 +1392,6 @@ fn compare_keyword<'s>(
Ok(_) => {}
};
let key = unquote(
get_property(emacs, ":key")?
.ok_or("Emacs keywords should have a :key")?
.as_atom()?,
)?;
if key != rust.key.to_uppercase() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Mismatchs keyword keys (emacs != rust) {:?} != {:?}",
key, rust.key
))
}
let value = unquote(
get_property(emacs, ":value")?
.ok_or("Emacs keywords should have a :value")?
.as_atom()?,
)?;
if value != rust.value {
this_status = DiffStatus::Bad;
message = Some(format!(
"Mismatchs keyword values (emacs != rust) {:?} != {:?}",
value, rust.value
))
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message,
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into())
}
fn compare_babel_call<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Keyword<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "babel-call";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
match assert_bounds(source, emacs, rust) {
Err(err) => {
this_status = DiffStatus::Bad;
message = Some(err.to_string())
}
Ok(_) => {}
};
// TODO: compare :call :inside-header :arguments :end-header
let value = unquote(
get_property(emacs, ":value")?
.ok_or("Emacs keywords should have a :value")?
.as_atom()?,
)?;
if value != rust.value {
this_status = DiffStatus::Bad;
message = Some(format!(
"Mismatchs keyword values (emacs != rust) {:?} != {:?}",
value, rust.value
))
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
@@ -1952,8 +1691,6 @@ fn compare_regular_link<'s>(
Ok(_) => {}
};
// TODO: Compare :type :path :format :raw-link :application :search-option
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
@@ -2481,8 +2218,6 @@ fn compare_subscript<'s>(
Ok(_) => {}
};
// TODO compare :use-brackets-p
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
@@ -2514,8 +2249,6 @@ fn compare_superscript<'s>(
Ok(_) => {}
};
// TODO compare :use-brackets-p
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),

View File

@@ -1,7 +1,7 @@
mod compare;
mod diff;
mod parse;
mod sexp;
mod util;
pub use compare::run_anonymous_compare;
pub use compare::run_compare_on_file;
pub use diff::compare_document;
pub use parse::emacs_parse_org_document;
pub use parse::get_emacs_version;
pub use parse::get_org_mode_version;

View File

@@ -1,9 +1,6 @@
use std::path::Path;
use std::process::Command;
pub fn emacs_parse_anonymous_org_document<C>(
file_contents: C,
) -> Result<String, Box<dyn std::error::Error>>
pub fn emacs_parse_org_document<C>(file_contents: C) -> Result<String, Box<dyn std::error::Error>>
where
C: AsRef<str>,
{
@@ -11,8 +8,6 @@ where
let elisp_script = format!(
r#"(progn
(erase-buffer)
(require 'org)
(defun org-table-align () t)
(insert "{escaped_file_contents}")
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
@@ -20,48 +15,14 @@ where
escaped_file_contents = escaped_file_contents
);
let mut cmd = Command::new("emacs");
let cmd = cmd
let proc = cmd
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
out.status.exit_ok()?;
let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?)
}
pub fn emacs_parse_file_org_document<P>(file_path: P) -> Result<String, Box<dyn std::error::Error>>
where
P: AsRef<Path>,
{
let file_path = file_path.as_ref().canonicalize()?;
let containing_directory = file_path.parent().ok_or(format!(
"Failed to get containing directory for path {}",
file_path.display()
))?;
let elisp_script = format!(
r#"(progn
(require 'org)
(defun org-table-align () t)
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)"#
);
let mut cmd = Command::new("emacs");
let cmd = cmd
.current_dir(containing_directory)
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--insert")
.arg(file_path.as_os_str())
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
let out = proc.output()?;
out.status.exit_ok()?;
let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?)
@@ -94,7 +55,7 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
(message "%s" (version))
)"#;
let mut cmd = Command::new("emacs");
let cmd = cmd
let proc = cmd
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
@@ -102,7 +63,7 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
let out = proc.output()?;
out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?)
}
@@ -113,7 +74,7 @@ pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
(message "%s" (org-version nil t nil))
)"#;
let mut cmd = Command::new("emacs");
let cmd = cmd
let proc = cmd
.arg("-q")
.arg("--no-site-file")
.arg("--no-splash")
@@ -121,7 +82,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 = proc.output()?;
out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?)
}

View File

@@ -1,5 +1,5 @@
use super::sexp::Token;
use crate::types::Source;
use crate::parser::sexp::Token;
use crate::parser::Source;
/// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool {
@@ -21,10 +21,7 @@ fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize
(offset, end)
}
pub(crate) fn assert_name<'s>(
emacs: &'s Token<'s>,
name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
pub fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let first_child = children
.first()
@@ -40,7 +37,7 @@ pub(crate) fn assert_name<'s>(
Ok(())
}
pub(crate) fn assert_bounds<'s, S: Source<'s>>(
pub fn assert_bounds<'s, S: Source<'s>>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s S,
@@ -144,12 +141,7 @@ fn maybe_token_to_usize(
.map_or(Ok(None), |r| r.map(Some))?)
}
/// Get a named property from the emacs token.
///
/// Returns Ok(None) if value is nil.
///
/// Returns error if the attribute is not specified on the token at all.
pub(crate) fn get_property<'s, 'x>(
pub fn get_property<'s, 'x>(
emacs: &'s Token<'s>,
key: &'x str,
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {

View File

@@ -1,185 +0,0 @@
use std::marker::PhantomData;
use nom::combinator::eof;
use nom::IResult;
use super::exiting::ExitClass;
use super::global_settings::GlobalSettings;
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;
#[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>),
/// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str),
/// Stores the name of the current object to prevent directly nesting elements of the same type.
ContextObject(&'r str),
/// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool),
/// 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>),
}
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>>,
}
impl<'g, 'r, 's> Context<'g, 'r, 's> {
pub(crate) fn new(
global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>,
) -> Self {
Self {
global_settings,
tree,
}
}
pub(crate) fn with_additional_node(&'r self, new_element: &'r ContextElement<'r, 's>) -> Self {
let new_tree = self.tree.push(new_element);
Self::new(self.global_settings, new_tree)
}
pub(crate) fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> {
self.tree.iter()
}
fn iter_context(&'r self) -> Iter<'g, 'r, 's> {
Iter {
next: self.tree.iter_list(),
global_settings: self.global_settings,
}
}
pub(crate) fn get_parent(&'r self) -> Option<Self> {
self.tree.get_parent().map(|parent_tree| Self {
global_settings: self.global_settings,
tree: parent_tree.clone(),
})
}
fn get_data(&self) -> &ContextElement<'r, 's> {
self.tree.get_data()
}
pub(crate) fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> {
self.global_settings
}
pub(crate) fn with_global_settings<'gg>(
&self,
new_settings: &'gg GlobalSettings<'gg, 's>,
) -> Context<'gg, 'r, 's> {
Context {
global_settings: new_settings,
tree: self.tree.clone(),
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn check_exit_matcher(
&'r self,
i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
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)(&current_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(),
))));
}
/// Indicates if elements should consume the whitespace after them.
///
/// Defaults to true.
pub(crate) fn should_consume_trailing_whitespace(&self) -> bool {
self._should_consume_trailing_whitespace().unwrap_or(true)
}
fn _should_consume_trailing_whitespace(&self) -> Option<bool> {
for current_node in self.iter() {
match current_node {
ContextElement::ConsumeTrailingWhitespace(should) => {
return Some(*should);
}
_ => {}
}
}
None
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn document_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
eof(input)
}
struct Iter<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>,
next: super::list::IterList<'r, &'r ContextElement<'r, 's>>,
}
impl<'g, 'r, 's> Iterator for Iter<'g, 'r, 's> {
type Item = Context<'g, 'r, 's>;
fn next(&mut self) -> Option<Self::Item> {
let next_tree = self.next.next();
let ret =
next_tree.map(|parent_tree| Context::new(self.global_settings, parent_tree.clone()));
ret
}
}
impl<'r, 's> ContextElement<'r, 's> {
pub(crate) fn document_context() -> Self {
Self::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &document_end,
class: ExitClass::Document,
})
}
}

View File

@@ -1,13 +0,0 @@
#[derive(Debug, 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)
}
}

View File

@@ -1,23 +0,0 @@
use std::fmt::Debug;
use std::path::PathBuf;
pub trait FileAccessInterface: Debug {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
}
#[derive(Debug, Clone)]
pub struct LocalFileAccessInterface {
pub working_directory: Option<PathBuf>,
}
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)
.map(|pb| pb.join(path))
.unwrap_or_else(|| PathBuf::from(path));
Ok(std::fs::read_to_string(final_path)?)
}
}

View File

@@ -1,58 +0,0 @@
use std::collections::BTreeSet;
use super::FileAccessInterface;
use super::LocalFileAccessInterface;
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)]
pub struct GlobalSettings<'g, 's> {
pub radio_targets: Vec<&'g Vec<Object<'s>>>,
pub file_access: &'g dyn FileAccessInterface,
pub in_progress_todo_keywords: BTreeSet<String>,
pub complete_todo_keywords: BTreeSet<String>,
/// Set to true to allow for plain lists using single letters as the bullet in the same way that numbers are used.
///
/// Corresponds to the org-list-allow-alphabetical elisp variable.
pub org_list_allow_alphabetical: bool,
/// How many spaces a tab should be equal to.
///
/// Corresponds to the tab-width elisp variable.
pub tab_width: IndentationLevel,
/// Whether to only allow odd headline levels.
///
/// Corresponds to org-odd-levels-only elisp variable.
pub odd_levels_only: HeadlineLevelFilter,
}
impl<'g, 's> GlobalSettings<'g, 's> {
fn new() -> GlobalSettings<'g, 's> {
GlobalSettings {
radio_targets: Vec::new(),
file_access: &LocalFileAccessInterface {
working_directory: None,
},
in_progress_todo_keywords: BTreeSet::new(),
complete_todo_keywords: BTreeSet::new(),
org_list_allow_alphabetical: false,
tab_width: 8,
odd_levels_only: HeadlineLevelFilter::OddEven,
}
}
}
impl<'g, 's> Default for GlobalSettings<'g, 's> {
fn default() -> GlobalSettings<'g, 's> {
GlobalSettings::new()
}
}
#[derive(Debug, Clone)]
pub enum HeadlineLevelFilter {
Odd,
OddEven,
}

View File

@@ -1,69 +0,0 @@
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub(crate) struct List<'parent, T> {
data: T,
parent: Link<'parent, T>,
}
type Link<'parent, T> = Option<&'parent List<'parent, T>>;
impl<'parent, T> List<'parent, T> {
pub(crate) fn new(first_item: T) -> Self {
Self {
data: first_item,
parent: None,
}
}
pub(crate) fn get_data(&self) -> &T {
&self.data
}
pub(crate) fn get_parent(&'parent self) -> Link<'parent, T> {
self.parent
}
pub(crate) fn iter(&self) -> Iter<'_, T> {
Iter { next: Some(self) }
}
pub(crate) fn iter_list(&self) -> IterList<'_, T> {
IterList { next: Some(self) }
}
pub(crate) fn push(&'parent self, item: T) -> Self {
Self {
data: item,
parent: Some(self),
}
}
}
pub(crate) struct Iter<'a, T> {
next: Link<'a, T>,
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'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();
ret
}
}
pub(crate) struct IterList<'a, T> {
next: Link<'a, T>,
}
impl<'a, T> Iterator for IterList<'a, T> {
type Item = &'a List<'a, T>;
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next;
self.next = self.next.map(|this| this.get_parent()).flatten();
ret
}
}

View File

@@ -1,30 +0,0 @@
use crate::error::Res;
use crate::parser::OrgSource;
mod context;
mod exiting;
mod file_access_interface;
mod global_settings;
mod list;
mod parser_with_context;
pub(crate) type RefContext<'b, 'g, 'r, 's> = &'b Context<'g, 'r, 's>;
pub(crate) trait ContextMatcher = for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>>;
type DynContextMatcher<'c> = dyn ContextMatcher + 'c;
pub(crate) trait Matcher = for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
#[allow(dead_code)]
type DynMatcher<'c> = dyn Matcher + 'c;
pub(crate) use context::Context;
pub(crate) use context::ContextElement;
pub(crate) use context::ExitMatcherNode;
pub(crate) use exiting::ExitClass;
pub use file_access_interface::FileAccessInterface;
pub use file_access_interface::LocalFileAccessInterface;
pub use global_settings::GlobalSettings;
pub use global_settings::HeadlineLevelFilter;
pub(crate) use list::List;
pub(crate) use parser_with_context::parser_with_context;

View File

@@ -2,18 +2,17 @@ use nom::error::ErrorKind;
use nom::error::ParseError;
use nom::IResult;
pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>;
pub type Res<T, U> = IResult<T, U, CustomError<T>>;
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum CustomError<I> {
MyError(MyError<I>),
Nom(I, ErrorKind),
IO(std::io::Error),
}
#[derive(Debug)]
pub struct MyError<I>(pub(crate) I);
#[derive(Debug, PartialEq)]
pub struct MyError<I>(pub I);
impl<I> ParseError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
@@ -25,9 +24,3 @@ impl<I> ParseError<I> for CustomError<I> {
other
}
}
impl<I> From<std::io::Error> for CustomError<I> {
fn from(value: std::io::Error) -> Self {
CustomError::IO(value)
}
}

View File

@@ -1,4 +1,4 @@
mod error;
pub(crate) use error::CustomError;
pub(crate) use error::MyError;
pub(crate) use error::Res;
pub use error::CustomError;
pub use error::MyError;
pub use error::Res;

View File

@@ -10,7 +10,7 @@ const SERVICE_NAME: &'static 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.
#[cfg(feature = "tracing")]
pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
// by default it will hit http://localhost:4317 with a gRPC payload
// TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned.
let exporter = opentelemetry_otlp::new_exporter()
@@ -55,17 +55,17 @@ pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
}
#[cfg(feature = "tracing")]
pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
opentelemetry::global::shutdown_tracer_provider();
Ok(())
}
#[cfg(not(feature = "tracing"))]
pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
#[cfg(not(feature = "tracing"))]
pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

View File

@@ -1,16 +1,16 @@
#![feature(round_char_boundary)]
#![feature(exit_status_error)]
#![feature(trait_alias)]
// TODO: #![warn(missing_docs)]
#[cfg(feature = "compare")]
pub mod compare;
mod compare;
#[cfg(feature = "compare")]
pub use compare::compare_document;
#[cfg(feature = "compare")]
pub use compare::emacs_parse_org_document;
#[cfg(feature = "compare")]
pub use compare::get_emacs_version;
#[cfg(feature = "compare")]
pub use compare::get_org_mode_version;
mod context;
mod error;
pub mod parser;
pub mod types;
pub use context::FileAccessInterface;
pub use context::GlobalSettings;
pub use context::LocalFileAccessInterface;

View File

@@ -1,12 +1,17 @@
#![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
use std::io::Read;
use std::path::Path;
use ::organic::parser::parse;
use organic::parser::parse_with_settings;
use organic::GlobalSettings;
use organic::LocalFileAccessInterface;
use ::organic::parser::document;
#[cfg(feature = "compare")]
use organic::compare_document;
#[cfg(feature = "compare")]
use organic::emacs_parse_org_document;
#[cfg(feature = "compare")]
use organic::get_emacs_version;
#[cfg(feature = "compare")]
use organic::get_org_mode_version;
#[cfg(feature = "compare")]
use organic::parser::sexp::sexp_with_padding;
#[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry;
@@ -34,16 +39,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args().skip(1);
if args.is_empty() {
let org_contents = read_stdin_to_string()?;
run_anonymous_parse(org_contents)
} else {
for arg in args {
run_parse_on_file(arg)?
}
Ok(())
}
run_compare(org_contents)
}
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
@@ -54,28 +51,42 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
Ok(stdin_contents)
}
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let rust_parsed = parse(org_contents.as_ref())?;
#[cfg(feature = "compare")]
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let emacs_version = get_emacs_version()?;
let org_mode_version = get_org_mode_version()?;
let org_contents = org_contents.as_ref();
eprintln!("Using emacs version: {}", emacs_version.trim());
eprintln!("Using org-mode version: {}", org_mode_version.trim());
let (remaining, rust_parsed) = document(org_contents).map_err(|e| e.to_string())?;
let org_sexp = emacs_parse_org_document(org_contents)?;
let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
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 diff_result.is_bad() {
Err("Diff results do not match.")?;
}
if remaining != "" {
Err(format!("There was unparsed text remaining: {}", remaining))?;
}
Ok(())
}
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
let org_contents = org_contents.as_str();
let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
#[cfg(not(feature = "compare"))]
fn run_compare<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let (_remaining, rust_parsed) = document(org_contents.as_ref()).map_err(|e| e.to_string())?;
println!("{:#?}", rust_parsed);
Ok(())
}

View File

@@ -6,20 +6,20 @@ use nom::multi::many_till;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_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::RefContext;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::plain_link::protocol;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::types::AngleLink;
use crate::parser::AngleLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn angle_link<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn angle_link<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, AngleLink<'s>> {
let (remaining, _) = tag("<")(input)?;
@@ -41,15 +41,15 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn path_angle<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &path_angle_end,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -58,8 +58,8 @@ fn path_angle<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn path_angle_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn path_angle_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag(">")(input)

View File

@@ -14,25 +14,24 @@ use super::citation_reference::must_balance_bracket;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::citation_reference::citation_reference;
use crate::parser::citation_reference::citation_reference_key;
use crate::parser::exiting::ExitClass;
use crate::parser::object::Citation;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::types::Citation;
use crate::types::Object;
use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn citation<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn citation<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Citation<'s>> {
// TODO: Despite being a standard object, citations cannot exist inside the global prefix/suffix for other citations because citations must contain something that matches @key which is forbidden inside the global prefix/suffix. This TODO is to evaluate if its worth putting in an explicit check for this (which can be easily accomplished by checking the output of `get_bracket_depth()`). I suspect its not worth it because I expect, outside of intentionally crafted inputs, this parser will exit immediately inside a citation since it is unlikely to find the "[cite" substring inside a citation global prefix/suffix.
@@ -61,7 +60,7 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn citestyle<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
fn citestyle<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _) = tuple((tag("/"), style))(input)?;
let (remaining, _) = opt(tuple((tag("/"), variant)))(remaining)?;
let source = get_consumed(input, remaining);
@@ -69,30 +68,30 @@ fn citestyle<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn style<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
fn style<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn variant<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
fn variant<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_-/".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_prefix<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn global_prefix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_prefix_end(input.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(standard_set_object)(&parser_context),
@@ -104,13 +103,17 @@ fn global_prefix<'b, 'g, 'r, 's>(
Ok((remaining, children))
}
fn global_prefix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| _global_prefix_end(context, input, starting_bracket_depth)
fn global_prefix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_prefix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _global_prefix_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn _global_prefix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -132,16 +135,16 @@ fn _global_prefix_end<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn global_suffix<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn global_suffix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = global_suffix_end(input.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(standard_set_object)(&parser_context),
@@ -152,13 +155,17 @@ fn global_suffix<'b, 'g, 'r, 's>(
Ok((remaining, children))
}
fn global_suffix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| _global_suffix_end(context, input, starting_bracket_depth)
fn global_suffix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_global_suffix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _global_suffix_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn _global_suffix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -182,23 +189,19 @@ fn _global_suffix_end<'b, 'g, 'r, 's>(
#[cfg(test)]
mod tests {
use super::*;
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::element_parser::element;
use crate::types::Element;
use crate::types::Source;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::Source;
#[test]
fn citation_simple() {
let input = OrgSource::new("[cite:@foo]");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let paragraph_matcher = parser_with_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,
crate::parser::Element::Paragraph(paragraph) => paragraph,
_ => panic!("Should be a paragraph!"),
};
assert_eq!(Into::<&str>::into(remaining), "");

View File

@@ -12,25 +12,24 @@ use nom::sequence::tuple;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::object::CitationReference;
use crate::parser::object_parser::minimal_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::CitationReference;
use crate::types::Object;
use crate::parser::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn citation_reference<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn citation_reference<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CitationReference<'s>> {
let (remaining, _prefix) =
@@ -49,8 +48,8 @@ pub(crate) fn citation_reference<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn citation_reference_key<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn citation_reference_key<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(tuple((
@@ -69,16 +68,16 @@ pub(crate) fn citation_reference_key<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_prefix<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn key_prefix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = key_prefix_end(input.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(minimal_set_object)(&parser_context),
@@ -90,16 +89,16 @@ fn key_prefix<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn key_suffix<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn key_suffix<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let exit_with_depth = key_suffix_end(input.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &exit_with_depth,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(minimal_set_object)(&parser_context),
@@ -110,13 +109,17 @@ fn key_suffix<'b, 'g, 'r, 's>(
Ok((remaining, children))
}
fn key_prefix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| _key_prefix_end(context, input, starting_bracket_depth)
fn key_prefix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_key_prefix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _key_prefix_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn _key_prefix_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -137,13 +140,17 @@ fn _key_prefix_end<'b, 'g, 'r, 's>(
))(input)
}
fn key_suffix_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| _key_suffix_end(context, input, starting_bracket_depth)
fn key_suffix_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_key_suffix_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _key_suffix_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn _key_suffix_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -161,7 +168,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
tag(";")(input)
}
pub(crate) fn must_balance_bracket<'s, F, O>(
pub fn must_balance_bracket<'s, F, O>(
mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>
where

View File

@@ -12,16 +12,16 @@ use nom::combinator::verify;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use crate::context::parser_with_context;
use crate::context::RefContext;
use super::Context;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::Clock;
use crate::parser::Clock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn clock<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn clock<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Clock<'s>> {
start_of_line(input)?;
@@ -44,8 +44,8 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp_range_duration<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn inactive_timestamp_range_duration<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
@@ -66,8 +66,8 @@ fn inactive_timestamp_range_duration<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inactive_timestamp<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn inactive_timestamp<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((

View File

@@ -13,20 +13,20 @@ use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::get_consumed;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line;
use crate::types::Comment;
use crate::parser::Comment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn comment<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn comment<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> {
if immediate_in_section(context, "comment") {
@@ -34,8 +34,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
"Cannot nest objects of the same element".into(),
))));
}
let parser_context = ContextElement::Context("comment");
let parser_context = context.with_additional_node(&parser_context);
let parser_context = context.with_additional_node(ContextElement::Context("comment"));
let comment_line_matcher = parser_with_context!(comment_line)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, _first_line) = comment_line_matcher(input)?;
@@ -52,8 +51,8 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn comment_line<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn comment_line<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
@@ -67,24 +66,11 @@ fn comment_line<'b, 'g, 'r, 's>(
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_comment<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((
start_of_line,
space0,
tag("#"),
alt((tag(" "), line_ending, eof)),
))(input)?;
Ok((input, ()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context;
#[test]
fn require_space_after_hash() {
@@ -93,9 +79,7 @@ mod tests {
#not a comment
# Comment again",
);
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let comment_matcher = parser_with_context!(comment)(&initial_context);
let (remaining, first_comment) = comment_matcher(input).expect("Parse first comment");
assert_eq!(

View File

@@ -1,26 +1,39 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use crate::context::RefContext;
use super::sexp::sexp;
use super::Context;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::DiarySexp;
use crate::parser::DiarySexp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
pub fn diary_sexp<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
start_of_line(input)?;
let (remaining, _clock) = tag("%%(")(input)?;
let (remaining, _contents) = is_not("\r\n")(remaining)?;
let (remaining, _eol) = alt((line_ending, eof))(remaining)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _clock) = tag("%%")(remaining)?;
let (remaining, _gap_whitespace) = space0(remaining)?;
let (remaining, _sexp) = recognize(sexp)(remaining)?;
let (remaining, _trailing_comment) = opt(tuple((
space0,
tag(";"),
many_till(anychar, alt((line_ending, eof))),
)))(remaining)?;
let (remaining, _trailing_whitespace) =
recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -30,9 +43,3 @@ 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>, ()> {
tuple((start_of_line, tag("%%(")))(input)?;
Ok((input, ()))
}

View File

@@ -1,114 +1,112 @@
use nom::combinator::all_consuming;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many1_count;
use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings;
use super::in_buffer_settings::scan_for_in_buffer_settings;
use super::element::Element;
use super::object::Object;
use super::org_source::convert_error;
use super::org_source::OrgSource;
use super::section::zeroth_section;
use super::parser_with_context::parser_with_context;
use super::source::Source;
use super::token::AllTokensIterator;
use super::token::Token;
use super::util::exit_matcher_parser;
use super::util::get_consumed;
use crate::context::parser_with_context;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use super::util::start_of_line;
use super::Context;
use crate::error::Res;
use crate::parser::org_source::convert_error;
use crate::parser::comment::comment;
use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::planning::planning;
use crate::parser::property_drawer::property_drawer;
use crate::parser::util::blank_line;
use crate::types::Document;
use crate::types::Object;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
/// Parse a full org-mode document.
///
/// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document.
#[allow(dead_code)]
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
parse_with_settings(input, &GlobalSettings::default())
#[derive(Debug)]
pub struct Document<'s> {
pub source: &'s str,
pub zeroth_section: Option<Section<'s>>,
pub children: Vec<Heading<'s>>,
}
/// Parse a full org-mode document with starting settings.
///
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied.
///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)]
pub fn parse_with_settings<'g, 's>(
input: &'s str,
global_settings: &'g GlobalSettings<'g, 's>,
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
let wrapped_input = OrgSource::new(input);
let ret =
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
.map_err(|err| err.to_string())
.map(|(_remaining, parsed_document)| parsed_document);
Ok(ret?)
#[derive(Debug)]
pub struct Heading<'s> {
pub source: &'s str,
pub stars: usize,
pub todo_keyword: Option<&'s str>,
// TODO: add todo-type enum
pub title: Vec<Object<'s>>,
pub tags: Vec<&'s str>,
pub children: Vec<DocumentElement<'s>>,
}
/// Parse a full org-mode document.
///
/// Use this entry point when you want to have direct control over the starting context or if you want to use this integrated with other nom parsers. For general-purpose usage, the `parse` and `parse_with_settings` functions are a lot simpler.
///
/// 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)?;
Ok((Into::<&str>::into(remaining), doc))
#[derive(Debug)]
pub struct Section<'s> {
pub source: &'s str,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub enum DocumentElement<'s> {
Heading(Heading<'s>),
Section(Section<'s>),
}
impl<'s> Source<'s> for Document<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for DocumentElement<'s> {
fn get_source(&'s self) -> &'s str {
match self {
DocumentElement::Heading(obj) => obj.source,
DocumentElement::Section(obj) => obj.source,
}
}
}
impl<'s> Source<'s> for Section<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Heading<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
#[allow(dead_code)]
fn document_org_source<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> {
let mut final_settings = Vec::new();
let (_, document_settings) = scan_for_in_buffer_settings(input)?;
let setup_files: Vec<String> = document_settings
.iter()
.filter(|kw| kw.key.eq_ignore_ascii_case("setupfile"))
.map(|kw| kw.value)
.map(|setup_file| {
context
.get_global_settings()
.file_access
.read_file(setup_file)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::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| {
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
final_settings.extend(setup_file_settings);
}
final_settings.extend(document_settings);
let new_settings = apply_in_buffer_settings(final_settings, context.get_global_settings())
.map_err(|_err| {
nom::Err::Error(CustomError::MyError(MyError(
"TODO: make this take an owned string so I can dump err.to_string() into it."
.into(),
)))
})?;
let new_context = context.with_global_settings(&new_settings);
let context = &new_context;
let (remaining, document) =
_document(context, input).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
pub fn document(input: &str) -> Res<&str, Document> {
let initial_context: ContextTree<'_, '_> = ContextTree::new();
let wrapped_input = OrgSource::new(input);
let (remaining, document) = _document(&initial_context, wrapped_input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
{
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
let all_radio_targets: Vec<&Vec<Object<'_>>> = document
@@ -124,11 +122,11 @@ fn document_org_source<'b, 'g, 'r, 's>(
.map(|rt| &rt.children)
.collect();
if !all_radio_targets.is_empty() {
let mut new_global_settings = context.get_global_settings().clone();
new_global_settings.radio_targets = all_radio_targets;
let parser_context = context.with_global_settings(&new_global_settings);
let (remaining, document) = _document(&parser_context, input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
let initial_context = initial_context
.with_additional_node(ContextElement::RadioTarget(all_radio_targets));
let (remaining, document) = _document(&initial_context, wrapped_input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
return Ok((remaining.into(), document));
}
}
@@ -136,8 +134,8 @@ fn document_org_source<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _document<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn _document<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Document<'s>> {
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
@@ -156,8 +154,259 @@ fn _document<'b, 'g, 'r, 's>(
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn zeroth_section<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("section"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}));
let without_consuming_whitespace_context =
parser_context.with_additional_node(ContextElement::ConsumeTrailingWhitespace(false));
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, comment_and_property_drawer_element) = opt(tuple((
opt(parser_with_context!(comment)(
&without_consuming_whitespace_context,
)),
parser_with_context!(property_drawer)(context),
many0(blank_line),
)))(input)?;
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some()
},
)(remaining)?;
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
children.insert(0, Element::PropertyDrawer(property_drawer));
comment
.map(Element::Comment)
.map(|ele| children.insert(0, ele));
});
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section<'r, 's>(
context: Context<'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("section"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}));
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (planning_element, property_drawer_element)) = tuple((
opt(parser_with_context!(planning)(&parser_context)),
opt(parser_with_context!(property_drawer)(&parser_context)),
))(input)?;
if planning_element.is_none() && property_drawer_element.is_none() {
let (remain, _ws) = many0(blank_line)(remaining)?;
remaining = remain;
input = remain;
}
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!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));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input)
}
const fn heading(
parent_stars: usize,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Heading<'s>> {
move |context: Context, input: OrgSource<'_>| _heading(context, input, parent_stars)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
title,
tags: heading_tags,
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
OrgSource<'s>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
}));
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let (
remaining,
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((heading_keyword, space1))),
many1(standard_set_object_matcher),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
ws,
maybe_todo_keyword,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
alt((tag("TODO"), tag("DONE")))(input)
}
impl<'s> Document<'s> {
fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
AllTokensIterator::new(Token::Document(self))
}
}

View File

@@ -11,29 +11,29 @@ use nom::multi::many_till;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
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::Paragraph;
use crate::types::SetSource;
use crate::parser::Drawer;
use crate::parser::Element;
use crate::parser::Paragraph;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn drawer<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn drawer<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Drawer<'s>> {
if immediate_in_section(context, "drawer") {
@@ -50,17 +50,13 @@ pub(crate) fn drawer<'b, 'g, 'r, 's>(
recognize(tuple((space0, line_ending))),
))(remaining)?;
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("drawer"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("drawer"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &drawer_end,
}),
];
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 element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -102,8 +98,8 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn drawer_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn drawer_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;

View File

@@ -1,43 +1,39 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::DynamicBlock;
use crate::parser::lesser_element::Paragraph;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::source::SetSource;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
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::Paragraph;
use crate::types::SetSource;
use crate::parser::Element;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn dynamic_block<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
// TODO: Do I need to differentiate between different dynamic block types.
@@ -54,40 +50,37 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
opt(tuple((space1, parameters))),
line_ending,
))(remaining)?;
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("dynamic block"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("dynamic block"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &dynamic_block_end,
}),
];
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 element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
not(exit_matcher)(remaining)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple((
let (remaining, children) = match tuple((
not(exit_matcher),
blank_line,
many0(preceded(not(exit_matcher), blank_line)),
))))(remaining)?;
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
many_till(blank_line, exit_matcher),
))(remaining)
{
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source.into());
element
});
let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
(remain, vec![element])
}
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
let source = get_consumed(input, remaining);
@@ -113,15 +106,14 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn dynamic_block_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn dynamic_block_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, source) = recognize(tuple((
space0,
tag_no_case("#+end"),
opt(tag(":")),
tag_no_case("#+end:"),
alt((eof, line_ending)),
)))(input)?;
Ok((remaining, source))

View File

@@ -18,9 +18,9 @@ use super::lesser_element::Paragraph;
use super::lesser_element::Planning;
use super::lesser_element::SrcBlock;
use super::lesser_element::VerseBlock;
use super::source::SetSource;
use super::source::Source;
use super::Drawer;
use super::SetSource;
use super::Source;
#[derive(Debug)]
pub enum Element<'s> {
@@ -44,35 +44,33 @@ pub enum Element<'s> {
FixedWidthArea(FixedWidthArea<'s>),
HorizontalRule(HorizontalRule<'s>),
Keyword(Keyword<'s>),
BabelCall(Keyword<'s>),
LatexEnvironment(LatexEnvironment<'s>),
}
impl<'s> Source<'s> for Element<'s> {
fn get_source(&'s self) -> &'s str {
match self {
Element::Paragraph(obj) => obj.get_source(),
Element::PlainList(obj) => obj.get_source(),
Element::GreaterBlock(obj) => obj.get_source(),
Element::DynamicBlock(obj) => obj.get_source(),
Element::FootnoteDefinition(obj) => obj.get_source(),
Element::Comment(obj) => obj.get_source(),
Element::Drawer(obj) => obj.get_source(),
Element::PropertyDrawer(obj) => obj.get_source(),
Element::Table(obj) => obj.get_source(),
Element::VerseBlock(obj) => obj.get_source(),
Element::CommentBlock(obj) => obj.get_source(),
Element::ExampleBlock(obj) => obj.get_source(),
Element::ExportBlock(obj) => obj.get_source(),
Element::SrcBlock(obj) => obj.get_source(),
Element::Clock(obj) => obj.get_source(),
Element::DiarySexp(obj) => obj.get_source(),
Element::Planning(obj) => obj.get_source(),
Element::FixedWidthArea(obj) => obj.get_source(),
Element::HorizontalRule(obj) => obj.get_source(),
Element::Keyword(obj) => obj.get_source(),
Element::BabelCall(obj) => obj.get_source(),
Element::LatexEnvironment(obj) => obj.get_source(),
Element::Paragraph(obj) => obj.source,
Element::PlainList(obj) => obj.source,
Element::GreaterBlock(obj) => obj.source,
Element::DynamicBlock(obj) => obj.source,
Element::FootnoteDefinition(obj) => obj.source,
Element::Comment(obj) => obj.source,
Element::Drawer(obj) => obj.source,
Element::PropertyDrawer(obj) => obj.source,
Element::Table(obj) => obj.source,
Element::VerseBlock(obj) => obj.source,
Element::CommentBlock(obj) => obj.source,
Element::ExampleBlock(obj) => obj.source,
Element::ExportBlock(obj) => obj.source,
Element::SrcBlock(obj) => obj.source,
Element::Clock(obj) => obj.source,
Element::DiarySexp(obj) => obj.source,
Element::Planning(obj) => obj.source,
Element::FixedWidthArea(obj) => obj.source,
Element::HorizontalRule(obj) => obj.source,
Element::Keyword(obj) => obj.source,
Element::LatexEnvironment(obj) => obj.source,
}
}
}
@@ -101,7 +99,6 @@ impl<'s> SetSource<'s> for Element<'s> {
Element::FixedWidthArea(obj) => obj.source = source,
Element::HorizontalRule(obj) => obj.source = source,
Element::Keyword(obj) => obj.source = source,
Element::BabelCall(obj) => obj.source = source,
Element::LatexEnvironment(obj) => obj.source = source,
}
}

View File

@@ -4,19 +4,15 @@ use nom::multi::many0;
use super::clock::clock;
use super::comment::comment;
use super::comment::detect_comment;
use super::diary_sexp::detect_diary_sexp;
use super::diary_sexp::diary_sexp;
use super::drawer::drawer;
use super::dynamic_block::dynamic_block;
use super::fixed_width_area::detect_fixed_width_area;
use super::element::Element;
use super::fixed_width_area::fixed_width_area;
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::babel_call_keyword;
use super::keyword::keyword;
use super::latex_environment::latex_environment;
use super::lesser_block::comment_block;
@@ -28,30 +24,25 @@ use super::org_source::OrgSource;
use super::paragraph::paragraph;
use super::plain_list::detect_plain_list;
use super::plain_list::plain_list;
use super::table::detect_table;
use super::source::SetSource;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::table::org_mode_table;
use crate::types::Element;
use crate::types::SetSource;
pub(crate) const fn element(
pub const fn element(
can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
move |context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Element<'s>> {
move |context: Context, input: OrgSource<'_>| _element(context, input, can_be_paragraph)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _element<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn _element<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> {
@@ -73,7 +64,6 @@ fn _element<'b, 'g, 'r, 's>(
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context);
@@ -97,7 +87,6 @@ fn _element<'b, 'g, 'r, 's>(
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),
))(remaining)
{
@@ -127,29 +116,19 @@ fn _element<'b, 'g, 'r, 's>(
Ok((remaining, element))
}
pub(crate) const fn detect_element(
pub const fn detect_element(
can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()>
{
move |context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
move |context: Context, input: OrgSource<'_>| _detect_element(context, input, can_be_paragraph)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _detect_element<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn _detect_element<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> {
if alt((
parser_with_context!(detect_plain_list)(context),
detect_footnote_definition,
detect_diary_sexp,
detect_comment,
detect_fixed_width_area,
detect_table,
))(input)
.is_ok()
{
if detect_plain_list(input).is_ok() {
return Ok((input, ()));
}
if _element(context, input, can_be_paragraph).is_ok() {

View File

@@ -1,19 +1,19 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::satisfy;
use nom::combinator::eof;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object::Entity;
use crate::parser::util::get_consumed;
use crate::types::Entity;
// TODO: Make this a user-provided variable corresponding to elisp's org-entities
const ORG_ENTITIES: [&'static str; 413] = [
@@ -433,13 +433,13 @@ const ORG_ENTITIES: [&'static str; 413] = [
];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn entity<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn entity<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Entity<'s>> {
let (remaining, _) = tag("\\")(input)?;
let (remaining, entity_name) = name(context, remaining)?;
let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -454,18 +454,15 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn name<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-entities and optionally org-entities-user
for entity in ORG_ENTITIES {
let result = tuple((
tag::<_, _, CustomError<_>>(entity),
alt((tag("{}"), peek(recognize(entity_end)))),
))(input);
let result = tag_no_case::<_, _, CustomError<_>>(entity)(input);
match result {
Ok((remaining, (ent, _))) => {
Ok((remaining, ent)) => {
return Ok((remaining, ent));
}
Err(_) => {}

20
src/parser/exiting.rs Normal file
View File

@@ -0,0 +1,20 @@
#[derive(Debug, Copy, Clone)]
pub enum ExitClass {
/// Headlines and sections.
Document = 1,
/// Elements who take priority over beta elements when matching.
Alpha = 20,
/// Elements who cede priority to alpha elements when matching.
Beta = 300,
/// Elements who cede priority to alpha and beta elements when matching.
Gamma = 4000,
}
impl std::fmt::Display for ExitClass {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}

View File

@@ -9,28 +9,28 @@ use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::maybe_consume_object_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::RefContext;
use super::Context;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::types::ExportSnippet;
use crate::parser::ExportSnippet;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn export_snippet<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportSnippet<'s>> {
let (remaining, _) = tag("@@")(input)?;
let (remaining, backend_name) = backend(context, remaining)?;
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &export_snippet_end,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let (remaining, backend_contents) = opt(tuple((
tag(":"),
parser_with_context!(contents)(&parser_context),
@@ -50,8 +50,8 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn backend<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn backend<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, backend_name) =
@@ -61,8 +61,8 @@ fn backend<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn contents<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn contents<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = recognize(verify(
@@ -73,8 +73,8 @@ fn contents<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn export_snippet_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn export_snippet_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag("@@")(input)

View File

@@ -3,27 +3,26 @@ use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
use nom::combinator::opt;
use nom::multi::many0;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::only_space1;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::RefContext;
use super::Context;
use crate::error::Res;
use crate::parser::parser_with_context::parser_with_context;
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::parser::FixedWidthArea;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn fixed_width_area<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
@@ -42,28 +41,17 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn fixed_width_area_line<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _indent) = space0(input)?;
let (remaining, _) = tuple((
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
tag(":"),
alt((recognize(tuple((only_space1, is_not("\r\n")))), space0)),
org_line_ending,
opt(tuple((space1, is_not("\r\n")))),
alt((line_ending, eof)),
))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((
start_of_line,
space0,
tag(":"),
alt((tag(" "), line_ending, eof)),
))(input)?;
Ok((input, ()))
}

View File

@@ -2,38 +2,36 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while;
use nom::character::complete::digit1;
use nom::character::complete::space0;
use nom::combinator::opt;
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::org_source::OrgSource;
use super::util::include_input;
use super::util::WORD_CONSTITUENT_CHARACTERS;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::exiting::ExitClass;
use crate::parser::greater_element::FootnoteDefinition;
use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
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;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn footnote_definition<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
if immediate_in_section(context, "footnote definition") {
@@ -43,42 +41,20 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
}
start_of_line(input)?;
// Cannot be indented.
let (remaining, (_, lbl, _, _, _)) = tuple((
tag_no_case("[fn:"),
label,
tag("]"),
space0,
opt(verify(many0(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() <= 2
})),
))(input)?;
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("footnote definition"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
let parser_context = context
.with_additional_node(ContextElement::ConsumeTrailingWhitespace(true))
.with_additional_node(ContextElement::Context("footnote definition"))
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &footnote_definition_end,
}),
];
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]);
}));
// TODO: The problem is we are not accounting for trailing whitespace like we do in section. Maybe it would be easier if we passed down whether or not to parse trailing whitespace into the element matcher similar to how tag takes in parameters.
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (mut children, _exit_contents)) =
many_till(include_input(element_matcher), exit_matcher)(remaining)?;
// Re-parse the last element of the footnote definition with consume trailing whitespace off because the trailing whitespace needs to belong to the footnote definition, not the contents.
if context.should_consume_trailing_whitespace() {
if let Some((final_item_input, _)) = children.pop() {
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (remain, reparsed_final_item) =
parser_with_context!(element(true))(&final_item_context)(final_item_input)?;
children.push((final_item_input, reparsed_final_item));
remaining = remain;
}
}
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -86,19 +62,22 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
FootnoteDefinition {
source: source.into(),
label: lbl.into(),
children: children.into_iter().map(|(_, item)| item).collect(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_definition_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn footnote_definition_end<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, source) = alt((
@@ -118,7 +97,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ()))
}
@@ -126,10 +105,9 @@ pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSou
#[cfg(test)]
mod tests {
use super::*;
use crate::context::Context;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::types::Source;
use crate::parser::parser_context::ContextTree;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::Source;
#[test]
fn two_paragraphs() {
@@ -140,9 +118,7 @@ mod tests {
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 initial_context: ContextTree<'_, '_> = ContextTree::new();
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");
@@ -173,9 +149,7 @@ line footnote.
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 initial_context: ContextTree<'_, '_> = ContextTree::new();
let footnote_definition_matcher = parser_with_context!(element(true))(&initial_context);
let (remaining, first_footnote_definition) =
footnote_definition_matcher(input).expect("Parse first footnote_definition");

View File

@@ -6,25 +6,24 @@ use nom::multi::many_till;
use super::org_source::BracketDepth;
use super::org_source::OrgSource;
use super::parser_context::ContextElement;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use super::Context;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::exiting::ExitClass;
use crate::parser::footnote_definition::label;
use crate::parser::object_parser::standard_set_object;
use crate::parser::parser_context::ExitMatcherNode;
use crate::parser::parser_with_context::parser_with_context;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::types::FootnoteReference;
use crate::parser::FootnoteReference;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn footnote_reference<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
pub fn footnote_reference<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
alt((
@@ -35,17 +34,17 @@ pub(crate) fn footnote_reference<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn anonymous_footnote<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn anonymous_footnote<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn::")(input)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &exit_with_depth,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(standard_set_object)(&parser_context),
@@ -69,19 +68,19 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn inline_footnote<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn inline_footnote<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?;
let (remaining, label_contents) = label(remaining)?;
let (remaining, _) = tag(":")(remaining)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
let parser_context =
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta,
exit_matcher: &exit_with_depth,
});
let parser_context = context.with_additional_node(&parser_context);
}));
let (remaining, (children, _exit_contents)) = verify(
many_till(
parser_with_context!(standard_set_object)(&parser_context),
@@ -105,8 +104,8 @@ fn inline_footnote<'b, 'g, 'r, 's>(
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn footnote_reference_only<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
fn footnote_reference_only<'r, 's>(
context: Context<'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
let (remaining, _) = tag_no_case("[fn:")(input)?;
@@ -125,15 +124,17 @@ fn footnote_reference_only<'b, 'g, 'r, 's>(
))
}
fn footnote_definition_end(starting_bracket_depth: BracketDepth) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| {
fn footnote_definition_end(
starting_bracket_depth: BracketDepth,
) -> impl for<'r, 's> Fn(Context<'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
move |context: Context, input: OrgSource<'_>| {
_footnote_definition_end(context, input, starting_bracket_depth)
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _footnote_definition_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
fn _footnote_definition_end<'r, 's>(
_context: Context<'r, 's>,
input: OrgSource<'s>,
starting_bracket_depth: BracketDepth,
) -> Res<OrgSource<'s>, OrgSource<'s>> {

Some files were not shown because too many files have changed in this diff Show More