Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de87b7df93 | ||
|
|
a267d13fd7 | ||
|
|
a29973a110 | ||
|
|
31c782499e | ||
|
|
b7c7057095 | ||
|
|
49e3c90a3a | ||
|
|
129228c5c5 | ||
|
|
f0a7493a89 | ||
|
|
dc5695ec9f | ||
|
|
4ff62fbfae | ||
|
|
c892d406c3 | ||
|
|
1a41cfc6c7 | ||
|
|
4f34ab9089 | ||
|
|
9b2348c0ef | ||
|
|
5716cbccea | ||
|
|
124cd50243 | ||
|
|
bac5d6e1d9 | ||
|
|
ba15999534 | ||
|
|
61c3e6c10e | ||
|
|
a7e130838d | ||
|
|
853adadf91 | ||
|
|
7b61329889 | ||
|
|
9bcfb2f1da | ||
|
|
4c8d9a3063 | ||
|
|
48cb3c4a02 | ||
|
|
9e60ff6683 | ||
|
|
c1de001786 | ||
|
|
716af5bb45 | ||
|
|
6137a46231 | ||
|
|
bdd04f4d5c | ||
|
|
36bdc54703 | ||
|
|
3031b6edd4 | ||
|
|
1a704dd312 | ||
|
|
a74ea730f4 | ||
|
|
8450785186 | ||
|
|
d443dbd468 | ||
|
|
c9ce32c881 | ||
|
|
85454a0a27 | ||
|
|
fdebf6dec5 | ||
|
|
444d6758aa | ||
|
|
6c7203410e | ||
|
|
bfe67b1f75 | ||
|
|
fd41ad9c29 | ||
|
|
7f751d4f28 | ||
|
|
52a4dab67c | ||
|
|
3d86e75059 | ||
|
|
ca6fdf1924 | ||
|
|
66d16d89ed | ||
|
|
ee5e0698b1 | ||
|
|
22681b6a58 | ||
|
|
876d33239e | ||
|
|
87941271a4 | ||
|
|
32b19d68d0 | ||
|
|
830097b0a9 | ||
|
|
44e9f708c9 | ||
|
|
fc4ff97c14 | ||
|
|
33372429dd | ||
|
|
ac0db64081 | ||
|
|
b8a4876779 | ||
|
|
925c42c8fb | ||
|
|
7d4100d956 | ||
|
|
53d90a2949 | ||
|
|
26f41b83aa | ||
|
|
e4c0e32536 | ||
|
|
37e85158ea | ||
|
|
6589a755a6 | ||
|
|
a651b79e77 | ||
|
|
98de5e4ec5 | ||
|
|
cf383fa394 | ||
|
|
84953c1669 | ||
|
|
7650a9edff | ||
|
|
a74319d381 | ||
|
|
7e57285ea7 | ||
|
|
f103d168d5 | ||
|
|
f54081437a | ||
|
|
aa5988bc2f | ||
|
|
76ca2b9762 | ||
|
|
1561e1e580 | ||
|
|
1f11bfa2ec | ||
|
|
8440a3b256 | ||
|
|
de7ad182b3 | ||
|
|
b75d9f5c91 | ||
|
|
612744ebd0 | ||
|
|
1b4b8b4bdb | ||
|
|
5587e19f16 | ||
|
|
80f7098f9b | ||
|
|
84d2babda9 | ||
|
|
cc56b79683 | ||
|
|
0105b49d0d | ||
|
|
d79035e14d | ||
|
|
7545fb7e1a | ||
|
|
f30069efe7 | ||
|
|
d1fe2f6b09 | ||
|
|
21c60d1036 | ||
|
|
6a1bdd5fee | ||
|
|
5d20d3e99b | ||
|
|
a8fbf01124 | ||
|
|
344ef04453 | ||
|
|
ceb722e476 | ||
|
|
b04341882c | ||
|
|
494fe5cceb | ||
|
|
0110d23387 | ||
|
|
0d7a15bfeb | ||
|
|
352c20d1d8 | ||
|
|
f82d2aada1 | ||
|
|
669da4073e | ||
|
|
0056657b65 | ||
|
|
8780976c15 | ||
|
|
dc8b8d08ab | ||
|
|
93d3d9471f | ||
|
|
c7c0deed74 | ||
|
|
b32c21eb1d | ||
|
|
2e6e6fdd2b | ||
|
|
3cc2294387 | ||
|
|
40f22034da | ||
|
|
ab612f293f | ||
|
|
57c2922e4a | ||
|
|
c2eb1f51c8 | ||
|
|
b0930df788 | ||
|
|
69512f559a | ||
|
|
76a81b73ac | ||
|
|
ba291c6776 | ||
|
|
6b82b46e09 | ||
|
|
6676012eb1 | ||
|
|
facbe716e9 | ||
|
|
827f3e1c98 | ||
|
|
fcea7e5a4b | ||
|
|
dda2b1e69f | ||
|
|
f79d07a7c8 | ||
|
|
45283b48d9 | ||
|
|
08e4c646e5 | ||
|
|
f8b99ed235 | ||
|
|
6fc607cfe0 | ||
|
|
49afcf0db6 | ||
|
|
c4d7e646fc | ||
|
|
3fc3a5d1ef | ||
|
|
6e2fc362ea | ||
|
|
90fa48661c | ||
|
|
5cefcd5fac | ||
|
|
b83a103c17 |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "organic"
|
||||
version = "0.1.5"
|
||||
version = "0.1.8"
|
||||
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||
description = "An org-mode parser."
|
||||
edition = "2021"
|
||||
@@ -25,6 +25,12 @@ path = "src/lib.rs"
|
||||
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"]
|
||||
|
||||
[dependencies]
|
||||
nom = "7.1.1"
|
||||
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
|
||||
|
||||
4
Makefile
4
Makefile
@@ -33,6 +33,10 @@ 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)
|
||||
|
||||
55
README.md
55
README.md
@@ -2,12 +2,63 @@
|
||||
|
||||
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). 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.
|
||||
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.
|
||||
|
||||
8
build.rs
8
build.rs
@@ -16,6 +16,9 @@ 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")
|
||||
@@ -71,10 +74,6 @@ fn write_header(test_file: &mut File) {
|
||||
test_file,
|
||||
r#"
|
||||
#[feature(exit_status_error)]
|
||||
use organic::compare_document;
|
||||
use organic::parser::parse;
|
||||
use organic::emacs_parse_anonymous_org_document;
|
||||
use organic::parser::sexp::sexp_with_padding;
|
||||
|
||||
"#
|
||||
)
|
||||
@@ -86,7 +85,6 @@ 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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
RUN apk add --no-cache build-base musl-dev git autoconf make texinfo gnutls-dev ncurses-dev gawk libgccjit-dev
|
||||
|
||||
|
||||
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
|
||||
RUN ./configure --prefix /usr --without-x --without-sound --with-native-compilation=aot
|
||||
RUN make
|
||||
RUN make DESTDIR="/root/dist" install
|
||||
|
||||
|
||||
FROM build AS build-org-mode
|
||||
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1
|
||||
ARG ORG_VERSION=c703541ffcc14965e3567f928de1683a1c1e33f6
|
||||
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.
|
||||
@@ -27,7 +27,7 @@ RUN make DESTDIR="/root/dist" install
|
||||
|
||||
FROM rustlang/rust:nightly-alpine3.17 AS tester
|
||||
ENV LANG=en_US.UTF-8
|
||||
RUN apk add --no-cache musl-dev ncurses gnutls
|
||||
RUN apk add --no-cache musl-dev ncurses gnutls libgccjit
|
||||
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/ /
|
||||
@@ -36,13 +36,72 @@ 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
|
||||
RUN apk add --no-cache bash coreutils
|
||||
RUN mkdir /foreign_documents
|
||||
COPY --from=build-org-mode /root/org-mode/doc /foreign_documents/org-mode
|
||||
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"]
|
||||
|
||||
@@ -5,6 +5,8 @@ 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 "${@}")
|
||||
}
|
||||
@@ -21,25 +23,29 @@ function main {
|
||||
if [ "${CARGO_TARGET_DIR:-}" = "" ]; then
|
||||
CARGO_TARGET_DIR=$(realpath target/)
|
||||
fi
|
||||
PARSE="${CARGO_TARGET_DIR}/release-lto/parse"
|
||||
PARSE="${CARGO_TARGET_DIR}/release-lto/compare"
|
||||
|
||||
run_compare "org-mode/org-guide.org" "/foreign_documents/org-mode/org-guide.org"
|
||||
run_compare "org-mode/org-manual.org" "/foreign_documents/org-mode/org-manual.org"
|
||||
}
|
||||
|
||||
function run_compare {
|
||||
local name="$1"
|
||||
local target_document="$2"
|
||||
local all_status=0
|
||||
set +e
|
||||
$PARSE "$target_document" &> /dev/null
|
||||
local status=$?
|
||||
|
||||
(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 [ "$status" -eq 0 ]; then
|
||||
echo "$(green_text "GOOD") $name"
|
||||
if [ "$all_status" -ne 0 ]; then
|
||||
red_text "Some tests failed."
|
||||
else
|
||||
echo "$(red_text "FAIL") $name"
|
||||
return 1
|
||||
green_text "All tests passed."
|
||||
fi
|
||||
return "$all_status"
|
||||
}
|
||||
|
||||
function green_text {
|
||||
@@ -54,4 +60,90 @@ 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 "${@}"
|
||||
|
||||
@@ -25,3 +25,4 @@ 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.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#+BEGIN: timestamp :format "%Y-%m-%d %H:%M"
|
||||
|
||||
#+END
|
||||
@@ -0,0 +1,8 @@
|
||||
* Footnotes
|
||||
|
||||
[fn:1]
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
baz
|
||||
#+END_EXAMPLE
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#+begin_quote
|
||||
|
||||
foo
|
||||
|
||||
#+end_quote
|
||||
@@ -0,0 +1,3 @@
|
||||
# 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
|
||||
@@ -0,0 +1,2 @@
|
||||
3. [@3] foo
|
||||
4. bar
|
||||
@@ -0,0 +1,6 @@
|
||||
- foo ::
|
||||
|
||||
- bar ::
|
||||
|
||||
|
||||
baz
|
||||
@@ -1 +1,2 @@
|
||||
- {{{foo(bar)}}} :: baz
|
||||
- =foo= :: bar
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
- foo :: bar
|
||||
- foo :: bar
|
||||
- foo :: bar
|
||||
@@ -0,0 +1,2 @@
|
||||
- =foo :: bar= :: baz
|
||||
- lorem :: ipsum :: dolar
|
||||
@@ -1,3 +1,5 @@
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
* headline
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
1. foo
|
||||
- bar
|
||||
- lorem :: ipsum
|
||||
@@ -0,0 +1,2 @@
|
||||
# Since this is an ordered list, the text before the " :: " is NOT parsed as a tag.
|
||||
1. foo :: bar
|
||||
@@ -0,0 +1,6 @@
|
||||
* Overwrite
|
||||
:PROPERTIES:
|
||||
:header-args: :var foo="lorem"
|
||||
:header-args:emacs-lisp: :var bar="ipsum"
|
||||
:header-args:emacs-lisp+: :results silent :var baz=7
|
||||
:END:
|
||||
@@ -0,0 +1,6 @@
|
||||
# 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|
|
||||
@@ -0,0 +1,6 @@
|
||||
src_elisp{(bar)}
|
||||
*src_elisp{(bar)}*
|
||||
|
||||
| foo *bar* |
|
||||
| foo src_elisp{(bar)} |
|
||||
| foo *src_elisp{(bar)}* |
|
||||
4
org_mode_samples/greater_element/table/empty_formula.org
Normal file
4
org_mode_samples/greater_element/table/empty_formula.org
Normal file
@@ -0,0 +1,4 @@
|
||||
| Name | Value |
|
||||
|------+-------|
|
||||
| foo | bar |
|
||||
#+tblfm:
|
||||
8
org_mode_samples/greater_element/table/with_formulas.org
Normal file
8
org_mode_samples/greater_element/table/with_formulas.org
Normal file
@@ -0,0 +1,8 @@
|
||||
| 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)
|
||||
@@ -0,0 +1,6 @@
|
||||
%%(foo
|
||||
)
|
||||
|
||||
%%(bar ; baz
|
||||
|
||||
lorem
|
||||
@@ -0,0 +1,2 @@
|
||||
# Fixed width areas must begin with colon followed by a space, not a tab, so this is not a fixed width area.
|
||||
: foo
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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
|
||||
1
org_mode_samples/lesser_element/keyword/babel_call.org
Normal file
1
org_mode_samples/lesser_element/keyword/babel_call.org
Normal file
@@ -0,0 +1 @@
|
||||
#+call: foo(bar="baz")
|
||||
@@ -0,0 +1 @@
|
||||
#+title:foo:bar: baz: lorem: ipsum
|
||||
@@ -0,0 +1,2 @@
|
||||
#+begin_src
|
||||
#+end_src
|
||||
@@ -0,0 +1,4 @@
|
||||
# There are trailing spaces after the begin and end src lines
|
||||
#+begin_src
|
||||
echo "this is a source block."
|
||||
#+end_src
|
||||
@@ -0,0 +1,3 @@
|
||||
*[fn:: /abcdef[fn::ghijklmnopqrstuvw]xyz/ r]*
|
||||
|
||||
*[fn:: /abcdef[fn::ghijk *lmnopq* rstuvw]xyz/ r]*
|
||||
11
org_mode_samples/object/plain_link/with_parenthesis.org
Normal file
11
org_mode_samples/object/plain_link/with_parenthesis.org
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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(
|
||||
3
org_mode_samples/object/radio_link/different_case.org
Normal file
3
org_mode_samples/object/radio_link/different_case.org
Normal file
@@ -0,0 +1,3 @@
|
||||
<<<Foo Bar Baz>>>
|
||||
|
||||
foo bar baz
|
||||
@@ -0,0 +1,6 @@
|
||||
<<<foo bar baz>>>
|
||||
|
||||
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
1
org_mode_samples/object/regular_link/elisp.org
Normal file
1
org_mode_samples/object/regular_link/elisp.org
Normal file
@@ -0,0 +1 @@
|
||||
[[elisp:(local-set-key "\M-\x" 'foo-bar-baz)]]
|
||||
@@ -0,0 +1 @@
|
||||
[[https://en.wikipedia.org/wiki/Shebang_(Unix)]]
|
||||
@@ -0,0 +1 @@
|
||||
[[[http://foo.bar/baz][lorem]]]
|
||||
4
org_mode_samples/object/statistics_cookie/empty.org
Normal file
4
org_mode_samples/object/statistics_cookie/empty.org
Normal file
@@ -0,0 +1,4 @@
|
||||
[/]
|
||||
[/2]
|
||||
[3/]
|
||||
[%]
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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}
|
||||
@@ -0,0 +1,13 @@
|
||||
foo_(bar)
|
||||
|
||||
foo_(b(ar)
|
||||
|
||||
foo_(b{ar)
|
||||
|
||||
foo_{b(ar}
|
||||
|
||||
foo_(b(a)r)
|
||||
|
||||
foo_b(a)r
|
||||
|
||||
foo_(b+ar)
|
||||
1
org_mode_samples/object/text_markup/double_star.org
Normal file
1
org_mode_samples/object/text_markup/double_star.org
Normal file
@@ -0,0 +1 @@
|
||||
foo ** bar ** baz
|
||||
1
org_mode_samples/object/text_markup/double_tilde.org
Normal file
1
org_mode_samples/object/text_markup/double_tilde.org
Normal file
@@ -0,0 +1 @@
|
||||
foo ~~ bar ~~ baz
|
||||
4
org_mode_samples/object/text_markup/target_substring.org
Normal file
4
org_mode_samples/object/text_markup/target_substring.org
Normal file
@@ -0,0 +1,4 @@
|
||||
# Since "foos" has an extra "s", this does not match the target.
|
||||
the foos bar
|
||||
|
||||
The <<<foo>>> and stuff.
|
||||
@@ -0,0 +1,2 @@
|
||||
* TODO [#A] COMMENT foo bar
|
||||
baz
|
||||
2
org_mode_samples/sections_and_headings/empty_heading.org
Normal file
2
org_mode_samples/sections_and_headings/empty_heading.org
Normal file
@@ -0,0 +1,2 @@
|
||||
* DONE
|
||||
*
|
||||
@@ -0,0 +1,6 @@
|
||||
#+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]
|
||||
@@ -0,0 +1,8 @@
|
||||
#+STARTUP: odd
|
||||
* Foo
|
||||
***** Bar
|
||||
* Baz
|
||||
*** Lorem
|
||||
* Ipsum
|
||||
**** Dolar
|
||||
***** Cat
|
||||
@@ -0,0 +1,4 @@
|
||||
* DONE foo
|
||||
DEADLINE: <2023-09-08 Fri>
|
||||
|
||||
* DONE bar
|
||||
@@ -0,0 +1 @@
|
||||
* [0/4] foo
|
||||
@@ -4,10 +4,10 @@ set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
cd "$DIR/../"
|
||||
|
||||
RUSTFLAGS="-C opt-level=0" cargo build --no-default-features
|
||||
valgrind --tool=callgrind --callgrind-out-file=callgrind.out target/debug/parse
|
||||
|
||||
(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" "${@}"
|
||||
|
||||
echo "You probably want to run:"
|
||||
echo "callgrind_annotate --auto=yes callgrind.out"
|
||||
echo "callgrind_annotate --auto=yes '$DIR/../callgrind.out'"
|
||||
|
||||
@@ -6,8 +6,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
: ${PROFILE:="perf"}
|
||||
|
||||
cd "$DIR/../"
|
||||
|
||||
function main {
|
||||
local additional_flags=()
|
||||
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||
@@ -15,12 +13,12 @@ function main {
|
||||
else
|
||||
additional_flags+=(--profile "$PROFILE")
|
||||
fi
|
||||
cargo build --no-default-features "${additional_flags[@]}"
|
||||
perf record --freq=2000 --call-graph dwarf --output=perf.data target/${PROFILE}/parse
|
||||
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
|
||||
perf record --freq=2000 --call-graph dwarf --output="$DIR/../perf.data" "$DIR/../target/${PROFILE}/parse" "${@}"
|
||||
|
||||
# Convert to a format firefox will read
|
||||
# flags to consider --show-info
|
||||
perf script -F +pid --input perf.data > perf.firefox
|
||||
perf script -F +pid --input "$DIR/../perf.data" > "$DIR/../perf.firefox"
|
||||
|
||||
echo "You probably want to go to https://profiler.firefox.com/"
|
||||
echo "Either that or run hotspot"
|
||||
|
||||
@@ -8,11 +8,26 @@ 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 "${@}"
|
||||
@@ -24,7 +39,6 @@ function build_container {
|
||||
|
||||
function launch_container {
|
||||
local additional_flags=()
|
||||
local additional_args=()
|
||||
local features=(compare)
|
||||
|
||||
if [ "$NO_COLOR" != "" ]; then
|
||||
@@ -38,11 +52,8 @@ 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
|
||||
|
||||
@@ -50,16 +61,50 @@ 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=$($REALPATH "$path")
|
||||
local containing_folder=$(dirname "$full_path")
|
||||
local file_name=$(basename "$full_path")
|
||||
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/input:ro" -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[@]}" -- "/input/$file_name"
|
||||
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
|
||||
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[@]}"
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -5,22 +5,10 @@ set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
cd "$DIR/../"
|
||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||
|
||||
############## Setup #########################
|
||||
|
||||
function cleanup {
|
||||
for f in "${folders[@]}"; do
|
||||
log "Deleting $f"
|
||||
rm -rf "$f"
|
||||
done
|
||||
}
|
||||
folders=()
|
||||
for sig in EXIT INT QUIT HUP TERM; do
|
||||
trap "set +e; cleanup" "$sig"
|
||||
done
|
||||
|
||||
function die {
|
||||
local status_code="$1"
|
||||
shift
|
||||
@@ -35,18 +23,18 @@ function log {
|
||||
############## Program #########################
|
||||
|
||||
function main {
|
||||
log "Is is recommended that the output of \`mktemp -d -t 'compare_bisect.XXXXXXXX'\` is inside a tmpfs filesystem since this script will make many writes to these folders."
|
||||
|
||||
local target_full_path=$($REALPATH "$1")
|
||||
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=$(wc -l "$SOURCE_FOLDER/$TARGET_DOCUMENT" | awk '{print $1}')
|
||||
local bad
|
||||
bad=$(wc -l "$SOURCE_FOLDER/$TARGET_DOCUMENT" | awk '{print $1}')
|
||||
|
||||
set +e
|
||||
run_parse "$bad" &> /dev/null
|
||||
(run_parse "$bad")
|
||||
local status=$?
|
||||
set -e
|
||||
if [ $status -eq 0 ]; then
|
||||
@@ -72,21 +60,12 @@ function main {
|
||||
echo "Bad line: $bad"
|
||||
}
|
||||
|
||||
function setup_temp_dir {
|
||||
local temp_dir=$(mktemp -d -t 'compare_bisect.XXXXXXXX')
|
||||
cp -r "$SOURCE_FOLDER/"* "$temp_dir/"
|
||||
echo "$temp_dir"
|
||||
}
|
||||
|
||||
function run_parse {
|
||||
local lines="$1"
|
||||
local temp_dir=$(setup_temp_dir)
|
||||
folders+=("$temp_dir")
|
||||
cat "$SOURCE_FOLDER/$TARGET_DOCUMENT" | head -n "$lines" > "$temp_dir/$TARGET_DOCUMENT"
|
||||
"${DIR}/run_docker_compare.bash" "$temp_dir/$TARGET_DOCUMENT"
|
||||
|
||||
cd "$SOURCE_FOLDER"
|
||||
head -n "$lines" "$SOURCE_FOLDER/$TARGET_DOCUMENT" | PROFILE=release-lto "${DIR}/run_docker_compare.bash"
|
||||
local status=$?
|
||||
rm -rf "$temp_dir"
|
||||
# TODO: Remove temp_dir from folders
|
||||
return "$status"
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ 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)
|
||||
|
||||
@@ -56,7 +55,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 ./):/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 "$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"
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ 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 {
|
||||
@@ -12,7 +11,7 @@ function main {
|
||||
|
||||
local test
|
||||
while read test; do
|
||||
cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output
|
||||
(cd "$DIR/../" && cargo test --no-default-features --features compare --no-fail-fast --test test_loader "$test" -- --show-output)
|
||||
done<<<"$test_names"
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
: ${PROFILE:="release-lto"}
|
||||
|
||||
cd "$DIR/../"
|
||||
|
||||
function main {
|
||||
local additional_flags=()
|
||||
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||
@@ -16,8 +14,8 @@ function main {
|
||||
else
|
||||
additional_flags+=(--profile "$PROFILE")
|
||||
fi
|
||||
cargo build --no-default-features "${additional_flags[@]}"
|
||||
time ./target/${PROFILE}/parse "${@}"
|
||||
(cd "$DIR/../" && cargo build --no-default-features "${additional_flags[@]}")
|
||||
time "$DIR/../target/${PROFILE}/parse" "${@}"
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
|
||||
52
src/bin_compare.rs
Normal file
52
src/bin_compare.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#![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)
|
||||
}
|
||||
77
src/compare/compare.rs
Normal file
77
src/compare/compare.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
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(())
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
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::parser::sexp::unquote;
|
||||
use crate::parser::sexp::Token;
|
||||
use crate::types::AngleLink;
|
||||
use crate::types::Bold;
|
||||
use crate::types::CheckboxType;
|
||||
use crate::types::Citation;
|
||||
use crate::types::CitationReference;
|
||||
use crate::types::Clock;
|
||||
@@ -36,6 +38,7 @@ 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;
|
||||
@@ -44,6 +47,7 @@ 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;
|
||||
@@ -83,7 +87,7 @@ pub struct DiffResult<'s> {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DiffStatus {
|
||||
enum DiffStatus {
|
||||
Good,
|
||||
Bad,
|
||||
}
|
||||
@@ -306,6 +310,7 @@ 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 {
|
||||
@@ -485,11 +490,11 @@ fn compare_heading<'s>(
|
||||
let level = get_property(emacs, ":level")?
|
||||
.ok_or("Level should not be nil")?
|
||||
.as_atom()?;
|
||||
if rust.stars.to_string() != level {
|
||||
if rust.level.to_string() != level {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"Headline level do not much (emacs != rust): {} != {}",
|
||||
level, rust.stars
|
||||
"Headline level do not match (emacs != rust): {} != {}",
|
||||
level, rust.level
|
||||
))
|
||||
}
|
||||
|
||||
@@ -542,7 +547,17 @@ fn compare_heading<'s>(
|
||||
};
|
||||
|
||||
// Compare title
|
||||
let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?;
|
||||
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_status = title
|
||||
.as_list()?
|
||||
.iter()
|
||||
@@ -550,8 +565,60 @@ 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)?);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Compare priority, :footnote-section-p, :archivedp, :commentedp
|
||||
// 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
|
||||
|
||||
// Compare section
|
||||
let section_status = children
|
||||
@@ -665,6 +732,10 @@ 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)?);
|
||||
}
|
||||
@@ -733,6 +804,27 @@ 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(),
|
||||
@@ -806,6 +898,8 @@ 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)?);
|
||||
}
|
||||
@@ -933,7 +1027,7 @@ fn compare_property_drawer<'s>(
|
||||
rust: &'s PropertyDrawer<'s>,
|
||||
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
|
||||
let children = emacs.as_list()?;
|
||||
let child_status = Vec::new();
|
||||
let mut child_status = Vec::new();
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let mut message = None;
|
||||
let emacs_name = "property-drawer";
|
||||
@@ -949,9 +1043,8 @@ fn compare_property_drawer<'s>(
|
||||
Ok(_) => {}
|
||||
};
|
||||
|
||||
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)?);
|
||||
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)?);
|
||||
}
|
||||
|
||||
Ok(DiffResult {
|
||||
@@ -965,6 +1058,40 @@ 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>,
|
||||
@@ -987,6 +1114,44 @@ 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)?);
|
||||
}
|
||||
@@ -1024,6 +1189,10 @@ 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)?);
|
||||
}
|
||||
@@ -1222,6 +1391,8 @@ 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(),
|
||||
@@ -1447,6 +1618,52 @@ fn compare_keyword<'s>(
|
||||
.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(),
|
||||
message,
|
||||
children: child_status,
|
||||
rust_source: rust.get_source(),
|
||||
emacs_token: emacs,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
fn compare_latex_environment<'s>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
@@ -1735,6 +1952,8 @@ 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(),
|
||||
@@ -2262,6 +2481,8 @@ fn compare_subscript<'s>(
|
||||
Ok(_) => {}
|
||||
};
|
||||
|
||||
// TODO compare :use-brackets-p
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
@@ -2293,6 +2514,8 @@ fn compare_superscript<'s>(
|
||||
Ok(_) => {}
|
||||
};
|
||||
|
||||
// TODO compare :use-brackets-p
|
||||
|
||||
Ok(DiffResult {
|
||||
status: this_status,
|
||||
name: emacs_name.to_owned(),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
mod compare;
|
||||
mod diff;
|
||||
mod parse;
|
||||
mod sexp;
|
||||
mod util;
|
||||
pub use diff::compare_document;
|
||||
pub use parse::emacs_parse_anonymous_org_document;
|
||||
pub use parse::emacs_parse_file_org_document;
|
||||
pub use parse::get_emacs_version;
|
||||
pub use parse::get_org_mode_version;
|
||||
pub use compare::run_anonymous_compare;
|
||||
pub use compare::run_compare_on_file;
|
||||
|
||||
@@ -11,6 +11,8 @@ 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)))
|
||||
@@ -42,6 +44,8 @@ where
|
||||
))?;
|
||||
let elisp_script = format!(
|
||||
r#"(progn
|
||||
(require 'org)
|
||||
(defun org-table-align () t)
|
||||
(org-mode)
|
||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||
)"#
|
||||
|
||||
@@ -16,9 +16,6 @@ use nom::sequence::delimited;
|
||||
use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::convert_error;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::get_consumed;
|
||||
use crate::error::Res;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -31,8 +28,8 @@ pub enum Token<'s> {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TextWithProperties<'s> {
|
||||
pub text: &'s str,
|
||||
pub properties: Vec<Token<'s>>,
|
||||
pub(crate) text: &'s str,
|
||||
pub(crate) properties: Vec<Token<'s>>,
|
||||
}
|
||||
|
||||
enum ParseState {
|
||||
@@ -41,35 +38,39 @@ enum ParseState {
|
||||
}
|
||||
|
||||
impl<'s> Token<'s> {
|
||||
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn as_vector<'p>(
|
||||
&'p self,
|
||||
) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||
Ok(match self {
|
||||
Token::Vector(children) => Ok(children),
|
||||
_ => Err(format!("wrong token type, expected vector: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
|
||||
Ok(match self {
|
||||
Token::List(children) => Ok(children),
|
||||
_ => Err(format!("wrong token type, expected list: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
|
||||
Ok(match self {
|
||||
Token::Atom(body) => Ok(*body),
|
||||
_ => Err(format!("wrong token type, expected atom: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
||||
pub(crate) fn as_text<'p>(
|
||||
&'p self,
|
||||
) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
|
||||
Ok(match self {
|
||||
Token::TextWithProperties(body) => Ok(body),
|
||||
_ => Err(format!("wrong token type, expected text: {:?}", self)),
|
||||
}?)
|
||||
}
|
||||
|
||||
pub fn as_map<'p>(
|
||||
pub(crate) fn as_map<'p>(
|
||||
&'p self,
|
||||
) -> Result<HashMap<&'s str, &'p Token<'s>>, Box<dyn std::error::Error>> {
|
||||
let mut hashmap = HashMap::new();
|
||||
@@ -95,7 +96,26 @@ impl<'s> Token<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
/// Check if the child string slice is a slice of the parent string slice.
|
||||
fn is_slice_of(parent: &str, child: &str) -> bool {
|
||||
let parent_start = parent.as_ptr() as usize;
|
||||
let parent_end = parent_start + parent.len();
|
||||
let child_start = child.as_ptr() as usize;
|
||||
let child_end = child_start + child.len();
|
||||
child_start >= parent_start && child_end <= parent_end
|
||||
}
|
||||
|
||||
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
||||
pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||
assert!(is_slice_of(input, remaining));
|
||||
let source = {
|
||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
||||
&input[..offset]
|
||||
};
|
||||
source.into()
|
||||
}
|
||||
|
||||
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut out = String::with_capacity(text.len());
|
||||
if !text.starts_with(r#"""#) {
|
||||
return Err("Quoted text does not start with quote.".into());
|
||||
@@ -132,29 +152,20 @@ pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
pub fn sexp<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = multispace0(input)?;
|
||||
let remaining = OrgSource::new(remaining);
|
||||
let (remaining, tkn) = token(remaining)
|
||||
.map(|(rem, out)| (Into::<&str>::into(rem), out))
|
||||
.map_err(convert_error)?;
|
||||
let (remaining, tkn) = token(remaining).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
||||
let (remaining, _) = multispace0(remaining)?;
|
||||
Ok((remaining, tkn))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
let (remaining, tkn) = token(input)?;
|
||||
Ok((remaining, tkn))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn token<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn token<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
alt((list, vector, atom))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = tag("(")(input)?;
|
||||
let (remaining, children) = delimited(
|
||||
multispace0,
|
||||
@@ -166,7 +177,7 @@ fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = tag("[")(input)?;
|
||||
let (remaining, children) = delimited(
|
||||
multispace0,
|
||||
@@ -178,7 +189,7 @@ fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
not(peek(one_of(")]")))(input)?;
|
||||
alt((
|
||||
text_with_properties,
|
||||
@@ -189,7 +200,7 @@ fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, body) = take_till1(|c| match c {
|
||||
' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
|
||||
_ => false,
|
||||
@@ -198,7 +209,7 @@ fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = tag(r#"""#)(input)?;
|
||||
let (remaining, _) = escaped(
|
||||
take_till1(|c| match c {
|
||||
@@ -214,7 +225,7 @@ fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = tag("#<")(input)?;
|
||||
let (remaining, _body) = take_till1(|c| match c {
|
||||
'>' => true,
|
||||
@@ -225,7 +236,7 @@ fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
Ok((remaining, Token::Atom(source.into())))
|
||||
}
|
||||
|
||||
fn text_with_properties<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
|
||||
fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = tag("#(")(input)?;
|
||||
let (remaining, (text, props)) = delimited(
|
||||
multispace0,
|
||||
@@ -255,7 +266,7 @@ mod tests {
|
||||
#[test]
|
||||
fn simple() {
|
||||
let input = " (foo bar baz ) ";
|
||||
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||
let (remaining, parsed) = sexp(input).expect("Parse the input");
|
||||
assert_eq!(remaining, "");
|
||||
assert!(match parsed {
|
||||
Token::Atom(_) => false,
|
||||
@@ -268,7 +279,7 @@ mod tests {
|
||||
#[test]
|
||||
fn quoted() {
|
||||
let input = r#" ("foo" bar baz ) "#;
|
||||
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||
let (remaining, parsed) = sexp(input).expect("Parse the input");
|
||||
assert_eq!(remaining, "");
|
||||
assert!(match parsed {
|
||||
Token::Atom(_) => false,
|
||||
@@ -292,7 +303,7 @@ mod tests {
|
||||
#[test]
|
||||
fn quoted_containing_paren() {
|
||||
let input = r#" (foo "b(a)r" baz ) "#;
|
||||
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||
let (remaining, parsed) = sexp(input).expect("Parse the input");
|
||||
assert_eq!(remaining, "");
|
||||
assert!(match parsed {
|
||||
Token::List(_) => true,
|
||||
@@ -328,7 +339,7 @@ mod tests {
|
||||
#[test]
|
||||
fn string_containing_escaped_characters() {
|
||||
let input = r#" (foo "\\( x=2 \\)" bar) "#;
|
||||
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input");
|
||||
let (remaining, parsed) = sexp(input).expect("Parse the input");
|
||||
assert_eq!(remaining, "");
|
||||
assert!(match parsed {
|
||||
Token::Atom(_) => false,
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::parser::sexp::Token;
|
||||
use super::sexp::Token;
|
||||
use crate::types::Source;
|
||||
|
||||
/// Check if the child string slice is a slice of the parent string slice.
|
||||
@@ -21,7 +21,10 @@ fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize
|
||||
(offset, end)
|
||||
}
|
||||
|
||||
pub fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub(crate) 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()
|
||||
@@ -37,7 +40,7 @@ pub fn assert_name<'s>(emacs: &'s Token<'s>, name: &str) -> Result<(), Box<dyn s
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn assert_bounds<'s, S: Source<'s>>(
|
||||
pub(crate) fn assert_bounds<'s, S: Source<'s>>(
|
||||
source: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s S,
|
||||
@@ -141,7 +144,12 @@ fn maybe_token_to_usize(
|
||||
.map_or(Ok(None), |r| r.map(Some))?)
|
||||
}
|
||||
|
||||
pub fn get_property<'s, 'x>(
|
||||
/// 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>(
|
||||
emacs: &'s Token<'s>,
|
||||
key: &'x str,
|
||||
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {
|
||||
|
||||
@@ -14,24 +14,28 @@ use crate::error::Res;
|
||||
use crate::parser::OrgSource;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ContextElement<'r, 's> {
|
||||
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 struct ExitMatcherNode<'r> {
|
||||
pub(crate) struct ExitMatcherNode<'r> {
|
||||
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
|
||||
pub exit_matcher: &'r DynContextMatcher<'r>,
|
||||
pub class: ExitClass,
|
||||
pub(crate) exit_matcher: &'r DynContextMatcher<'r>,
|
||||
pub(crate) class: ExitClass,
|
||||
}
|
||||
|
||||
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
||||
@@ -43,13 +47,13 @@ impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Context<'g, 'r, 's> {
|
||||
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 fn new(
|
||||
pub(crate) fn new(
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
tree: List<'r, &'r ContextElement<'r, 's>>,
|
||||
) -> Self {
|
||||
@@ -59,38 +63,38 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_additional_node(&'r self, new_element: &'r ContextElement<'r, 's>) -> Self {
|
||||
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 fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> {
|
||||
pub(crate) fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> {
|
||||
self.tree.iter()
|
||||
}
|
||||
|
||||
pub fn iter_context(&'r self) -> Iter<'g, 'r, 's> {
|
||||
fn iter_context(&'r self) -> Iter<'g, 'r, 's> {
|
||||
Iter {
|
||||
next: self.tree.iter_list(),
|
||||
global_settings: self.global_settings,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_parent(&'r self) -> Option<Self> {
|
||||
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(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_data(&self) -> &ContextElement<'r, 's> {
|
||||
fn get_data(&self) -> &ContextElement<'r, 's> {
|
||||
self.tree.get_data()
|
||||
}
|
||||
|
||||
pub fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> {
|
||||
pub(crate) fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> {
|
||||
self.global_settings
|
||||
}
|
||||
|
||||
pub fn with_global_settings<'gg>(
|
||||
pub(crate) fn with_global_settings<'gg>(
|
||||
&self,
|
||||
new_settings: &'gg GlobalSettings<'gg, 's>,
|
||||
) -> Context<'gg, 'r, 's> {
|
||||
@@ -101,7 +105,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn check_exit_matcher(
|
||||
pub(crate) fn check_exit_matcher(
|
||||
&'r self,
|
||||
i: OrgSource<'s>,
|
||||
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
|
||||
@@ -130,7 +134,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
|
||||
/// Indicates if elements should consume the whitespace after them.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub fn should_consume_trailing_whitespace(&self) -> bool {
|
||||
pub(crate) fn should_consume_trailing_whitespace(&self) -> bool {
|
||||
self._should_consume_trailing_whitespace().unwrap_or(true)
|
||||
}
|
||||
|
||||
@@ -155,7 +159,7 @@ fn document_end<'b, 'g, 'r, 's>(
|
||||
eof(input)
|
||||
}
|
||||
|
||||
pub struct Iter<'g, 'r, 's> {
|
||||
struct Iter<'g, 'r, 's> {
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
next: super::list::IterList<'r, &'r ContextElement<'r, 's>>,
|
||||
}
|
||||
@@ -172,7 +176,7 @@ impl<'g, 'r, 's> Iterator for Iter<'g, 'r, 's> {
|
||||
}
|
||||
|
||||
impl<'r, 's> ContextElement<'r, 's> {
|
||||
pub fn document_context() -> Self {
|
||||
pub(crate) fn document_context() -> Self {
|
||||
Self::ExitMatcherNode(ExitMatcherNode {
|
||||
exit_matcher: &document_end,
|
||||
class: ExitClass::Document,
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum ExitClass {
|
||||
/// Headlines and sections.
|
||||
pub(crate) enum ExitClass {
|
||||
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,
|
||||
Alpha = 2,
|
||||
Beta = 3,
|
||||
Gamma = 4,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for ExitClass {
|
||||
|
||||
@@ -2,6 +2,7 @@ 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
|
||||
@@ -12,10 +13,24 @@ pub struct GlobalSettings<'g, '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> {
|
||||
pub fn new() -> GlobalSettings<'g, 's> {
|
||||
fn new() -> GlobalSettings<'g, 's> {
|
||||
GlobalSettings {
|
||||
radio_targets: Vec::new(),
|
||||
file_access: &LocalFileAccessInterface {
|
||||
@@ -23,6 +38,9 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
||||
},
|
||||
in_progress_todo_keywords: BTreeSet::new(),
|
||||
complete_todo_keywords: BTreeSet::new(),
|
||||
org_list_allow_alphabetical: false,
|
||||
tab_width: 8,
|
||||
odd_levels_only: HeadlineLevelFilter::OddEven,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,3 +50,9 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
|
||||
GlobalSettings::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HeadlineLevelFilter {
|
||||
Odd,
|
||||
OddEven,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct List<'parent, T> {
|
||||
pub(crate) struct List<'parent, T> {
|
||||
data: T,
|
||||
parent: Link<'parent, T>,
|
||||
}
|
||||
@@ -9,30 +9,30 @@ pub struct List<'parent, T> {
|
||||
type Link<'parent, T> = Option<&'parent List<'parent, T>>;
|
||||
|
||||
impl<'parent, T> List<'parent, T> {
|
||||
pub fn new(first_item: T) -> Self {
|
||||
pub(crate) fn new(first_item: T) -> Self {
|
||||
Self {
|
||||
data: first_item,
|
||||
parent: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_data(&self) -> &T {
|
||||
pub(crate) fn get_data(&self) -> &T {
|
||||
&self.data
|
||||
}
|
||||
|
||||
pub fn get_parent(&'parent self) -> Link<'parent, T> {
|
||||
pub(crate) fn get_parent(&'parent self) -> Link<'parent, T> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> Iter<'_, T> {
|
||||
pub(crate) fn iter(&self) -> Iter<'_, T> {
|
||||
Iter { next: Some(self) }
|
||||
}
|
||||
|
||||
pub fn iter_list(&self) -> IterList<'_, T> {
|
||||
pub(crate) fn iter_list(&self) -> IterList<'_, T> {
|
||||
IterList { next: Some(self) }
|
||||
}
|
||||
|
||||
pub fn push(&'parent self, item: T) -> Self {
|
||||
pub(crate) fn push(&'parent self, item: T) -> Self {
|
||||
Self {
|
||||
data: item,
|
||||
parent: Some(self),
|
||||
@@ -40,7 +40,7 @@ impl<'parent, T> List<'parent, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'a, T> {
|
||||
pub(crate) struct Iter<'a, T> {
|
||||
next: Link<'a, T>,
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ impl<'a, T> Iterator for Iter<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IterList<'a, T> {
|
||||
pub(crate) struct IterList<'a, T> {
|
||||
next: Link<'a, T>,
|
||||
}
|
||||
|
||||
|
||||
@@ -8,22 +8,23 @@ mod global_settings;
|
||||
mod list;
|
||||
mod parser_with_context;
|
||||
|
||||
pub type RefContext<'b, 'g, 'r, 's> = &'b Context<'g, 'r, 's>;
|
||||
pub trait ContextMatcher = for<'b, 'g, 'r, 's> Fn(
|
||||
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>>;
|
||||
pub type DynContextMatcher<'c> = dyn ContextMatcher + 'c;
|
||||
pub trait Matcher = for<'s> Fn(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)]
|
||||
pub type DynMatcher<'c> = dyn Matcher + 'c;
|
||||
type DynMatcher<'c> = dyn Matcher + 'c;
|
||||
|
||||
pub use context::Context;
|
||||
pub use context::ContextElement;
|
||||
pub use context::ExitMatcherNode;
|
||||
pub use exiting::ExitClass;
|
||||
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 list::List;
|
||||
pub use global_settings::HeadlineLevelFilter;
|
||||
pub(crate) use list::List;
|
||||
pub(crate) use parser_with_context::parser_with_context;
|
||||
|
||||
@@ -2,7 +2,7 @@ use nom::error::ErrorKind;
|
||||
use nom::error::ParseError;
|
||||
use nom::IResult;
|
||||
|
||||
pub type Res<T, U> = IResult<T, U, CustomError<T>>;
|
||||
pub(crate) 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)]
|
||||
@@ -13,7 +13,7 @@ pub enum CustomError<I> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MyError<I>(pub I);
|
||||
pub struct MyError<I>(pub(crate) I);
|
||||
|
||||
impl<I> ParseError<I> for CustomError<I> {
|
||||
fn from_error_kind(input: I, kind: ErrorKind) -> Self {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod error;
|
||||
pub use error::CustomError;
|
||||
pub use error::MyError;
|
||||
pub use error::Res;
|
||||
pub(crate) use error::CustomError;
|
||||
pub(crate) use error::MyError;
|
||||
pub(crate) use error::Res;
|
||||
|
||||
@@ -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 fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub(crate) 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 fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
opentelemetry::global::shutdown_tracer_provider();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tracing"))]
|
||||
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
14
src/lib.rs
14
src/lib.rs
@@ -1,24 +1,16 @@
|
||||
#![feature(round_char_boundary)]
|
||||
#![feature(exit_status_error)]
|
||||
#![feature(trait_alias)]
|
||||
// TODO: #![warn(missing_docs)]
|
||||
|
||||
#[cfg(feature = "compare")]
|
||||
mod compare;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::compare_document;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::emacs_parse_anonymous_org_document;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::emacs_parse_file_org_document;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::get_emacs_version;
|
||||
#[cfg(feature = "compare")]
|
||||
pub use compare::get_org_mode_version;
|
||||
pub mod compare;
|
||||
|
||||
mod context;
|
||||
mod error;
|
||||
pub mod parser;
|
||||
pub mod types;
|
||||
|
||||
pub use context::FileAccessInterface;
|
||||
pub use context::GlobalSettings;
|
||||
pub use context::LocalFileAccessInterface;
|
||||
|
||||
83
src/main.rs
83
src/main.rs
@@ -4,19 +4,7 @@ use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use ::organic::parser::parse;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::compare_document;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::emacs_parse_anonymous_org_document;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::emacs_parse_file_org_document;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::get_emacs_version;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::get_org_mode_version;
|
||||
use organic::parser::parse_with_settings;
|
||||
#[cfg(feature = "compare")]
|
||||
use organic::parser::sexp::sexp_with_padding;
|
||||
use organic::GlobalSettings;
|
||||
use organic::LocalFileAccessInterface;
|
||||
|
||||
@@ -66,85 +54,14 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
|
||||
Ok(stdin_contents)
|
||||
}
|
||||
|
||||
#[cfg(feature = "compare")]
|
||||
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let org_contents = org_contents.as_ref();
|
||||
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_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.")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "compare"))]
|
||||
fn run_anonymous_parse<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 rust_parsed = parse(org_contents.as_ref())?;
|
||||
println!("{:#?}", rust_parsed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "compare")]
|
||||
fn run_parse_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)?;
|
||||
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_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.")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "compare"))]
|
||||
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let org_path = org_path.as_ref();
|
||||
eprintln!(
|
||||
"This program was built with compare disabled. Only parsing with organic, not comparing."
|
||||
);
|
||||
let parent_directory = org_path
|
||||
.parent()
|
||||
.ok_or("Should be contained inside a directory.")?;
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::parser::util::get_consumed;
|
||||
use crate::types::AngleLink;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn angle_link<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn angle_link<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, AngleLink<'s>> {
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::types::Citation;
|
||||
use crate::types::Object;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn citation<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn citation<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Citation<'s>> {
|
||||
|
||||
@@ -29,7 +29,7 @@ use crate::types::CitationReference;
|
||||
use crate::types::Object;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn citation_reference<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn citation_reference<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, CitationReference<'s>> {
|
||||
@@ -49,7 +49,7 @@ pub fn citation_reference<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn citation_reference_key<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn citation_reference_key<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
@@ -161,7 +161,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
|
||||
tag(";")(input)
|
||||
}
|
||||
|
||||
pub fn must_balance_bracket<'s, F, O>(
|
||||
pub(crate) fn must_balance_bracket<'s, F, O>(
|
||||
mut inner: F,
|
||||
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>
|
||||
where
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::Clock;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn clock<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn clock<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Clock<'s>> {
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::Comment;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn comment<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn comment<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Comment<'s>> {
|
||||
@@ -67,6 +67,17 @@ 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::*;
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
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 super::sexp::sexp;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
@@ -18,22 +13,14 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::DiarySexp;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn diary_sexp<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
|
||||
start_of_line(input)?;
|
||||
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 (remaining, _clock) = tag("%%(")(input)?;
|
||||
let (remaining, _contents) = is_not("\r\n")(remaining)?;
|
||||
let (remaining, _eol) = alt((line_ending, eof))(remaining)?;
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
@@ -43,3 +30,9 @@ pub 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, ()))
|
||||
}
|
||||
|
||||
@@ -1,63 +1,34 @@
|
||||
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::all_consuming;
|
||||
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::org_source::OrgSource;
|
||||
use super::section::zeroth_section;
|
||||
use super::token::AllTokensIterator;
|
||||
use super::token::Token;
|
||||
use super::util::exit_matcher_parser;
|
||||
use super::util::get_consumed;
|
||||
use super::util::start_of_line;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::Context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::GlobalSettings;
|
||||
use crate::context::List;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::comment::comment;
|
||||
use crate::parser::element_parser::element;
|
||||
use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::org_source::convert_error;
|
||||
use crate::parser::planning::planning;
|
||||
use crate::parser::property_drawer::property_drawer;
|
||||
use crate::parser::util::blank_line;
|
||||
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use crate::types::Document;
|
||||
use crate::types::DocumentElement;
|
||||
use crate::types::Element;
|
||||
use crate::types::Heading;
|
||||
use crate::types::Object;
|
||||
use crate::types::Section;
|
||||
use crate::types::TodoKeywordType;
|
||||
|
||||
/// 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>, String> {
|
||||
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||
parse_with_settings(input, &GlobalSettings::default())
|
||||
}
|
||||
|
||||
@@ -70,7 +41,7 @@ pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, String> {
|
||||
pub fn parse_with_settings<'g, 's>(
|
||||
input: &'s str,
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
) -> Result<Document<'s>, String> {
|
||||
) -> 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);
|
||||
@@ -78,7 +49,7 @@ pub fn parse_with_settings<'g, 's>(
|
||||
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
|
||||
.map_err(|err| err.to_string())
|
||||
.map(|(_remaining, parsed_document)| parsed_document);
|
||||
ret
|
||||
Ok(ret?)
|
||||
}
|
||||
|
||||
/// Parse a full org-mode document.
|
||||
@@ -87,7 +58,7 @@ pub fn parse_with_settings<'g, 's>(
|
||||
///
|
||||
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
||||
#[allow(dead_code)]
|
||||
pub fn document<'b, 'g, 'r, 's>(
|
||||
fn document<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: &'s str,
|
||||
) -> Res<&'s str, Document<'s>> {
|
||||
@@ -185,314 +156,8 @@ fn _document<'b, 'g, 'r, 's>(
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn zeroth_section<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Section<'s>> {
|
||||
// TODO: The zeroth section is specialized so it probably needs its own parser
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
ContextElement::Context("section"),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Document,
|
||||
exit_matcher: §ion_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 without_consuming_whitespace_context = ContextElement::ConsumeTrailingWhitespace(false);
|
||||
let without_consuming_whitespace_context =
|
||||
parser_context.with_additional_node(&without_consuming_whitespace_context);
|
||||
|
||||
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<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, '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 contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
ContextElement::Context("section"),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Document,
|
||||
exit_matcher: §ion_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);
|
||||
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<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(detect_headline)(input)
|
||||
}
|
||||
|
||||
const fn heading(
|
||||
parent_stars: usize,
|
||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _heading<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, '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_type, todo_keyword), _ws)| {
|
||||
(todo_keyword_type, 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<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
parent_stars: usize,
|
||||
) -> Res<
|
||||
OrgSource<'s>,
|
||||
(
|
||||
usize,
|
||||
OrgSource<'s>,
|
||||
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
|
||||
Vec<Object<'s>>,
|
||||
Vec<&'s str>,
|
||||
),
|
||||
> {
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Document,
|
||||
exit_matcher: &headline_title_end,
|
||||
});
|
||||
let parser_context = context.with_additional_node(&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((
|
||||
parser_with_context!(heading_keyword)(&parser_context),
|
||||
space1,
|
||||
))),
|
||||
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
||||
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<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, '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<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
|
||||
let global_settings = context.get_global_settings();
|
||||
if global_settings.in_progress_todo_keywords.is_empty()
|
||||
&& global_settings.complete_todo_keywords.is_empty()
|
||||
{
|
||||
alt((
|
||||
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
|
||||
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
|
||||
))(input)
|
||||
} else {
|
||||
for todo_keyword in global_settings
|
||||
.in_progress_todo_keywords
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
{
|
||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, (TodoKeywordType::Todo, ent)));
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
for todo_keyword in global_settings
|
||||
.complete_todo_keywords
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
{
|
||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, (TodoKeywordType::Done, ent)));
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoTodoKeyword".into(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Document<'s> {
|
||||
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
||||
fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
||||
AllTokensIterator::new(Token::Document(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ use crate::types::Paragraph;
|
||||
use crate::types::SetSource;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn drawer<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn drawer<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Drawer<'s>> {
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
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;
|
||||
@@ -32,7 +36,7 @@ use crate::types::Paragraph;
|
||||
use crate::types::SetSource;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn dynamic_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
|
||||
@@ -67,24 +71,23 @@ pub fn dynamic_block<'b, 'g, 'r, 's>(
|
||||
};
|
||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
let (remaining, children) = match tuple((
|
||||
not(exit_matcher),
|
||||
not(exit_matcher)(remaining)?;
|
||||
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
||||
blank_line,
|
||||
many_till(blank_line, exit_matcher),
|
||||
))(remaining)
|
||||
{
|
||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
||||
many0(preceded(not(exit_matcher), blank_line)),
|
||||
))))(remaining)?;
|
||||
let leading_blank_lines =
|
||||
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||
let source = get_consumed(remaining, remain);
|
||||
element.set_source(source.into());
|
||||
(remain, vec![element])
|
||||
}
|
||||
Err(_) => {
|
||||
let (remaining, (children, _exit_contents)) =
|
||||
element
|
||||
});
|
||||
let (remaining, (mut children, _exit_contents)) =
|
||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||
(remaining, children)
|
||||
if let Some(lines) = leading_blank_lines {
|
||||
children.insert(0, lines);
|
||||
}
|
||||
};
|
||||
|
||||
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
@@ -117,7 +120,8 @@ fn dynamic_block_end<'b, 'g, 'r, 's>(
|
||||
start_of_line(input)?;
|
||||
let (remaining, source) = recognize(tuple((
|
||||
space0,
|
||||
tag_no_case("#+end:"),
|
||||
tag_no_case("#+end"),
|
||||
opt(tag(":")),
|
||||
alt((eof, line_ending)),
|
||||
)))(input)?;
|
||||
Ok((remaining, source))
|
||||
|
||||
@@ -4,14 +4,19 @@ 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::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;
|
||||
@@ -23,6 +28,7 @@ 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::util::get_consumed;
|
||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||
use crate::context::parser_with_context;
|
||||
@@ -34,7 +40,7 @@ use crate::parser::table::org_mode_table;
|
||||
use crate::types::Element;
|
||||
use crate::types::SetSource;
|
||||
|
||||
pub const fn element(
|
||||
pub(crate) const fn element(
|
||||
can_be_paragraph: bool,
|
||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
@@ -67,6 +73,7 @@ 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);
|
||||
|
||||
@@ -90,6 +97,7 @@ 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)
|
||||
{
|
||||
@@ -119,7 +127,7 @@ fn _element<'b, 'g, 'r, 's>(
|
||||
Ok((remaining, element))
|
||||
}
|
||||
|
||||
pub const fn detect_element(
|
||||
pub(crate) 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>, ()>
|
||||
{
|
||||
@@ -132,7 +140,16 @@ fn _detect_element<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
can_be_paragraph: bool,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
if detect_plain_list(input).is_ok() {
|
||||
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()
|
||||
{
|
||||
return Ok((input, ()));
|
||||
}
|
||||
if _element(context, input, can_be_paragraph).is_ok() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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;
|
||||
@@ -433,13 +433,13 @@ const ORG_ENTITIES: [&'static str; 413] = [
|
||||
];
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn entity<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn entity<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, '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)?;
|
||||
|
||||
@@ -460,9 +460,12 @@ fn name<'b, 'g, 'r, '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 = tag_no_case::<_, _, CustomError<_>>(entity)(input);
|
||||
let result = tuple((
|
||||
tag::<_, _, CustomError<_>>(entity),
|
||||
alt((tag("{}"), peek(recognize(entity_end)))),
|
||||
))(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
Ok((remaining, (ent, _))) => {
|
||||
return Ok((remaining, ent));
|
||||
}
|
||||
Err(_) => {}
|
||||
|
||||
@@ -20,7 +20,7 @@ use crate::parser::util::get_consumed;
|
||||
use crate::types::ExportSnippet;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn export_snippet<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ExportSnippet<'s>> {
|
||||
|
||||
@@ -3,15 +3,16 @@ 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::opt;
|
||||
use nom::combinator::recognize;
|
||||
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 crate::error::Res;
|
||||
@@ -21,7 +22,7 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::FixedWidthArea;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn fixed_width_area<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
|
||||
@@ -47,11 +48,22 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, _indent) = space0(input)?;
|
||||
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
|
||||
let (remaining, _) = tuple((
|
||||
tag(":"),
|
||||
opt(tuple((space1, is_not("\r\n")))),
|
||||
alt((line_ending, eof)),
|
||||
alt((recognize(tuple((only_space1, is_not("\r\n")))), space0)),
|
||||
org_line_ending,
|
||||
))(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, ()))
|
||||
}
|
||||
|
||||
@@ -2,15 +2,17 @@ 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;
|
||||
@@ -30,7 +32,7 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::FootnoteDefinition;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn footnote_definition<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
|
||||
@@ -41,8 +43,15 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
|
||||
}
|
||||
start_of_line(input)?;
|
||||
// Cannot be indented.
|
||||
let (remaining, (_lead_in, lbl, _lead_out, _ws)) =
|
||||
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?;
|
||||
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"),
|
||||
@@ -54,11 +63,22 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
|
||||
let parser_context = context.with_additional_node(&contexts[0]);
|
||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||
// 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 (remaining, (children, _exit_contents)) =
|
||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||
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 source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
@@ -66,17 +86,14 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
|
||||
FootnoteDefinition {
|
||||
source: source.into(),
|
||||
label: lbl.into(),
|
||||
children,
|
||||
children: children.into_iter().map(|(_, item)| item).collect(),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
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)
|
||||
pub(crate) fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -101,7 +118,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||
pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
||||
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
|
||||
Ok((input, ()))
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::parser::util::get_consumed;
|
||||
use crate::types::FootnoteReference;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn footnote_reference<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn footnote_reference<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
|
||||
@@ -42,7 +42,7 @@ fn anonymous_footnote<'b, 'g, 'r, '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::Beta,
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &exit_with_depth,
|
||||
});
|
||||
let parser_context = context.with_additional_node(&parser_context);
|
||||
@@ -78,7 +78,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
|
||||
let (remaining, _) = tag(":")(remaining)?;
|
||||
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Beta,
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &exit_with_depth,
|
||||
});
|
||||
let parser_context = context.with_additional_node(&parser_context);
|
||||
|
||||
@@ -4,11 +4,14 @@ 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::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
@@ -33,7 +36,7 @@ use crate::types::Paragraph;
|
||||
use crate::types::SetSource;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn greater_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
|
||||
@@ -80,25 +83,23 @@ pub fn greater_block<'b, 'g, 'r, 's>(
|
||||
|
||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
// Check for a completely empty block
|
||||
let (remaining, children) = match tuple((
|
||||
not(exit_matcher),
|
||||
not(exit_matcher)(remaining)?;
|
||||
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
||||
blank_line,
|
||||
many_till(blank_line, exit_matcher),
|
||||
))(remaining)
|
||||
{
|
||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
||||
many0(preceded(not(exit_matcher), blank_line)),
|
||||
))))(remaining)?;
|
||||
let leading_blank_lines =
|
||||
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||
let source = get_consumed(remaining, remain);
|
||||
element.set_source(source.into());
|
||||
(remain, vec![element])
|
||||
}
|
||||
Err(_) => {
|
||||
let (remaining, (children, _exit_contents)) =
|
||||
element
|
||||
});
|
||||
let (remaining, (mut children, _exit_contents)) =
|
||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||
(remaining, children)
|
||||
if let Some(lines) = leading_blank_lines {
|
||||
children.insert(0, lines);
|
||||
}
|
||||
};
|
||||
|
||||
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
|
||||
|
||||
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
|
||||
@@ -126,7 +127,6 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
}
|
||||
|
||||
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
|
||||
// TODO: Can this be done without making an owned copy?
|
||||
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
|
||||
}
|
||||
|
||||
|
||||
289
src/parser/headline.rs
Normal file
289
src/parser/headline.rs
Normal file
@@ -0,0 +1,289 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_a;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many1;
|
||||
use nom::multi::separated_list1;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::section::section;
|
||||
use super::util::get_consumed;
|
||||
use super::util::org_line_ending;
|
||||
use super::util::org_space;
|
||||
use super::util::org_space_or_line_ending;
|
||||
use super::util::start_of_line;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ExitClass;
|
||||
use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::util::blank_line;
|
||||
use crate::types::DocumentElement;
|
||||
use crate::types::Heading;
|
||||
use crate::types::HeadlineLevel;
|
||||
use crate::types::Object;
|
||||
use crate::types::PriorityCookie;
|
||||
use crate::types::TodoKeywordType;
|
||||
|
||||
pub(crate) const fn heading(
|
||||
parent_level: HeadlineLevel,
|
||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
move |context, input: OrgSource<'_>| _heading(context, input, parent_level)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _heading<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
not(|i| context.check_exit_matcher(i))(input)?;
|
||||
let (
|
||||
remaining,
|
||||
(
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword,
|
||||
maybe_priority,
|
||||
maybe_comment,
|
||||
title,
|
||||
heading_tags,
|
||||
),
|
||||
) = headline(context, input, parent_star_count)?;
|
||||
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, _ws) = opt(tuple((start_of_line, many0(blank_line))))(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 is_archived = heading_tags.contains(&"ARCHIVE");
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
Heading {
|
||||
source: source.into(),
|
||||
level: headline_level,
|
||||
todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
|
||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||
}),
|
||||
priority_cookie: maybe_priority.map(|(_, priority)| priority),
|
||||
title,
|
||||
tags: heading_tags,
|
||||
children,
|
||||
is_comment: maybe_comment.is_some(),
|
||||
is_archived,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) 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<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<
|
||||
OrgSource<'s>,
|
||||
(
|
||||
HeadlineLevel,
|
||||
HeadlineLevel,
|
||||
Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||
Option<(OrgSource<'s>, PriorityCookie)>,
|
||||
Option<OrgSource<'s>>,
|
||||
Vec<Object<'s>>,
|
||||
Vec<&'s str>,
|
||||
),
|
||||
> {
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Document,
|
||||
exit_matcher: &headline_title_end,
|
||||
});
|
||||
let parser_context = context.with_additional_node(&parser_context);
|
||||
|
||||
let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
|
||||
start_of_line,
|
||||
verify(
|
||||
parser_with_context!(headline_level)(&parser_context),
|
||||
|(_, count, _)| *count > parent_star_count,
|
||||
),
|
||||
peek(org_space),
|
||||
))(input)?;
|
||||
|
||||
let (remaining, maybe_todo_keyword) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(heading_keyword)(&parser_context),
|
||||
peek(org_space_or_line_ending),
|
||||
)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_priority) = opt(tuple((space1, priority_cookie)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_comment) = opt(tuple((
|
||||
space1,
|
||||
tag("COMMENT"),
|
||||
peek(org_space_or_line_ending),
|
||||
)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_title) = opt(tuple((
|
||||
space1,
|
||||
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
||||
)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
|
||||
|
||||
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
||||
|
||||
Ok((
|
||||
remaining,
|
||||
(
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||
maybe_priority,
|
||||
maybe_comment.map(|(_, comment, _)| comment),
|
||||
maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
|
||||
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<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(tuple((space0, opt(tuple((tags, space0))), org_line_ending)))(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<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
|
||||
let global_settings = context.get_global_settings();
|
||||
if global_settings.in_progress_todo_keywords.is_empty()
|
||||
&& global_settings.complete_todo_keywords.is_empty()
|
||||
{
|
||||
alt((
|
||||
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
|
||||
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
|
||||
))(input)
|
||||
} else {
|
||||
for todo_keyword in global_settings
|
||||
.in_progress_todo_keywords
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
{
|
||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, (TodoKeywordType::Todo, ent)));
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
for todo_keyword in global_settings
|
||||
.complete_todo_keywords
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
{
|
||||
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
|
||||
match result {
|
||||
Ok((remaining, ent)) => {
|
||||
return Ok((remaining, (TodoKeywordType::Done, ent)));
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
}
|
||||
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"NoTodoKeyword".into(),
|
||||
))))
|
||||
}
|
||||
}
|
||||
|
||||
fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCookie> {
|
||||
let (remaining, (_, priority_character, _)) = tuple((
|
||||
tag("[#"),
|
||||
verify(anychar, |c| c.is_alphanumeric()),
|
||||
tag("]"),
|
||||
))(input)?;
|
||||
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
|
||||
nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Failed to cast priority cookie to number.".into(),
|
||||
)))
|
||||
})?;
|
||||
Ok((remaining, cookie))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn headline_level<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, (HeadlineLevel, HeadlineLevel, OrgSource<'s>)> {
|
||||
let (remaining, stars) = is_a("*")(input)?;
|
||||
let count = stars.len().try_into().unwrap();
|
||||
let level = match context.get_global_settings().odd_levels_only {
|
||||
crate::context::HeadlineLevelFilter::Odd => {
|
||||
if count % 2 == 0 {
|
||||
(count + 2) / 2
|
||||
} else {
|
||||
(count + 1) / 2
|
||||
}
|
||||
}
|
||||
crate::context::HeadlineLevelFilter::OddEven => count,
|
||||
};
|
||||
Ok((remaining, (level, count, stars)))
|
||||
}
|
||||
@@ -15,7 +15,7 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::HorizontalRule;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn horizontal_rule<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::map;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::multi::separated_list0;
|
||||
|
||||
use super::keyword::filtered_keyword;
|
||||
use super::keyword_todo::todo_keywords;
|
||||
use super::OrgSource;
|
||||
use crate::context::HeadlineLevelFilter;
|
||||
use crate::error::Res;
|
||||
use crate::types::Keyword;
|
||||
use crate::GlobalSettings;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn scan_for_in_buffer_settings<'s>(
|
||||
pub(crate) fn scan_for_in_buffer_settings<'s>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
|
||||
// TODO: Optimization idea: since this is slicing the OrgSource at each character, it might be more efficient to do a parser that uses a search function like take_until, and wrap it in a function similar to consumed but returning the input along with the normal output, then pass all of that into a verify that confirms we were at the start of a line using the input we just returned.
|
||||
@@ -44,12 +48,13 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
|
||||
))(input)
|
||||
}
|
||||
|
||||
pub fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
keywords: Vec<Keyword<'sf>>,
|
||||
original_settings: &'g GlobalSettings<'g, 's>,
|
||||
) -> Result<GlobalSettings<'g, 's>, String> {
|
||||
let mut new_settings = original_settings.clone();
|
||||
|
||||
// Todo Keywords
|
||||
for kw in keywords.iter().filter(|kw| {
|
||||
kw.key.eq_ignore_ascii_case("todo")
|
||||
|| kw.key.eq_ignore_ascii_case("seq_todo")
|
||||
@@ -65,5 +70,21 @@ pub fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
.extend(complete_words.into_iter().map(str::to_string));
|
||||
}
|
||||
|
||||
// Startup settings
|
||||
for kw in keywords
|
||||
.iter()
|
||||
.filter(|kw| kw.key.eq_ignore_ascii_case("startup"))
|
||||
{
|
||||
let (_remaining, settings) =
|
||||
separated_list0(space1::<&str, nom::error::Error<_>>, is_not(" \t"))(kw.value)
|
||||
.map_err(|err: nom::Err<_>| err.to_string())?;
|
||||
if settings.contains(&"odd") {
|
||||
new_settings.odd_levels_only = HeadlineLevelFilter::Odd;
|
||||
}
|
||||
if settings.contains(&"oddeven") {
|
||||
new_settings.odd_levels_only = HeadlineLevelFilter::OddEven;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(new_settings)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::parser::util::get_consumed;
|
||||
use crate::types::InlineBabelCall;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn inline_babel_call<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, InlineBabelCall<'s>> {
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::parser::util::get_consumed;
|
||||
use crate::types::InlineSourceBlock;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn inline_source_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> {
|
||||
|
||||
@@ -5,13 +5,14 @@ use nom::bytes::complete::tag_no_case;
|
||||
use nom::bytes::complete::take_while1;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::consumed;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
@@ -26,12 +27,12 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::Keyword;
|
||||
|
||||
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
|
||||
"caption", "data", "header", "headers", "label", "name", "plot", "resname", "result",
|
||||
"results", "source", "srcname", "tblname",
|
||||
"caption", "data", "headers", "header", "label", "name", "plot", "resname", "results",
|
||||
"result", "source", "srcname", "tblname",
|
||||
];
|
||||
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
|
||||
|
||||
pub fn filtered_keyword<F: Matcher>(
|
||||
pub(crate) fn filtered_keyword<F: Matcher>(
|
||||
key_parser: F,
|
||||
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
move |input| _filtered_keyword(&key_parser, input)
|
||||
@@ -66,7 +67,7 @@ fn _filtered_keyword<'s, F: Matcher>(
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
let (remaining, _ws) = space1(remaining)?;
|
||||
let (remaining, _ws) = space0(remaining)?;
|
||||
let (remaining, parsed_value) = recognize(many_till(
|
||||
anychar,
|
||||
peek(tuple((space0, alt((line_ending, eof))))),
|
||||
@@ -83,7 +84,7 @@ fn _filtered_keyword<'s, F: Matcher>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn keyword<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn keyword<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
@@ -91,20 +92,52 @@ pub fn keyword<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn affiliated_keyword<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
filtered_keyword(affiliated_key)(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
filtered_keyword(babel_call_key)(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn babel_call_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
tag_no_case("call")(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn table_formula_keyword<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||
verify(filtered_keyword(table_formula_key), |kw| {
|
||||
!kw.value.is_empty()
|
||||
})(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn table_formula_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
tag_no_case("tblfm")(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
recognize(tuple((
|
||||
not(peek(tag_no_case("call"))),
|
||||
not(peek(tag_no_case("begin"))),
|
||||
is_not(" \t\r\n:"),
|
||||
)))(input)
|
||||
not(peek(alt((tag_no_case("call"), tag_no_case("begin")))))(input)?;
|
||||
recognize(many_till(
|
||||
anychar,
|
||||
peek(alt((
|
||||
recognize(one_of(" \t\r\n")), // Give up if we hit whitespace
|
||||
recognize(tuple((tag(":"), one_of(" \t\r\n")))), // Stop if we see a colon followed by whitespace
|
||||
recognize(tuple((tag(":"), is_not(" \t\r\n:"), not(tag(":"))))), // Stop if we see a colon that is the last colon before whitespace. This is for keywords like "#+foo:bar:baz: lorem: ipsum" which would have the key "foo:bar:baz".
|
||||
))),
|
||||
))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::error::Res;
|
||||
/// Parses the text in the value of a #+TODO keyword.
|
||||
///
|
||||
/// Example input: "foo bar baz | lorem ipsum"
|
||||
pub fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
|
||||
pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
|
||||
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
|
||||
let (remaining, after_pipe_words) = opt(tuple((
|
||||
tuple((space0, tag("|"), space0)),
|
||||
@@ -44,9 +44,17 @@ pub fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s
|
||||
}
|
||||
|
||||
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
||||
verify(take_till(|c| " \t\r\n|".contains(c)), |result: &str| {
|
||||
let (remaining, keyword) = verify(take_till(|c| "( \t\r\n|".contains(c)), |result: &str| {
|
||||
!result.is_empty()
|
||||
})(input)
|
||||
})(input)?;
|
||||
|
||||
let (remaining, _) = opt(tuple((
|
||||
tag("("),
|
||||
take_till(|c| "() \t\r\n|".contains(c)),
|
||||
tag(")"),
|
||||
)))(remaining)?;
|
||||
|
||||
Ok((remaining, keyword))
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::parser::util::start_of_line;
|
||||
use crate::types::LatexEnvironment;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn latex_environment<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::parser::util::get_consumed;
|
||||
use crate::types::LatexFragment;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn latex_fragment<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, LatexFragment<'s>> {
|
||||
@@ -174,7 +174,7 @@ fn dollar_char_fragment<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn pre<'b, 'g, 'r, 's>(
|
||||
fn pre<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
@@ -188,7 +188,7 @@ pub fn pre<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn post<'b, 'g, 'r, 's>(
|
||||
fn post<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
@@ -226,7 +226,7 @@ fn bordered_dollar_fragment<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn open_border<'b, 'g, 'r, 's>(
|
||||
fn open_border<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
@@ -234,7 +234,7 @@ pub fn open_border<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn close_border<'b, 'g, 'r, 's>(
|
||||
fn close_border<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ()> {
|
||||
|
||||
@@ -7,6 +7,7 @@ use nom::character::complete::space1;
|
||||
use nom::combinator::consumed;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::tuple;
|
||||
@@ -34,13 +35,13 @@ use crate::types::SrcBlock;
|
||||
use crate::types::VerseBlock;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn verse_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn verse_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, VerseBlock<'s>> {
|
||||
let (remaining, name) = lesser_block_begin("verse")(context, input)?;
|
||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||
let (remaining, _nl) = line_ending(remaining)?;
|
||||
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
|
||||
let lesser_block_end_specialized = lesser_block_end("verse");
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
@@ -89,13 +90,13 @@ pub fn verse_block<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn comment_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn comment_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, CommentBlock<'s>> {
|
||||
let (remaining, name) = lesser_block_begin("comment")(context, input)?;
|
||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||
let (remaining, _nl) = line_ending(remaining)?;
|
||||
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
|
||||
let lesser_block_end_specialized = lesser_block_end("comment");
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
@@ -129,13 +130,13 @@ pub fn comment_block<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn example_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn example_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
|
||||
let (remaining, _name) = lesser_block_begin("example")(context, input)?;
|
||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||
let (remaining, _nl) = line_ending(remaining)?;
|
||||
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
|
||||
let lesser_block_end_specialized = lesser_block_end("example");
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
@@ -169,14 +170,14 @@ pub fn example_block<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn export_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn export_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, ExportBlock<'s>> {
|
||||
let (remaining, name) = lesser_block_begin("export")(context, input)?;
|
||||
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
|
||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||
let (remaining, _nl) = line_ending(remaining)?;
|
||||
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
|
||||
let lesser_block_end_specialized = lesser_block_end("export");
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
@@ -210,14 +211,14 @@ pub fn export_block<'b, 'g, 'r, 's>(
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub fn src_block<'b, 'g, 'r, 's>(
|
||||
pub(crate) fn src_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, SrcBlock<'s>> {
|
||||
let (remaining, name) = lesser_block_begin("src")(context, input)?;
|
||||
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
|
||||
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
|
||||
let (remaining, _nl) = line_ending(remaining)?;
|
||||
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
|
||||
let lesser_block_end_specialized = lesser_block_end("src");
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
@@ -275,9 +276,10 @@ fn _lesser_block_end<'b, 'g, 'r, 's, 'c>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, _leading_whitespace) = space0(input)?;
|
||||
let (remaining, (_begin, _name, _ws)) = tuple((
|
||||
let (remaining, (_begin, _name, _ws, _ending)) = tuple((
|
||||
tag_no_case("#+end_"),
|
||||
tag_no_case(current_name_lower),
|
||||
space0,
|
||||
alt((eof, line_ending)),
|
||||
))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user