Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e673aa862e | ||
|
|
3b6659c5fd | ||
|
|
68a3f8b87e | ||
|
|
b1244de1dc | ||
|
|
e5a402ee1b | ||
|
|
d4a2ad4a7f | ||
|
|
3d1b2713ed | ||
|
|
60bec4695b | ||
|
|
d992947ff1 | ||
|
|
76fb24d1d1 | ||
|
|
b56318fbe4 | ||
|
|
8169499de3 | ||
|
|
29d9e76545 | ||
|
|
4d356b855e | ||
|
|
ae66d1bd89 | ||
|
|
c551938904 | ||
|
|
0fb80e3fee | ||
|
|
590e7fba0e | ||
|
|
4a72747dc9 | ||
|
|
2352636672 | ||
|
|
36217f5704 | ||
|
|
0654b676f7 | ||
|
|
a80d171e4d | ||
|
|
2e1a946ac9 | ||
|
|
01c2f1bf66 | ||
|
|
be483110ef | ||
|
|
94401dcf00 | ||
|
|
2b5df83956 | ||
|
|
d53b9e1e1f | ||
|
|
5c929ffc13 | ||
|
|
bc3224be7a | ||
|
|
54c66fb4d6 | ||
|
|
6a8ae9d838 | ||
|
|
512432c5f0 | ||
|
|
890cd3e4fd | ||
|
|
9846cde2f0 | ||
|
|
dec3242e72 | ||
|
|
a8a34e2d9c | ||
|
|
c55fae86f8 | ||
|
|
e7ec23af3d | ||
|
|
10ae36a419 | ||
|
|
ecdfd7087f | ||
|
|
3ed9b552e2 | ||
|
|
d04c8c832c | ||
|
|
9575ef30ac | ||
|
|
06ecf41663 | ||
|
|
10d03fd432 | ||
|
|
a62c3fc522 | ||
|
|
25f664e69e | ||
|
|
52e0d305aa | ||
|
|
418c5c1ce8 | ||
|
|
ecd523fa8f | ||
|
|
c0555dec0b | ||
|
|
1b788f3f21 | ||
|
|
b3382c66cd | ||
|
|
2a003b85fd | ||
|
|
270ba53150 | ||
|
|
de5788d8f3 | ||
|
|
5a254392cb | ||
|
|
178894680b | ||
|
|
599b3b8f0a | ||
|
|
d78ce10a0b | ||
|
|
12ab9beada | ||
|
|
186201a4b5 | ||
|
|
d38b0a84f6 | ||
|
|
6ed35f4674 | ||
|
|
846a8b3729 | ||
|
|
896250836b | ||
|
|
6c77586960 | ||
|
|
fc7d4bd949 | ||
|
|
f1e35e317b | ||
|
|
3fb2b5d31c | ||
|
|
d1dac0b8de | ||
|
|
93f1bcd744 | ||
|
|
47674a6907 | ||
|
|
5d1582be4d | ||
|
|
dae10c2eef | ||
|
|
5e127fec11 | ||
|
|
064a4eeee7 | ||
|
|
7727b5ef47 | ||
|
|
967e74c147 | ||
|
|
13697df7ea | ||
|
|
07e11e359a | ||
|
|
0c363c8dd6 | ||
|
|
9a479b33e0 | ||
|
|
7a854838ef | ||
|
|
2012e5a6d5 | ||
|
|
f1261ddce8 | ||
|
|
3a422e6435 | ||
|
|
6670f8c768 | ||
|
|
d7a36c8aca | ||
|
|
f820e27b17 | ||
|
|
a4b1d462c3 | ||
|
|
1b7326eafe | ||
|
|
90433aa55f | ||
|
|
a5b4eb40f6 | ||
|
|
48d550e1fc | ||
|
|
9ce042d5b6 | ||
|
|
8784da5179 | ||
|
|
875a50ae46 | ||
|
|
c4ea3fbf88 | ||
|
|
95fa834420 | ||
|
|
32a7ce3f36 | ||
|
|
d8c52568db | ||
|
|
c5be75ee8d | ||
|
|
282417ee94 | ||
|
|
ab46a9e5c6 | ||
|
|
4359fc9266 | ||
|
|
7419b75d76 | ||
|
|
e4cfc296e5 | ||
|
|
9a1d91ae45 | ||
|
|
df5d699a39 | ||
|
|
9111408d83 | ||
|
|
35f058a354 | ||
|
|
dd91e506bd | ||
|
|
cd781a7dcf | ||
|
|
8cd0e4ec63 | ||
|
|
f9460b88d7 | ||
|
|
0b2a5f4fbf | ||
|
|
6097e4df18 | ||
|
|
d5b1014fe4 | ||
|
|
dd8a8207ce | ||
|
|
b4c985071c | ||
|
|
d4f27ef297 | ||
|
|
f25246556c | ||
|
|
3fe56e9aa3 | ||
|
|
f180412ff3 | ||
|
|
f0e28206ff | ||
|
|
1f64e289a2 | ||
|
|
f7690ff64b | ||
|
|
bd5e50d558 | ||
|
|
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 |
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "organic"
|
name = "organic"
|
||||||
version = "0.1.7"
|
version = "0.1.9"
|
||||||
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||||
description = "An org-mode parser."
|
description = "An org-mode parser."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -33,6 +33,10 @@ release:
|
|||||||
clean:
|
clean:
|
||||||
> cargo clean
|
> cargo clean
|
||||||
|
|
||||||
|
.PHONY: format
|
||||||
|
format:
|
||||||
|
> $(MAKE) -C docker/cargo_fmt run
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
> cargo test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||||
|
|||||||
78
README.md
78
README.md
@@ -2,12 +2,84 @@
|
|||||||
|
|
||||||
Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) parser.
|
Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) parser.
|
||||||
|
|
||||||
|
|
||||||
## Project Status
|
## 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.
|
This project is still under HEAVY development. While the version remains v0.1.x the API will be changing often. Once we hit v0.2.x we will start following semver.
|
||||||
|
|
||||||
|
Currently, the parser is able to correctly identify the start/end bounds of all the org-mode objects and elements (except table.el tables, org-mode tables are supported) but many of the interior properties are not yet populated.
|
||||||
|
|
||||||
|
### Project Goals
|
||||||
|
- We aim to provide perfect parity with the emacs org-mode parser. In that regard, any document that parses differently between Emacs and Organic is considered a bug.
|
||||||
|
- The parser should be fast. We're not doing anything special, but since this is written in Rust and natively compiled we should be able to beat the existing parsers.
|
||||||
|
- The parser should have minimal dependencies. This should reduce effort w.r.t.: security audits, legal compliance, portability.
|
||||||
|
- The parser should be usable everywhere. In the interest of getting org-mode used in as many places as possible, this parser should be usable by everyone everywhere. This means:
|
||||||
|
- It must have a permissive license for use in proprietary code bases.
|
||||||
|
- We will investigate compiling to WASM. This is an important goal of the project and will definitely happen, but only after the parser has a more stable API.
|
||||||
|
- We will investigate compiling to a C library for native linking to other code. This is more of a maybe-goal for the project.
|
||||||
|
### Project Non-Goals
|
||||||
|
- This project will not include an elisp engine since that would drastically increase the complexity of the code. Any features requiring an elisp engine will not be implemented (for example, Emacs supports embedded eval expressions in documents but this parser will never support that).
|
||||||
|
- This project is exclusively an org-mode **parser**. This limits its scope to roughly the output of `(org-element-parse-buffer)`. It will not render org-mode documents in other formats like HTML or LaTeX.
|
||||||
|
### Project Maybe-Goals
|
||||||
|
- table.el support. Currently we support org-mode tables but org-mode also allows table.el tables. So far, their use in org-mode documents seems rather uncommon so this is a low-priority feature.
|
||||||
|
- Document editing support. I do not anticipate any advanced editing features to make editing ergonomic, but it should be relatively easy to be able to parse an org-mode document and serialize it back into org-mode. This would enable cool features to be built on top of the library like auto-formatters. To accomplish this feature, We'd have to capture all of the various separators and whitespace that we are currently simply throwing away. This would add many additional fields to the parsed structs and it would add more noise to the parsers themselves, so I do not want to approach this feature until the parser is more complete since it would make modifications and refactoring more difficult.
|
||||||
|
### Supported Versions
|
||||||
|
This project targets the version of Emacs and Org-mode that are built into the [organic-test docker image](docker/organic_test/Dockerfile). This is newer than the version of Org-mode that shipped with Emacs 29.1. The parser itself does not depend on Emacs or Org-mode though, so this only matters for development purposes when running the automated tests that compare against upstream Org-mode.
|
||||||
|
|
||||||
|
## Using this library
|
||||||
|
TODO: Add section on using Organic as a library (which is the intended use for this project). This will be added when we have a bit more API stability since currently the library is under heavy development.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### 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
|
## 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.
|
||||||
|
|||||||
28
build.rs
28
build.rs
@@ -16,7 +16,8 @@ fn main() {
|
|||||||
let destination = Path::new(&out_dir).join("tests.rs");
|
let destination = Path::new(&out_dir).join("tests.rs");
|
||||||
let mut test_file = File::create(&destination).unwrap();
|
let mut test_file = File::create(&destination).unwrap();
|
||||||
|
|
||||||
write_header(&mut test_file);
|
// Re-generate the tests if any org-mode files change
|
||||||
|
println!("cargo:rerun-if-changed=org_mode_samples");
|
||||||
|
|
||||||
let test_files = WalkDir::new("org_mode_samples")
|
let test_files = WalkDir::new("org_mode_samples")
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -51,28 +52,15 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
|
|||||||
.strip_suffix(".org")
|
.strip_suffix(".org")
|
||||||
.expect("Should have .org extension")
|
.expect("Should have .org extension")
|
||||||
.replace("/", "_");
|
.replace("/", "_");
|
||||||
let test_name = format!("autogen_{}", test_name);
|
|
||||||
|
|
||||||
if let Some(_reason) = is_expect_fail(test_name.as_str()) {
|
|
||||||
write!(test_file, "#[ignore]\n").unwrap();
|
|
||||||
}
|
|
||||||
write!(
|
write!(
|
||||||
test_file,
|
test_file,
|
||||||
include_str!("./tests/test_template"),
|
include_str!("./tests/test_template"),
|
||||||
name = test_name,
|
name = test_name,
|
||||||
path = test.path().display()
|
path = test.path().display(),
|
||||||
)
|
expect_fail = is_expect_fail(test_name.as_str())
|
||||||
.unwrap();
|
.map(|_| "#[ignore]\n")
|
||||||
}
|
.unwrap_or("")
|
||||||
|
|
||||||
#[cfg(feature = "compare")]
|
|
||||||
fn write_header(test_file: &mut File) {
|
|
||||||
write!(
|
|
||||||
test_file,
|
|
||||||
r#"
|
|
||||||
#[feature(exit_status_error)]
|
|
||||||
|
|
||||||
"#
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -80,8 +68,8 @@ fn write_header(test_file: &mut File) {
|
|||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
fn is_expect_fail(name: &str) -> Option<&str> {
|
fn is_expect_fail(name: &str) -> Option<&str> {
|
||||||
match name {
|
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."),
|
"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."),
|
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM alpine:3.17 AS build
|
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
|
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
|
WORKDIR /root/emacs
|
||||||
RUN mkdir /root/dist
|
RUN mkdir /root/dist
|
||||||
RUN ./autogen.sh
|
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
|
||||||
RUN make DESTDIR="/root/dist" install
|
RUN make DESTDIR="/root/dist" install
|
||||||
|
|
||||||
|
|
||||||
FROM build AS build-org-mode
|
FROM build AS build-org-mode
|
||||||
ARG ORG_VERSION=163bafb43dcc2bc94a2c7ccaa77d3d1dd488f1af
|
ARG ORG_VERSION=c703541ffcc14965e3567f928de1683a1c1e33f6
|
||||||
COPY --from=build-emacs /root/dist/ /
|
COPY --from=build-emacs /root/dist/ /
|
||||||
RUN mkdir /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.
|
# 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
|
FROM rustlang/rust:nightly-alpine3.17 AS tester
|
||||||
ENV LANG=en_US.UTF-8
|
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
|
RUN cargo install --locked --no-default-features --features ci-autoclean cargo-cache
|
||||||
COPY --from=build-emacs /root/dist/ /
|
COPY --from=build-emacs /root/dist/ /
|
||||||
COPY --from=build-org-mode /root/dist/ /
|
COPY --from=build-org-mode /root/dist/ /
|
||||||
@@ -88,14 +88,20 @@ ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
|
|||||||
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
|
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
|
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=ba6cda890f200d428a5d68e819eef15b5306055f
|
||||||
|
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
|
FROM tester as foreign-document-test
|
||||||
RUN apk add --no-cache bash coreutils
|
RUN apk add --no-cache bash coreutils
|
||||||
RUN mkdir /foreign_documents
|
RUN mkdir /foreign_documents
|
||||||
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
|
|
||||||
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
|
|
||||||
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
|
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
|
||||||
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
|
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
|
||||||
|
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
|
||||||
|
COPY --from=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
|
COPY foreign_document_test_entrypoint.sh /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ function main {
|
|||||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||||
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
|
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
|
||||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
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)
|
(run_compare_function "howard_abrams" compare_howard_abrams)
|
||||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||||
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")
|
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")
|
||||||
@@ -39,9 +41,9 @@ function main {
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
if [ "$all_status" -ne 0 ]; then
|
if [ "$all_status" -ne 0 ]; then
|
||||||
echo "$(red_text "Some tests failed.")"
|
red_text "Some tests failed."
|
||||||
else
|
else
|
||||||
echo "$(green_text "All tests passed.")"
|
green_text "All tests passed."
|
||||||
fi
|
fi
|
||||||
return "$all_status"
|
return "$all_status"
|
||||||
}
|
}
|
||||||
@@ -62,8 +64,9 @@ function indent {
|
|||||||
local depth="$1"
|
local depth="$1"
|
||||||
local scaled_depth=$((depth * 2))
|
local scaled_depth=$((depth * 2))
|
||||||
shift 1
|
shift 1
|
||||||
local prefix=$(printf -- "%${scaled_depth}s")
|
local prefix
|
||||||
while read l; do
|
prefix=$(printf -- "%${scaled_depth}s")
|
||||||
|
while read -r l; do
|
||||||
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
|
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@@ -91,12 +94,13 @@ function compare_all_org_document {
|
|||||||
local target_document
|
local target_document
|
||||||
local all_status=0
|
local all_status=0
|
||||||
while read target_document; do
|
while read target_document; do
|
||||||
local relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
|
local relative_path
|
||||||
|
relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
|
||||||
set +e
|
set +e
|
||||||
(run_compare "$relative_path" "$target_document")
|
(run_compare "$relative_path" "$target_document")
|
||||||
if [ "$?" -ne 0 ]; then all_status=1; fi
|
if [ "$?" -ne 0 ]; then all_status=1; fi
|
||||||
set -e
|
set -e
|
||||||
done<<<$(find "$root_dir" -type f -iname '*.org')
|
done<<<"$(find "$root_dir" -type f -iname '*.org' | sort)"
|
||||||
return "$all_status"
|
return "$all_status"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,3 +25,4 @@ This could significantly reduce our calls to exit matchers.
|
|||||||
I think targets would break this.
|
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.
|
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.
|
||||||
|
|||||||
7
notes/test_names.org
Normal file
7
notes/test_names.org
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
* Autogen tests
|
||||||
|
The autogen tests are the tests automatically generated to compare the output of Organic vs the upstream Emacs Org-mode parser using the sample documents in the =org_mode_samples= folder. They will have a prefix based on the settings for each test.
|
||||||
|
|
||||||
|
- default :: The test is run with the default settings (The upstream Emacs Org-mode determines the default settings)
|
||||||
|
- la :: Short for "list alphabetic". Enables alphabetic plain lists.
|
||||||
|
- t# :: Sets the tab-width to # (as in t4 sets the tab-width to 4).
|
||||||
|
- odd :: Sets the org-odd-levels-only setting to true (meaning "odd" as opposed to "oddeven").
|
||||||
1
org_mode_samples/document/category.org
Normal file
1
org_mode_samples/document/category.org
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#+CATEGORY: theory
|
||||||
5
org_mode_samples/document/category_multiple.org
Normal file
5
org_mode_samples/document/category_multiple.org
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#+CATEGORY: foo
|
||||||
|
#+CATEGORY: bar
|
||||||
|
#+begin_src text
|
||||||
|
#+CATEGORY: baz
|
||||||
|
#+end_src
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#+BEGIN: timestamp :format "%Y-%m-%d %H:%M"
|
||||||
|
|
||||||
|
#+END
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#+begin_quote
|
||||||
|
|
||||||
|
foo
|
||||||
|
|
||||||
|
#+end_quote
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#+begin_defun foo bar baz
|
||||||
|
lorem
|
||||||
|
#+end_defun
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# An ordered list starting at 3
|
||||||
|
1. [@3] foo
|
||||||
|
|
||||||
|
|
||||||
|
# An ordered list starting at 11
|
||||||
|
1. [@D] bar
|
||||||
|
|
||||||
|
|
||||||
|
# An ordered list starting at 1 with the contents of "[@kk] baz"
|
||||||
|
1. [@kk] baz
|
||||||
|
|
||||||
|
|
||||||
|
# A paragraph when org-list-allow-alphabetical is nil
|
||||||
|
m. lorem
|
||||||
|
|
||||||
|
|
||||||
|
# A paragraph when org-list-allow-alphabetical is nil
|
||||||
|
m. [@k] ipsum
|
||||||
|
|
||||||
|
|
||||||
|
# An unordered list with :counter set to 3
|
||||||
|
- [@3] dolar
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Alphabetic lists larger than 26 elements should become numbered. From M-x describe-variable org-list-allow-alphabetical:
|
||||||
|
#
|
||||||
|
# > Lists with more than 26 items will fallback to standard numbering.
|
||||||
|
a. 1
|
||||||
|
a. 2
|
||||||
|
a. 3
|
||||||
|
a. 4
|
||||||
|
a. 5
|
||||||
|
a. 6
|
||||||
|
a. 7
|
||||||
|
a. 8
|
||||||
|
a. 9
|
||||||
|
a. 10
|
||||||
|
a. 11
|
||||||
|
a. 12
|
||||||
|
a. 13
|
||||||
|
a. 14
|
||||||
|
a. 15
|
||||||
|
a. 16
|
||||||
|
a. 17
|
||||||
|
a. 18
|
||||||
|
a. 19
|
||||||
|
a. 20
|
||||||
|
a. 21
|
||||||
|
a. 22
|
||||||
|
a. 23
|
||||||
|
a. 24
|
||||||
|
a. 25
|
||||||
|
a. 26
|
||||||
|
a. 27
|
||||||
@@ -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,6 @@
|
|||||||
|
- foo ::
|
||||||
|
|
||||||
|
- bar ::
|
||||||
|
|
||||||
|
|
||||||
|
baz
|
||||||
@@ -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,5 @@
|
|||||||
|
# "lorem" is prefixed by a tab instead of spaces, so the editor's tab-width value determines whether lorem is a sibling of baz (tab-width 8), a sibling of bar (tab-width < 8), or a child of baz (tab-width > 8).
|
||||||
|
1. foo
|
||||||
|
1. bar
|
||||||
|
1. baz
|
||||||
|
1. lorem
|
||||||
@@ -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|
|
||||||
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:
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# Comment
|
# Comment
|
||||||
|
#
|
||||||
# indented line
|
# indented line
|
||||||
# At the top of the file
|
# At the top of the file
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
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]]]
|
||||||
@@ -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,7 @@
|
|||||||
|
# All the marks for repeater and warning delay
|
||||||
|
[1970-01-01 Thu 8:15-13:15foo +1h -2h]
|
||||||
|
[1970-01-01 Thu 8:15-13:15foo ++1d -2d]
|
||||||
|
[1970-01-01 Thu 8:15-13:15foo .+1w -2w]
|
||||||
|
[1970-01-01 Thu 8:15-13:15foo +1m --2m]
|
||||||
|
[1970-01-01 Thu 8:15-13:15foo ++1y --2y]
|
||||||
|
[1970-01-01 Thu 8:15-13:15foo .+1d --2h]
|
||||||
@@ -2,13 +2,17 @@
|
|||||||
<%%(foo bar baz)>
|
<%%(foo bar baz)>
|
||||||
# active
|
# active
|
||||||
<1970-01-01 Thu 8:15rest +1w -1d>
|
<1970-01-01 Thu 8:15rest +1w -1d>
|
||||||
|
# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range.
|
||||||
|
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
|
||||||
# inactive
|
# inactive
|
||||||
[1970-01-01 Thu 8:15rest +1w -1d]
|
[1970-01-01 Thu 8:15rest +1w -1d]
|
||||||
|
# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range.
|
||||||
|
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
|
||||||
# active date range
|
# active date range
|
||||||
<1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d>
|
<1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d>
|
||||||
# active time range
|
# active time range
|
||||||
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
|
<1970-01-01 Thu 8:15-13:15otherrest +1w -1d>
|
||||||
# inactive date range
|
# inactive date range
|
||||||
[1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d]
|
[1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d]
|
||||||
# inactive time range
|
# inactive time range
|
||||||
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
|
[1970-01-01 Thu 8:15-13:15otherrest +1w -1d]
|
||||||
|
|||||||
2
org_mode_samples/object/timestamp/timeless_rest.org
Normal file
2
org_mode_samples/object/timestamp/timeless_rest.org
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# This should be a malformed timestamp according to the current org-mode documentation but it is accepted anyway (with no repeater).
|
||||||
|
<1970-01-01 Thu ++y>
|
||||||
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,2 @@
|
|||||||
|
<<<Footnotes>>> and stuff
|
||||||
|
* Footnotes
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
* FOOTNOTES
|
||||||
|
* Footnotes
|
||||||
|
* footnotes
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* Footnotes
|
||||||
|
* Footnotes
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
* Foo
|
||||||
|
* Footnotes :foo:bar:
|
||||||
|
* Footnotes and stuff
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#+STARTUP: odd
|
||||||
|
* Foo
|
||||||
|
***** Bar
|
||||||
|
* Baz
|
||||||
|
*** Lorem
|
||||||
|
* Ipsum
|
||||||
|
**** Dolar
|
||||||
|
***** Cat
|
||||||
@@ -4,10 +4,23 @@ set -euo pipefail
|
|||||||
IFS=$'\n\t'
|
IFS=$'\n\t'
|
||||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
: ${PROFILE:="perf"}
|
||||||
|
|
||||||
|
function main {
|
||||||
|
local additional_flags=()
|
||||||
|
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||||
|
PROFILE="debug"
|
||||||
|
else
|
||||||
|
additional_flags+=(--profile "$PROFILE")
|
||||||
|
# We have to disable avx512 because valgrind does not yet support it.
|
||||||
|
export RUSTFLAGS="-C target-feature=-avx512"
|
||||||
|
fi
|
||||||
|
|
||||||
(cd "$DIR/../" && RUSTFLAGS="-C opt-level=0" cargo build --no-default-features)
|
(cd "$DIR/../" && RUSTFLAGS="-C target-cpu=x86-64-v3" cargo build --no-default-features "${additional_flags[@]}")
|
||||||
valgrind --tool=callgrind --callgrind-out-file="$DIR/../callgrind.out" "$DIR/../target/debug/parse" "${@}"
|
valgrind --tool=callgrind --callgrind-out-file="$DIR/../callgrind.out" "$DIR/../target/${PROFILE}/parse" "${@}"
|
||||||
|
|
||||||
echo "You probably want to run:"
|
echo "You probably want to run:"
|
||||||
echo "callgrind_annotate --auto=yes '$DIR/../callgrind.out'"
|
echo "callgrind_annotate --auto=yes '$DIR/../callgrind.out'"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "${@}"
|
||||||
|
|||||||
@@ -8,10 +8,26 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|||||||
: ${TRACE:="NO"} # or YES to send traces to jaeger
|
: ${TRACE:="NO"} # or YES to send traces to jaeger
|
||||||
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
|
: ${BACKTRACE:="NO"} # or YES to print a rust backtrace when panicking
|
||||||
: ${NO_COLOR:=""} # Set to anything to disable color output
|
: ${NO_COLOR:=""} # Set to anything to disable color output
|
||||||
|
: ${PROFILE:="debug"}
|
||||||
|
|
||||||
REALPATH=$(command -v uu-realpath || command -v realpath)
|
REALPATH=$(command -v uu-realpath || command -v realpath)
|
||||||
MAKE=$(command -v gmake || command -v make)
|
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 {
|
function main {
|
||||||
build_container
|
build_container
|
||||||
launch_container "${@}"
|
launch_container "${@}"
|
||||||
@@ -23,7 +39,6 @@ function build_container {
|
|||||||
|
|
||||||
function launch_container {
|
function launch_container {
|
||||||
local additional_flags=()
|
local additional_flags=()
|
||||||
local additional_args=()
|
|
||||||
local features=(compare)
|
local features=(compare)
|
||||||
|
|
||||||
if [ "$NO_COLOR" != "" ]; then
|
if [ "$NO_COLOR" != "" ]; then
|
||||||
@@ -37,11 +52,8 @@ function launch_container {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$SHELL" != "YES" ]; then
|
if [ "$SHELL" != "YES" ]; then
|
||||||
local features_joined=$(IFS=","; echo "${features[*]}")
|
|
||||||
additional_args+=(cargo run --bin compare --no-default-features --features "$features_joined")
|
|
||||||
additional_flags+=(--read-only)
|
additional_flags+=(--read-only)
|
||||||
else
|
else
|
||||||
additional_args+=(/bin/sh)
|
|
||||||
additional_flags+=(-t)
|
additional_flags+=(-t)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -49,16 +61,50 @@ function launch_container {
|
|||||||
additional_flags+=(--env RUST_BACKTRACE=full)
|
additional_flags+=(--env RUST_BACKTRACE=full)
|
||||||
fi
|
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 [ $# -gt 0 ]; then
|
||||||
# If we passed in args, we need to forward them along
|
# If we passed in args, we need to forward them along
|
||||||
for path in "${@}"; do
|
for path in "${@}"; do
|
||||||
local full_path=$($REALPATH "$path")
|
local full_path
|
||||||
local containing_folder=$(dirname "$full_path")
|
full_path=$($REALPATH "$path")
|
||||||
local file_name=$(basename "$full_path")
|
init_script=$(cat <<EOF
|
||||||
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/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 "${additional_args[@]}" -- "/input/$file_name"
|
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
|
done
|
||||||
else
|
else
|
||||||
docker run "${additional_flags[@]}" --init --rm -i --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 "${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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,6 @@ REALPATH=$(command -v uu-realpath || command -v realpath)
|
|||||||
|
|
||||||
############## Setup #########################
|
############## 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 {
|
function die {
|
||||||
local status_code="$1"
|
local status_code="$1"
|
||||||
shift
|
shift
|
||||||
@@ -34,18 +23,18 @@ function log {
|
|||||||
############## Program #########################
|
############## Program #########################
|
||||||
|
|
||||||
function main {
|
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
|
||||||
|
target_full_path=$($REALPATH "$1")
|
||||||
local target_full_path=$($REALPATH "$1")
|
|
||||||
SOURCE_FOLDER=$(dirname "$target_full_path")
|
SOURCE_FOLDER=$(dirname "$target_full_path")
|
||||||
TARGET_DOCUMENT=$(basename "$target_full_path")
|
TARGET_DOCUMENT=$(basename "$target_full_path")
|
||||||
|
|
||||||
|
|
||||||
local good=0
|
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
|
set +e
|
||||||
run_parse "$bad" &> /dev/null
|
(run_parse "$bad")
|
||||||
local status=$?
|
local status=$?
|
||||||
set -e
|
set -e
|
||||||
if [ $status -eq 0 ]; then
|
if [ $status -eq 0 ]; then
|
||||||
@@ -71,21 +60,12 @@ function main {
|
|||||||
echo "Bad line: $bad"
|
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 {
|
function run_parse {
|
||||||
local lines="$1"
|
local lines="$1"
|
||||||
local temp_dir=$(setup_temp_dir)
|
|
||||||
folders+=("$temp_dir")
|
cd "$SOURCE_FOLDER"
|
||||||
cat "$SOURCE_FOLDER/$TARGET_DOCUMENT" | head -n "$lines" > "$temp_dir/$TARGET_DOCUMENT"
|
head -n "$lines" "$SOURCE_FOLDER/$TARGET_DOCUMENT" | PROFILE=release-lto "${DIR}/run_docker_compare.bash"
|
||||||
"${DIR}/run_docker_compare.bash" "$temp_dir/$TARGET_DOCUMENT"
|
|
||||||
local status=$?
|
local status=$?
|
||||||
rm -rf "$temp_dir"
|
|
||||||
# TODO: Remove temp_dir from folders
|
|
||||||
return "$status"
|
return "$status"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,31 @@ use crate::compare::parse::emacs_parse_file_org_document;
|
|||||||
use crate::compare::parse::get_emacs_version;
|
use crate::compare::parse::get_emacs_version;
|
||||||
use crate::compare::parse::get_org_mode_version;
|
use crate::compare::parse::get_org_mode_version;
|
||||||
use crate::compare::sexp::sexp;
|
use crate::compare::sexp::sexp;
|
||||||
use crate::parser::parse;
|
use crate::context::GlobalSettings;
|
||||||
|
use crate::context::LocalFileAccessInterface;
|
||||||
|
use crate::parser::parse_file_with_settings;
|
||||||
use crate::parser::parse_with_settings;
|
use crate::parser::parse_with_settings;
|
||||||
use crate::GlobalSettings;
|
|
||||||
use crate::LocalFileAccessInterface;
|
|
||||||
|
|
||||||
pub fn run_anonymous_compare<P: AsRef<str>>(
|
pub fn run_anonymous_compare<P: AsRef<str>>(
|
||||||
org_contents: P,
|
org_contents: P,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let org_contents = org_contents.as_ref();
|
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default())
|
||||||
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
}
|
||||||
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
|
||||||
let rust_parsed = parse(org_contents)?;
|
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?;
|
run_compare_on_file_with_settings(org_path, &GlobalSettings::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
|
||||||
|
org_contents: P,
|
||||||
|
global_settings: &GlobalSettings,
|
||||||
|
) -> 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();
|
||||||
|
print_versions()?;
|
||||||
|
let rust_parsed = parse_with_settings(org_contents, global_settings)?;
|
||||||
|
let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings)?;
|
||||||
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
println!("{}\n\n\n", org_contents);
|
println!("{}\n\n\n", org_contents);
|
||||||
@@ -36,25 +48,29 @@ pub fn run_anonymous_compare<P: AsRef<str>>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
|
||||||
|
org_path: P,
|
||||||
|
global_settings: &GlobalSettings,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let org_path = org_path.as_ref();
|
let org_path = org_path.as_ref();
|
||||||
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
print_versions()?;
|
||||||
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
|
||||||
let parent_directory = org_path
|
let parent_directory = org_path
|
||||||
.parent()
|
.parent()
|
||||||
.ok_or("Should be contained inside a directory.")?;
|
.ok_or("Should be contained inside a directory.")?;
|
||||||
let org_contents = std::fs::read_to_string(org_path)?;
|
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 org_contents = org_contents.as_str();
|
||||||
let file_access_interface = LocalFileAccessInterface {
|
let file_access_interface = LocalFileAccessInterface {
|
||||||
working_directory: Some(parent_directory.to_path_buf()),
|
working_directory: Some(parent_directory.to_path_buf()),
|
||||||
};
|
};
|
||||||
let global_settings = {
|
let global_settings = {
|
||||||
let mut global_settings = GlobalSettings::default();
|
let mut global_settings = global_settings.clone();
|
||||||
global_settings.file_access = &file_access_interface;
|
global_settings.file_access = &file_access_interface;
|
||||||
global_settings
|
global_settings
|
||||||
};
|
};
|
||||||
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
|
let rust_parsed = parse_file_with_settings(org_contents, &global_settings, Some(org_path))?;
|
||||||
let org_sexp = emacs_parse_file_org_document(org_path)?;
|
let org_sexp = emacs_parse_file_org_document(org_path, &global_settings)?;
|
||||||
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
println!("{}\n\n\n", org_contents);
|
println!("{}\n\n\n", org_contents);
|
||||||
@@ -71,3 +87,9 @@ pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn st
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
||||||
|
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
2539
src/compare/diff.rs
2539
src/compare/diff.rs
File diff suppressed because it is too large
Load Diff
552
src/compare/elisp_fact.rs
Normal file
552
src/compare/elisp_fact.rs
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::types::AngleLink;
|
||||||
|
use crate::types::AstNode;
|
||||||
|
use crate::types::BabelCall;
|
||||||
|
use crate::types::Bold;
|
||||||
|
use crate::types::CenterBlock;
|
||||||
|
use crate::types::Citation;
|
||||||
|
use crate::types::CitationReference;
|
||||||
|
use crate::types::Clock;
|
||||||
|
use crate::types::Code;
|
||||||
|
use crate::types::Comment;
|
||||||
|
use crate::types::CommentBlock;
|
||||||
|
use crate::types::DiarySexp;
|
||||||
|
use crate::types::Document;
|
||||||
|
use crate::types::Drawer;
|
||||||
|
use crate::types::DynamicBlock;
|
||||||
|
use crate::types::Element;
|
||||||
|
use crate::types::Entity;
|
||||||
|
use crate::types::ExampleBlock;
|
||||||
|
use crate::types::ExportBlock;
|
||||||
|
use crate::types::ExportSnippet;
|
||||||
|
use crate::types::FixedWidthArea;
|
||||||
|
use crate::types::FootnoteDefinition;
|
||||||
|
use crate::types::FootnoteReference;
|
||||||
|
use crate::types::Heading;
|
||||||
|
use crate::types::HorizontalRule;
|
||||||
|
use crate::types::InlineBabelCall;
|
||||||
|
use crate::types::InlineSourceBlock;
|
||||||
|
use crate::types::Italic;
|
||||||
|
use crate::types::Keyword;
|
||||||
|
use crate::types::LatexEnvironment;
|
||||||
|
use crate::types::LatexFragment;
|
||||||
|
use crate::types::LineBreak;
|
||||||
|
use crate::types::NodeProperty;
|
||||||
|
use crate::types::Object;
|
||||||
|
use crate::types::OrgMacro;
|
||||||
|
use crate::types::Paragraph;
|
||||||
|
use crate::types::PlainLink;
|
||||||
|
use crate::types::PlainList;
|
||||||
|
use crate::types::PlainListItem;
|
||||||
|
use crate::types::PlainText;
|
||||||
|
use crate::types::Planning;
|
||||||
|
use crate::types::PropertyDrawer;
|
||||||
|
use crate::types::QuoteBlock;
|
||||||
|
use crate::types::RadioLink;
|
||||||
|
use crate::types::RadioTarget;
|
||||||
|
use crate::types::RegularLink;
|
||||||
|
use crate::types::Section;
|
||||||
|
use crate::types::SpecialBlock;
|
||||||
|
use crate::types::SrcBlock;
|
||||||
|
use crate::types::StatisticsCookie;
|
||||||
|
use crate::types::StrikeThrough;
|
||||||
|
use crate::types::Subscript;
|
||||||
|
use crate::types::Superscript;
|
||||||
|
use crate::types::Table;
|
||||||
|
use crate::types::TableCell;
|
||||||
|
use crate::types::TableRow;
|
||||||
|
use crate::types::Target;
|
||||||
|
use crate::types::Timestamp;
|
||||||
|
use crate::types::Underline;
|
||||||
|
use crate::types::Verbatim;
|
||||||
|
use crate::types::VerseBlock;
|
||||||
|
|
||||||
|
pub(crate) trait ElispFact<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait GetElispFact<'s> {
|
||||||
|
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, I: ElispFact<'s>> GetElispFact<'s> for I {
|
||||||
|
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> GetElispFact<'s> for AstNode<'r, 's> {
|
||||||
|
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||||
|
match self {
|
||||||
|
AstNode::Document(inner) => *inner,
|
||||||
|
AstNode::Heading(inner) => *inner,
|
||||||
|
AstNode::Section(inner) => *inner,
|
||||||
|
AstNode::Paragraph(inner) => *inner,
|
||||||
|
AstNode::PlainList(inner) => *inner,
|
||||||
|
AstNode::PlainListItem(inner) => *inner,
|
||||||
|
AstNode::CenterBlock(inner) => *inner,
|
||||||
|
AstNode::QuoteBlock(inner) => *inner,
|
||||||
|
AstNode::SpecialBlock(inner) => *inner,
|
||||||
|
AstNode::DynamicBlock(inner) => *inner,
|
||||||
|
AstNode::FootnoteDefinition(inner) => *inner,
|
||||||
|
AstNode::Comment(inner) => *inner,
|
||||||
|
AstNode::Drawer(inner) => *inner,
|
||||||
|
AstNode::PropertyDrawer(inner) => *inner,
|
||||||
|
AstNode::NodeProperty(inner) => *inner,
|
||||||
|
AstNode::Table(inner) => *inner,
|
||||||
|
AstNode::TableRow(inner) => *inner,
|
||||||
|
AstNode::VerseBlock(inner) => *inner,
|
||||||
|
AstNode::CommentBlock(inner) => *inner,
|
||||||
|
AstNode::ExampleBlock(inner) => *inner,
|
||||||
|
AstNode::ExportBlock(inner) => *inner,
|
||||||
|
AstNode::SrcBlock(inner) => *inner,
|
||||||
|
AstNode::Clock(inner) => *inner,
|
||||||
|
AstNode::DiarySexp(inner) => *inner,
|
||||||
|
AstNode::Planning(inner) => *inner,
|
||||||
|
AstNode::FixedWidthArea(inner) => *inner,
|
||||||
|
AstNode::HorizontalRule(inner) => *inner,
|
||||||
|
AstNode::Keyword(inner) => *inner,
|
||||||
|
AstNode::BabelCall(inner) => *inner,
|
||||||
|
AstNode::LatexEnvironment(inner) => *inner,
|
||||||
|
AstNode::Bold(inner) => *inner,
|
||||||
|
AstNode::Italic(inner) => *inner,
|
||||||
|
AstNode::Underline(inner) => *inner,
|
||||||
|
AstNode::StrikeThrough(inner) => *inner,
|
||||||
|
AstNode::Code(inner) => *inner,
|
||||||
|
AstNode::Verbatim(inner) => *inner,
|
||||||
|
AstNode::PlainText(inner) => *inner,
|
||||||
|
AstNode::RegularLink(inner) => *inner,
|
||||||
|
AstNode::RadioLink(inner) => *inner,
|
||||||
|
AstNode::RadioTarget(inner) => *inner,
|
||||||
|
AstNode::PlainLink(inner) => *inner,
|
||||||
|
AstNode::AngleLink(inner) => *inner,
|
||||||
|
AstNode::OrgMacro(inner) => *inner,
|
||||||
|
AstNode::Entity(inner) => *inner,
|
||||||
|
AstNode::LatexFragment(inner) => *inner,
|
||||||
|
AstNode::ExportSnippet(inner) => *inner,
|
||||||
|
AstNode::FootnoteReference(inner) => *inner,
|
||||||
|
AstNode::Citation(inner) => *inner,
|
||||||
|
AstNode::CitationReference(inner) => *inner,
|
||||||
|
AstNode::InlineBabelCall(inner) => *inner,
|
||||||
|
AstNode::InlineSourceBlock(inner) => *inner,
|
||||||
|
AstNode::LineBreak(inner) => *inner,
|
||||||
|
AstNode::Target(inner) => *inner,
|
||||||
|
AstNode::StatisticsCookie(inner) => *inner,
|
||||||
|
AstNode::Subscript(inner) => *inner,
|
||||||
|
AstNode::Superscript(inner) => *inner,
|
||||||
|
AstNode::TableCell(inner) => *inner,
|
||||||
|
AstNode::Timestamp(inner) => *inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> GetElispFact<'s> for Element<'s> {
|
||||||
|
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||||
|
match self {
|
||||||
|
Element::Paragraph(inner) => inner,
|
||||||
|
Element::PlainList(inner) => inner,
|
||||||
|
Element::CenterBlock(inner) => inner,
|
||||||
|
Element::QuoteBlock(inner) => inner,
|
||||||
|
Element::SpecialBlock(inner) => inner,
|
||||||
|
Element::DynamicBlock(inner) => inner,
|
||||||
|
Element::FootnoteDefinition(inner) => inner,
|
||||||
|
Element::Comment(inner) => inner,
|
||||||
|
Element::Drawer(inner) => inner,
|
||||||
|
Element::PropertyDrawer(inner) => inner,
|
||||||
|
Element::Table(inner) => inner,
|
||||||
|
Element::VerseBlock(inner) => inner,
|
||||||
|
Element::CommentBlock(inner) => inner,
|
||||||
|
Element::ExampleBlock(inner) => inner,
|
||||||
|
Element::ExportBlock(inner) => inner,
|
||||||
|
Element::SrcBlock(inner) => inner,
|
||||||
|
Element::Clock(inner) => inner,
|
||||||
|
Element::DiarySexp(inner) => inner,
|
||||||
|
Element::Planning(inner) => inner,
|
||||||
|
Element::FixedWidthArea(inner) => inner,
|
||||||
|
Element::HorizontalRule(inner) => inner,
|
||||||
|
Element::Keyword(inner) => inner,
|
||||||
|
Element::BabelCall(inner) => inner,
|
||||||
|
Element::LatexEnvironment(inner) => inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> GetElispFact<'s> for Object<'s> {
|
||||||
|
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||||
|
match self {
|
||||||
|
Object::Bold(inner) => inner,
|
||||||
|
Object::Italic(inner) => inner,
|
||||||
|
Object::Underline(inner) => inner,
|
||||||
|
Object::StrikeThrough(inner) => inner,
|
||||||
|
Object::Code(inner) => inner,
|
||||||
|
Object::Verbatim(inner) => inner,
|
||||||
|
Object::PlainText(inner) => inner,
|
||||||
|
Object::RegularLink(inner) => inner,
|
||||||
|
Object::RadioLink(inner) => inner,
|
||||||
|
Object::RadioTarget(inner) => inner,
|
||||||
|
Object::PlainLink(inner) => inner,
|
||||||
|
Object::AngleLink(inner) => inner,
|
||||||
|
Object::OrgMacro(inner) => inner,
|
||||||
|
Object::Entity(inner) => inner,
|
||||||
|
Object::LatexFragment(inner) => inner,
|
||||||
|
Object::ExportSnippet(inner) => inner,
|
||||||
|
Object::FootnoteReference(inner) => inner,
|
||||||
|
Object::Citation(inner) => inner,
|
||||||
|
Object::CitationReference(inner) => inner,
|
||||||
|
Object::InlineBabelCall(inner) => inner,
|
||||||
|
Object::InlineSourceBlock(inner) => inner,
|
||||||
|
Object::LineBreak(inner) => inner,
|
||||||
|
Object::Target(inner) => inner,
|
||||||
|
Object::StatisticsCookie(inner) => inner,
|
||||||
|
Object::Subscript(inner) => inner,
|
||||||
|
Object::Superscript(inner) => inner,
|
||||||
|
Object::Timestamp(inner) => inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Document<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"org-data".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Section<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"section".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Heading<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"headline".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for PlainList<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"plain-list".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for PlainListItem<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"item".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for CenterBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"center-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for QuoteBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"quote-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for SpecialBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"special-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for DynamicBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"dynamic-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for FootnoteDefinition<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"footnote-definition".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Drawer<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"drawer".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for PropertyDrawer<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"property-drawer".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for NodeProperty<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"node-property".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Table<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"table".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for TableRow<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"table-row".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Paragraph<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"paragraph".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for TableCell<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"table-cell".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Comment<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"comment".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for VerseBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"verse-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> ElispFact<'s> for CommentBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"comment-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> ElispFact<'s> for ExampleBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"example-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> ElispFact<'s> for ExportBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"export-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'s> ElispFact<'s> for SrcBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"src-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Clock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"clock".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for DiarySexp<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"diary-sexp".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Planning<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"planning".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for FixedWidthArea<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"fixed-width".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for HorizontalRule<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"horizontal-rule".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Keyword<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"keyword".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for BabelCall<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"babel-call".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for LatexEnvironment<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"latex-environment".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Bold<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"bold".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Italic<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"italic".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Underline<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"underline".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for StrikeThrough<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"strike-through".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Code<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"code".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Verbatim<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"verbatim".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for RegularLink<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"link".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for RadioLink<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"link".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for RadioTarget<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"radio-target".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for PlainLink<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"link".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for AngleLink<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"link".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for OrgMacro<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"macro".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Entity<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"entity".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for LatexFragment<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"latex-fragment".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for ExportSnippet<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"export-snippet".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for FootnoteReference<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"footnote-reference".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Citation<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"citation".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for CitationReference<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"citation-reference".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for InlineBabelCall<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"inline-babel-call".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for InlineSourceBlock<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"inline-src-block".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for LineBreak<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"line-break".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Target<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"target".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for StatisticsCookie<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"statistics-cookie".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Subscript<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"subscript".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Superscript<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"superscript".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for Timestamp<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
"timestamp".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> ElispFact<'s> for PlainText<'s> {
|
||||||
|
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||||
|
// plain text from upstream emacs does not actually have a name but this is included here to make rendering the status diff easier.
|
||||||
|
"plain-text".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
mod compare;
|
mod compare;
|
||||||
mod diff;
|
mod diff;
|
||||||
|
mod elisp_fact;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod sexp;
|
mod sexp;
|
||||||
mod util;
|
mod util;
|
||||||
pub use compare::run_anonymous_compare;
|
pub use compare::run_anonymous_compare;
|
||||||
|
pub use compare::run_anonymous_compare_with_settings;
|
||||||
pub use compare::run_compare_on_file;
|
pub use compare::run_compare_on_file;
|
||||||
|
pub use compare::run_compare_on_file_with_settings;
|
||||||
|
|||||||
@@ -1,8 +1,33 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
pub fn emacs_parse_anonymous_org_document<C>(
|
use crate::context::HeadlineLevelFilter;
|
||||||
|
use crate::settings::GlobalSettings;
|
||||||
|
|
||||||
|
/// Generate elisp to configure org-mode parsing settings
|
||||||
|
///
|
||||||
|
/// Currently only org-list-allow-alphabetical is supported.
|
||||||
|
fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
|
||||||
|
// This string concatenation is wildly inefficient but its only called in tests 🤷.
|
||||||
|
let mut ret = "".to_owned();
|
||||||
|
if global_settings.list_allow_alphabetical {
|
||||||
|
ret += "(setq org-list-allow-alphabetical t)\n"
|
||||||
|
}
|
||||||
|
if global_settings.tab_width != crate::settings::DEFAULT_TAB_WIDTH {
|
||||||
|
ret += format!("(setq-default tab-width {})", global_settings.tab_width).as_str();
|
||||||
|
}
|
||||||
|
if global_settings.odd_levels_only != HeadlineLevelFilter::default() {
|
||||||
|
ret += match global_settings.odd_levels_only {
|
||||||
|
HeadlineLevelFilter::Odd => "(setq org-odd-levels-only t)\n",
|
||||||
|
HeadlineLevelFilter::OddEven => "(setq org-odd-levels-only nil)\n",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn emacs_parse_anonymous_org_document<C>(
|
||||||
file_contents: C,
|
file_contents: C,
|
||||||
|
global_settings: &GlobalSettings,
|
||||||
) -> Result<String, Box<dyn std::error::Error>>
|
) -> Result<String, Box<dyn std::error::Error>>
|
||||||
where
|
where
|
||||||
C: AsRef<str>,
|
C: AsRef<str>,
|
||||||
@@ -11,11 +36,15 @@ where
|
|||||||
let elisp_script = format!(
|
let elisp_script = format!(
|
||||||
r#"(progn
|
r#"(progn
|
||||||
(erase-buffer)
|
(erase-buffer)
|
||||||
|
(require 'org)
|
||||||
|
(defun org-table-align () t)
|
||||||
(insert "{escaped_file_contents}")
|
(insert "{escaped_file_contents}")
|
||||||
|
{global_settings}
|
||||||
(org-mode)
|
(org-mode)
|
||||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||||
)"#,
|
)"#,
|
||||||
escaped_file_contents = escaped_file_contents
|
escaped_file_contents = escaped_file_contents,
|
||||||
|
global_settings = global_settings_elisp(global_settings)
|
||||||
);
|
);
|
||||||
let mut cmd = Command::new("emacs");
|
let mut cmd = Command::new("emacs");
|
||||||
let cmd = cmd
|
let cmd = cmd
|
||||||
@@ -31,7 +60,10 @@ where
|
|||||||
Ok(String::from_utf8(org_sexp)?)
|
Ok(String::from_utf8(org_sexp)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emacs_parse_file_org_document<P>(file_path: P) -> Result<String, Box<dyn std::error::Error>>
|
pub(crate) fn emacs_parse_file_org_document<P>(
|
||||||
|
file_path: P,
|
||||||
|
global_settings: &GlobalSettings,
|
||||||
|
) -> Result<String, Box<dyn std::error::Error>>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
@@ -42,9 +74,19 @@ where
|
|||||||
))?;
|
))?;
|
||||||
let elisp_script = format!(
|
let elisp_script = format!(
|
||||||
r#"(progn
|
r#"(progn
|
||||||
|
(require 'org)
|
||||||
|
(defun org-table-align () t)
|
||||||
|
(setq vc-handled-backends nil)
|
||||||
|
{global_settings}
|
||||||
|
(find-file-read-only "{file_path}")
|
||||||
(org-mode)
|
(org-mode)
|
||||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||||
)"#
|
)"#,
|
||||||
|
global_settings = global_settings_elisp(global_settings),
|
||||||
|
file_path = file_path
|
||||||
|
.as_os_str()
|
||||||
|
.to_str()
|
||||||
|
.expect("File name should be valid utf-8.")
|
||||||
);
|
);
|
||||||
let mut cmd = Command::new("emacs");
|
let mut cmd = Command::new("emacs");
|
||||||
let cmd = cmd
|
let cmd = cmd
|
||||||
@@ -53,8 +95,6 @@ where
|
|||||||
.arg("--no-site-file")
|
.arg("--no-site-file")
|
||||||
.arg("--no-splash")
|
.arg("--no-splash")
|
||||||
.arg("--batch")
|
.arg("--batch")
|
||||||
.arg("--insert")
|
|
||||||
.arg(file_path.as_os_str())
|
|
||||||
.arg("--eval")
|
.arg("--eval")
|
||||||
.arg(elisp_script);
|
.arg(elisp_script);
|
||||||
let out = cmd.output()?;
|
let out = cmd.output()?;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::escaped;
|
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::take_till1;
|
use nom::bytes::complete::take_till1;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::digit1;
|
||||||
use nom::character::complete::multispace0;
|
use nom::character::complete::multispace0;
|
||||||
use nom::character::complete::multispace1;
|
use nom::character::complete::multispace1;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
@@ -11,6 +12,7 @@ use nom::combinator::map;
|
|||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
use nom::multi::separated_list1;
|
use nom::multi::separated_list1;
|
||||||
use nom::sequence::delimited;
|
use nom::sequence::delimited;
|
||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
@@ -18,6 +20,8 @@ use nom::sequence::tuple;
|
|||||||
|
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
|
|
||||||
|
const MAX_OCTAL_LENGTH: usize = 3;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Token<'s> {
|
pub enum Token<'s> {
|
||||||
Atom(&'s str),
|
Atom(&'s str),
|
||||||
@@ -35,6 +39,7 @@ pub struct TextWithProperties<'s> {
|
|||||||
enum ParseState {
|
enum ParseState {
|
||||||
Normal,
|
Normal,
|
||||||
Escape,
|
Escape,
|
||||||
|
Octal(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Token<'s> {
|
impl<'s> Token<'s> {
|
||||||
@@ -106,8 +111,8 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
||||||
pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||||
assert!(is_slice_of(input, remaining));
|
debug_assert!(is_slice_of(input, remaining));
|
||||||
let source = {
|
let source = {
|
||||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
||||||
&input[..offset]
|
&input[..offset]
|
||||||
@@ -116,7 +121,7 @@ pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let mut out = String::with_capacity(text.len());
|
let mut out: Vec<u8> = Vec::with_capacity(text.len());
|
||||||
if !text.starts_with(r#"""#) {
|
if !text.starts_with(r#"""#) {
|
||||||
return Err("Quoted text does not start with quote.".into());
|
return Err("Quoted text does not start with quote.".into());
|
||||||
}
|
}
|
||||||
@@ -125,30 +130,53 @@ pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>>
|
|||||||
}
|
}
|
||||||
let interior_text = &text[1..(text.len() - 1)];
|
let interior_text = &text[1..(text.len() - 1)];
|
||||||
let mut state = ParseState::Normal;
|
let mut state = ParseState::Normal;
|
||||||
for current_char in interior_text.chars().into_iter() {
|
for current_char in interior_text.bytes().into_iter() {
|
||||||
|
// Check to see if octal finished
|
||||||
state = match (state, current_char) {
|
state = match (state, current_char) {
|
||||||
(ParseState::Normal, '\\') => ParseState::Escape,
|
(ParseState::Octal(octal), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => {
|
||||||
|
ParseState::Octal(octal)
|
||||||
|
}
|
||||||
|
(ParseState::Octal(octal), _) => {
|
||||||
|
let octal_number_string = String::from_utf8(octal)?;
|
||||||
|
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
|
||||||
|
out.push(decoded_byte);
|
||||||
|
ParseState::Normal
|
||||||
|
}
|
||||||
|
(state, _) => state,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = match (state, current_char) {
|
||||||
|
(ParseState::Normal, b'\\') => ParseState::Escape,
|
||||||
(ParseState::Normal, _) => {
|
(ParseState::Normal, _) => {
|
||||||
out.push(current_char);
|
out.push(current_char);
|
||||||
ParseState::Normal
|
ParseState::Normal
|
||||||
}
|
}
|
||||||
(ParseState::Escape, 'n') => {
|
(ParseState::Escape, b'n') => {
|
||||||
out.push('\n');
|
out.push(b'\n');
|
||||||
ParseState::Normal
|
ParseState::Normal
|
||||||
}
|
}
|
||||||
(ParseState::Escape, '\\') => {
|
(ParseState::Escape, b'\\') => {
|
||||||
out.push('\\');
|
out.push(b'\\');
|
||||||
ParseState::Normal
|
ParseState::Normal
|
||||||
}
|
}
|
||||||
(ParseState::Escape, '"') => {
|
(ParseState::Escape, b'"') => {
|
||||||
out.push('"');
|
out.push(b'"');
|
||||||
ParseState::Normal
|
ParseState::Normal
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
(ParseState::Escape, b'0'..=b'7') => {
|
||||||
|
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
|
||||||
|
octal.push(current_char);
|
||||||
|
ParseState::Octal(octal)
|
||||||
|
}
|
||||||
|
(ParseState::Octal(mut octal), b'0'..=b'7') => {
|
||||||
|
octal.push(current_char);
|
||||||
|
ParseState::Octal(octal)
|
||||||
|
}
|
||||||
|
_ => panic!("Invalid state unquoting string."),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(out)
|
Ok(String::from_utf8(out)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -210,15 +238,30 @@ fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||||
let (remaining, _) = tag(r#"""#)(input)?;
|
let (mut remaining, _) = tag(r#"""#)(input)?;
|
||||||
let (remaining, _) = escaped(
|
let mut in_escape = false;
|
||||||
take_till1(|c| match c {
|
loop {
|
||||||
'\\' | '"' => true,
|
if in_escape {
|
||||||
_ => false,
|
let (remain, _) = alt((recognize(one_of(r#""n\\"#)), digit1))(remaining)?;
|
||||||
}),
|
remaining = remain;
|
||||||
'\\',
|
in_escape = false;
|
||||||
one_of(r#""n\\"#),
|
} else {
|
||||||
)(remaining)?;
|
let end_quote = tag::<_, _, nom::error::Error<_>>(r#"""#)(remaining);
|
||||||
|
if end_quote.is_ok() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let escape_backslash = tag::<_, _, nom::error::Error<_>>("\\")(remaining);
|
||||||
|
if let Ok((remain, _)) = escape_backslash {
|
||||||
|
remaining = remain;
|
||||||
|
in_escape = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (remain, _) = anychar(remaining)?;
|
||||||
|
remaining = remain;
|
||||||
|
}
|
||||||
|
}
|
||||||
let (remaining, _) = tag(r#"""#)(remaining)?;
|
let (remaining, _) = tag(r#"""#)(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, Token::Atom(source.into())))
|
Ok((remaining, Token::Atom(source.into())))
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use super::elisp_fact::GetElispFact;
|
||||||
use super::sexp::Token;
|
use super::sexp::Token;
|
||||||
use crate::types::Source;
|
use crate::compare::sexp::unquote;
|
||||||
|
use crate::types::GetStandardProperties;
|
||||||
|
use crate::types::StandardProperties;
|
||||||
|
|
||||||
/// Check if the child string slice is a slice of the parent string slice.
|
/// Check if the child string slice is a slice of the parent string slice.
|
||||||
fn is_slice_of(parent: &str, child: &str) -> bool {
|
fn is_slice_of(parent: &str, child: &str) -> bool {
|
||||||
@@ -10,21 +15,39 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
|
|||||||
child_start >= parent_start && child_end <= parent_end
|
child_start >= parent_start && child_end <= parent_end
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the offset into source that the rust object exists at.
|
/// Get the byte offset into source that the rust object exists at.
|
||||||
///
|
///
|
||||||
/// These offsets are zero-based unlike the elisp ones.
|
/// These offsets are zero-based unlike the elisp ones.
|
||||||
fn get_offsets<'s, S: Source<'s>>(source: &'s str, rust_object: &'s S) -> (usize, usize) {
|
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||||
let rust_object_source = rust_object.get_source();
|
original_document: &'s str,
|
||||||
assert!(is_slice_of(source, rust_object_source));
|
rust_ast_node: &'b S,
|
||||||
let offset = rust_object_source.as_ptr() as usize - source.as_ptr() as usize;
|
) -> (usize, usize) {
|
||||||
|
let rust_object_source = rust_ast_node.get_source();
|
||||||
|
debug_assert!(is_slice_of(original_document, rust_object_source));
|
||||||
|
let offset = rust_object_source.as_ptr() as usize - original_document.as_ptr() as usize;
|
||||||
let end = offset + rust_object_source.len();
|
let end = offset + rust_object_source.len();
|
||||||
(offset, end)
|
(offset, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_name<'s>(
|
pub(crate) fn compare_standard_properties<
|
||||||
emacs: &'s Token<'s>,
|
'b,
|
||||||
name: &str,
|
's,
|
||||||
|
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized,
|
||||||
|
>(
|
||||||
|
original_document: &'s str,
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
rust: &'b S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
|
||||||
|
assert_bounds(original_document, emacs, rust.get_standard_properties())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
name: S,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let name = name.as_ref();
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let first_child = children
|
let first_child = children
|
||||||
.first()
|
.first()
|
||||||
@@ -32,7 +55,7 @@ pub(crate) fn assert_name<'s>(
|
|||||||
.as_atom()?;
|
.as_atom()?;
|
||||||
if first_child != name {
|
if first_child != name {
|
||||||
Err(format!(
|
Err(format!(
|
||||||
"Expected a {expected} cell, but found a {found} cell.",
|
"AST node name mismatch. Expected a (rust) {expected} cell, but found a (emacs) {found} cell.",
|
||||||
expected = name,
|
expected = name,
|
||||||
found = first_child
|
found = first_child
|
||||||
))?;
|
))?;
|
||||||
@@ -40,30 +63,33 @@ pub(crate) fn assert_name<'s>(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn assert_bounds<'s, S: Source<'s>>(
|
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
|
||||||
source: &'s str,
|
///
|
||||||
emacs: &'s Token<'s>,
|
/// This does **not** handle plain text because plain text is a special case.
|
||||||
rust: &'s S,
|
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||||
|
original_document: &'s str,
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
rust: &'b S,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let standard_properties = get_standard_properties(emacs)?;
|
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
||||||
let (begin, end) = (
|
let (begin, end) = (
|
||||||
standard_properties
|
standard_properties
|
||||||
.begin
|
.begin
|
||||||
.ok_or("Token should have a begin.")?,
|
.ok_or("Token should have a begin.")?,
|
||||||
standard_properties.end.ok_or("Token should have an end.")?,
|
standard_properties.end.ok_or("Token should have an end.")?,
|
||||||
);
|
);
|
||||||
let (rust_begin, rust_end) = get_offsets(source, rust);
|
let (rust_begin, rust_end) = get_rust_byte_offsets(original_document, rust); // 0-based
|
||||||
let rust_begin_char_offset = (&source[..rust_begin]).chars().count();
|
let rust_begin_char_offset = (&original_document[..rust_begin]).chars().count() + 1; // 1-based
|
||||||
let rust_end_char_offset =
|
let rust_end_char_offset =
|
||||||
rust_begin_char_offset + (&source[rust_begin..rust_end]).chars().count();
|
rust_begin_char_offset + (&original_document[rust_begin..rust_end]).chars().count(); // 1-based
|
||||||
if (rust_begin_char_offset + 1) != begin || (rust_end_char_offset + 1) != end {
|
if rust_begin_char_offset != begin || rust_end_char_offset != end {
|
||||||
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset + 1, rust_end = rust_end_char_offset + 1, emacs_begin=begin, emacs_end=end))?;
|
Err(format!("Rust bounds (in chars) ({rust_begin}, {rust_end}) do not match emacs bounds ({emacs_begin}, {emacs_end})", rust_begin = rust_begin_char_offset, rust_end = rust_end_char_offset, emacs_begin=begin, emacs_end=end))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StandardProperties {
|
struct EmacsStandardProperties {
|
||||||
begin: Option<usize>,
|
begin: Option<usize>,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
post_affiliated: Option<usize>,
|
post_affiliated: Option<usize>,
|
||||||
@@ -76,9 +102,9 @@ struct StandardProperties {
|
|||||||
post_blank: Option<usize>,
|
post_blank: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_standard_properties<'s>(
|
fn get_emacs_standard_properties<'b, 's>(
|
||||||
emacs: &'s Token<'s>,
|
emacs: &'b Token<'s>,
|
||||||
) -> Result<StandardProperties, Box<dyn std::error::Error>> {
|
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let attributes_child = children
|
let attributes_child = children
|
||||||
.iter()
|
.iter()
|
||||||
@@ -97,7 +123,7 @@ fn get_standard_properties<'s>(
|
|||||||
let contents_end = maybe_token_to_usize(std_props.next())?;
|
let contents_end = maybe_token_to_usize(std_props.next())?;
|
||||||
let end = maybe_token_to_usize(std_props.next())?;
|
let end = maybe_token_to_usize(std_props.next())?;
|
||||||
let post_blank = maybe_token_to_usize(std_props.next())?;
|
let post_blank = maybe_token_to_usize(std_props.next())?;
|
||||||
StandardProperties {
|
EmacsStandardProperties {
|
||||||
begin,
|
begin,
|
||||||
post_affiliated,
|
post_affiliated,
|
||||||
contents_begin,
|
contents_begin,
|
||||||
@@ -116,7 +142,7 @@ fn get_standard_properties<'s>(
|
|||||||
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
|
maybe_token_to_usize(attributes_map.get(":post-blank").map(|token| *token))?;
|
||||||
let post_affiliated =
|
let post_affiliated =
|
||||||
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
|
maybe_token_to_usize(attributes_map.get(":post-affiliated").map(|token| *token))?;
|
||||||
StandardProperties {
|
EmacsStandardProperties {
|
||||||
begin,
|
begin,
|
||||||
post_affiliated,
|
post_affiliated,
|
||||||
contents_begin,
|
contents_begin,
|
||||||
@@ -146,25 +172,83 @@ fn maybe_token_to_usize(
|
|||||||
|
|
||||||
/// Get a named property from the emacs token.
|
/// Get a named property from the emacs token.
|
||||||
///
|
///
|
||||||
/// Returns Ok(None) if value is nil.
|
/// Returns Ok(None) if value is nil or absent.
|
||||||
///
|
pub(crate) fn get_property<'b, 's, 'x>(
|
||||||
/// Returns error if the attribute is not specified on the token at all.
|
emacs: &'b Token<'s>,
|
||||||
pub(crate) fn get_property<'s, 'x>(
|
|
||||||
emacs: &'s Token<'s>,
|
|
||||||
key: &'x str,
|
key: &'x str,
|
||||||
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {
|
) -> Result<Option<&'b Token<'s>>, Box<dyn std::error::Error>> {
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let attributes_child = children
|
let attributes_child = children
|
||||||
.iter()
|
.iter()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.ok_or("Should have an attributes child.")?;
|
.ok_or("Should have an attributes child.")?;
|
||||||
let attributes_map = attributes_child.as_map()?;
|
let attributes_map = attributes_child.as_map()?;
|
||||||
let prop = attributes_map
|
let prop = attributes_map.get(key).map(|token| *token);
|
||||||
.get(key)
|
match prop.map(|token| token.as_atom()) {
|
||||||
.ok_or(format!("Missing {} attribute.", key))?;
|
Some(Ok("nil")) => return Ok(None),
|
||||||
match prop.as_atom() {
|
|
||||||
Ok("nil") => return Ok(None),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
Ok(Some(*prop))
|
Ok(prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a named property containing an unquoted atom from the emacs token.
|
||||||
|
///
|
||||||
|
/// Returns None if key is not found.
|
||||||
|
pub(crate) fn get_property_unquoted_atom<'b, 's, 'x>(
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
key: &'x str,
|
||||||
|
) -> Result<Option<&'s str>, Box<dyn std::error::Error>> {
|
||||||
|
Ok(get_property(emacs, key)?
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a named property containing an quoted string from the emacs token.
|
||||||
|
///
|
||||||
|
/// Returns None if key is not found.
|
||||||
|
pub(crate) fn get_property_quoted_string<'b, 's, 'x>(
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
key: &'x str,
|
||||||
|
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||||
|
Ok(get_property(emacs, key)?
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.map(unquote)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a named property containing a boolean value.
|
||||||
|
///
|
||||||
|
/// This uses the elisp convention of nil == false, non-nil == true.
|
||||||
|
///
|
||||||
|
/// Returns false if key is not found.
|
||||||
|
pub(crate) fn get_property_boolean<'b, 's, 'x>(
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
key: &'x str,
|
||||||
|
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||||
|
Ok(get_property(emacs, key)?
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.unwrap_or("nil")
|
||||||
|
!= "nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a named property containing an unquoted numeric value.
|
||||||
|
///
|
||||||
|
/// Returns None if key is not found.
|
||||||
|
pub(crate) fn get_property_numeric<'b, 's, 'x, N: FromStr>(
|
||||||
|
emacs: &'b Token<'s>,
|
||||||
|
key: &'x str,
|
||||||
|
) -> Result<Option<N>, Box<dyn std::error::Error + 's>>
|
||||||
|
where
|
||||||
|
<N as FromStr>::Err: std::error::Error,
|
||||||
|
<N as FromStr>::Err: 's,
|
||||||
|
{
|
||||||
|
let unparsed_string = get_property(emacs, key)?
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?;
|
||||||
|
let parsed_number = unparsed_string
|
||||||
|
.map(|val| val.parse::<N>())
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?;
|
||||||
|
Ok(parsed_number)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::collections::BTreeSet;
|
|||||||
|
|
||||||
use super::FileAccessInterface;
|
use super::FileAccessInterface;
|
||||||
use super::LocalFileAccessInterface;
|
use super::LocalFileAccessInterface;
|
||||||
|
use crate::types::IndentationLevel;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
|
|
||||||
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
|
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
|
||||||
@@ -12,8 +13,29 @@ pub struct GlobalSettings<'g, 's> {
|
|||||||
pub file_access: &'g dyn FileAccessInterface,
|
pub file_access: &'g dyn FileAccessInterface,
|
||||||
pub in_progress_todo_keywords: BTreeSet<String>,
|
pub in_progress_todo_keywords: BTreeSet<String>,
|
||||||
pub complete_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 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,
|
||||||
|
|
||||||
|
/// If a headline title matches this string exactly, then that section will become a "footnote section".
|
||||||
|
///
|
||||||
|
/// Corresponds to org-footnote-section elisp variable.
|
||||||
|
pub footnote_section: &'g str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
|
||||||
|
|
||||||
impl<'g, 's> GlobalSettings<'g, 's> {
|
impl<'g, 's> GlobalSettings<'g, 's> {
|
||||||
fn new() -> GlobalSettings<'g, 's> {
|
fn new() -> GlobalSettings<'g, 's> {
|
||||||
GlobalSettings {
|
GlobalSettings {
|
||||||
@@ -23,6 +45,10 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
|||||||
},
|
},
|
||||||
in_progress_todo_keywords: BTreeSet::new(),
|
in_progress_todo_keywords: BTreeSet::new(),
|
||||||
complete_todo_keywords: BTreeSet::new(),
|
complete_todo_keywords: BTreeSet::new(),
|
||||||
|
list_allow_alphabetical: false,
|
||||||
|
tab_width: DEFAULT_TAB_WIDTH,
|
||||||
|
odd_levels_only: HeadlineLevelFilter::default(),
|
||||||
|
footnote_section: "Footnotes",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,3 +58,15 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
|
|||||||
GlobalSettings::new()
|
GlobalSettings::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum HeadlineLevelFilter {
|
||||||
|
Odd,
|
||||||
|
OddEven,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HeadlineLevelFilter {
|
||||||
|
fn default() -> Self {
|
||||||
|
HeadlineLevelFilter::OddEven
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ pub(crate) struct List<'parent, T> {
|
|||||||
parent: Link<'parent, T>,
|
parent: Link<'parent, T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Should I be defining a lifetime for T in the generics here? Ref: https://quinedot.github.io/rust-learning/dyn-elision-advanced.html#iteraction-with-type-aliases
|
||||||
type Link<'parent, T> = Option<&'parent List<'parent, T>>;
|
type Link<'parent, T> = Option<&'parent List<'parent, T>>;
|
||||||
|
|
||||||
impl<'parent, T> List<'parent, T> {
|
impl<'parent, T> List<'parent, T> {
|
||||||
|
|||||||
@@ -25,5 +25,7 @@ pub(crate) use exiting::ExitClass;
|
|||||||
pub use file_access_interface::FileAccessInterface;
|
pub use file_access_interface::FileAccessInterface;
|
||||||
pub use file_access_interface::LocalFileAccessInterface;
|
pub use file_access_interface::LocalFileAccessInterface;
|
||||||
pub use global_settings::GlobalSettings;
|
pub use global_settings::GlobalSettings;
|
||||||
|
pub use global_settings::HeadlineLevelFilter;
|
||||||
|
pub use global_settings::DEFAULT_TAB_WIDTH;
|
||||||
pub(crate) use list::List;
|
pub(crate) use list::List;
|
||||||
pub(crate) use parser_with_context::parser_with_context;
|
pub(crate) use parser_with_context::parser_with_context;
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ use nom::IResult;
|
|||||||
|
|
||||||
pub(crate) 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)]
|
#[derive(Debug)]
|
||||||
pub enum CustomError<I> {
|
pub enum CustomError<I> {
|
||||||
MyError(MyError<I>),
|
MyError(MyError<&'static str>),
|
||||||
Nom(I, ErrorKind),
|
Nom(I, ErrorKind),
|
||||||
IO(std::io::Error),
|
IO(std::io::Error),
|
||||||
|
BoxedError(Box<dyn std::error::Error>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -31,3 +31,15 @@ impl<I> From<std::io::Error> for CustomError<I> {
|
|||||||
CustomError::IO(value)
|
CustomError::IO(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<I> From<&'static str> for CustomError<I> {
|
||||||
|
fn from(value: &'static str) -> Self {
|
||||||
|
CustomError::MyError(MyError(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
|
||||||
|
fn from(value: Box<dyn std::error::Error>) -> Self {
|
||||||
|
CustomError::BoxedError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
104
src/iter/all_ast_node_iter.rs
Normal file
104
src/iter/all_ast_node_iter.rs
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use super::ast_node_iter::AstNodeIter;
|
||||||
|
use crate::types::AstNode;
|
||||||
|
|
||||||
|
pub struct AllAstNodeIter<'r, 's> {
|
||||||
|
root: Option<AstNode<'r, 's>>,
|
||||||
|
queue: VecDeque<AstNodeIter<'r, 's>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some(root) = self.root.take() {
|
||||||
|
self.queue.push_back(AstNodeIter::from_ast_node(&root));
|
||||||
|
return Some(root);
|
||||||
|
}
|
||||||
|
while let Some(child) = self.queue.front_mut() {
|
||||||
|
let next_elem_this_iter = match child {
|
||||||
|
AstNodeIter::Document(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Heading(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Section(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Paragraph(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::PlainList(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::PlainListItem(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::CenterBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::QuoteBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::SpecialBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::DynamicBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::FootnoteDefinition(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Comment(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Drawer(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::PropertyDrawer(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::NodeProperty(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Table(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::TableRow(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::VerseBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::CommentBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::ExampleBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::ExportBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::SrcBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Clock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::DiarySexp(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Planning(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::FixedWidthArea(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::HorizontalRule(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Keyword(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::BabelCall(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::LatexEnvironment(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Bold(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Italic(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Underline(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::StrikeThrough(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Code(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Verbatim(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::PlainText(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::RegularLink(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::RadioLink(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::RadioTarget(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::PlainLink(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::AngleLink(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::OrgMacro(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Entity(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::LatexFragment(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::ExportSnippet(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::FootnoteReference(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Citation(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::CitationReference(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::InlineBabelCall(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::InlineSourceBlock(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::LineBreak(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Target(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::StatisticsCookie(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Subscript(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Superscript(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::TableCell(ref mut i) => i.next(),
|
||||||
|
AstNodeIter::Timestamp(ref mut i) => i.next(),
|
||||||
|
};
|
||||||
|
if let Some(next_elem_this_iter) = next_elem_this_iter {
|
||||||
|
self.queue
|
||||||
|
.push_back(AstNodeIter::from_ast_node(&next_elem_this_iter));
|
||||||
|
return Some(next_elem_this_iter);
|
||||||
|
} else {
|
||||||
|
self.queue.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> IntoIterator for AstNode<'r, 's> {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
type IntoIter = AllAstNodeIter<'r, 's>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
AllAstNodeIter {
|
||||||
|
root: Some(self),
|
||||||
|
queue: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
348
src/iter/ast_node_iter.rs
Normal file
348
src/iter/ast_node_iter.rs
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use super::macros::children_iter;
|
||||||
|
use super::macros::empty_iter;
|
||||||
|
use super::macros::multi_field_iter;
|
||||||
|
use crate::types::AngleLink;
|
||||||
|
use crate::types::AstNode;
|
||||||
|
use crate::types::BabelCall;
|
||||||
|
use crate::types::Bold;
|
||||||
|
use crate::types::CenterBlock;
|
||||||
|
use crate::types::Citation;
|
||||||
|
use crate::types::CitationReference;
|
||||||
|
use crate::types::Clock;
|
||||||
|
use crate::types::Code;
|
||||||
|
use crate::types::Comment;
|
||||||
|
use crate::types::CommentBlock;
|
||||||
|
use crate::types::DiarySexp;
|
||||||
|
use crate::types::Document;
|
||||||
|
use crate::types::DocumentElement;
|
||||||
|
use crate::types::Drawer;
|
||||||
|
use crate::types::DynamicBlock;
|
||||||
|
use crate::types::Element;
|
||||||
|
use crate::types::Entity;
|
||||||
|
use crate::types::ExampleBlock;
|
||||||
|
use crate::types::ExportBlock;
|
||||||
|
use crate::types::ExportSnippet;
|
||||||
|
use crate::types::FixedWidthArea;
|
||||||
|
use crate::types::FootnoteDefinition;
|
||||||
|
use crate::types::FootnoteReference;
|
||||||
|
use crate::types::Heading;
|
||||||
|
use crate::types::HorizontalRule;
|
||||||
|
use crate::types::InlineBabelCall;
|
||||||
|
use crate::types::InlineSourceBlock;
|
||||||
|
use crate::types::Italic;
|
||||||
|
use crate::types::Keyword;
|
||||||
|
use crate::types::LatexEnvironment;
|
||||||
|
use crate::types::LatexFragment;
|
||||||
|
use crate::types::LineBreak;
|
||||||
|
use crate::types::NodeProperty;
|
||||||
|
use crate::types::Object;
|
||||||
|
use crate::types::OrgMacro;
|
||||||
|
use crate::types::Paragraph;
|
||||||
|
use crate::types::PlainLink;
|
||||||
|
use crate::types::PlainList;
|
||||||
|
use crate::types::PlainListItem;
|
||||||
|
use crate::types::PlainText;
|
||||||
|
use crate::types::Planning;
|
||||||
|
use crate::types::PropertyDrawer;
|
||||||
|
use crate::types::QuoteBlock;
|
||||||
|
use crate::types::RadioLink;
|
||||||
|
use crate::types::RadioTarget;
|
||||||
|
use crate::types::RegularLink;
|
||||||
|
use crate::types::Section;
|
||||||
|
use crate::types::SpecialBlock;
|
||||||
|
use crate::types::SrcBlock;
|
||||||
|
use crate::types::StatisticsCookie;
|
||||||
|
use crate::types::StrikeThrough;
|
||||||
|
use crate::types::Subscript;
|
||||||
|
use crate::types::Superscript;
|
||||||
|
use crate::types::Table;
|
||||||
|
use crate::types::TableCell;
|
||||||
|
use crate::types::TableRow;
|
||||||
|
use crate::types::Target;
|
||||||
|
use crate::types::Timestamp;
|
||||||
|
use crate::types::Underline;
|
||||||
|
use crate::types::Verbatim;
|
||||||
|
use crate::types::VerseBlock;
|
||||||
|
|
||||||
|
/// Iterator over the AST nodes contained within the starting node.
|
||||||
|
///
|
||||||
|
/// This only iterates over the children, not the starting node itself. So an AstNodeIter::PlainList would only return PlainListItems, not the PlainList.
|
||||||
|
///
|
||||||
|
/// This only iterates over AST nodes, so an AstNodeIter::Heading would iterate over both the title and section contents, but it would not iterate over simple strings like the TODO keyword or priority.
|
||||||
|
pub(crate) enum AstNodeIter<'r, 's> {
|
||||||
|
// Document Nodes
|
||||||
|
Document(DocumentIter<'r, 's>),
|
||||||
|
Heading(HeadingIter<'r, 's>),
|
||||||
|
Section(SectionIter<'r, 's>),
|
||||||
|
// Elements
|
||||||
|
Paragraph(ParagraphIter<'r, 's>),
|
||||||
|
PlainList(PlainListIter<'r, 's>),
|
||||||
|
PlainListItem(PlainListItemIter<'r, 's>),
|
||||||
|
CenterBlock(CenterBlockIter<'r, 's>),
|
||||||
|
QuoteBlock(QuoteBlockIter<'r, 's>),
|
||||||
|
SpecialBlock(SpecialBlockIter<'r, 's>),
|
||||||
|
DynamicBlock(DynamicBlockIter<'r, 's>),
|
||||||
|
FootnoteDefinition(FootnoteDefinitionIter<'r, 's>),
|
||||||
|
Comment(CommentIter<'r, 's>),
|
||||||
|
Drawer(DrawerIter<'r, 's>),
|
||||||
|
PropertyDrawer(PropertyDrawerIter<'r, 's>),
|
||||||
|
NodeProperty(NodePropertyIter<'r, 's>),
|
||||||
|
Table(TableIter<'r, 's>),
|
||||||
|
TableRow(TableRowIter<'r, 's>),
|
||||||
|
VerseBlock(VerseBlockIter<'r, 's>),
|
||||||
|
CommentBlock(CommentBlockIter<'r, 's>),
|
||||||
|
ExampleBlock(ExampleBlockIter<'r, 's>),
|
||||||
|
ExportBlock(ExportBlockIter<'r, 's>),
|
||||||
|
SrcBlock(SrcBlockIter<'r, 's>),
|
||||||
|
Clock(ClockIter<'r, 's>),
|
||||||
|
DiarySexp(DiarySexpIter<'r, 's>),
|
||||||
|
Planning(PlanningIter<'r, 's>),
|
||||||
|
FixedWidthArea(FixedWidthAreaIter<'r, 's>),
|
||||||
|
HorizontalRule(HorizontalRuleIter<'r, 's>),
|
||||||
|
Keyword(KeywordIter<'r, 's>),
|
||||||
|
BabelCall(BabelCallIter<'r, 's>),
|
||||||
|
LatexEnvironment(LatexEnvironmentIter<'r, 's>),
|
||||||
|
// Objects
|
||||||
|
Bold(BoldIter<'r, 's>),
|
||||||
|
Italic(ItalicIter<'r, 's>),
|
||||||
|
Underline(UnderlineIter<'r, 's>),
|
||||||
|
StrikeThrough(StrikeThroughIter<'r, 's>),
|
||||||
|
Code(CodeIter<'r, 's>),
|
||||||
|
Verbatim(VerbatimIter<'r, 's>),
|
||||||
|
PlainText(PlainTextIter<'r, 's>),
|
||||||
|
RegularLink(RegularLinkIter<'r, 's>),
|
||||||
|
RadioLink(RadioLinkIter<'r, 's>),
|
||||||
|
RadioTarget(RadioTargetIter<'r, 's>),
|
||||||
|
PlainLink(PlainLinkIter<'r, 's>),
|
||||||
|
AngleLink(AngleLinkIter<'r, 's>),
|
||||||
|
OrgMacro(OrgMacroIter<'r, 's>),
|
||||||
|
Entity(EntityIter<'r, 's>),
|
||||||
|
LatexFragment(LatexFragmentIter<'r, 's>),
|
||||||
|
ExportSnippet(ExportSnippetIter<'r, 's>),
|
||||||
|
FootnoteReference(FootnoteReferenceIter<'r, 's>),
|
||||||
|
Citation(CitationIter<'r, 's>),
|
||||||
|
CitationReference(CitationReferenceIter<'r, 's>),
|
||||||
|
InlineBabelCall(InlineBabelCallIter<'r, 's>),
|
||||||
|
InlineSourceBlock(InlineSourceBlockIter<'r, 's>),
|
||||||
|
LineBreak(LineBreakIter<'r, 's>),
|
||||||
|
Target(TargetIter<'r, 's>),
|
||||||
|
StatisticsCookie(StatisticsCookieIter<'r, 's>),
|
||||||
|
Subscript(SubscriptIter<'r, 's>),
|
||||||
|
Superscript(SuperscriptIter<'r, 's>),
|
||||||
|
TableCell(TableCellIter<'r, 's>),
|
||||||
|
Timestamp(TimestampIter<'r, 's>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> AstNodeIter<'r, 's> {
|
||||||
|
pub(crate) fn from_ast_node(node: &AstNode<'r, 's>) -> AstNodeIter<'r, 's> {
|
||||||
|
match node {
|
||||||
|
AstNode::Document(inner) => AstNodeIter::Document(inner.into_iter()),
|
||||||
|
AstNode::Heading(inner) => AstNodeIter::Heading(inner.into_iter()),
|
||||||
|
AstNode::Section(inner) => AstNodeIter::Section(inner.into_iter()),
|
||||||
|
AstNode::Paragraph(inner) => AstNodeIter::Paragraph(inner.into_iter()),
|
||||||
|
AstNode::PlainList(inner) => AstNodeIter::PlainList(inner.into_iter()),
|
||||||
|
AstNode::PlainListItem(inner) => AstNodeIter::PlainListItem(inner.into_iter()),
|
||||||
|
AstNode::CenterBlock(inner) => AstNodeIter::CenterBlock(inner.into_iter()),
|
||||||
|
AstNode::QuoteBlock(inner) => AstNodeIter::QuoteBlock(inner.into_iter()),
|
||||||
|
AstNode::SpecialBlock(inner) => AstNodeIter::SpecialBlock(inner.into_iter()),
|
||||||
|
AstNode::DynamicBlock(inner) => AstNodeIter::DynamicBlock(inner.into_iter()),
|
||||||
|
AstNode::FootnoteDefinition(inner) => {
|
||||||
|
AstNodeIter::FootnoteDefinition(inner.into_iter())
|
||||||
|
}
|
||||||
|
AstNode::Comment(inner) => AstNodeIter::Comment(inner.into_iter()),
|
||||||
|
AstNode::Drawer(inner) => AstNodeIter::Drawer(inner.into_iter()),
|
||||||
|
AstNode::PropertyDrawer(inner) => AstNodeIter::PropertyDrawer(inner.into_iter()),
|
||||||
|
AstNode::NodeProperty(inner) => AstNodeIter::NodeProperty(inner.into_iter()),
|
||||||
|
AstNode::Table(inner) => AstNodeIter::Table(inner.into_iter()),
|
||||||
|
AstNode::TableRow(inner) => AstNodeIter::TableRow(inner.into_iter()),
|
||||||
|
AstNode::VerseBlock(inner) => AstNodeIter::VerseBlock(inner.into_iter()),
|
||||||
|
AstNode::CommentBlock(inner) => AstNodeIter::CommentBlock(inner.into_iter()),
|
||||||
|
AstNode::ExampleBlock(inner) => AstNodeIter::ExampleBlock(inner.into_iter()),
|
||||||
|
AstNode::ExportBlock(inner) => AstNodeIter::ExportBlock(inner.into_iter()),
|
||||||
|
AstNode::SrcBlock(inner) => AstNodeIter::SrcBlock(inner.into_iter()),
|
||||||
|
AstNode::Clock(inner) => AstNodeIter::Clock(inner.into_iter()),
|
||||||
|
AstNode::DiarySexp(inner) => AstNodeIter::DiarySexp(inner.into_iter()),
|
||||||
|
AstNode::Planning(inner) => AstNodeIter::Planning(inner.into_iter()),
|
||||||
|
AstNode::FixedWidthArea(inner) => AstNodeIter::FixedWidthArea(inner.into_iter()),
|
||||||
|
AstNode::HorizontalRule(inner) => AstNodeIter::HorizontalRule(inner.into_iter()),
|
||||||
|
AstNode::Keyword(inner) => AstNodeIter::Keyword(inner.into_iter()),
|
||||||
|
AstNode::BabelCall(inner) => AstNodeIter::BabelCall(inner.into_iter()),
|
||||||
|
AstNode::LatexEnvironment(inner) => AstNodeIter::LatexEnvironment(inner.into_iter()),
|
||||||
|
AstNode::Bold(inner) => AstNodeIter::Bold(inner.into_iter()),
|
||||||
|
AstNode::Italic(inner) => AstNodeIter::Italic(inner.into_iter()),
|
||||||
|
AstNode::Underline(inner) => AstNodeIter::Underline(inner.into_iter()),
|
||||||
|
AstNode::StrikeThrough(inner) => AstNodeIter::StrikeThrough(inner.into_iter()),
|
||||||
|
AstNode::Code(inner) => AstNodeIter::Code(inner.into_iter()),
|
||||||
|
AstNode::Verbatim(inner) => AstNodeIter::Verbatim(inner.into_iter()),
|
||||||
|
AstNode::PlainText(inner) => AstNodeIter::PlainText(inner.into_iter()),
|
||||||
|
AstNode::RegularLink(inner) => AstNodeIter::RegularLink(inner.into_iter()),
|
||||||
|
AstNode::RadioLink(inner) => AstNodeIter::RadioLink(inner.into_iter()),
|
||||||
|
AstNode::RadioTarget(inner) => AstNodeIter::RadioTarget(inner.into_iter()),
|
||||||
|
AstNode::PlainLink(inner) => AstNodeIter::PlainLink(inner.into_iter()),
|
||||||
|
AstNode::AngleLink(inner) => AstNodeIter::AngleLink(inner.into_iter()),
|
||||||
|
AstNode::OrgMacro(inner) => AstNodeIter::OrgMacro(inner.into_iter()),
|
||||||
|
AstNode::Entity(inner) => AstNodeIter::Entity(inner.into_iter()),
|
||||||
|
AstNode::LatexFragment(inner) => AstNodeIter::LatexFragment(inner.into_iter()),
|
||||||
|
AstNode::ExportSnippet(inner) => AstNodeIter::ExportSnippet(inner.into_iter()),
|
||||||
|
AstNode::FootnoteReference(inner) => AstNodeIter::FootnoteReference(inner.into_iter()),
|
||||||
|
AstNode::Citation(inner) => AstNodeIter::Citation(inner.into_iter()),
|
||||||
|
AstNode::CitationReference(inner) => AstNodeIter::CitationReference(inner.into_iter()),
|
||||||
|
AstNode::InlineBabelCall(inner) => AstNodeIter::InlineBabelCall(inner.into_iter()),
|
||||||
|
AstNode::InlineSourceBlock(inner) => AstNodeIter::InlineSourceBlock(inner.into_iter()),
|
||||||
|
AstNode::LineBreak(inner) => AstNodeIter::LineBreak(inner.into_iter()),
|
||||||
|
AstNode::Target(inner) => AstNodeIter::Target(inner.into_iter()),
|
||||||
|
AstNode::StatisticsCookie(inner) => AstNodeIter::StatisticsCookie(inner.into_iter()),
|
||||||
|
AstNode::Subscript(inner) => AstNodeIter::Subscript(inner.into_iter()),
|
||||||
|
AstNode::Superscript(inner) => AstNodeIter::Superscript(inner.into_iter()),
|
||||||
|
AstNode::TableCell(inner) => AstNodeIter::TableCell(inner.into_iter()),
|
||||||
|
AstNode::Timestamp(inner) => AstNodeIter::Timestamp(inner.into_iter()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multi_field_iter!(
|
||||||
|
Document<'s>,
|
||||||
|
DocumentIter,
|
||||||
|
zeroth_section,
|
||||||
|
std::option::Iter<'r, Section<'s>>,
|
||||||
|
children,
|
||||||
|
std::slice::Iter<'r, Heading<'s>>
|
||||||
|
);
|
||||||
|
multi_field_iter!(
|
||||||
|
Heading<'s>,
|
||||||
|
HeadingIter,
|
||||||
|
title,
|
||||||
|
std::slice::Iter<'r, Object<'s>>,
|
||||||
|
children,
|
||||||
|
std::slice::Iter<'r, DocumentElement<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(Section<'s>, SectionIter, std::slice::Iter<'r, Element<'s>>);
|
||||||
|
children_iter!(
|
||||||
|
Paragraph<'s>,
|
||||||
|
ParagraphIter,
|
||||||
|
std::slice::Iter<'r, Object<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
PlainList<'s>,
|
||||||
|
PlainListIter,
|
||||||
|
std::slice::Iter<'r, PlainListItem<'s>>
|
||||||
|
);
|
||||||
|
multi_field_iter!(
|
||||||
|
PlainListItem<'s>,
|
||||||
|
PlainListItemIter,
|
||||||
|
tag,
|
||||||
|
std::slice::Iter<'r, Object<'s>>,
|
||||||
|
children,
|
||||||
|
std::slice::Iter<'r, Element<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
CenterBlock<'s>,
|
||||||
|
CenterBlockIter,
|
||||||
|
std::slice::Iter<'r, Element<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
QuoteBlock<'s>,
|
||||||
|
QuoteBlockIter,
|
||||||
|
std::slice::Iter<'r, Element<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
SpecialBlock<'s>,
|
||||||
|
SpecialBlockIter,
|
||||||
|
std::slice::Iter<'r, Element<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
DynamicBlock<'s>,
|
||||||
|
DynamicBlockIter,
|
||||||
|
std::slice::Iter<'r, Element<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
FootnoteDefinition<'s>,
|
||||||
|
FootnoteDefinitionIter,
|
||||||
|
std::slice::Iter<'r, Element<'s>>
|
||||||
|
);
|
||||||
|
empty_iter!(Comment<'s>, CommentIter);
|
||||||
|
children_iter!(Drawer<'s>, DrawerIter, std::slice::Iter<'r, Element<'s>>);
|
||||||
|
children_iter!(
|
||||||
|
PropertyDrawer<'s>,
|
||||||
|
PropertyDrawerIter,
|
||||||
|
std::slice::Iter<'r, NodeProperty<'s>>
|
||||||
|
);
|
||||||
|
empty_iter!(NodeProperty<'s>, NodePropertyIter);
|
||||||
|
children_iter!(Table<'s>, TableIter, std::slice::Iter<'r, TableRow<'s>>);
|
||||||
|
children_iter!(
|
||||||
|
TableRow<'s>,
|
||||||
|
TableRowIter,
|
||||||
|
std::slice::Iter<'r, TableCell<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
VerseBlock<'s>,
|
||||||
|
VerseBlockIter,
|
||||||
|
std::slice::Iter<'r, Object<'s>>
|
||||||
|
);
|
||||||
|
empty_iter!(CommentBlock<'s>, CommentBlockIter);
|
||||||
|
empty_iter!(ExampleBlock<'s>, ExampleBlockIter);
|
||||||
|
empty_iter!(ExportBlock<'s>, ExportBlockIter);
|
||||||
|
empty_iter!(SrcBlock<'s>, SrcBlockIter);
|
||||||
|
empty_iter!(Clock<'s>, ClockIter);
|
||||||
|
empty_iter!(DiarySexp<'s>, DiarySexpIter);
|
||||||
|
empty_iter!(Planning<'s>, PlanningIter);
|
||||||
|
empty_iter!(FixedWidthArea<'s>, FixedWidthAreaIter);
|
||||||
|
empty_iter!(HorizontalRule<'s>, HorizontalRuleIter);
|
||||||
|
empty_iter!(Keyword<'s>, KeywordIter);
|
||||||
|
empty_iter!(BabelCall<'s>, BabelCallIter);
|
||||||
|
empty_iter!(LatexEnvironment<'s>, LatexEnvironmentIter);
|
||||||
|
children_iter!(Bold<'s>, BoldIter, std::slice::Iter<'r, Object<'s>>);
|
||||||
|
children_iter!(Italic<'s>, ItalicIter, std::slice::Iter<'r, Object<'s>>);
|
||||||
|
children_iter!(
|
||||||
|
Underline<'s>,
|
||||||
|
UnderlineIter,
|
||||||
|
std::slice::Iter<'r, Object<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
StrikeThrough<'s>,
|
||||||
|
StrikeThroughIter,
|
||||||
|
std::slice::Iter<'r, Object<'s>>
|
||||||
|
);
|
||||||
|
empty_iter!(Code<'s>, CodeIter);
|
||||||
|
empty_iter!(Verbatim<'s>, VerbatimIter);
|
||||||
|
empty_iter!(PlainText<'s>, PlainTextIter);
|
||||||
|
empty_iter!(RegularLink<'s>, RegularLinkIter);
|
||||||
|
children_iter!(
|
||||||
|
RadioLink<'s>,
|
||||||
|
RadioLinkIter,
|
||||||
|
std::slice::Iter<'r, Object<'s>>
|
||||||
|
);
|
||||||
|
children_iter!(
|
||||||
|
RadioTarget<'s>,
|
||||||
|
RadioTargetIter,
|
||||||
|
std::slice::Iter<'r, Object<'s>>
|
||||||
|
);
|
||||||
|
empty_iter!(PlainLink<'s>, PlainLinkIter);
|
||||||
|
empty_iter!(AngleLink<'s>, AngleLinkIter);
|
||||||
|
empty_iter!(OrgMacro<'s>, OrgMacroIter);
|
||||||
|
empty_iter!(Entity<'s>, EntityIter);
|
||||||
|
empty_iter!(LatexFragment<'s>, LatexFragmentIter);
|
||||||
|
empty_iter!(ExportSnippet<'s>, ExportSnippetIter);
|
||||||
|
multi_field_iter!(
|
||||||
|
FootnoteReference<'s>,
|
||||||
|
FootnoteReferenceIter,
|
||||||
|
definition,
|
||||||
|
std::slice::Iter<'r, Object<'s>>,
|
||||||
|
);
|
||||||
|
empty_iter!(Citation<'s>, CitationIter);
|
||||||
|
empty_iter!(CitationReference<'s>, CitationReferenceIter);
|
||||||
|
empty_iter!(InlineBabelCall<'s>, InlineBabelCallIter);
|
||||||
|
empty_iter!(InlineSourceBlock<'s>, InlineSourceBlockIter);
|
||||||
|
empty_iter!(LineBreak<'s>, LineBreakIter);
|
||||||
|
empty_iter!(Target<'s>, TargetIter);
|
||||||
|
empty_iter!(StatisticsCookie<'s>, StatisticsCookieIter);
|
||||||
|
empty_iter!(Subscript<'s>, SubscriptIter);
|
||||||
|
empty_iter!(Superscript<'s>, SuperscriptIter);
|
||||||
|
children_iter!(
|
||||||
|
TableCell<'s>,
|
||||||
|
TableCellIter,
|
||||||
|
std::slice::Iter<'r, Object<'s>>
|
||||||
|
);
|
||||||
|
empty_iter!(Timestamp<'s>, TimestampIter);
|
||||||
101
src/iter/macros.rs
Normal file
101
src/iter/macros.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/// Create iterators for ast nodes where it only has to iterate over children
|
||||||
|
macro_rules! children_iter {
|
||||||
|
($astnodetype:ty, $itertype:ident, $innertype:ty) => {
|
||||||
|
pub struct $itertype<'r, 's> {
|
||||||
|
next: $innertype,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> Iterator for $itertype<'r, 's> {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.next.next().map(Into::<AstNode>::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> IntoIterator for &'r $astnodetype {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
type IntoIter = $itertype<'r, 's>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
$itertype {
|
||||||
|
next: self.children.iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use children_iter;
|
||||||
|
|
||||||
|
/// Create iterators for ast nodes that do not contain any ast node children.
|
||||||
|
macro_rules! empty_iter {
|
||||||
|
($astnodetype:ty, $itertype:ident) => {
|
||||||
|
pub struct $itertype<'r, 's> {
|
||||||
|
phantom: PhantomData<&'r $astnodetype>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> Iterator for $itertype<'r, 's> {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> IntoIterator for &'r $astnodetype {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
type IntoIter = $itertype<'r, 's>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
$itertype {
|
||||||
|
phantom: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use empty_iter;
|
||||||
|
|
||||||
|
/// Create iterators for ast nodes where it has to iterate over multiple child lists.
|
||||||
|
macro_rules! multi_field_iter {
|
||||||
|
($astnodetype:ty, $itertype:ident, $firstfieldname: ident, $firstinnertype:ty, $($fieldname: ident, $innertype:ty),*) => {
|
||||||
|
pub struct $itertype<'r, 's> {
|
||||||
|
$firstfieldname: $firstinnertype,
|
||||||
|
$(
|
||||||
|
$fieldname: $innertype,
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> Iterator for $itertype<'r, 's> {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.$firstfieldname.next().map(Into::<AstNode>::into)
|
||||||
|
$(
|
||||||
|
.or_else(|| self.$fieldname.next().map(Into::<AstNode>::into))
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> IntoIterator for &'r $astnodetype {
|
||||||
|
type Item = AstNode<'r, 's>;
|
||||||
|
|
||||||
|
type IntoIter = $itertype<'r, 's>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
$itertype {
|
||||||
|
$firstfieldname: self.$firstfieldname.iter(),
|
||||||
|
$(
|
||||||
|
$fieldname: self.$fieldname.iter(),
|
||||||
|
),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use multi_field_iter;
|
||||||
3
src/iter/mod.rs
Normal file
3
src/iter/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod all_ast_node_iter;
|
||||||
|
mod ast_node_iter;
|
||||||
|
mod macros;
|
||||||
13
src/lib.rs
13
src/lib.rs
@@ -1,6 +1,6 @@
|
|||||||
#![feature(round_char_boundary)]
|
|
||||||
#![feature(exit_status_error)]
|
#![feature(exit_status_error)]
|
||||||
#![feature(trait_alias)]
|
#![feature(trait_alias)]
|
||||||
|
#![feature(path_file_prefix)]
|
||||||
// TODO: #![warn(missing_docs)]
|
// TODO: #![warn(missing_docs)]
|
||||||
|
|
||||||
#[cfg(feature = "compare")]
|
#[cfg(feature = "compare")]
|
||||||
@@ -8,9 +8,14 @@ pub mod compare;
|
|||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod iter;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
pub use context::FileAccessInterface;
|
pub mod settings {
|
||||||
pub use context::GlobalSettings;
|
pub use crate::context::FileAccessInterface;
|
||||||
pub use context::LocalFileAccessInterface;
|
pub use crate::context::GlobalSettings;
|
||||||
|
pub use crate::context::HeadlineLevelFilter;
|
||||||
|
pub use crate::context::LocalFileAccessInterface;
|
||||||
|
pub use crate::context::DEFAULT_TAB_WIDTH;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use std::path::Path;
|
|||||||
|
|
||||||
use ::organic::parser::parse;
|
use ::organic::parser::parse;
|
||||||
use organic::parser::parse_with_settings;
|
use organic::parser::parse_with_settings;
|
||||||
use organic::GlobalSettings;
|
use organic::settings::GlobalSettings;
|
||||||
use organic::LocalFileAccessInterface;
|
use organic::settings::LocalFileAccessInterface;
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
use crate::init_tracing::init_telemetry;
|
use crate::init_tracing::init_telemetry;
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ mod tests {
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::Source;
|
use crate::types::GetStandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn citation_simple() {
|
fn citation_simple() {
|
||||||
@@ -202,7 +202,10 @@ mod tests {
|
|||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "[cite:@foo]");
|
assert_eq!(
|
||||||
|
first_paragraph.get_standard_properties().get_source(),
|
||||||
|
"[cite:@foo]"
|
||||||
|
);
|
||||||
assert_eq!(first_paragraph.children.len(), 1);
|
assert_eq!(first_paragraph.children.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_paragraph
|
first_paragraph
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::is_not;
|
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
|
use nom::multi::many_till;
|
||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
|
use super::util::org_line_ending;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
@@ -38,15 +39,29 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
|
|||||||
let parser_context = context.with_additional_node(&parser_context);
|
let parser_context = context.with_additional_node(&parser_context);
|
||||||
let comment_line_matcher = parser_with_context!(comment_line)(&parser_context);
|
let comment_line_matcher = parser_with_context!(comment_line)(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
let (remaining, _first_line) = comment_line_matcher(input)?;
|
let (remaining, first_line) = comment_line_matcher(input)?;
|
||||||
let (remaining, _remaining_lines) =
|
let (remaining, mut remaining_lines) =
|
||||||
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
|
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
|
||||||
|
let last_line = remaining_lines.pop();
|
||||||
|
if let Some(last_line) = last_line {
|
||||||
|
value.push(Into::<&str>::into(first_line));
|
||||||
|
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
|
||||||
|
let last_line = Into::<&str>::into(last_line);
|
||||||
|
// Trim the line ending from the final line.
|
||||||
|
value.push(&last_line[..(last_line.len() - 1)])
|
||||||
|
} else {
|
||||||
|
// Trim the line ending from the only line.
|
||||||
|
let only_line = Into::<&str>::into(first_line);
|
||||||
|
value.push(&only_line[..(only_line.len() - 1)])
|
||||||
|
}
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Comment {
|
Comment {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
value,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -57,14 +72,13 @@ fn comment_line<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _indent) = space0(input)?;
|
let (remaining, _) = tuple((space0, tag("#")))(input)?;
|
||||||
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
|
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
|
||||||
tag("#"),
|
return Ok((remaining, line_break));
|
||||||
opt(tuple((space1, is_not("\r\n")))),
|
}
|
||||||
alt((line_ending, eof)),
|
let (remaining, _) = tag(" ")(remaining)?;
|
||||||
))(remaining)?;
|
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
Ok((remaining, value))
|
||||||
Ok((remaining, source))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use nom::combinator::all_consuming;
|
use nom::combinator::all_consuming;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
|
|
||||||
use super::headline::heading;
|
use super::headline::heading;
|
||||||
use super::in_buffer_settings::apply_in_buffer_settings;
|
use super::in_buffer_settings::apply_in_buffer_settings;
|
||||||
|
use super::in_buffer_settings::apply_post_parse_in_buffer_settings;
|
||||||
use super::in_buffer_settings::scan_for_in_buffer_settings;
|
use super::in_buffer_settings::scan_for_in_buffer_settings;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::section::zeroth_section;
|
use super::section::zeroth_section;
|
||||||
use super::token::AllTokensIterator;
|
|
||||||
use super::token::Token;
|
|
||||||
use super::util::get_consumed;
|
use super::util::get_consumed;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
@@ -21,35 +22,77 @@ use crate::error::MyError;
|
|||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::org_source::convert_error;
|
use crate::parser::org_source::convert_error;
|
||||||
use crate::parser::util::blank_line;
|
use crate::parser::util::blank_line;
|
||||||
|
use crate::types::AstNode;
|
||||||
use crate::types::Document;
|
use crate::types::Document;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
|
|
||||||
/// Parse a full org-mode document.
|
/// 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.
|
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document without an underlying file attached.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||||
parse_with_settings(input, &GlobalSettings::default())
|
parse_file_with_settings::<&Path>(input, &GlobalSettings::default(), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a full org-mode document.
|
||||||
|
///
|
||||||
|
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path.
|
||||||
|
///
|
||||||
|
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn parse_file<'s, P: AsRef<Path>>(
|
||||||
|
input: &'s str,
|
||||||
|
file_path: Option<P>,
|
||||||
|
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||||
|
parse_file_with_settings(input, &GlobalSettings::default(), file_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a full org-mode document with starting settings.
|
/// Parse a full org-mode document with starting settings.
|
||||||
///
|
///
|
||||||
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied.
|
/// This is a secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied without an underlying file attached.
|
||||||
///
|
///
|
||||||
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn parse_with_settings<'g, 's>(
|
pub fn parse_with_settings<'g, 's>(
|
||||||
input: &'s str,
|
input: &'s str,
|
||||||
global_settings: &'g GlobalSettings<'g, 's>,
|
global_settings: &'g GlobalSettings<'g, 's>,
|
||||||
|
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||||
|
parse_file_with_settings::<&Path>(input, global_settings, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a full org-mode document with starting settings.
|
||||||
|
///
|
||||||
|
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path starting with the settings you supplied.
|
||||||
|
///
|
||||||
|
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
||||||
|
///
|
||||||
|
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
|
||||||
|
input: &'s str,
|
||||||
|
global_settings: &'g GlobalSettings<'g, 's>,
|
||||||
|
file_path: Option<P>,
|
||||||
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
||||||
let wrapped_input = OrgSource::new(input);
|
let wrapped_input = OrgSource::new(input);
|
||||||
let ret =
|
let mut doc =
|
||||||
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
|
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
|
||||||
.map_err(|err| err.to_string())
|
.map_err(|err| err.to_string())
|
||||||
.map(|(_remaining, parsed_document)| parsed_document);
|
.map(|(_remaining, parsed_document)| parsed_document)?;
|
||||||
Ok(ret?)
|
if let Some(file_path) = file_path {
|
||||||
|
let full_path = file_path.as_ref().canonicalize()?;
|
||||||
|
if doc.category.is_none() {
|
||||||
|
let category = full_path
|
||||||
|
.file_stem()
|
||||||
|
.expect("File should have a name.")
|
||||||
|
.to_str()
|
||||||
|
.expect("File name should be valid utf-8.");
|
||||||
|
doc.category = Some(category.to_owned());
|
||||||
|
}
|
||||||
|
doc.path = Some(full_path);
|
||||||
|
}
|
||||||
|
Ok(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a full org-mode document.
|
/// Parse a full org-mode document.
|
||||||
@@ -107,19 +150,18 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
|||||||
let new_context = context.with_global_settings(&new_settings);
|
let new_context = context.with_global_settings(&new_settings);
|
||||||
let context = &new_context;
|
let context = &new_context;
|
||||||
|
|
||||||
let (remaining, document) =
|
let (remaining, mut document) =
|
||||||
_document(context, input).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
_document(context, input).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
||||||
{
|
{
|
||||||
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
||||||
let all_radio_targets: Vec<&Vec<Object<'_>>> = document
|
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
|
||||||
.iter_tokens()
|
.into_iter()
|
||||||
.filter_map(|tkn| match tkn {
|
.filter_map(|ast_node| {
|
||||||
Token::Object(obj) => Some(obj),
|
if let AstNode::RadioTarget(ast_node) = ast_node {
|
||||||
_ => None,
|
Some(ast_node)
|
||||||
})
|
} else {
|
||||||
.filter_map(|obj| match obj {
|
None
|
||||||
Object::RadioTarget(rt) => Some(rt),
|
}
|
||||||
_ => None,
|
|
||||||
})
|
})
|
||||||
.map(|rt| &rt.children)
|
.map(|rt| &rt.children)
|
||||||
.collect();
|
.collect();
|
||||||
@@ -127,11 +169,18 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
|||||||
let mut new_global_settings = context.get_global_settings().clone();
|
let mut new_global_settings = context.get_global_settings().clone();
|
||||||
new_global_settings.radio_targets = all_radio_targets;
|
new_global_settings.radio_targets = all_radio_targets;
|
||||||
let parser_context = context.with_global_settings(&new_global_settings);
|
let parser_context = context.with_global_settings(&new_global_settings);
|
||||||
let (remaining, document) = _document(&parser_context, input)
|
let (remaining, mut document) = _document(&parser_context, input)
|
||||||
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
||||||
|
apply_post_parse_in_buffer_settings(&mut document)
|
||||||
|
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
||||||
return Ok((remaining.into(), document));
|
return Ok((remaining.into(), document));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Find final in-buffer settings that do not impact parsing
|
||||||
|
apply_post_parse_in_buffer_settings(&mut document)
|
||||||
|
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
||||||
|
|
||||||
Ok((remaining.into(), document))
|
Ok((remaining.into(), document))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,14 +199,10 @@ fn _document<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Document {
|
Document {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
category: None,
|
||||||
|
path: None,
|
||||||
zeroth_section,
|
zeroth_section,
|
||||||
children,
|
children,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Document<'s> {
|
|
||||||
fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
|
||||||
AllTokensIterator::new(Token::Document(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::is_not;
|
use nom::bytes::complete::is_not;
|
||||||
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
|
use nom::multi::many0;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
@@ -36,7 +42,6 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
|
) -> Res<OrgSource<'s>, DynamicBlock<'s>> {
|
||||||
// TODO: Do I need to differentiate between different dynamic block types.
|
|
||||||
if immediate_in_section(context, "dynamic block") {
|
if immediate_in_section(context, "dynamic block") {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element".into(),
|
||||||
@@ -44,10 +49,11 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, name, parameters, _ws)) = tuple((
|
let (remaining, (_, name, parameters, _, _)) = tuple((
|
||||||
recognize(tuple((tag_no_case("#+begin:"), space1))),
|
recognize(tuple((tag_no_case("#+begin:"), space1))),
|
||||||
name,
|
name,
|
||||||
opt(tuple((space1, parameters))),
|
opt(tuple((space1, parameters))),
|
||||||
|
space0,
|
||||||
line_ending,
|
line_ending,
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let contexts = [
|
let contexts = [
|
||||||
@@ -67,24 +73,23 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
|||||||
};
|
};
|
||||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
let (remaining, children) = match tuple((
|
not(exit_matcher)(remaining)?;
|
||||||
not(exit_matcher),
|
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
||||||
blank_line,
|
blank_line,
|
||||||
many_till(blank_line, exit_matcher),
|
many0(preceded(not(exit_matcher), blank_line)),
|
||||||
))(remaining)
|
))))(remaining)?;
|
||||||
{
|
let leading_blank_lines =
|
||||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||||
let source = get_consumed(remaining, remain);
|
|
||||||
element.set_source(source.into());
|
element.set_source(source.into());
|
||||||
(remain, vec![element])
|
element
|
||||||
}
|
});
|
||||||
Err(_) => {
|
let (remaining, (mut children, _exit_contents)) =
|
||||||
let (remaining, (children, _exit_contents)) =
|
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
if let Some(lines) = leading_blank_lines {
|
||||||
(remaining, children)
|
children.insert(0, lines);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
|
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -106,7 +111,7 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not("\r\n")(input)
|
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -117,7 +122,8 @@ fn dynamic_block_end<'b, 'g, 'r, 's>(
|
|||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, source) = recognize(tuple((
|
let (remaining, source) = recognize(tuple((
|
||||||
space0,
|
space0,
|
||||||
tag_no_case("#+end:"),
|
tag_no_case("#+end"),
|
||||||
|
opt(tag(":")),
|
||||||
alt((eof, line_ending)),
|
alt((eof, line_ending)),
|
||||||
)))(input)?;
|
)))(input)?;
|
||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ fn _element<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
|
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
|
||||||
let (remaining, mut element) = match alt((
|
let (remaining, mut element) = match alt((
|
||||||
map(plain_list_matcher, Element::PlainList),
|
map(plain_list_matcher, Element::PlainList),
|
||||||
map(greater_block_matcher, Element::GreaterBlock),
|
greater_block_matcher,
|
||||||
map(dynamic_block_matcher, Element::DynamicBlock),
|
map(dynamic_block_matcher, Element::DynamicBlock),
|
||||||
map(footnote_definition_matcher, Element::FootnoteDefinition),
|
map(footnote_definition_matcher, Element::FootnoteDefinition),
|
||||||
map(comment_matcher, Element::Comment),
|
map(comment_matcher, Element::Comment),
|
||||||
@@ -107,6 +107,7 @@ fn _element<'b, 'g, 'r, 's>(
|
|||||||
match map(paragraph_matcher, Element::Paragraph)(remaining) {
|
match map(paragraph_matcher, Element::Paragraph)(remaining) {
|
||||||
the_ok @ Ok(_) => the_ok,
|
the_ok @ Ok(_) => the_ok,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
// TODO: Because this function expects a single element, if there are multiple affiliated keywords before an element that cannot have affiliated keywords, we end up re-parsing the affiliated keywords many times.
|
||||||
affiliated_keywords.clear();
|
affiliated_keywords.clear();
|
||||||
map(affiliated_keyword_matcher, Element::Keyword)(input)
|
map(affiliated_keyword_matcher, Element::Keyword)(input)
|
||||||
}
|
}
|
||||||
@@ -141,7 +142,7 @@ fn _detect_element<'b, 'g, 'r, 's>(
|
|||||||
can_be_paragraph: bool,
|
can_be_paragraph: bool,
|
||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
if alt((
|
if alt((
|
||||||
detect_plain_list,
|
parser_with_context!(detect_plain_list)(context),
|
||||||
detect_footnote_definition,
|
detect_footnote_definition,
|
||||||
detect_diary_sexp,
|
detect_diary_sexp,
|
||||||
detect_comment,
|
detect_comment,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::tag_no_case;
|
|
||||||
use nom::character::complete::satisfy;
|
use nom::character::complete::satisfy;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
||||||
@@ -439,7 +439,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, Entity<'s>> {
|
) -> Res<OrgSource<'s>, Entity<'s>> {
|
||||||
let (remaining, _) = tag("\\")(input)?;
|
let (remaining, _) = tag("\\")(input)?;
|
||||||
let (remaining, entity_name) = name(context, remaining)?;
|
let (remaining, entity_name) = name(context, remaining)?;
|
||||||
let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?;
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, _trailing_whitespace) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
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>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// TODO: This should be defined by org-entities and optionally org-entities-user
|
// TODO: This should be defined by org-entities and optionally org-entities-user
|
||||||
for entity in ORG_ENTITIES {
|
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 {
|
match result {
|
||||||
Ok((remaining, ent)) => {
|
Ok((remaining, (ent, _))) => {
|
||||||
return Ok((remaining, ent));
|
return Ok((remaining, ent));
|
||||||
}
|
}
|
||||||
Err(_) => {}
|
Err(_) => {}
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ use nom::bytes::complete::is_not;
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::recognize;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::sequence::preceded;
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
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::parser_with_context;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
@@ -47,10 +48,10 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _indent) = space0(input)?;
|
let (remaining, _indent) = space0(input)?;
|
||||||
let (remaining, (_colon, _leading_whitespace_and_content, _line_ending)) = tuple((
|
let (remaining, _) = tuple((
|
||||||
tag(":"),
|
tag(":"),
|
||||||
opt(tuple((space1, is_not("\r\n")))),
|
alt((recognize(tuple((only_space1, is_not("\r\n")))), space0)),
|
||||||
alt((line_ending, eof)),
|
org_line_ending,
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use nom::branch::alt;
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::bytes::complete::take_while;
|
use nom::bytes::complete::take_while;
|
||||||
use nom::character::complete::digit1;
|
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
@@ -94,10 +93,7 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
pub(crate) fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
|
||||||
digit1,
|
|
||||||
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
|
|
||||||
))(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -133,7 +129,7 @@ mod tests {
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::types::Source;
|
use crate::types::GetStandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_paragraphs() {
|
fn two_paragraphs() {
|
||||||
@@ -154,13 +150,17 @@ line footnote.",
|
|||||||
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
footnote_definition_matcher(remaining).expect("Parse second footnote_definition.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_footnote_definition.get_source(),
|
first_footnote_definition
|
||||||
|
.get_standard_properties()
|
||||||
|
.get_source(),
|
||||||
"[fn:1] A footnote.
|
"[fn:1] A footnote.
|
||||||
|
|
||||||
"
|
"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
second_footnote_definition.get_source(),
|
second_footnote_definition
|
||||||
|
.get_standard_properties()
|
||||||
|
.get_source(),
|
||||||
"[fn:2] A multi-
|
"[fn:2] A multi-
|
||||||
|
|
||||||
line footnote."
|
line footnote."
|
||||||
@@ -185,7 +185,9 @@ not in the footnote.",
|
|||||||
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
footnote_definition_matcher(input).expect("Parse first footnote_definition");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
|
assert_eq!(Into::<&str>::into(remaining), "not in the footnote.");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_footnote_definition.get_source(),
|
first_footnote_definition
|
||||||
|
.get_standard_properties()
|
||||||
|
.get_source(),
|
||||||
"[fn:2] A multi-
|
"[fn:2] A multi-
|
||||||
|
|
||||||
line footnote.
|
line footnote.
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::is_not;
|
use nom::bytes::complete::is_not;
|
||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many0;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::preceded;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
@@ -27,17 +33,18 @@ use crate::parser::util::blank_line;
|
|||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::start_of_line;
|
use crate::parser::util::start_of_line;
|
||||||
|
use crate::types::CenterBlock;
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
use crate::types::GreaterBlock;
|
|
||||||
use crate::types::Paragraph;
|
use crate::types::Paragraph;
|
||||||
|
use crate::types::QuoteBlock;
|
||||||
use crate::types::SetSource;
|
use crate::types::SetSource;
|
||||||
|
use crate::types::SpecialBlock;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||||
// TODO: Do I need to differentiate between different greater block types.
|
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, (_begin, name)) = tuple((
|
let (remaining, (_begin, name)) = tuple((
|
||||||
@@ -49,22 +56,97 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
))(remaining)?;
|
))(remaining)?;
|
||||||
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
|
let name = Into::<&str>::into(name);
|
||||||
"center" => "center block".to_owned(),
|
let (remaining, element) = match name.to_lowercase().as_str() {
|
||||||
"quote" => "quote block".to_owned(),
|
"center" => center_block(context, remaining, input)?,
|
||||||
name @ _ => format!("special block {}", name),
|
"quote" => quote_block(context, remaining, input)?,
|
||||||
|
_ => special_block(name)(context, remaining, input)?,
|
||||||
};
|
};
|
||||||
if in_section(context, context_name.as_str()) {
|
Ok((remaining, element))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn center_block<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
original_input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||||
|
let (remaining, (source, children)) =
|
||||||
|
greater_block_body(context, input, original_input, "center", "center block")?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Element::CenterBlock(CenterBlock { source, children }),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn quote_block<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
original_input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||||
|
let (remaining, (source, children)) =
|
||||||
|
greater_block_body(context, input, original_input, "quote", "quote block")?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Element::QuoteBlock(QuoteBlock { source, children }),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn special_block<'s>(
|
||||||
|
name: &'s str,
|
||||||
|
) -> impl for<'b, 'g, 'r> Fn(
|
||||||
|
RefContext<'b, 'g, 'r, 's>,
|
||||||
|
OrgSource<'s>,
|
||||||
|
OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Element<'s>>
|
||||||
|
+ 's {
|
||||||
|
let context_name = format!("special block {}", name);
|
||||||
|
move |context, input, original_input| {
|
||||||
|
_special_block(context, input, original_input, name, context_name.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn _special_block<'c, 'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
original_input: OrgSource<'s>,
|
||||||
|
name: &'s str,
|
||||||
|
context_name: &'c str,
|
||||||
|
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||||
|
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
|
||||||
|
let (remaining, (source, children)) =
|
||||||
|
greater_block_body(context, remaining, original_input, name, context_name)?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Element::SpecialBlock(SpecialBlock {
|
||||||
|
source,
|
||||||
|
children,
|
||||||
|
name,
|
||||||
|
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
original_input: OrgSource<'s>,
|
||||||
|
name: &'c str,
|
||||||
|
context_name: &'c str,
|
||||||
|
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
|
||||||
|
if in_section(context, context_name) {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Cannot nest objects of the same element".into(),
|
"Cannot nest objects of the same element".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
let exit_with_name = greater_block_end(name.into());
|
let exit_with_name = greater_block_end(name);
|
||||||
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
|
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
|
||||||
let (remaining, _nl) = line_ending(remaining)?;
|
|
||||||
let contexts = [
|
let contexts = [
|
||||||
ContextElement::ConsumeTrailingWhitespace(true),
|
ContextElement::ConsumeTrailingWhitespace(true),
|
||||||
ContextElement::Context(context_name.as_str()),
|
ContextElement::Context(context_name),
|
||||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Alpha,
|
class: ExitClass::Alpha,
|
||||||
exit_matcher: &exit_with_name,
|
exit_matcher: &exit_with_name,
|
||||||
@@ -73,46 +155,31 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
let parameters = match parameters {
|
|
||||||
Some((_ws, parameters)) => Some(parameters),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||||
// Check for a completely empty block
|
not(exit_matcher)(remaining)?;
|
||||||
let (remaining, children) = match tuple((
|
let (remaining, leading_blank_lines) = opt(consumed(tuple((
|
||||||
not(exit_matcher),
|
|
||||||
blank_line,
|
blank_line,
|
||||||
many_till(blank_line, exit_matcher),
|
many0(preceded(not(exit_matcher), blank_line)),
|
||||||
))(remaining)
|
))))(remaining)?;
|
||||||
{
|
let leading_blank_lines =
|
||||||
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
|
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
|
||||||
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
|
||||||
let source = get_consumed(remaining, remain);
|
|
||||||
element.set_source(source.into());
|
element.set_source(source.into());
|
||||||
(remain, vec![element])
|
element
|
||||||
}
|
});
|
||||||
Err(_) => {
|
let (remaining, (mut children, _exit_contents)) =
|
||||||
let (remaining, (children, _exit_contents)) =
|
many_till(element_matcher, exit_matcher)(remaining)?;
|
||||||
many_till(element_matcher, exit_matcher)(remaining)?;
|
if let Some(lines) = leading_blank_lines {
|
||||||
(remaining, children)
|
children.insert(0, lines);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
|
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
|
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(original_input, remaining);
|
||||||
Ok((
|
Ok((remaining, (Into::<&str>::into(source), children)))
|
||||||
remaining,
|
|
||||||
GreaterBlock {
|
|
||||||
source: source.into(),
|
|
||||||
name: name.into(),
|
|
||||||
parameters: parameters.map(|val| Into::<&str>::into(val)),
|
|
||||||
children,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -122,11 +189,10 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
is_not("\r\n")(input)
|
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
|
fn greater_block_end<'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)
|
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,27 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::is_a;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::consumed;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::multi::many1;
|
use nom::multi::many1;
|
||||||
use nom::multi::many1_count;
|
|
||||||
use nom::multi::separated_list1;
|
use nom::multi::separated_list1;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::section::section;
|
use super::section::section;
|
||||||
use super::util::get_consumed;
|
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 super::util::start_of_line;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
@@ -31,39 +34,49 @@ use crate::error::Res;
|
|||||||
use crate::parser::object_parser::standard_set_object;
|
use crate::parser::object_parser::standard_set_object;
|
||||||
use crate::parser::util::blank_line;
|
use crate::parser::util::blank_line;
|
||||||
use crate::types::DocumentElement;
|
use crate::types::DocumentElement;
|
||||||
|
use crate::types::Element;
|
||||||
use crate::types::Heading;
|
use crate::types::Heading;
|
||||||
|
use crate::types::HeadlineLevel;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
use crate::types::PriorityCookie;
|
use crate::types::PriorityCookie;
|
||||||
use crate::types::TodoKeywordType;
|
use crate::types::TodoKeywordType;
|
||||||
|
|
||||||
pub(crate) const fn heading(
|
pub(crate) const fn heading(
|
||||||
parent_stars: usize,
|
parent_level: HeadlineLevel,
|
||||||
) -> impl for<'b, 'g, 'r, 's> Fn(
|
) -> impl for<'b, 'g, 'r, 's> Fn(
|
||||||
RefContext<'b, 'g, 'r, 's>,
|
RefContext<'b, 'g, 'r, 's>,
|
||||||
OrgSource<'s>,
|
OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||||
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
|
move |context, input: OrgSource<'_>| _heading(context, input, parent_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _heading<'b, 'g, 'r, 's>(
|
fn _heading<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
parent_stars: usize,
|
parent_star_count: HeadlineLevel,
|
||||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||||
|
let mut scheduled = None;
|
||||||
|
let mut deadline = None;
|
||||||
|
let mut closed = None;
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
not(|i| context.check_exit_matcher(i))(input)?;
|
||||||
let (
|
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
|
||||||
remaining,
|
|
||||||
(star_count, maybe_todo_keyword, maybe_priority, maybe_comment, title, heading_tags),
|
|
||||||
) = headline(context, input, parent_stars)?;
|
|
||||||
let section_matcher = parser_with_context!(section)(context);
|
let section_matcher = parser_with_context!(section)(context);
|
||||||
let heading_matcher = parser_with_context!(heading(star_count))(context);
|
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
|
||||||
let (remaining, maybe_section) =
|
let (remaining, maybe_section) =
|
||||||
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
|
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
|
||||||
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
|
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
|
||||||
let (remaining, mut children) =
|
let (remaining, mut children) =
|
||||||
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
|
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
|
||||||
if let Some(section) = maybe_section {
|
if let Some(section) = maybe_section {
|
||||||
|
// If the section has a planning then the timestamp values are copied to the heading.
|
||||||
|
if let DocumentElement::Section(inner_section) = §ion {
|
||||||
|
if let Some(Element::Planning(planning)) = inner_section.children.first() {
|
||||||
|
scheduled = planning.scheduled.clone();
|
||||||
|
deadline = planning.deadline.clone();
|
||||||
|
closed = planning.closed.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
children.insert(0, section);
|
children.insert(0, section);
|
||||||
}
|
}
|
||||||
let remaining = if children.is_empty() {
|
let remaining = if children.is_empty() {
|
||||||
@@ -73,23 +86,29 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
} else {
|
} else {
|
||||||
remaining
|
remaining
|
||||||
};
|
};
|
||||||
let is_archived = heading_tags.contains(&"ARCHIVE");
|
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
|
||||||
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Heading {
|
Heading {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
stars: star_count,
|
level: pre_headline.headline_level,
|
||||||
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
|
todo_keyword: pre_headline
|
||||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
.todo_keyword
|
||||||
}),
|
.map(|(todo_keyword_type, todo_keyword)| {
|
||||||
priority_cookie: maybe_priority.map(|(priority, _)| priority),
|
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||||
title,
|
}),
|
||||||
tags: heading_tags,
|
priority_cookie: pre_headline.priority_cookie.map(|(_, priority)| priority),
|
||||||
|
title: pre_headline.title,
|
||||||
|
tags: pre_headline.tags,
|
||||||
children,
|
children,
|
||||||
is_comment: maybe_comment.is_some(),
|
is_comment: pre_headline.comment.is_some(),
|
||||||
is_archived,
|
is_archived,
|
||||||
|
is_footnote_section: pre_headline.is_footnote_section,
|
||||||
|
scheduled,
|
||||||
|
deadline,
|
||||||
|
closed,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -100,75 +119,95 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
|
|||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fields from a not-yet-fully-parsed Headline.
|
||||||
|
///
|
||||||
|
/// This struct exists to give names to the fields of a partially-parsed Headline to avoid returning a large tuple of nameless fields.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct PreHeadline<'s> {
|
||||||
|
headline_level: HeadlineLevel,
|
||||||
|
star_count: HeadlineLevel,
|
||||||
|
todo_keyword: Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||||
|
priority_cookie: Option<(OrgSource<'s>, PriorityCookie)>,
|
||||||
|
comment: Option<OrgSource<'s>>,
|
||||||
|
title: Vec<Object<'s>>,
|
||||||
|
tags: Vec<&'s str>,
|
||||||
|
is_footnote_section: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn headline<'b, 'g, 'r, 's>(
|
fn headline<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
parent_stars: usize,
|
parent_star_count: HeadlineLevel,
|
||||||
) -> Res<
|
) -> Res<OrgSource<'s>, PreHeadline<'s>> {
|
||||||
OrgSource<'s>,
|
|
||||||
(
|
|
||||||
usize,
|
|
||||||
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
|
|
||||||
Option<(PriorityCookie, OrgSource<'s>)>,
|
|
||||||
Option<(OrgSource<'s>, OrgSource<'s>)>,
|
|
||||||
Vec<Object<'s>>,
|
|
||||||
Vec<&'s str>,
|
|
||||||
),
|
|
||||||
> {
|
|
||||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Document,
|
class: ExitClass::Document,
|
||||||
exit_matcher: &headline_title_end,
|
exit_matcher: &headline_title_end,
|
||||||
});
|
});
|
||||||
let parser_context = context.with_additional_node(&parser_context);
|
let parser_context = context.with_additional_node(&parser_context);
|
||||||
|
|
||||||
let (
|
let (remaining, (_, (headline_level, star_count, _), _)) = tuple((
|
||||||
remaining,
|
|
||||||
(
|
|
||||||
_,
|
|
||||||
star_count,
|
|
||||||
_,
|
|
||||||
maybe_todo_keyword,
|
|
||||||
maybe_priority,
|
|
||||||
maybe_comment,
|
|
||||||
title,
|
|
||||||
maybe_tags,
|
|
||||||
_,
|
|
||||||
_,
|
|
||||||
),
|
|
||||||
) = tuple((
|
|
||||||
start_of_line,
|
start_of_line,
|
||||||
verify(many1_count(tag("*")), |star_count| {
|
verify(
|
||||||
*star_count > parent_stars
|
parser_with_context!(headline_level)(&parser_context),
|
||||||
}),
|
|(_, count, _)| *count > parent_star_count,
|
||||||
space1,
|
),
|
||||||
opt(tuple((
|
peek(org_space),
|
||||||
parser_with_context!(heading_keyword)(&parser_context),
|
|
||||||
space1,
|
|
||||||
))),
|
|
||||||
opt(tuple((priority_cookie, space1))),
|
|
||||||
opt(tuple((tag("COMMENT"), space1))),
|
|
||||||
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
|
||||||
opt(tuple((space0, tags))),
|
|
||||||
space0,
|
|
||||||
alt((line_ending, eof)),
|
|
||||||
))(input)?;
|
))(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,
|
||||||
|
consumed(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)?;
|
||||||
|
|
||||||
|
let is_footnote_section = maybe_title
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, (raw_title, _))| raw_title)
|
||||||
|
.map(|raw_title| {
|
||||||
|
Into::<&str>::into(raw_title) == context.get_global_settings().footnote_section
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
(
|
PreHeadline {
|
||||||
|
headline_level,
|
||||||
star_count,
|
star_count,
|
||||||
maybe_todo_keyword,
|
todo_keyword: maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||||
maybe_priority,
|
priority_cookie: maybe_priority,
|
||||||
maybe_comment,
|
comment: maybe_comment.map(|(_, comment, _)| comment),
|
||||||
title,
|
title: maybe_title
|
||||||
maybe_tags
|
.map(|(_, (_, title))| title)
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
tags: maybe_tags
|
||||||
.map(|(_ws, tags)| {
|
.map(|(_ws, tags)| {
|
||||||
tags.into_iter()
|
tags.into_iter()
|
||||||
.map(|single_tag| Into::<&str>::into(single_tag))
|
.map(|single_tag| Into::<&str>::into(single_tag))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.unwrap_or(Vec::new()),
|
.unwrap_or(Vec::new()),
|
||||||
),
|
is_footnote_section,
|
||||||
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,10 +216,7 @@ fn headline_title_end<'b, 'g, 'r, 's>(
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((
|
recognize(tuple((space0, opt(tuple((tags, space0))), org_line_ending)))(input)
|
||||||
opt(tuple((space0, tags, space0))),
|
|
||||||
alt((line_ending, eof)),
|
|
||||||
)))(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -256,3 +292,23 @@ fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCooki
|
|||||||
})?;
|
})?;
|
||||||
Ok((remaining, cookie))
|
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)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,59 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::is_not;
|
||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::character::complete::anychar;
|
use nom::bytes::complete::take_until;
|
||||||
use nom::combinator::map;
|
use nom::character::complete::space1;
|
||||||
use nom::multi::many0;
|
use nom::multi::separated_list0;
|
||||||
use nom::multi::many_till;
|
|
||||||
|
|
||||||
use super::keyword::filtered_keyword;
|
use super::keyword::filtered_keyword;
|
||||||
use super::keyword_todo::todo_keywords;
|
use super::keyword_todo::todo_keywords;
|
||||||
use super::OrgSource;
|
use super::OrgSource;
|
||||||
|
use crate::context::HeadlineLevelFilter;
|
||||||
|
use crate::error::CustomError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
|
use crate::settings::GlobalSettings;
|
||||||
|
use crate::types::AstNode;
|
||||||
|
use crate::types::Document;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::GlobalSettings;
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn scan_for_in_buffer_settings<'s>(
|
pub(crate) fn scan_for_in_buffer_settings<'s>(
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Vec<Keyword<'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.
|
// TODO: Write some tests to make sure this is functioning properly.
|
||||||
|
|
||||||
let keywords = many0(map(
|
let mut keywords = Vec::new();
|
||||||
many_till(anychar, filtered_keyword(in_buffer_settings_key)),
|
let mut remaining = input;
|
||||||
|(_, kw)| kw,
|
loop {
|
||||||
))(input);
|
// Skip text until possible in_buffer_setting
|
||||||
keywords
|
let start_of_pound = take_until::<_, _, CustomError<_>>("#+")(remaining);
|
||||||
|
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
|
||||||
|
start_of_pound
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
// Go backwards to the start of the line and run the filtered_keyword parser
|
||||||
|
let start_of_line = start_of_pound.get_start_of_line();
|
||||||
|
|
||||||
|
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
|
||||||
|
Ok((remain, kw)) => (remain, Some(kw)),
|
||||||
|
Err(_) => {
|
||||||
|
let end_of_line = take_until::<_, _, CustomError<_>>("\n")(start_of_pound);
|
||||||
|
if let Ok((end_of_line, _)) = end_of_line {
|
||||||
|
(end_of_line, None)
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(kw) = maybe_kw {
|
||||||
|
keywords.push(kw);
|
||||||
|
}
|
||||||
|
remaining = remain;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((remaining, keywords))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -50,6 +81,7 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
) -> Result<GlobalSettings<'g, 's>, String> {
|
) -> Result<GlobalSettings<'g, 's>, String> {
|
||||||
let mut new_settings = original_settings.clone();
|
let mut new_settings = original_settings.clone();
|
||||||
|
|
||||||
|
// Todo Keywords
|
||||||
for kw in keywords.iter().filter(|kw| {
|
for kw in keywords.iter().filter(|kw| {
|
||||||
kw.key.eq_ignore_ascii_case("todo")
|
kw.key.eq_ignore_ascii_case("todo")
|
||||||
|| kw.key.eq_ignore_ascii_case("seq_todo")
|
|| kw.key.eq_ignore_ascii_case("seq_todo")
|
||||||
@@ -65,5 +97,70 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
|||||||
.extend(complete_words.into_iter().map(str::to_string));
|
.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)
|
Ok(new_settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
|
||||||
|
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
|
||||||
|
document: &mut Document<'s>,
|
||||||
|
) -> Result<(), &'static str> {
|
||||||
|
document.category = Into::<AstNode>::into(&*document)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|ast_node| {
|
||||||
|
if let AstNode::Keyword(ast_node) = ast_node {
|
||||||
|
if ast_node.key.eq_ignore_ascii_case("category") {
|
||||||
|
return Some(ast_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.last()
|
||||||
|
.map(|kw| kw.value.to_owned());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scan_test() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let input = OrgSource::new(
|
||||||
|
r#"
|
||||||
|
foo
|
||||||
|
#+archive: bar
|
||||||
|
|
||||||
|
baz #+category: lorem
|
||||||
|
|
||||||
|
#+label: ipsum
|
||||||
|
|
||||||
|
#+todo: dolar
|
||||||
|
cat
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
let (remaining, settings) = scan_for_in_buffer_settings(input)?;
|
||||||
|
assert_eq!(Into::<&str>::into(remaining), "cat\n");
|
||||||
|
let keys: Vec<_> = settings.iter().map(|kw| kw.key).collect();
|
||||||
|
// category is skipped because it is not the first non-whitespace on the line.
|
||||||
|
//
|
||||||
|
// label is skipped because it is not an in-buffer setting.
|
||||||
|
assert_eq!(keys, vec!["archive", "todo"]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use nom::combinator::eof;
|
|||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
|
use nom::combinator::verify;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ use crate::error::CustomError;
|
|||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::start_of_line;
|
use crate::parser::util::start_of_line;
|
||||||
|
use crate::types::BabelCall;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
|
|
||||||
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
|
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
|
||||||
@@ -102,8 +104,16 @@ pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
|
|||||||
pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
|
pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
|
||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
) -> Res<OrgSource<'s>, BabelCall<'s>> {
|
||||||
filtered_keyword(babel_call_key)(input)
|
let (remaining, kw) = filtered_keyword(babel_call_key)(input)?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
BabelCall {
|
||||||
|
source: kw.source,
|
||||||
|
key: kw.key,
|
||||||
|
value: kw.value,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -116,7 +126,9 @@ pub(crate) fn table_formula_keyword<'b, 'g, 'r, 's>(
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
) -> Res<OrgSource<'s>, Keyword<'s>> {
|
||||||
filtered_keyword(table_formula_key)(input)
|
verify(filtered_keyword(table_formula_key), |kw| {
|
||||||
|
!kw.value.is_empty()
|
||||||
|
})(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
|||||||
@@ -44,9 +44,17 @@ pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, V
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
|
fn todo_keyword_word<'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()
|
!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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
@@ -261,11 +261,10 @@ fn data<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
is_not("\r\n")(input)
|
is_not("\r\n")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lesser_block_end(current_name: &str) -> impl ContextMatcher {
|
fn lesser_block_end<'c>(current_name: &'c str) -> impl ContextMatcher + 'c {
|
||||||
let current_name_lower = current_name.to_lowercase();
|
// Since the lesser block names are statically defined in code, we can simply assert that the name is lowercase instead of causing an allocation by converting to lowercase.
|
||||||
move |context, input: OrgSource<'_>| {
|
debug_assert!(current_name == current_name.to_lowercase());
|
||||||
_lesser_block_end(context, input, current_name_lower.as_str())
|
move |context, input: OrgSource<'_>| _lesser_block_end(context, input, current_name)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
|||||||
@@ -43,8 +43,9 @@ mod table;
|
|||||||
mod target;
|
mod target;
|
||||||
mod text_markup;
|
mod text_markup;
|
||||||
mod timestamp;
|
mod timestamp;
|
||||||
mod token;
|
|
||||||
mod util;
|
mod util;
|
||||||
pub use document::parse;
|
pub use document::parse;
|
||||||
|
pub use document::parse_file;
|
||||||
|
pub use document::parse_file_with_settings;
|
||||||
pub use document::parse_with_settings;
|
pub use document::parse_with_settings;
|
||||||
pub(crate) use org_source::OrgSource;
|
pub(crate) use org_source::OrgSource;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::ops::RangeBounds;
|
use std::ops::RangeBounds;
|
||||||
|
|
||||||
use nom::Compare;
|
use nom::Compare;
|
||||||
|
use nom::FindSubstring;
|
||||||
use nom::InputIter;
|
use nom::InputIter;
|
||||||
use nom::InputLength;
|
use nom::InputLength;
|
||||||
use nom::InputTake;
|
use nom::InputTake;
|
||||||
@@ -59,6 +60,10 @@ impl<'s> OrgSource<'s> {
|
|||||||
self.end - self.start
|
self.end - self.start
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_byte_offset(&self) -> usize {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_preceding_character(&self) -> Option<char> {
|
pub(crate) fn get_preceding_character(&self) -> Option<char> {
|
||||||
self.preceding_character
|
self.preceding_character
|
||||||
}
|
}
|
||||||
@@ -68,11 +73,66 @@ impl<'s> OrgSource<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> {
|
pub(crate) fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> {
|
||||||
assert!(other.start >= self.start);
|
debug_assert!(other.start >= self.start);
|
||||||
assert!(other.end <= self.end);
|
debug_assert!(other.end <= self.end);
|
||||||
self.slice(..(other.start - self.start))
|
self.slice(..(other.start - self.start))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_until_end_of(&self, other: OrgSource<'s>) -> OrgSource<'s> {
|
||||||
|
debug_assert!(other.start >= self.start);
|
||||||
|
debug_assert!(other.end <= self.end);
|
||||||
|
self.slice(..(other.end - self.start))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> {
|
||||||
|
let skipped_text = self.text_since_line_break();
|
||||||
|
let mut bracket_depth = self.bracket_depth;
|
||||||
|
let mut brace_depth = self.brace_depth;
|
||||||
|
let mut parenthesis_depth = self.parenthesis_depth;
|
||||||
|
// Since we're going backwards, this does the opposite.
|
||||||
|
for byte in skipped_text.bytes() {
|
||||||
|
match byte {
|
||||||
|
b'\n' => {
|
||||||
|
panic!("Should not hit a line break when only going back to the start of the line.");
|
||||||
|
}
|
||||||
|
b'[' => {
|
||||||
|
bracket_depth -= 1;
|
||||||
|
}
|
||||||
|
b']' => {
|
||||||
|
bracket_depth += 1;
|
||||||
|
}
|
||||||
|
b'{' => {
|
||||||
|
brace_depth -= 1;
|
||||||
|
}
|
||||||
|
b'}' => {
|
||||||
|
brace_depth += 1;
|
||||||
|
}
|
||||||
|
b'(' => {
|
||||||
|
parenthesis_depth -= 1;
|
||||||
|
}
|
||||||
|
b')' => {
|
||||||
|
parenthesis_depth += 1;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
OrgSource {
|
||||||
|
full_source: self.full_source,
|
||||||
|
start: self.start_of_line,
|
||||||
|
end: self.end,
|
||||||
|
start_of_line: self.start_of_line,
|
||||||
|
preceding_character: if self.start_of_line > 0 {
|
||||||
|
Some('\n')
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
bracket_depth,
|
||||||
|
brace_depth,
|
||||||
|
parenthesis_depth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_bracket_depth(&self) -> BracketDepth {
|
pub(crate) fn get_bracket_depth(&self) -> BracketDepth {
|
||||||
self.bracket_depth
|
self.bracket_depth
|
||||||
}
|
}
|
||||||
@@ -306,6 +366,12 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'n, 's> FindSubstring<&'n str> for OrgSource<'s> {
|
||||||
|
fn find_substring(&self, substr: &'n str) -> Option<usize> {
|
||||||
|
Into::<&str>::into(self).find(substr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
|
pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
|
||||||
err: nom::Err<I>,
|
err: nom::Err<I>,
|
||||||
) -> nom::Err<CustomError<&'a str>> {
|
) -> nom::Err<CustomError<&'a str>> {
|
||||||
@@ -322,6 +388,7 @@ impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
|
|||||||
CustomError::MyError(err) => CustomError::MyError(err.into()),
|
CustomError::MyError(err) => CustomError::MyError(err.into()),
|
||||||
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
|
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
|
||||||
CustomError::IO(err) => CustomError::IO(err),
|
CustomError::IO(err) => CustomError::IO(err),
|
||||||
|
CustomError::BoxedError(err) => CustomError::BoxedError(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ mod tests {
|
|||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::parser::org_source::OrgSource;
|
use crate::parser::org_source::OrgSource;
|
||||||
use crate::types::Source;
|
use crate::types::GetStandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn two_paragraphs() {
|
fn two_paragraphs() {
|
||||||
@@ -87,7 +87,13 @@ mod tests {
|
|||||||
let (remaining, second_paragraph) =
|
let (remaining, second_paragraph) =
|
||||||
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
paragraph_matcher(remaining).expect("Parse second paragraph.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
|
assert_eq!(
|
||||||
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
|
first_paragraph.get_standard_properties().get_source(),
|
||||||
|
"foo bar baz\n\n"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
second_paragraph.get_standard_properties().get_source(),
|
||||||
|
"lorem ipsum"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,24 @@ use nom::character::complete::anychar;
|
|||||||
use nom::character::complete::none_of;
|
use nom::character::complete::none_of;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
|
use nom::combinator::not;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many0;
|
||||||
|
use nom::multi::many1;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
|
use super::org_source::BracketDepth;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
|
use crate::context::ContextMatcher;
|
||||||
use crate::context::ExitClass;
|
use crate::context::ExitClass;
|
||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
|
use crate::context::Matcher;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -130,17 +137,77 @@ fn path_plain<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// TODO: "optionally containing parenthesis-wrapped non-whitespace non-bracket substrings up to a depth of two. The string must end with either a non-punctation non-whitespace character, a forwards slash, or a parenthesis-wrapped substring"
|
let path_plain_end = path_plain_end(input.get_parenthesis_depth());
|
||||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Gamma,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &path_plain_end,
|
exit_matcher: &path_plain_end,
|
||||||
});
|
});
|
||||||
let parser_context = context.with_additional_node(&parser_context);
|
let parser_context = context.with_additional_node(&parser_context);
|
||||||
|
|
||||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
let (remaining, _components) = many1(alt((
|
||||||
|
parser_with_context!(path_plain_no_parenthesis)(&parser_context),
|
||||||
|
parser_with_context!(path_plain_parenthesis)(&parser_context),
|
||||||
|
)))(input)?;
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_plain_end(starting_parenthesis_depth: BracketDepth) -> impl ContextMatcher {
|
||||||
|
move |context, input: OrgSource<'_>| _path_plain_end(context, input, starting_parenthesis_depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn _path_plain_end<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
starting_parenthesis_depth: BracketDepth,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let (remaining, _leading_punctuation) = many0(verify(anychar, |c| {
|
||||||
|
!" \t\r\n[]<>()/".contains(*c) && c.is_ascii_punctuation()
|
||||||
|
}))(input)?;
|
||||||
|
|
||||||
|
let disallowed_character = recognize(one_of(" \t\r\n[]<>"))(remaining);
|
||||||
|
if disallowed_character.is_ok() {
|
||||||
|
return disallowed_character;
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_depth = remaining.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_parenthesis =
|
||||||
|
tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(remaining);
|
||||||
|
if close_parenthesis.is_ok() {
|
||||||
|
return close_parenthesis;
|
||||||
|
}
|
||||||
|
|
||||||
|
let open_parenthesis_without_match = recognize(tuple((
|
||||||
|
peek(tag("(")),
|
||||||
|
not(parser_with_context!(path_plain_parenthesis)(context)),
|
||||||
|
)))(remaining);
|
||||||
|
if open_parenthesis_without_match.is_ok() {
|
||||||
|
return open_parenthesis_without_match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// many0 punctuation
|
||||||
|
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"No path plain end".into(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn path_plain_no_parenthesis<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, path) = recognize(verify(
|
let (remaining, path) = recognize(verify(
|
||||||
many_till(anychar, peek(exit_matcher)),
|
many_till(
|
||||||
|
anychar,
|
||||||
|
alt((
|
||||||
|
peek(path_plain_no_parenthesis_disallowed_character),
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|(children, _exit_contents)| !children.is_empty(),
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
|
|
||||||
@@ -148,14 +215,65 @@ fn path_plain<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn path_plain_end<'b, 'g, 'r, 's>(
|
fn path_plain_no_parenthesis_disallowed_character<'s>(
|
||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(many_till(
|
recognize(verify(anychar, |c| {
|
||||||
verify(anychar, |c| {
|
c.is_whitespace() || "()[]<>".contains(*c)
|
||||||
*c != '/' && (c.is_ascii_punctuation() || c.is_whitespace())
|
}))(input)
|
||||||
}),
|
}
|
||||||
one_of(" \t\r\n()[]<>"),
|
|
||||||
))(input)
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn path_plain_parenthesis<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let (remaining, _opening) = tag("(")(input)?;
|
||||||
|
let starting_depth = remaining.get_parenthesis_depth();
|
||||||
|
|
||||||
|
let (remaining, _path) = recognize(verify(
|
||||||
|
many_till(
|
||||||
|
anychar,
|
||||||
|
alt((
|
||||||
|
peek(path_plain_parenthesis_end(starting_depth)),
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
|(children, _exit_contents)| !children.is_empty(),
|
||||||
|
))(remaining)?;
|
||||||
|
let (remaining, _opening) = tag(")")(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_plain_parenthesis_end(starting_parenthesis_depth: BracketDepth) -> impl Matcher {
|
||||||
|
move |input: OrgSource<'_>| _path_plain_parenthesis_end(input, starting_parenthesis_depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn _path_plain_parenthesis_end<'s>(
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
starting_parenthesis_depth: BracketDepth,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing parenthesis should end the link.
|
||||||
|
unreachable!("Exceeded plain link parenthesis depth.")
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
||||||
|
if close_parenthesis.is_ok() {
|
||||||
|
return close_parenthesis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if current_depth == 1 {
|
||||||
|
let open_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>("(")(input);
|
||||||
|
if open_parenthesis.is_ok() {
|
||||||
|
return open_parenthesis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"No closing parenthesis".into(),
|
||||||
|
))))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use nom::character::complete::one_of;
|
|||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::eof;
|
||||||
|
use nom::combinator::map;
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
@@ -21,6 +22,7 @@ use super::element_parser::element;
|
|||||||
use super::object_parser::standard_set_object;
|
use super::object_parser::standard_set_object;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::include_input;
|
use super::util::include_input;
|
||||||
|
use super::util::indentation_level;
|
||||||
use super::util::non_whitespace_character;
|
use super::util::non_whitespace_character;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
@@ -35,22 +37,31 @@ use crate::parser::util::blank_line;
|
|||||||
use crate::parser::util::exit_matcher_parser;
|
use crate::parser::util::exit_matcher_parser;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
|
use crate::parser::util::org_space;
|
||||||
use crate::parser::util::start_of_line;
|
use crate::parser::util::start_of_line;
|
||||||
|
use crate::types::CheckboxType;
|
||||||
|
use crate::types::IndentationLevel;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
use crate::types::PlainList;
|
use crate::types::PlainList;
|
||||||
use crate::types::PlainListItem;
|
use crate::types::PlainListItem;
|
||||||
|
use crate::types::PlainListItemCounter;
|
||||||
|
use crate::types::PlainListItemPreBlank;
|
||||||
|
use crate::types::PlainListType;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
|
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
if verify(
|
if verify(
|
||||||
tuple((
|
tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
space0,
|
parser_with_context!(indentation_level)(context),
|
||||||
bullet,
|
parser_with_context!(bullet)(context),
|
||||||
alt((space1, line_ending, eof)),
|
alt((space1, line_ending, eof)),
|
||||||
)),
|
)),
|
||||||
|(_start, indent, bull, _after_whitespace)| {
|
|(_start, (indent_level, _), (_bullet_type, bull), _after_whitespace)| {
|
||||||
Into::<&str>::into(bull) != "*" || indent.len() > 0
|
!Into::<&str>::into(bull).starts_with("*") || *indent_level > 0
|
||||||
},
|
},
|
||||||
)(input)
|
)(input)
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -81,7 +92,8 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
|||||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||||
// children stores tuple of (input string, parsed object) so we can re-parse the final item
|
// children stores tuple of (input string, parsed object) so we can re-parse the final item
|
||||||
let mut children = Vec::new();
|
let mut children = Vec::new();
|
||||||
let mut first_item_indentation: Option<usize> = None;
|
let mut first_item_indentation: Option<IndentationLevel> = None;
|
||||||
|
let mut first_item_list_type: Option<PlainListType> = None;
|
||||||
let mut remaining = input;
|
let mut remaining = input;
|
||||||
|
|
||||||
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
|
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
|
||||||
@@ -94,8 +106,15 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining);
|
let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining);
|
||||||
|
match (&first_item_list_type, &list_item) {
|
||||||
|
(None, Ok((_remain, (list_type, _item)))) => {
|
||||||
|
let _ = first_item_list_type.insert(*list_type);
|
||||||
|
}
|
||||||
|
(None, Err(_)) => {}
|
||||||
|
(Some(_), _) => {}
|
||||||
|
};
|
||||||
match list_item {
|
match list_item {
|
||||||
Ok((remain, item))
|
Ok((remain, (_list_type, item)))
|
||||||
if item.indentation == *first_item_indentation.get_or_insert(item.indentation) =>
|
if item.indentation == *first_item_indentation.get_or_insert(item.indentation) =>
|
||||||
{
|
{
|
||||||
children.push((remaining, item));
|
children.push((remaining, item));
|
||||||
@@ -122,7 +141,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
|||||||
};
|
};
|
||||||
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
|
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
|
||||||
let final_item_context = parser_context.with_additional_node(&final_item_context);
|
let final_item_context = parser_context.with_additional_node(&final_item_context);
|
||||||
let (remaining, reparsed_final_item) =
|
let (remaining, (_, reparsed_final_item)) =
|
||||||
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
|
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
|
||||||
children.push((final_child_start, reparsed_final_item));
|
children.push((final_child_start, reparsed_final_item));
|
||||||
|
|
||||||
@@ -131,6 +150,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
PlainList {
|
PlainList {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
list_type: first_item_list_type.expect("Plain lists require at least one element."),
|
||||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -140,46 +160,32 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
|||||||
fn plain_list_item<'b, 'g, 'r, 's>(
|
fn plain_list_item<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, PlainListItem<'s>> {
|
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, leading_whitespace) = space0(input)?;
|
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
|
||||||
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
let (remaining, (bullet_type, bull)) = verify(
|
||||||
let indent_level = leading_whitespace.len();
|
parser_with_context!(bullet)(context),
|
||||||
let (remaining, bull) = verify(bullet, |bull: &OrgSource<'_>| {
|
|(_bullet_type, bull)| !Into::<&str>::into(bull).starts_with("*") || indent_level > 0,
|
||||||
Into::<&str>::into(bull) != "*" || indent_level > 0
|
)(remaining)?;
|
||||||
})(remaining)?;
|
|
||||||
|
|
||||||
let (remaining, _maybe_counter_set) =
|
let (remaining, maybe_counter_set) =
|
||||||
opt(tuple((space1, tag("[@"), counter, tag("]"))))(remaining)?;
|
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
|
||||||
|
let maybe_counter_set = maybe_counter_set.map(|(_, _, val, _)| val);
|
||||||
|
|
||||||
// TODO: parse checkbox
|
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
|
||||||
|
|
||||||
let (remaining, maybe_tag) =
|
let (remaining, maybe_tag) = if let BulletType::Unordered = bullet_type {
|
||||||
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?;
|
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?
|
||||||
|
} else {
|
||||||
let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
|
(remaining, None)
|
||||||
detect_contentless_item_contents
|
|
||||||
)(context))(remaining);
|
|
||||||
match maybe_contentless_item {
|
|
||||||
Ok((_rem, _ws)) => {
|
|
||||||
let (remaining, _trailing_ws) = opt(blank_line)(remaining)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
|
||||||
return Ok((
|
|
||||||
remaining,
|
|
||||||
PlainListItem {
|
|
||||||
source: source.into(),
|
|
||||||
indentation: indent_level,
|
|
||||||
bullet: bull.into(),
|
|
||||||
tag: maybe_tag
|
|
||||||
.map(|(_ws, item_tag)| item_tag)
|
|
||||||
.unwrap_or(Vec::new()),
|
|
||||||
children: Vec::new(),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Err(_) => {}
|
|
||||||
};
|
};
|
||||||
let (remaining, _ws) = item_tag_post_gap(context, remaining)?;
|
let list_type = match (&maybe_tag, bullet_type) {
|
||||||
|
(None, BulletType::Ordered) => PlainListType::Ordered,
|
||||||
|
(None, BulletType::Unordered) => PlainListType::Unordered,
|
||||||
|
(Some(_), BulletType::Ordered) => unreachable!(),
|
||||||
|
(Some(_), BulletType::Unordered) => PlainListType::Descriptive,
|
||||||
|
};
|
||||||
|
|
||||||
let exit_matcher = plain_list_item_end(indent_level);
|
let exit_matcher = plain_list_item_end(indent_level);
|
||||||
let contexts = [
|
let contexts = [
|
||||||
ContextElement::ConsumeTrailingWhitespace(true),
|
ContextElement::ConsumeTrailingWhitespace(true),
|
||||||
@@ -191,6 +197,44 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
let parser_context = context.with_additional_node(&contexts[0]);
|
let parser_context = context.with_additional_node(&contexts[0]);
|
||||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||||
|
|
||||||
|
let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
|
||||||
|
detect_contentless_item_contents
|
||||||
|
)(&parser_context))(remaining);
|
||||||
|
match maybe_contentless_item {
|
||||||
|
Ok((_rem, _ws)) => {
|
||||||
|
let (remaining, _trailing_ws) = if context.should_consume_trailing_whitespace() {
|
||||||
|
recognize(alt((recognize(many1(blank_line)), eof)))(remaining)?
|
||||||
|
} else {
|
||||||
|
recognize(alt((blank_line, eof)))(remaining)?
|
||||||
|
};
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
return Ok((
|
||||||
|
remaining,
|
||||||
|
(
|
||||||
|
list_type,
|
||||||
|
PlainListItem {
|
||||||
|
source: source.into(),
|
||||||
|
indentation: indent_level,
|
||||||
|
bullet: bull.into(),
|
||||||
|
counter: maybe_counter_set,
|
||||||
|
checkbox: None,
|
||||||
|
tag: maybe_tag
|
||||||
|
.map(|(_ws, item_tag)| item_tag)
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
pre_blank: 0,
|
||||||
|
children: Vec::new(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
let (remaining, pre_blank) = item_tag_post_gap(&parser_context, remaining)?;
|
||||||
|
let pre_blank = Into::<&str>::into(pre_blank)
|
||||||
|
.bytes()
|
||||||
|
.filter(|b| *b == b'\n')
|
||||||
|
.count();
|
||||||
|
|
||||||
let (mut remaining, (mut children, _exit_contents)) = many_till(
|
let (mut remaining, (mut children, _exit_contents)) = many_till(
|
||||||
include_input(parser_with_context!(element(true))(&parser_context)),
|
include_input(parser_with_context!(element(true))(&parser_context)),
|
||||||
parser_with_context!(exit_matcher_parser)(&parser_context),
|
parser_with_context!(exit_matcher_parser)(&parser_context),
|
||||||
@@ -215,31 +259,95 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
return Ok((
|
return Ok((
|
||||||
remaining,
|
remaining,
|
||||||
PlainListItem {
|
(
|
||||||
source: source.into(),
|
list_type,
|
||||||
indentation: indent_level,
|
PlainListItem {
|
||||||
bullet: bull.into(),
|
source: source.into(),
|
||||||
tag: maybe_tag
|
indentation: indent_level,
|
||||||
.map(|(_ws, item_tag)| item_tag)
|
bullet: bull.into(),
|
||||||
.unwrap_or(Vec::new()),
|
counter: maybe_counter_set,
|
||||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
checkbox: maybe_checkbox.map(|(_, (checkbox_type, source))| {
|
||||||
},
|
(checkbox_type, Into::<&str>::into(source))
|
||||||
|
}),
|
||||||
|
tag: maybe_tag
|
||||||
|
.map(|(_ws, item_tag)| item_tag)
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
pre_blank: PlainListItemPreBlank::try_from(pre_blank)
|
||||||
|
.expect("pre-blank cannot be larger than 2."),
|
||||||
|
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||||
|
},
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[derive(Debug)]
|
||||||
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
enum BulletType {
|
||||||
alt((
|
Ordered,
|
||||||
tag("*"),
|
Unordered,
|
||||||
tag("-"),
|
|
||||||
tag("+"),
|
|
||||||
recognize(tuple((counter, alt((tag("."), tag(")")))))),
|
|
||||||
))(i)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn counter<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn bullet<'b, 'g, 'r, 's>(
|
||||||
alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i)
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, (BulletType, OrgSource<'s>)> {
|
||||||
|
let (remaining, ((bullet_type, _without_space), peek_trailing_space)) = tuple((
|
||||||
|
alt((
|
||||||
|
map(tag("*"), |bull| (BulletType::Unordered, bull)),
|
||||||
|
map(tag("-"), |bull| (BulletType::Unordered, bull)),
|
||||||
|
map(tag("+"), |bull| (BulletType::Unordered, bull)),
|
||||||
|
map(
|
||||||
|
recognize(tuple((
|
||||||
|
parser_with_context!(counter)(context),
|
||||||
|
alt((tag("."), tag(")"))),
|
||||||
|
))),
|
||||||
|
|bull| (BulletType::Ordered, bull),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
peek(space0),
|
||||||
|
))(input)?;
|
||||||
|
let with_space = input.get_until_end_of(peek_trailing_space);
|
||||||
|
Ok((remaining, (bullet_type, with_space)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn counter<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
if context.get_global_settings().list_allow_alphabetical {
|
||||||
|
alt((
|
||||||
|
recognize(one_of(
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
)),
|
||||||
|
digit1,
|
||||||
|
))(input)
|
||||||
|
} else {
|
||||||
|
digit1(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PlainListItemCounter> {
|
||||||
|
alt((
|
||||||
|
map(
|
||||||
|
one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
|
||||||
|
|letter| {
|
||||||
|
let num = match letter {
|
||||||
|
'a'..='z' => (letter as u32) - ('a' as u32) + 1,
|
||||||
|
'A'..='Z' => (letter as u32) - ('A' as u32) + 1,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
PlainListItemCounter::try_from(num)
|
||||||
|
.expect("Counter set value should be between 1 and 26 inclusive.")
|
||||||
|
},
|
||||||
|
),
|
||||||
|
map(digit1, |num: OrgSource<'_>| {
|
||||||
|
Into::<&str>::into(num)
|
||||||
|
.parse()
|
||||||
|
.expect("digit1 must parse to a number.")
|
||||||
|
}),
|
||||||
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -255,7 +363,7 @@ fn plain_list_end<'b, 'g, 'r, 's>(
|
|||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn plain_list_item_end(indent_level: usize) -> impl ContextMatcher {
|
const fn plain_list_item_end(indent_level: IndentationLevel) -> impl ContextMatcher {
|
||||||
let line_indented_lte_matcher = line_indented_lte(indent_level);
|
let line_indented_lte_matcher = line_indented_lte(indent_level);
|
||||||
move |context, input: OrgSource<'_>| {
|
move |context, input: OrgSource<'_>| {
|
||||||
_plain_list_item_end(context, input, &line_indented_lte_matcher)
|
_plain_list_item_end(context, input, &line_indented_lte_matcher)
|
||||||
@@ -278,20 +386,23 @@ fn _plain_list_item_end<'b, 'g, 'r, 's>(
|
|||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn line_indented_lte(indent_level: usize) -> impl ContextMatcher {
|
const fn line_indented_lte(indent_level: IndentationLevel) -> impl ContextMatcher {
|
||||||
move |context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level)
|
move |context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _line_indented_lte<'b, 'g, 'r, 's>(
|
fn _line_indented_lte<'b, 'g, 'r, 's>(
|
||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
indent_level: usize,
|
indent_level: IndentationLevel,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let matched = recognize(verify(
|
let matched = recognize(verify(
|
||||||
tuple((space0::<OrgSource<'_>, _>, non_whitespace_character)),
|
tuple((
|
||||||
|
parser_with_context!(indentation_level)(context),
|
||||||
|
non_whitespace_character,
|
||||||
|
)),
|
||||||
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|
||||||
|(_space0, _anychar)| _space0.len() <= indent_level,
|
|((indentation_level, _leading_whitespace), _anychar)| *indentation_level <= indent_level,
|
||||||
))(input)?;
|
))(input)?;
|
||||||
|
|
||||||
Ok(matched)
|
Ok(matched)
|
||||||
@@ -324,22 +435,22 @@ fn item_tag_end<'b, 'g, 'r, 's>(
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
alt((
|
alt((item_tag_divider, line_ending))(input)
|
||||||
recognize(tuple((
|
}
|
||||||
item_tag_divider,
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
recognize(tuple((
|
||||||
|
one_of(" \t"),
|
||||||
|
tag("::"),
|
||||||
|
peek(tuple((
|
||||||
opt(tuple((
|
opt(tuple((
|
||||||
peek(one_of(" \t")),
|
peek(one_of(" \t")),
|
||||||
many_till(anychar, peek(alt((item_tag_divider, line_ending, eof)))),
|
many_till(anychar, peek(alt((item_tag_divider, line_ending, eof)))),
|
||||||
))),
|
))),
|
||||||
alt((line_ending, eof)),
|
alt((line_ending, eof)),
|
||||||
))),
|
))),
|
||||||
line_ending,
|
)))(input)
|
||||||
))(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
|
||||||
fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|
||||||
recognize(tuple((one_of(" \t"), tag("::"))))(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -363,6 +474,18 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
|
|||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn item_checkbox<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, (CheckboxType, OrgSource<'s>)> {
|
||||||
|
alt((
|
||||||
|
map(
|
||||||
|
recognize(tuple((tag("["), org_space, tag("]")))),
|
||||||
|
|capture| (CheckboxType::Off, capture),
|
||||||
|
),
|
||||||
|
map(tag("[-]"), |capture| (CheckboxType::Trans, capture)),
|
||||||
|
map(tag("[X]"), |capture| (CheckboxType::On, capture)),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
|
fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
@@ -381,7 +504,7 @@ mod tests {
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::types::Source;
|
use crate::types::GetStandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_list_item_empty() {
|
fn plain_list_item_empty() {
|
||||||
@@ -390,9 +513,9 @@ mod tests {
|
|||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
||||||
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1.");
|
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -402,9 +525,9 @@ mod tests {
|
|||||||
let initial_context = ContextElement::document_context();
|
let initial_context = ContextElement::document_context();
|
||||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
||||||
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1. foo");
|
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -416,7 +539,7 @@ mod tests {
|
|||||||
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||||
let (remaining, result) = plain_list_matcher(input).unwrap();
|
let (remaining, result) = plain_list_matcher(input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1.");
|
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -428,7 +551,7 @@ mod tests {
|
|||||||
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
let plain_list_matcher = parser_with_context!(plain_list)(&initial_context);
|
||||||
let (remaining, result) = plain_list_matcher(input).unwrap();
|
let (remaining, result) = plain_list_matcher(input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.source, "1. foo");
|
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -475,7 +598,7 @@ mod tests {
|
|||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
|
assert_eq!(Into::<&str>::into(remaining), " ipsum\n");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_source(),
|
result.get_standard_properties().get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
2. bar
|
2. bar
|
||||||
baz
|
baz
|
||||||
@@ -503,7 +626,7 @@ baz"#,
|
|||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "baz");
|
assert_eq!(Into::<&str>::into(remaining), "baz");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_source(),
|
result.get_standard_properties().get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
1. bar
|
1. bar
|
||||||
|
|
||||||
@@ -536,7 +659,7 @@ dolar"#,
|
|||||||
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
plain_list_matcher(input).expect("Should parse the plain list successfully.");
|
||||||
assert_eq!(Into::<&str>::into(remaining), "dolar");
|
assert_eq!(Into::<&str>::into(remaining), "dolar");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.get_source(),
|
result.get_standard_properties().get_source(),
|
||||||
r#"1. foo
|
r#"1. foo
|
||||||
|
|
||||||
bar
|
bar
|
||||||
@@ -558,21 +681,30 @@ dolar"#,
|
|||||||
r#"+
|
r#"+
|
||||||
"#,
|
"#,
|
||||||
);
|
);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_eof() {
|
fn detect_eof() {
|
||||||
let input = OrgSource::new(r#"+"#);
|
let input = OrgSource::new(r#"+"#);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_no_gap() {
|
fn detect_no_gap() {
|
||||||
let input = OrgSource::new(r#"+foo"#);
|
let input = OrgSource::new(r#"+foo"#);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
// Since there is no whitespace after the '+' this is a paragraph, not a plain list.
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
}
|
}
|
||||||
@@ -580,7 +712,10 @@ dolar"#,
|
|||||||
#[test]
|
#[test]
|
||||||
fn detect_with_gap() {
|
fn detect_with_gap() {
|
||||||
let input = OrgSource::new(r#"+ foo"#);
|
let input = OrgSource::new(r#"+ foo"#);
|
||||||
let result = detect_plain_list(input);
|
let global_settings = GlobalSettings::default();
|
||||||
|
let initial_context = ContextElement::document_context();
|
||||||
|
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||||
|
let result = detect_plain_list(&initial_context, input);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::is_not;
|
||||||
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::combinator::map;
|
use nom::character::complete::line_ending;
|
||||||
|
use nom::character::complete::one_of;
|
||||||
|
use nom::combinator::eof;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
|
use nom::multi::many1;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::radio_link::RematchObject;
|
use super::radio_link::RematchObject;
|
||||||
use super::util::exit_matcher_parser;
|
use super::util::exit_matcher_parser;
|
||||||
|
use super::util::get_consumed;
|
||||||
|
use super::util::org_space_or_line_ending;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
|
use crate::error::CustomError;
|
||||||
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::types::Object;
|
use crate::types::Object;
|
||||||
use crate::types::PlainText;
|
use crate::types::PlainText;
|
||||||
@@ -72,11 +81,58 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Object<'s>> {
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
map(tag(self.source), |s| {
|
let mut remaining = input;
|
||||||
|
let mut goal = self.source;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if goal.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
|
||||||
|
match is_not_whitespace {
|
||||||
|
Ok((new_goal, payload)) => {
|
||||||
|
let (new_remaining, _) = tuple((
|
||||||
|
tag_no_case(payload),
|
||||||
|
// TODO: Test to see what the REAL condition is. Checking for not-alphabetic works fine for now, but the real criteria might be something like the plain text exit matcher.
|
||||||
|
peek(alt((
|
||||||
|
recognize(verify(anychar, |c| !c.is_alphanumeric())),
|
||||||
|
eof,
|
||||||
|
))),
|
||||||
|
))(remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
goal = new_goal;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_whitespace = recognize(many1(alt((
|
||||||
|
recognize(one_of::<&str, &str, CustomError<_>>(" \t")),
|
||||||
|
line_ending,
|
||||||
|
))))(goal);
|
||||||
|
match is_whitespace {
|
||||||
|
Ok((new_goal, _)) => {
|
||||||
|
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
goal = new_goal;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Target does not match.".into(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
Object::PlainText(PlainText {
|
Object::PlainText(PlainText {
|
||||||
source: Into::<&str>::into(s),
|
source: Into::<&str>::into(source),
|
||||||
})
|
}),
|
||||||
})(input)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +146,7 @@ mod tests {
|
|||||||
use crate::context::GlobalSettings;
|
use crate::context::GlobalSettings;
|
||||||
use crate::context::List;
|
use crate::context::List;
|
||||||
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
|
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
|
||||||
use crate::types::Source;
|
use crate::types::GetStandardProperties;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_text_simple() {
|
fn plain_text_simple() {
|
||||||
@@ -103,6 +159,9 @@ mod tests {
|
|||||||
))(&initial_context);
|
))(&initial_context);
|
||||||
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
|
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(result.get_source(), Into::<&str>::into(input));
|
assert_eq!(
|
||||||
|
result.get_standard_properties().get_source(),
|
||||||
|
Into::<&str>::into(input)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::is_not;
|
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::tag_no_case;
|
use nom::bytes::complete::tag_no_case;
|
||||||
use nom::character::complete::line_ending;
|
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::eof;
|
use nom::combinator::map;
|
||||||
use nom::multi::separated_list1;
|
use nom::multi::many1;
|
||||||
use nom::sequence::tuple;
|
use nom::sequence::tuple;
|
||||||
|
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
|
use super::timestamp::timestamp;
|
||||||
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
|
||||||
|
use super::util::org_line_ending;
|
||||||
|
use crate::context::parser_with_context;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
use crate::parser::util::start_of_line;
|
use crate::parser::util::start_of_line;
|
||||||
use crate::types::Planning;
|
use crate::types::Planning;
|
||||||
|
use crate::types::Timestamp;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn planning<'b, 'g, 'r, 's>(
|
pub(crate) fn planning<'b, 'g, 'r, 's>(
|
||||||
@@ -24,31 +26,63 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, Planning<'s>> {
|
) -> Res<OrgSource<'s>, Planning<'s>> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _leading_whitespace) = space0(input)?;
|
let (remaining, _leading_whitespace) = space0(input)?;
|
||||||
let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?;
|
let (remaining, planning_parameters) =
|
||||||
let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
|
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
|
||||||
|
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_ws) =
|
let (remaining, _trailing_ws) =
|
||||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|
||||||
|
let mut scheduled = None;
|
||||||
|
let mut deadline = None;
|
||||||
|
let mut closed = None;
|
||||||
|
|
||||||
|
for (timestamp_type, timestamp) in planning_parameters.into_iter() {
|
||||||
|
match timestamp_type {
|
||||||
|
PlanningTimestampType::Scheduled => {
|
||||||
|
scheduled = Some(timestamp);
|
||||||
|
}
|
||||||
|
PlanningTimestampType::Deadline => {
|
||||||
|
deadline = Some(timestamp);
|
||||||
|
}
|
||||||
|
PlanningTimestampType::Closed => {
|
||||||
|
closed = Some(timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
Planning {
|
Planning {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
scheduled,
|
||||||
|
deadline,
|
||||||
|
closed,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PlanningTimestampType {
|
||||||
|
Scheduled,
|
||||||
|
Deadline,
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn planning_parameter<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
fn planning_parameter<'b, 'g, 'r, 's>(
|
||||||
let (remaining, _planning_type) = alt((
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
tag_no_case("DEADLINE"),
|
input: OrgSource<'s>,
|
||||||
tag_no_case("SCHEDULED"),
|
) -> Res<OrgSource<'s>, (PlanningTimestampType, Timestamp<'s>)> {
|
||||||
tag_no_case("CLOSED"),
|
let (remaining, planning_type) = alt((
|
||||||
|
map(tag_no_case("DEADLINE"), |_| PlanningTimestampType::Deadline),
|
||||||
|
map(tag_no_case("SCHEDULED"), |_| {
|
||||||
|
PlanningTimestampType::Scheduled
|
||||||
|
}),
|
||||||
|
map(tag_no_case("CLOSED"), |_| PlanningTimestampType::Closed),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
let (remaining, _gap) = tuple((tag(":"), space1))(remaining)?;
|
let (remaining, _gap) = tuple((tag(":"), space1))(remaining)?;
|
||||||
// TODO: Make this invoke the real timestamp parser.
|
let (remaining, timestamp) = timestamp(context, remaining)?;
|
||||||
let (remaining, _timestamp) = tuple((tag("<"), is_not("\r\n>"), tag(">")))(remaining)?;
|
Ok((remaining, (planning_type, timestamp)))
|
||||||
let source = get_consumed(input, remaining);
|
|
||||||
Ok((remaining, source))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ fn node_property<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, NodeProperty<'s>> {
|
) -> Res<OrgSource<'s>, NodeProperty<'s>> {
|
||||||
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, _name, _close_colon)) =
|
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, name, _close_colon)) =
|
||||||
tuple((
|
tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
space0,
|
space0,
|
||||||
@@ -120,6 +120,7 @@ fn node_property<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
NodeProperty {
|
NodeProperty {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
name: Into::<&str>::into(name),
|
||||||
value: None,
|
value: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
@@ -132,6 +133,7 @@ fn node_property<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
NodeProperty {
|
NodeProperty {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
name: Into::<&str>::into(name),
|
||||||
value: Some(value.into()),
|
value: Some(value.into()),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -62,6 +62,22 @@ pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
|
|||||||
remaining = new_remaining;
|
remaining = new_remaining;
|
||||||
new_matches.push(new_match);
|
new_matches.push(new_match);
|
||||||
}
|
}
|
||||||
|
Object::Italic(italic) => {
|
||||||
|
let (new_remaining, new_match) = italic.rematch_object(context, remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
new_matches.push(new_match);
|
||||||
|
}
|
||||||
|
Object::Underline(underline) => {
|
||||||
|
let (new_remaining, new_match) = underline.rematch_object(context, remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
new_matches.push(new_match);
|
||||||
|
}
|
||||||
|
Object::StrikeThrough(strikethrough) => {
|
||||||
|
let (new_remaining, new_match) =
|
||||||
|
strikethrough.rematch_object(context, remaining)?;
|
||||||
|
remaining = new_remaining;
|
||||||
|
new_matches.push(new_match);
|
||||||
|
}
|
||||||
Object::PlainText(plaintext) => {
|
Object::PlainText(plaintext) => {
|
||||||
let (new_remaining, new_match) = plaintext.rematch_object(context, remaining)?;
|
let (new_remaining, new_match) = plaintext.rematch_object(context, remaining)?;
|
||||||
remaining = new_remaining;
|
remaining = new_remaining;
|
||||||
@@ -135,8 +151,8 @@ mod tests {
|
|||||||
use crate::parser::element_parser::element;
|
use crate::parser::element_parser::element;
|
||||||
use crate::types::Bold;
|
use crate::types::Bold;
|
||||||
use crate::types::Element;
|
use crate::types::Element;
|
||||||
|
use crate::types::GetStandardProperties;
|
||||||
use crate::types::PlainText;
|
use crate::types::PlainText;
|
||||||
use crate::types::Source;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_text_radio_target() {
|
fn plain_text_radio_target() {
|
||||||
@@ -156,7 +172,10 @@ mod tests {
|
|||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "foo bar baz");
|
assert_eq!(
|
||||||
|
first_paragraph.get_standard_properties().get_source(),
|
||||||
|
"foo bar baz"
|
||||||
|
);
|
||||||
assert_eq!(first_paragraph.children.len(), 3);
|
assert_eq!(first_paragraph.children.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_paragraph
|
first_paragraph
|
||||||
@@ -192,7 +211,10 @@ mod tests {
|
|||||||
_ => panic!("Should be a paragraph!"),
|
_ => panic!("Should be a paragraph!"),
|
||||||
};
|
};
|
||||||
assert_eq!(Into::<&str>::into(remaining), "");
|
assert_eq!(Into::<&str>::into(remaining), "");
|
||||||
assert_eq!(first_paragraph.get_source(), "foo *bar* baz");
|
assert_eq!(
|
||||||
|
first_paragraph.get_standard_properties().get_source(),
|
||||||
|
"foo *bar* baz"
|
||||||
|
);
|
||||||
assert_eq!(first_paragraph.children.len(), 3);
|
assert_eq!(first_paragraph.children.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
first_paragraph
|
first_paragraph
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use nom::branch::alt;
|
|||||||
use nom::bytes::complete::escaped;
|
use nom::bytes::complete::escaped;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::bytes::complete::take_till1;
|
use nom::bytes::complete::take_till1;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::anychar;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
|
||||||
@@ -78,11 +78,11 @@ fn pathreg<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, path) = escaped(
|
let (remaining, path) = escaped(
|
||||||
take_till1(|c| match c {
|
take_till1(|c| match c {
|
||||||
'\\' | ']' => true,
|
'\\' | '[' | ']' => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}),
|
}),
|
||||||
'\\',
|
'\\',
|
||||||
one_of(r#"]"#),
|
anychar,
|
||||||
)(input)?;
|
)(input)?;
|
||||||
Ok((remaining, path))
|
Ok((remaining, path))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ use crate::context::ContextElement;
|
|||||||
use crate::context::ContextMatcher;
|
use crate::context::ContextMatcher;
|
||||||
use crate::context::ExitClass;
|
use crate::context::ExitClass;
|
||||||
use crate::context::ExitMatcherNode;
|
use crate::context::ExitMatcherNode;
|
||||||
|
use crate::context::Matcher;
|
||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
@@ -112,6 +113,10 @@ fn script_body<'b, 'g, 'r, 's>(
|
|||||||
map(parser_with_context!(script_with_braces)(context), |body| {
|
map(parser_with_context!(script_with_braces)(context), |body| {
|
||||||
ScriptBody::WithBraces(body.into())
|
ScriptBody::WithBraces(body.into())
|
||||||
}),
|
}),
|
||||||
|
map(
|
||||||
|
parser_with_context!(script_with_parenthesis)(context),
|
||||||
|
|body| ScriptBody::Braceless(body.into()),
|
||||||
|
),
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,3 +204,49 @@ fn _script_with_braces_end<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
tag("}")(input)
|
tag("}")(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn script_with_parenthesis<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let (remaining, _) = tag("(")(input)?;
|
||||||
|
let exit_with_depth = script_with_parenthesis_end(remaining.get_parenthesis_depth());
|
||||||
|
|
||||||
|
let (remaining, _) = many_till(
|
||||||
|
anychar,
|
||||||
|
alt((
|
||||||
|
peek(exit_with_depth),
|
||||||
|
parser_with_context!(exit_matcher_parser)(context),
|
||||||
|
)),
|
||||||
|
)(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = tag(")")(remaining)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((remaining, source))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script_with_parenthesis_end(starting_parenthesis_depth: BracketDepth) -> impl Matcher {
|
||||||
|
move |input: OrgSource<'_>| _script_with_parenthesis_end(input, starting_parenthesis_depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn _script_with_parenthesis_end<'s>(
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
starting_parenthesis_depth: BracketDepth,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
let current_depth = input.get_parenthesis_depth() - starting_parenthesis_depth;
|
||||||
|
if current_depth < 0 {
|
||||||
|
// This shouldn't be possible because if depth is 0 then a closing bracket should end the citation.
|
||||||
|
unreachable!("Exceeded citation key suffix bracket depth.")
|
||||||
|
}
|
||||||
|
if current_depth == 0 {
|
||||||
|
let close_parenthesis = tag::<&str, OrgSource<'_>, CustomError<OrgSource<'_>>>(")")(input);
|
||||||
|
if close_parenthesis.is_ok() {
|
||||||
|
return close_parenthesis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"No script parenthesis end.".into(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use nom::bytes::complete::is_not;
|
|||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::space0;
|
use nom::character::complete::space0;
|
||||||
use nom::combinator::eof;
|
|
||||||
use nom::combinator::not;
|
use nom::combinator::not;
|
||||||
|
use nom::combinator::opt;
|
||||||
use nom::combinator::peek;
|
use nom::combinator::peek;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
@@ -17,6 +17,7 @@ use super::keyword::table_formula_keyword;
|
|||||||
use super::object_parser::table_cell_set_object;
|
use super::object_parser::table_cell_set_object;
|
||||||
use super::org_source::OrgSource;
|
use super::org_source::OrgSource;
|
||||||
use super::util::exit_matcher_parser;
|
use super::util::exit_matcher_parser;
|
||||||
|
use super::util::org_line_ending;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::ExitClass;
|
use crate::context::ExitClass;
|
||||||
@@ -105,7 +106,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
||||||
start_of_line(input)?;
|
start_of_line(input)?;
|
||||||
let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?;
|
let (remaining, _) = tuple((space0, tag("|-"), opt(is_not("\r\n")), org_line_ending))(input)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -125,7 +126,7 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
|
|||||||
let (remaining, _) = tuple((space0, tag("|")))(input)?;
|
let (remaining, _) = tuple((space0, tag("|")))(input)?;
|
||||||
let (remaining, children) =
|
let (remaining, children) =
|
||||||
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
|
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
|
||||||
let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
|
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use super::org_source::OrgSource;
|
|||||||
use super::radio_link::RematchObject;
|
use super::radio_link::RematchObject;
|
||||||
use super::util::in_object_section;
|
use super::util::in_object_section;
|
||||||
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
|
||||||
|
use super::util::start_of_line;
|
||||||
use crate::context::parser_with_context;
|
use crate::context::parser_with_context;
|
||||||
use crate::context::ContextElement;
|
use crate::context::ContextElement;
|
||||||
use crate::context::ContextMatcher;
|
use crate::context::ContextMatcher;
|
||||||
@@ -64,8 +65,7 @@ fn bold<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Bold<'s>> {
|
) -> Res<OrgSource<'s>, Bold<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("*");
|
let (remaining, children) = text_markup_object("*")(context, input)?;
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -81,8 +81,7 @@ fn italic<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Italic<'s>> {
|
) -> Res<OrgSource<'s>, Italic<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("/");
|
let (remaining, children) = text_markup_object("/")(context, input)?;
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -98,8 +97,7 @@ fn underline<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Underline<'s>> {
|
) -> Res<OrgSource<'s>, Underline<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("_");
|
let (remaining, children) = text_markup_object("_")(context, input)?;
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -115,8 +113,7 @@ fn strike_through<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
|
) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
|
||||||
let text_markup_object_specialized = text_markup_object("+");
|
let (remaining, children) = text_markup_object("+")(context, input)?;
|
||||||
let (remaining, children) = text_markup_object_specialized(context, input)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -132,8 +129,7 @@ fn verbatim<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Verbatim<'s>> {
|
) -> Res<OrgSource<'s>, Verbatim<'s>> {
|
||||||
let text_markup_string_specialized = text_markup_string("=");
|
let (remaining, contents) = text_markup_string("=")(context, input)?;
|
||||||
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -149,8 +145,7 @@ fn code<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Code<'s>> {
|
) -> Res<OrgSource<'s>, Code<'s>> {
|
||||||
let text_markup_string_specialized = text_markup_string("~");
|
let (remaining, contents) = text_markup_string("~")(context, input)?;
|
||||||
let (remaining, contents) = text_markup_string_specialized(context, input)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
@@ -168,8 +163,7 @@ fn text_markup_object<'c>(
|
|||||||
OrgSource<'s>,
|
OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Vec<Object<'s>>>
|
) -> Res<OrgSource<'s>, Vec<Object<'s>>>
|
||||||
+ 'c {
|
+ 'c {
|
||||||
let marker_symbol = marker_symbol.to_owned();
|
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol)
|
||||||
move |context, input: OrgSource<'_>| _text_markup_object(context, input, marker_symbol.as_str())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -188,7 +182,7 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
|
|||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) =
|
let (remaining, _peek_not_whitespace) =
|
||||||
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
|
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
|
||||||
let text_markup_end_specialized = text_markup_end(open.into());
|
let text_markup_end_specialized = text_markup_end(open.into(), remaining.get_byte_offset());
|
||||||
let contexts = [
|
let contexts = [
|
||||||
ContextElement::ContextObject(marker_symbol),
|
ContextElement::ContextObject(marker_symbol),
|
||||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
@@ -250,7 +244,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
|
|||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) =
|
let (remaining, _peek_not_whitespace) =
|
||||||
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
|
peek(verify(anychar, |c| !c.is_whitespace() && *c != '\u{200B}'))(remaining)?;
|
||||||
let text_markup_end_specialized = text_markup_end(open.into());
|
let text_markup_end_specialized = text_markup_end(open.into(), remaining.get_byte_offset());
|
||||||
let contexts = [
|
let contexts = [
|
||||||
ContextElement::ContextObject(marker_symbol),
|
ContextElement::ContextObject(marker_symbol),
|
||||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
@@ -292,16 +286,22 @@ fn pre<'b, 'g, 'r, 's>(
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
|
if start_of_line(input).is_ok() {
|
||||||
|
return Ok((input, ()));
|
||||||
|
}
|
||||||
|
if preceded_by_whitespace(true)(input).is_ok() {
|
||||||
|
return Ok((input, ()));
|
||||||
|
}
|
||||||
let preceding_character = input.get_preceding_character();
|
let preceding_character = input.get_preceding_character();
|
||||||
match preceding_character {
|
match preceding_character {
|
||||||
// If None, we are at the start of the file which is technically the beginning of a line.
|
// If None, we are at the start of the file which is technically the beginning of a line.
|
||||||
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
|
Some('-') | Some('(') | Some('{') | Some('\'') | Some('"') => {}
|
||||||
| Some('{') | Some('\'') | Some('"') | Some('<') => {}
|
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
"Not a valid pre character for text markup.".into(),
|
"Not a valid pre character for text markup.".into(),
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
None => unreachable!(), // None is for start of file, which should already be handled by the start_of_line matcher above.
|
||||||
};
|
};
|
||||||
Ok((input, ()))
|
Ok((input, ()))
|
||||||
}
|
}
|
||||||
@@ -311,12 +311,17 @@ fn post<'b, 'g, 'r, 's>(
|
|||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, ()> {
|
) -> Res<OrgSource<'s>, ()> {
|
||||||
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\"")), line_ending))(input)?;
|
let (remaining, _) = alt((recognize(one_of(" \r\n\t-.,;:!?')}[\"\\")), line_ending))(input)?;
|
||||||
Ok((remaining, ()))
|
Ok((remaining, ()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_markup_end<'c>(marker_symbol: &'c str) -> impl ContextMatcher + 'c {
|
fn text_markup_end<'c>(
|
||||||
move |context, input: OrgSource<'_>| _text_markup_end(context, input, marker_symbol)
|
marker_symbol: &'c str,
|
||||||
|
contents_start_offset: usize,
|
||||||
|
) -> impl ContextMatcher + 'c {
|
||||||
|
move |context, input: OrgSource<'_>| {
|
||||||
|
_text_markup_end(context, input, marker_symbol, contents_start_offset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -324,7 +329,13 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
marker_symbol: &'c str,
|
marker_symbol: &'c str,
|
||||||
|
contents_start_offset: usize,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
if input.get_byte_offset() == contents_start_offset {
|
||||||
|
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||||
|
"Text markup cannot be empty".into(),
|
||||||
|
))));
|
||||||
|
}
|
||||||
not(preceded_by_whitespace(false))(input)?;
|
not(preceded_by_whitespace(false))(input)?;
|
||||||
let (remaining, _marker) = terminated(
|
let (remaining, _marker) = terminated(
|
||||||
tag(marker_symbol),
|
tag(marker_symbol),
|
||||||
@@ -354,6 +365,66 @@ impl<'x> RematchObject<'x> for Bold<'x> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'x> RematchObject<'x> for Italic<'x> {
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn rematch_object<'b, 'g, 'r, 's>(
|
||||||
|
&'x self,
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
|
let (remaining, children) =
|
||||||
|
_rematch_text_markup_object(_context, input, "/", &self.children)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Object::Italic(Italic {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'x> RematchObject<'x> for Underline<'x> {
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn rematch_object<'b, 'g, 'r, 's>(
|
||||||
|
&'x self,
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
|
let (remaining, children) =
|
||||||
|
_rematch_text_markup_object(_context, input, "_", &self.children)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Object::Underline(Underline {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'x> RematchObject<'x> for StrikeThrough<'x> {
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn rematch_object<'b, 'g, 'r, 's>(
|
||||||
|
&'x self,
|
||||||
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, Object<'s>> {
|
||||||
|
let (remaining, children) =
|
||||||
|
_rematch_text_markup_object(_context, input, "+", &self.children)?;
|
||||||
|
let source = get_consumed(input, remaining);
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Object::StrikeThrough(StrikeThrough {
|
||||||
|
source: source.into(),
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
@@ -364,7 +435,7 @@ fn _rematch_text_markup_object<'b, 'g, 'r, 's, 'x>(
|
|||||||
let (remaining, _) = pre(context, input)?;
|
let (remaining, _) = pre(context, input)?;
|
||||||
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
let (remaining, open) = tag(marker_symbol)(remaining)?;
|
||||||
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
let (remaining, _peek_not_whitespace) = peek(not(multispace1))(remaining)?;
|
||||||
let text_markup_end_specialized = text_markup_end(open.into());
|
let text_markup_end_specialized = text_markup_end(open.into(), remaining.get_byte_offset());
|
||||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Gamma,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &text_markup_end_specialized,
|
exit_matcher: &text_markup_end_specialized,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use nom::character::complete::anychar;
|
|||||||
use nom::character::complete::digit1;
|
use nom::character::complete::digit1;
|
||||||
use nom::character::complete::one_of;
|
use nom::character::complete::one_of;
|
||||||
use nom::character::complete::space1;
|
use nom::character::complete::space1;
|
||||||
|
use nom::combinator::map;
|
||||||
use nom::combinator::opt;
|
use nom::combinator::opt;
|
||||||
use nom::combinator::recognize;
|
use nom::combinator::recognize;
|
||||||
use nom::combinator::verify;
|
use nom::combinator::verify;
|
||||||
@@ -20,7 +21,21 @@ use crate::context::ExitMatcherNode;
|
|||||||
use crate::context::RefContext;
|
use crate::context::RefContext;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
use crate::parser::util::get_consumed;
|
use crate::parser::util::get_consumed;
|
||||||
|
use crate::types::Date;
|
||||||
|
use crate::types::DayOfMonth;
|
||||||
|
use crate::types::Hour;
|
||||||
|
use crate::types::Minute;
|
||||||
|
use crate::types::Month;
|
||||||
|
use crate::types::Repeater;
|
||||||
|
use crate::types::RepeaterType;
|
||||||
|
use crate::types::Time;
|
||||||
|
use crate::types::TimeUnit;
|
||||||
use crate::types::Timestamp;
|
use crate::types::Timestamp;
|
||||||
|
use crate::types::TimestampRangeType;
|
||||||
|
use crate::types::TimestampType;
|
||||||
|
use crate::types::WarningDelay;
|
||||||
|
use crate::types::WarningDelayType;
|
||||||
|
use crate::types::Year;
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
pub(crate) fn timestamp<'b, 'g, 'r, 's>(
|
pub(crate) fn timestamp<'b, 'g, 'r, 's>(
|
||||||
@@ -56,6 +71,14 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Timestamp {
|
Timestamp {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
timestamp_type: TimestampType::Diary,
|
||||||
|
range_type: TimestampRangeType::None,
|
||||||
|
start: None,
|
||||||
|
end: None,
|
||||||
|
start_time: None,
|
||||||
|
end_time: None,
|
||||||
|
repeater: None,
|
||||||
|
warning_delay: None,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -96,17 +119,26 @@ fn active_timestamp<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("<")(input)?;
|
let (remaining, _) = tag("<")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, start) = date(context, remaining)?;
|
||||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Gamma,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &active_time_rest_end,
|
exit_matcher: &active_time_rest_end,
|
||||||
});
|
});
|
||||||
let time_context = context.with_additional_node(&time_context);
|
let time_context = context.with_additional_node(&time_context);
|
||||||
let (remaining, _time) =
|
let (remaining, time) = opt(tuple((
|
||||||
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
|
space1,
|
||||||
let (remaining, _repeater) =
|
parser_with_context!(time(true))(&time_context),
|
||||||
|
)))(remaining)?;
|
||||||
|
let remaining = if time.is_none() {
|
||||||
|
// Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'.
|
||||||
|
let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?;
|
||||||
|
remain
|
||||||
|
} else {
|
||||||
|
remaining
|
||||||
|
};
|
||||||
|
let (remaining, repeater) =
|
||||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
let (remaining, _warning_delay) = opt(tuple((
|
let (remaining, warning_delay) = opt(tuple((
|
||||||
space1,
|
space1,
|
||||||
parser_with_context!(warning_delay)(context),
|
parser_with_context!(warning_delay)(context),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
@@ -120,6 +152,14 @@ fn active_timestamp<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Timestamp {
|
Timestamp {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
timestamp_type: TimestampType::Active,
|
||||||
|
range_type: TimestampRangeType::None,
|
||||||
|
start: Some(start.clone()),
|
||||||
|
end: Some(start),
|
||||||
|
start_time: time.as_ref().map(|(_, time)| time.clone()),
|
||||||
|
end_time: time.map(|(_, time)| time),
|
||||||
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -130,17 +170,26 @@ fn inactive_timestamp<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, start) = date(context, remaining)?;
|
||||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Gamma,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &inactive_time_rest_end,
|
exit_matcher: &inactive_time_rest_end,
|
||||||
});
|
});
|
||||||
let time_context = context.with_additional_node(&time_context);
|
let time_context = context.with_additional_node(&time_context);
|
||||||
let (remaining, _time) =
|
let (remaining, time) = opt(tuple((
|
||||||
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
|
space1,
|
||||||
let (remaining, _repeater) =
|
parser_with_context!(time(true))(&time_context),
|
||||||
|
)))(remaining)?;
|
||||||
|
let remaining = if time.is_none() {
|
||||||
|
// Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'.
|
||||||
|
let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?;
|
||||||
|
remain
|
||||||
|
} else {
|
||||||
|
remaining
|
||||||
|
};
|
||||||
|
let (remaining, repeater) =
|
||||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
let (remaining, _warning_delay) = opt(tuple((
|
let (remaining, warning_delay) = opt(tuple((
|
||||||
space1,
|
space1,
|
||||||
parser_with_context!(warning_delay)(context),
|
parser_with_context!(warning_delay)(context),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
@@ -154,6 +203,14 @@ fn inactive_timestamp<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Timestamp {
|
Timestamp {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
timestamp_type: TimestampType::Inactive,
|
||||||
|
range_type: TimestampRangeType::None,
|
||||||
|
start: Some(start.clone()),
|
||||||
|
end: Some(start),
|
||||||
|
start_time: time.as_ref().map(|(_, time)| time.clone()),
|
||||||
|
end_time: time.map(|(_, time)| time),
|
||||||
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -163,10 +220,10 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _first_timestamp) = active_timestamp(context, input)?;
|
let (remaining, first_timestamp) = active_timestamp(context, input)?;
|
||||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||||
let (remaining, _separator) = tag("--")(remaining)?;
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
let (remaining, _second_timestamp) = active_timestamp(context, remaining)?;
|
let (remaining, second_timestamp) = active_timestamp(context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, _trailing_whitespace) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
@@ -176,6 +233,16 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Timestamp {
|
Timestamp {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
timestamp_type: TimestampType::ActiveRange,
|
||||||
|
range_type: TimestampRangeType::DateRange,
|
||||||
|
start: first_timestamp.start,
|
||||||
|
end: second_timestamp.end,
|
||||||
|
start_time: first_timestamp.start_time,
|
||||||
|
end_time: second_timestamp.end_time,
|
||||||
|
repeater: first_timestamp.repeater.or(second_timestamp.repeater),
|
||||||
|
warning_delay: first_timestamp
|
||||||
|
.warning_delay
|
||||||
|
.or(second_timestamp.warning_delay),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -186,7 +253,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("<")(input)?;
|
let (remaining, _) = tag("<")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, start_date) = date(context, remaining)?;
|
||||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Gamma,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &active_time_rest_end,
|
exit_matcher: &active_time_rest_end,
|
||||||
@@ -197,13 +264,15 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
exit_matcher: &time_range_rest_end,
|
exit_matcher: &time_range_rest_end,
|
||||||
});
|
});
|
||||||
let first_time_context = time_context.with_additional_node(&first_time_context);
|
let first_time_context = time_context.with_additional_node(&first_time_context);
|
||||||
let (remaining, _first_time) =
|
let (remaining, (_, first_time)) = tuple((
|
||||||
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
|
space1,
|
||||||
|
parser_with_context!(time(false))(&first_time_context),
|
||||||
|
))(remaining)?;
|
||||||
let (remaining, _) = tag("-")(remaining)?;
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
|
let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?;
|
||||||
let (remaining, _repeater) =
|
let (remaining, repeater) =
|
||||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
let (remaining, _warning_delay) = opt(tuple((
|
let (remaining, warning_delay) = opt(tuple((
|
||||||
space1,
|
space1,
|
||||||
parser_with_context!(warning_delay)(context),
|
parser_with_context!(warning_delay)(context),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
@@ -217,6 +286,14 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Timestamp {
|
Timestamp {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
timestamp_type: TimestampType::ActiveRange,
|
||||||
|
range_type: TimestampRangeType::TimeRange,
|
||||||
|
start: Some(start_date.clone()),
|
||||||
|
end: Some(start_date),
|
||||||
|
start_time: Some(first_time),
|
||||||
|
end_time: Some(second_time),
|
||||||
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -226,10 +303,10 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _first_timestamp) = inactive_timestamp(context, input)?;
|
let (remaining, first_timestamp) = inactive_timestamp(context, input)?;
|
||||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||||
let (remaining, _separator) = tag("--")(remaining)?;
|
let (remaining, _separator) = tag("--")(remaining)?;
|
||||||
let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?;
|
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?;
|
||||||
|
|
||||||
let (remaining, _trailing_whitespace) =
|
let (remaining, _trailing_whitespace) =
|
||||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||||
@@ -239,6 +316,17 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Timestamp {
|
Timestamp {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
|
||||||
|
timestamp_type: TimestampType::InactiveRange,
|
||||||
|
range_type: TimestampRangeType::DateRange,
|
||||||
|
start: first_timestamp.start,
|
||||||
|
end: second_timestamp.end,
|
||||||
|
start_time: first_timestamp.start_time,
|
||||||
|
end_time: second_timestamp.end_time,
|
||||||
|
repeater: first_timestamp.repeater.or(second_timestamp.repeater),
|
||||||
|
warning_delay: first_timestamp
|
||||||
|
.warning_delay
|
||||||
|
.or(second_timestamp.warning_delay),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -249,7 +337,7 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||||
let (remaining, _) = tag("[")(input)?;
|
let (remaining, _) = tag("[")(input)?;
|
||||||
let (remaining, _date) = date(context, remaining)?;
|
let (remaining, start_date) = date(context, remaining)?;
|
||||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Gamma,
|
class: ExitClass::Gamma,
|
||||||
exit_matcher: &inactive_time_rest_end,
|
exit_matcher: &inactive_time_rest_end,
|
||||||
@@ -260,13 +348,15 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
exit_matcher: &time_range_rest_end,
|
exit_matcher: &time_range_rest_end,
|
||||||
});
|
});
|
||||||
let first_time_context = time_context.with_additional_node(&first_time_context);
|
let first_time_context = time_context.with_additional_node(&first_time_context);
|
||||||
let (remaining, _first_time) =
|
let (remaining, (_, first_time)) = tuple((
|
||||||
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
|
space1,
|
||||||
|
parser_with_context!(time(false))(&first_time_context),
|
||||||
|
))(remaining)?;
|
||||||
let (remaining, _) = tag("-")(remaining)?;
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
|
let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?;
|
||||||
let (remaining, _repeater) =
|
let (remaining, repeater) =
|
||||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||||
let (remaining, _warning_delay) = opt(tuple((
|
let (remaining, warning_delay) = opt(tuple((
|
||||||
space1,
|
space1,
|
||||||
parser_with_context!(warning_delay)(context),
|
parser_with_context!(warning_delay)(context),
|
||||||
)))(remaining)?;
|
)))(remaining)?;
|
||||||
@@ -280,6 +370,14 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Timestamp {
|
Timestamp {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
|
timestamp_type: TimestampType::InactiveRange,
|
||||||
|
range_type: TimestampRangeType::TimeRange,
|
||||||
|
start: Some(start_date.clone()),
|
||||||
|
end: Some(start_date),
|
||||||
|
start_time: Some(first_time),
|
||||||
|
end_time: Some(second_time),
|
||||||
|
repeater: repeater.map(|(_, repeater)| repeater),
|
||||||
|
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -288,18 +386,33 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
|||||||
fn date<'b, 'g, 'r, 's>(
|
fn date<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, Date<'s>> {
|
||||||
let (remaining, _year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
|
let (remaining, year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
|
||||||
let (remaining, _) = tag("-")(remaining)?;
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
let (remaining, _month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
|
let (remaining, month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
|
||||||
let (remaining, _) = tag("-")(remaining)?;
|
let (remaining, _) = tag("-")(remaining)?;
|
||||||
let (remaining, _day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
|
let (remaining, day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
|
||||||
day_of_month.len() == 2
|
day_of_month.len() == 2
|
||||||
})(remaining)?;
|
})(remaining)?;
|
||||||
let (remaining, _dayname) =
|
let (remaining, day_name) =
|
||||||
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
|
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
|
||||||
Ok((remaining, source))
|
let year = Year::new(Into::<&str>::into(year))
|
||||||
|
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||||
|
let month = Month::new(Into::<&str>::into(month))
|
||||||
|
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||||
|
let day_of_month = DayOfMonth::new(Into::<&str>::into(day_of_month))
|
||||||
|
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||||
|
|
||||||
|
let date = Date::new(
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
day_of_month,
|
||||||
|
day_name.map(|(_, day_name)| Into::<&str>::into(day_name)),
|
||||||
|
)
|
||||||
|
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||||
|
|
||||||
|
Ok((remaining, date))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -334,20 +447,39 @@ fn dayname_end<'b, 'g, 'r, 's>(
|
|||||||
}))(input)
|
}))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn time<'c>(
|
||||||
|
allow_rest: bool,
|
||||||
|
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Time<'s>>
|
||||||
|
{
|
||||||
|
move |context, input| _time(context, input, allow_rest)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn time<'b, 'g, 'r, 's>(
|
fn _time<'b, 'g, 'r, 's>(
|
||||||
context: RefContext<'b, 'g, 'r, 's>,
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
allow_rest: bool,
|
||||||
let (remaining, _hour) = verify(digit1, |hour: &OrgSource<'_>| {
|
) -> Res<OrgSource<'s>, Time<'s>> {
|
||||||
|
let (remaining, hour) = verify(digit1, |hour: &OrgSource<'_>| {
|
||||||
hour.len() >= 1 && hour.len() <= 2
|
hour.len() >= 1 && hour.len() <= 2
|
||||||
})(input)?;
|
})(input)?;
|
||||||
let (remaining, _) = tag(":")(remaining)?;
|
let (remaining, _) = tag(":")(remaining)?;
|
||||||
let (remaining, _minute) =
|
let (remaining, minute) =
|
||||||
verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?;
|
verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?;
|
||||||
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
|
let (remaining, time_rest) = if allow_rest {
|
||||||
let source = get_consumed(input, remaining);
|
opt(parser_with_context!(time_rest)(context))(remaining)?
|
||||||
Ok((remaining, source))
|
} else {
|
||||||
|
(remaining, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let hour = Hour::new(Into::<&str>::into(hour))
|
||||||
|
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||||
|
let minute = Minute::new(Into::<&str>::into(minute))
|
||||||
|
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||||
|
let time = Time::new(hour, minute, time_rest.map(Into::<&str>::into))
|
||||||
|
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||||
|
|
||||||
|
Ok((remaining, time))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -400,8 +532,10 @@ fn time_range_rest_end<'b, 'g, 'r, 's>(
|
|||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
|
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
|
||||||
let parent_node = context.get_parent().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
|
let parent_node = context.get_parent().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
|
||||||
let exit_contents =
|
let exit_contents = recognize(tuple((
|
||||||
recognize(tuple((tag("-"), parser_with_context!(time)(&parent_node))))(input);
|
tag("-"),
|
||||||
|
parser_with_context!(time(true))(&parent_node),
|
||||||
|
)))(input);
|
||||||
exit_contents
|
exit_contents
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,29 +543,66 @@ fn time_range_rest_end<'b, 'g, 'r, 's>(
|
|||||||
fn repeater<'b, 'g, 'r, 's>(
|
fn repeater<'b, 'g, 'r, 's>(
|
||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, Repeater> {
|
||||||
// + for cumulative type
|
// + for cumulative type
|
||||||
// ++ for catch-up type
|
// ++ for catch-up type
|
||||||
// .+ for restart type
|
// .+ for restart type
|
||||||
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
|
let (remaining, repeater_type) = alt((
|
||||||
let (remaining, _value) = digit1(remaining)?;
|
map(tag("++"), |_| RepeaterType::CatchUp),
|
||||||
|
map(tag("+"), |_| RepeaterType::Cumulative),
|
||||||
|
map(tag(".+"), |_| RepeaterType::Restart),
|
||||||
|
))(input)?;
|
||||||
|
let (remaining, value) = digit1(remaining)?;
|
||||||
|
let value = Into::<&str>::into(value)
|
||||||
|
.parse()
|
||||||
|
.expect("digit1 ensures this will parse as a number.");
|
||||||
// h = hour, d = day, w = week, m = month, y = year
|
// h = hour, d = day, w = week, m = month, y = year
|
||||||
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
|
let (remaining, unit) = alt((
|
||||||
let source = get_consumed(input, remaining);
|
map(tag("h"), |_| TimeUnit::Hour),
|
||||||
Ok((remaining, source))
|
map(tag("d"), |_| TimeUnit::Day),
|
||||||
|
map(tag("w"), |_| TimeUnit::Week),
|
||||||
|
map(tag("m"), |_| TimeUnit::Month),
|
||||||
|
map(tag("y"), |_| TimeUnit::Year),
|
||||||
|
))(remaining)?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
Repeater {
|
||||||
|
repeater_type,
|
||||||
|
value,
|
||||||
|
unit,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn warning_delay<'b, 'g, 'r, 's>(
|
fn warning_delay<'b, 'g, 'r, 's>(
|
||||||
_context: RefContext<'b, 'g, 'r, 's>,
|
_context: RefContext<'b, 'g, 'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
) -> Res<OrgSource<'s>, WarningDelay> {
|
||||||
// - for all type
|
// - for all type
|
||||||
// -- for first type
|
// -- for first type
|
||||||
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
|
let (remaining, warning_delay_type) = alt((
|
||||||
let (remaining, _value) = digit1(remaining)?;
|
map(tag("--"), |_| WarningDelayType::First),
|
||||||
|
map(tag("-"), |_| WarningDelayType::All),
|
||||||
|
))(input)?;
|
||||||
|
let (remaining, value) = digit1(remaining)?;
|
||||||
|
let value = Into::<&str>::into(value)
|
||||||
|
.parse()
|
||||||
|
.expect("digit1 ensures this will parse as a number.");
|
||||||
// h = hour, d = day, w = week, m = month, y = year
|
// h = hour, d = day, w = week, m = month, y = year
|
||||||
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
|
let (remaining, unit) = alt((
|
||||||
let source = get_consumed(input, remaining);
|
map(tag("h"), |_| TimeUnit::Hour),
|
||||||
Ok((remaining, source))
|
map(tag("d"), |_| TimeUnit::Day),
|
||||||
|
map(tag("w"), |_| TimeUnit::Week),
|
||||||
|
map(tag("m"), |_| TimeUnit::Month),
|
||||||
|
map(tag("y"), |_| TimeUnit::Year),
|
||||||
|
))(remaining)?;
|
||||||
|
Ok((
|
||||||
|
remaining,
|
||||||
|
WarningDelay {
|
||||||
|
warning_delay_type,
|
||||||
|
value,
|
||||||
|
unit,
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use crate::types::Document;
|
|
||||||
use crate::types::DocumentElement;
|
|
||||||
use crate::types::Element;
|
|
||||||
use crate::types::Heading;
|
|
||||||
use crate::types::NodeProperty;
|
|
||||||
use crate::types::Object;
|
|
||||||
use crate::types::PlainListItem;
|
|
||||||
use crate::types::Section;
|
|
||||||
use crate::types::TableCell;
|
|
||||||
use crate::types::TableRow;
|
|
||||||
|
|
||||||
pub(crate) enum Token<'r, 's> {
|
|
||||||
Document(&'r Document<'s>),
|
|
||||||
Heading(&'r Heading<'s>),
|
|
||||||
Section(&'r Section<'s>),
|
|
||||||
Object(&'r Object<'s>),
|
|
||||||
Element(&'r Element<'s>),
|
|
||||||
PlainListItem(&'r PlainListItem<'s>),
|
|
||||||
TableRow(&'r TableRow<'s>),
|
|
||||||
TableCell(&'r TableCell<'s>),
|
|
||||||
NodeProperty(&'r NodeProperty<'s>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r, 's> Token<'r, 's> {
|
|
||||||
fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> {
|
|
||||||
match self {
|
|
||||||
Token::Document(document) => Box::new(
|
|
||||||
document
|
|
||||||
.zeroth_section
|
|
||||||
.iter()
|
|
||||||
.map(Token::Section)
|
|
||||||
.chain(document.children.iter().map(Token::Heading)),
|
|
||||||
),
|
|
||||||
Token::Heading(heading) => Box::new(heading.title.iter().map(Token::Object).chain(
|
|
||||||
heading.children.iter().map(|de| match de {
|
|
||||||
DocumentElement::Heading(ref obj) => Token::Heading(obj),
|
|
||||||
DocumentElement::Section(ref obj) => Token::Section(obj),
|
|
||||||
}),
|
|
||||||
)),
|
|
||||||
Token::Section(section) => Box::new(section.children.iter().map(Token::Element)),
|
|
||||||
Token::Object(obj) => match obj {
|
|
||||||
Object::Bold(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Object::Italic(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Object::Underline(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Object::StrikeThrough(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Object::Code(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::Verbatim(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::PlainText(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::RegularLink(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::RadioLink(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Object::RadioTarget(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Object::PlainLink(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::AngleLink(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::OrgMacro(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::Entity(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::LatexFragment(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::ExportSnippet(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::FootnoteReference(inner) => {
|
|
||||||
Box::new(inner.definition.iter().map(Token::Object))
|
|
||||||
}
|
|
||||||
Object::Citation(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
|
||||||
Object::CitationReference(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
|
||||||
Object::InlineBabelCall(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::InlineSourceBlock(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::LineBreak(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::Target(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::StatisticsCookie(_) => Box::new(std::iter::empty()),
|
|
||||||
Object::Subscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
|
||||||
Object::Superscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
|
||||||
Object::Timestamp(_) => Box::new(std::iter::empty()),
|
|
||||||
},
|
|
||||||
Token::Element(elem) => match elem {
|
|
||||||
Element::Paragraph(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Element::PlainList(inner) => {
|
|
||||||
Box::new(inner.children.iter().map(Token::PlainListItem))
|
|
||||||
}
|
|
||||||
Element::GreaterBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
|
||||||
Element::DynamicBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
|
||||||
Element::FootnoteDefinition(inner) => {
|
|
||||||
Box::new(inner.children.iter().map(Token::Element))
|
|
||||||
}
|
|
||||||
Element::Comment(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::Drawer(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
|
||||||
Element::PropertyDrawer(inner) => {
|
|
||||||
Box::new(inner.children.iter().map(Token::NodeProperty))
|
|
||||||
}
|
|
||||||
Element::Table(inner) => Box::new(inner.children.iter().map(Token::TableRow)),
|
|
||||||
Element::VerseBlock(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
|
||||||
Element::CommentBlock(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::ExampleBlock(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::ExportBlock(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::SrcBlock(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::Clock(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::DiarySexp(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::Planning(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::FixedWidthArea(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::HorizontalRule(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::Keyword(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::BabelCall(_) => Box::new(std::iter::empty()),
|
|
||||||
Element::LatexEnvironment(_) => Box::new(std::iter::empty()),
|
|
||||||
},
|
|
||||||
Token::PlainListItem(elem) => Box::new(elem.children.iter().map(Token::Element)),
|
|
||||||
Token::TableRow(elem) => Box::new(elem.children.iter().map(Token::TableCell)),
|
|
||||||
Token::TableCell(elem) => Box::new(elem.children.iter().map(Token::Object)),
|
|
||||||
Token::NodeProperty(_) => Box::new(std::iter::empty()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct AllTokensIterator<'r, 's> {
|
|
||||||
queued_tokens: VecDeque<Token<'r, 's>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r, 's> AllTokensIterator<'r, 's> {
|
|
||||||
pub(crate) fn new(tkn: Token<'r, 's>) -> Self {
|
|
||||||
let mut queued_tokens = VecDeque::new();
|
|
||||||
queued_tokens.push_back(tkn);
|
|
||||||
AllTokensIterator { queued_tokens }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r, 's> Iterator for AllTokensIterator<'r, 's> {
|
|
||||||
type Item = Token<'r, 's>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let next_token = match self.queued_tokens.pop_front() {
|
|
||||||
Some(tkn) => tkn,
|
|
||||||
None => return None,
|
|
||||||
};
|
|
||||||
self.queued_tokens.extend(next_token.iter_tokens());
|
|
||||||
Some(next_token)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
|
use nom::bytes::complete::is_a;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
use nom::character::complete::line_ending;
|
use nom::character::complete::line_ending;
|
||||||
use nom::character::complete::none_of;
|
use nom::character::complete::none_of;
|
||||||
@@ -20,6 +21,7 @@ use crate::context::RefContext;
|
|||||||
use crate::error::CustomError;
|
use crate::error::CustomError;
|
||||||
use crate::error::MyError;
|
use crate::error::MyError;
|
||||||
use crate::error::Res;
|
use crate::error::Res;
|
||||||
|
use crate::types::IndentationLevel;
|
||||||
|
|
||||||
pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
|
pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
@@ -212,6 +214,9 @@ fn text_until_eol<'r, 's>(
|
|||||||
Ok(line.trim())
|
Ok(line.trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a tuple of (input, output) from a nom parser.
|
||||||
|
///
|
||||||
|
/// This is similar to recognize except it returns the input instead of the portion of the input that was consumed.
|
||||||
pub(crate) fn include_input<'s, F, O>(
|
pub(crate) fn include_input<'s, F, O>(
|
||||||
mut inner: F,
|
mut inner: F,
|
||||||
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
|
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
|
||||||
@@ -223,3 +228,50 @@ where
|
|||||||
Ok((remaining, (input, output)))
|
Ok((remaining, (input, output)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Match at least one space character.
|
||||||
|
///
|
||||||
|
/// This is similar to nom's space1 parser except space1 matches both spaces and tabs whereas this only matches spaces.
|
||||||
|
pub(crate) fn only_space1<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
is_a(" ")(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match single space or tab.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax, spaces and tabs are often (but not always!) interchangeable.
|
||||||
|
pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> {
|
||||||
|
one_of(" \t")(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches a single space, tab, line ending, or end of file.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax there are often delimiters that could be any whitespace at all or the end of file.
|
||||||
|
pub(crate) fn org_space_or_line_ending<'s>(
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
alt((recognize(org_space), org_line_ending))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match a line break or the end of the file.
|
||||||
|
///
|
||||||
|
/// In org-mode syntax, the end of the file can serve the same purpose as a line break syntactically.
|
||||||
|
pub(crate) fn org_line_ending<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
alt((line_ending, eof))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match the whitespace at the beginning of a line and give it an indentation level.
|
||||||
|
pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, (IndentationLevel, OrgSource<'s>)> {
|
||||||
|
let (remaining, leading_whitespace) = space0(input)?;
|
||||||
|
let indentation_level = Into::<&str>::into(leading_whitespace)
|
||||||
|
.chars()
|
||||||
|
.map(|c| match c {
|
||||||
|
' ' => 1,
|
||||||
|
'\t' => context.get_global_settings().tab_width,
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
Ok((remaining, (indentation_level, leading_whitespace)))
|
||||||
|
}
|
||||||
|
|||||||
325
src/types/ast_node.rs
Normal file
325
src/types/ast_node.rs
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
use super::macros::to_ast_node;
|
||||||
|
use super::CenterBlock;
|
||||||
|
use super::QuoteBlock;
|
||||||
|
use super::SpecialBlock;
|
||||||
|
use crate::types::AngleLink;
|
||||||
|
use crate::types::BabelCall;
|
||||||
|
use crate::types::Bold;
|
||||||
|
use crate::types::Citation;
|
||||||
|
use crate::types::CitationReference;
|
||||||
|
use crate::types::Clock;
|
||||||
|
use crate::types::Code;
|
||||||
|
use crate::types::Comment;
|
||||||
|
use crate::types::CommentBlock;
|
||||||
|
use crate::types::DiarySexp;
|
||||||
|
use crate::types::Document;
|
||||||
|
use crate::types::DocumentElement;
|
||||||
|
use crate::types::Drawer;
|
||||||
|
use crate::types::DynamicBlock;
|
||||||
|
use crate::types::Element;
|
||||||
|
use crate::types::Entity;
|
||||||
|
use crate::types::ExampleBlock;
|
||||||
|
use crate::types::ExportBlock;
|
||||||
|
use crate::types::ExportSnippet;
|
||||||
|
use crate::types::FixedWidthArea;
|
||||||
|
use crate::types::FootnoteDefinition;
|
||||||
|
use crate::types::FootnoteReference;
|
||||||
|
use crate::types::GetStandardProperties;
|
||||||
|
use crate::types::Heading;
|
||||||
|
use crate::types::HorizontalRule;
|
||||||
|
use crate::types::InlineBabelCall;
|
||||||
|
use crate::types::InlineSourceBlock;
|
||||||
|
use crate::types::Italic;
|
||||||
|
use crate::types::Keyword;
|
||||||
|
use crate::types::LatexEnvironment;
|
||||||
|
use crate::types::LatexFragment;
|
||||||
|
use crate::types::LineBreak;
|
||||||
|
use crate::types::NodeProperty;
|
||||||
|
use crate::types::Object;
|
||||||
|
use crate::types::OrgMacro;
|
||||||
|
use crate::types::Paragraph;
|
||||||
|
use crate::types::PlainLink;
|
||||||
|
use crate::types::PlainList;
|
||||||
|
use crate::types::PlainListItem;
|
||||||
|
use crate::types::PlainText;
|
||||||
|
use crate::types::Planning;
|
||||||
|
use crate::types::PropertyDrawer;
|
||||||
|
use crate::types::RadioLink;
|
||||||
|
use crate::types::RadioTarget;
|
||||||
|
use crate::types::RegularLink;
|
||||||
|
use crate::types::Section;
|
||||||
|
use crate::types::SrcBlock;
|
||||||
|
use crate::types::StatisticsCookie;
|
||||||
|
use crate::types::StrikeThrough;
|
||||||
|
use crate::types::Subscript;
|
||||||
|
use crate::types::Superscript;
|
||||||
|
use crate::types::Table;
|
||||||
|
use crate::types::TableCell;
|
||||||
|
use crate::types::TableRow;
|
||||||
|
use crate::types::Target;
|
||||||
|
use crate::types::Timestamp;
|
||||||
|
use crate::types::Underline;
|
||||||
|
use crate::types::Verbatim;
|
||||||
|
use crate::types::VerseBlock;
|
||||||
|
|
||||||
|
pub enum AstNode<'r, 's> {
|
||||||
|
// Document Nodes
|
||||||
|
Document(&'r Document<'s>),
|
||||||
|
Heading(&'r Heading<'s>),
|
||||||
|
Section(&'r Section<'s>),
|
||||||
|
// Elements
|
||||||
|
Paragraph(&'r Paragraph<'s>),
|
||||||
|
PlainList(&'r PlainList<'s>),
|
||||||
|
PlainListItem(&'r PlainListItem<'s>),
|
||||||
|
CenterBlock(&'r CenterBlock<'s>),
|
||||||
|
QuoteBlock(&'r QuoteBlock<'s>),
|
||||||
|
SpecialBlock(&'r SpecialBlock<'s>),
|
||||||
|
DynamicBlock(&'r DynamicBlock<'s>),
|
||||||
|
FootnoteDefinition(&'r FootnoteDefinition<'s>),
|
||||||
|
Comment(&'r Comment<'s>),
|
||||||
|
Drawer(&'r Drawer<'s>),
|
||||||
|
PropertyDrawer(&'r PropertyDrawer<'s>),
|
||||||
|
NodeProperty(&'r NodeProperty<'s>),
|
||||||
|
Table(&'r Table<'s>),
|
||||||
|
TableRow(&'r TableRow<'s>),
|
||||||
|
VerseBlock(&'r VerseBlock<'s>),
|
||||||
|
CommentBlock(&'r CommentBlock<'s>),
|
||||||
|
ExampleBlock(&'r ExampleBlock<'s>),
|
||||||
|
ExportBlock(&'r ExportBlock<'s>),
|
||||||
|
SrcBlock(&'r SrcBlock<'s>),
|
||||||
|
Clock(&'r Clock<'s>),
|
||||||
|
DiarySexp(&'r DiarySexp<'s>),
|
||||||
|
Planning(&'r Planning<'s>),
|
||||||
|
FixedWidthArea(&'r FixedWidthArea<'s>),
|
||||||
|
HorizontalRule(&'r HorizontalRule<'s>),
|
||||||
|
Keyword(&'r Keyword<'s>),
|
||||||
|
BabelCall(&'r BabelCall<'s>),
|
||||||
|
LatexEnvironment(&'r LatexEnvironment<'s>),
|
||||||
|
// Objects
|
||||||
|
Bold(&'r Bold<'s>),
|
||||||
|
Italic(&'r Italic<'s>),
|
||||||
|
Underline(&'r Underline<'s>),
|
||||||
|
StrikeThrough(&'r StrikeThrough<'s>),
|
||||||
|
Code(&'r Code<'s>),
|
||||||
|
Verbatim(&'r Verbatim<'s>),
|
||||||
|
PlainText(&'r PlainText<'s>),
|
||||||
|
RegularLink(&'r RegularLink<'s>),
|
||||||
|
RadioLink(&'r RadioLink<'s>),
|
||||||
|
RadioTarget(&'r RadioTarget<'s>),
|
||||||
|
PlainLink(&'r PlainLink<'s>),
|
||||||
|
AngleLink(&'r AngleLink<'s>),
|
||||||
|
OrgMacro(&'r OrgMacro<'s>),
|
||||||
|
Entity(&'r Entity<'s>),
|
||||||
|
LatexFragment(&'r LatexFragment<'s>),
|
||||||
|
ExportSnippet(&'r ExportSnippet<'s>),
|
||||||
|
FootnoteReference(&'r FootnoteReference<'s>),
|
||||||
|
Citation(&'r Citation<'s>),
|
||||||
|
CitationReference(&'r CitationReference<'s>),
|
||||||
|
InlineBabelCall(&'r InlineBabelCall<'s>),
|
||||||
|
InlineSourceBlock(&'r InlineSourceBlock<'s>),
|
||||||
|
LineBreak(&'r LineBreak<'s>),
|
||||||
|
Target(&'r Target<'s>),
|
||||||
|
StatisticsCookie(&'r StatisticsCookie<'s>),
|
||||||
|
Subscript(&'r Subscript<'s>),
|
||||||
|
Superscript(&'r Superscript<'s>),
|
||||||
|
TableCell(&'r TableCell<'s>),
|
||||||
|
Timestamp(&'r Timestamp<'s>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> From<&'r DocumentElement<'s>> for AstNode<'r, 's> {
|
||||||
|
fn from(value: &'r DocumentElement<'s>) -> Self {
|
||||||
|
match value {
|
||||||
|
DocumentElement::Heading(inner) => inner.into(),
|
||||||
|
DocumentElement::Section(inner) => inner.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> From<&'r Element<'s>> for AstNode<'r, 's> {
|
||||||
|
fn from(value: &'r Element<'s>) -> Self {
|
||||||
|
match value {
|
||||||
|
Element::Paragraph(inner) => inner.into(),
|
||||||
|
Element::PlainList(inner) => inner.into(),
|
||||||
|
Element::CenterBlock(inner) => inner.into(),
|
||||||
|
Element::QuoteBlock(inner) => inner.into(),
|
||||||
|
Element::SpecialBlock(inner) => inner.into(),
|
||||||
|
Element::DynamicBlock(inner) => inner.into(),
|
||||||
|
Element::FootnoteDefinition(inner) => inner.into(),
|
||||||
|
Element::Comment(inner) => inner.into(),
|
||||||
|
Element::Drawer(inner) => inner.into(),
|
||||||
|
Element::PropertyDrawer(inner) => inner.into(),
|
||||||
|
Element::Table(inner) => inner.into(),
|
||||||
|
Element::VerseBlock(inner) => inner.into(),
|
||||||
|
Element::CommentBlock(inner) => inner.into(),
|
||||||
|
Element::ExampleBlock(inner) => inner.into(),
|
||||||
|
Element::ExportBlock(inner) => inner.into(),
|
||||||
|
Element::SrcBlock(inner) => inner.into(),
|
||||||
|
Element::Clock(inner) => inner.into(),
|
||||||
|
Element::DiarySexp(inner) => inner.into(),
|
||||||
|
Element::Planning(inner) => inner.into(),
|
||||||
|
Element::FixedWidthArea(inner) => inner.into(),
|
||||||
|
Element::HorizontalRule(inner) => inner.into(),
|
||||||
|
Element::Keyword(inner) => inner.into(),
|
||||||
|
Element::BabelCall(inner) => inner.into(),
|
||||||
|
Element::LatexEnvironment(inner) => inner.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, 's> From<&'r Object<'s>> for AstNode<'r, 's> {
|
||||||
|
fn from(value: &'r Object<'s>) -> Self {
|
||||||
|
match value {
|
||||||
|
Object::Bold(inner) => inner.into(),
|
||||||
|
Object::Italic(inner) => inner.into(),
|
||||||
|
Object::Underline(inner) => inner.into(),
|
||||||
|
Object::StrikeThrough(inner) => inner.into(),
|
||||||
|
Object::Code(inner) => inner.into(),
|
||||||
|
Object::Verbatim(inner) => inner.into(),
|
||||||
|
Object::PlainText(inner) => inner.into(),
|
||||||
|
Object::RegularLink(inner) => inner.into(),
|
||||||
|
Object::RadioLink(inner) => inner.into(),
|
||||||
|
Object::RadioTarget(inner) => inner.into(),
|
||||||
|
Object::PlainLink(inner) => inner.into(),
|
||||||
|
Object::AngleLink(inner) => inner.into(),
|
||||||
|
Object::OrgMacro(inner) => inner.into(),
|
||||||
|
Object::Entity(inner) => inner.into(),
|
||||||
|
Object::LatexFragment(inner) => inner.into(),
|
||||||
|
Object::ExportSnippet(inner) => inner.into(),
|
||||||
|
Object::FootnoteReference(inner) => inner.into(),
|
||||||
|
Object::Citation(inner) => inner.into(),
|
||||||
|
Object::CitationReference(inner) => inner.into(),
|
||||||
|
Object::InlineBabelCall(inner) => inner.into(),
|
||||||
|
Object::InlineSourceBlock(inner) => inner.into(),
|
||||||
|
Object::LineBreak(inner) => inner.into(),
|
||||||
|
Object::Target(inner) => inner.into(),
|
||||||
|
Object::StatisticsCookie(inner) => inner.into(),
|
||||||
|
Object::Subscript(inner) => inner.into(),
|
||||||
|
Object::Superscript(inner) => inner.into(),
|
||||||
|
Object::Timestamp(inner) => inner.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
to_ast_node!(&'r Document<'s>, AstNode::Document);
|
||||||
|
to_ast_node!(&'r Heading<'s>, AstNode::Heading);
|
||||||
|
to_ast_node!(&'r Section<'s>, AstNode::Section);
|
||||||
|
to_ast_node!(&'r Paragraph<'s>, AstNode::Paragraph);
|
||||||
|
to_ast_node!(&'r PlainList<'s>, AstNode::PlainList);
|
||||||
|
to_ast_node!(&'r PlainListItem<'s>, AstNode::PlainListItem);
|
||||||
|
to_ast_node!(&'r CenterBlock<'s>, AstNode::CenterBlock);
|
||||||
|
to_ast_node!(&'r QuoteBlock<'s>, AstNode::QuoteBlock);
|
||||||
|
to_ast_node!(&'r SpecialBlock<'s>, AstNode::SpecialBlock);
|
||||||
|
to_ast_node!(&'r DynamicBlock<'s>, AstNode::DynamicBlock);
|
||||||
|
to_ast_node!(&'r FootnoteDefinition<'s>, AstNode::FootnoteDefinition);
|
||||||
|
to_ast_node!(&'r Comment<'s>, AstNode::Comment);
|
||||||
|
to_ast_node!(&'r Drawer<'s>, AstNode::Drawer);
|
||||||
|
to_ast_node!(&'r PropertyDrawer<'s>, AstNode::PropertyDrawer);
|
||||||
|
to_ast_node!(&'r NodeProperty<'s>, AstNode::NodeProperty);
|
||||||
|
to_ast_node!(&'r Table<'s>, AstNode::Table);
|
||||||
|
to_ast_node!(&'r TableRow<'s>, AstNode::TableRow);
|
||||||
|
to_ast_node!(&'r VerseBlock<'s>, AstNode::VerseBlock);
|
||||||
|
to_ast_node!(&'r CommentBlock<'s>, AstNode::CommentBlock);
|
||||||
|
to_ast_node!(&'r ExampleBlock<'s>, AstNode::ExampleBlock);
|
||||||
|
to_ast_node!(&'r ExportBlock<'s>, AstNode::ExportBlock);
|
||||||
|
to_ast_node!(&'r SrcBlock<'s>, AstNode::SrcBlock);
|
||||||
|
to_ast_node!(&'r Clock<'s>, AstNode::Clock);
|
||||||
|
to_ast_node!(&'r DiarySexp<'s>, AstNode::DiarySexp);
|
||||||
|
to_ast_node!(&'r Planning<'s>, AstNode::Planning);
|
||||||
|
to_ast_node!(&'r FixedWidthArea<'s>, AstNode::FixedWidthArea);
|
||||||
|
to_ast_node!(&'r HorizontalRule<'s>, AstNode::HorizontalRule);
|
||||||
|
to_ast_node!(&'r Keyword<'s>, AstNode::Keyword);
|
||||||
|
to_ast_node!(&'r BabelCall<'s>, AstNode::BabelCall);
|
||||||
|
to_ast_node!(&'r LatexEnvironment<'s>, AstNode::LatexEnvironment);
|
||||||
|
to_ast_node!(&'r Bold<'s>, AstNode::Bold);
|
||||||
|
to_ast_node!(&'r Italic<'s>, AstNode::Italic);
|
||||||
|
to_ast_node!(&'r Underline<'s>, AstNode::Underline);
|
||||||
|
to_ast_node!(&'r StrikeThrough<'s>, AstNode::StrikeThrough);
|
||||||
|
to_ast_node!(&'r Code<'s>, AstNode::Code);
|
||||||
|
to_ast_node!(&'r Verbatim<'s>, AstNode::Verbatim);
|
||||||
|
to_ast_node!(&'r PlainText<'s>, AstNode::PlainText);
|
||||||
|
to_ast_node!(&'r RegularLink<'s>, AstNode::RegularLink);
|
||||||
|
to_ast_node!(&'r RadioLink<'s>, AstNode::RadioLink);
|
||||||
|
to_ast_node!(&'r RadioTarget<'s>, AstNode::RadioTarget);
|
||||||
|
to_ast_node!(&'r PlainLink<'s>, AstNode::PlainLink);
|
||||||
|
to_ast_node!(&'r AngleLink<'s>, AstNode::AngleLink);
|
||||||
|
to_ast_node!(&'r OrgMacro<'s>, AstNode::OrgMacro);
|
||||||
|
to_ast_node!(&'r Entity<'s>, AstNode::Entity);
|
||||||
|
to_ast_node!(&'r LatexFragment<'s>, AstNode::LatexFragment);
|
||||||
|
to_ast_node!(&'r ExportSnippet<'s>, AstNode::ExportSnippet);
|
||||||
|
to_ast_node!(&'r FootnoteReference<'s>, AstNode::FootnoteReference);
|
||||||
|
to_ast_node!(&'r Citation<'s>, AstNode::Citation);
|
||||||
|
to_ast_node!(&'r CitationReference<'s>, AstNode::CitationReference);
|
||||||
|
to_ast_node!(&'r InlineBabelCall<'s>, AstNode::InlineBabelCall);
|
||||||
|
to_ast_node!(&'r InlineSourceBlock<'s>, AstNode::InlineSourceBlock);
|
||||||
|
to_ast_node!(&'r LineBreak<'s>, AstNode::LineBreak);
|
||||||
|
to_ast_node!(&'r Target<'s>, AstNode::Target);
|
||||||
|
to_ast_node!(&'r StatisticsCookie<'s>, AstNode::StatisticsCookie);
|
||||||
|
to_ast_node!(&'r Subscript<'s>, AstNode::Subscript);
|
||||||
|
to_ast_node!(&'r Superscript<'s>, AstNode::Superscript);
|
||||||
|
to_ast_node!(&'r TableCell<'s>, AstNode::TableCell);
|
||||||
|
to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp);
|
||||||
|
|
||||||
|
impl<'r, 's> GetStandardProperties<'s> for AstNode<'r, 's> {
|
||||||
|
fn get_standard_properties<'b>(&'b self) -> &'b dyn crate::types::StandardProperties<'s> {
|
||||||
|
match self {
|
||||||
|
AstNode::Document(inner) => *inner,
|
||||||
|
AstNode::Heading(inner) => *inner,
|
||||||
|
AstNode::Section(inner) => *inner,
|
||||||
|
AstNode::Paragraph(inner) => *inner,
|
||||||
|
AstNode::PlainList(inner) => *inner,
|
||||||
|
AstNode::PlainListItem(inner) => *inner,
|
||||||
|
AstNode::CenterBlock(inner) => *inner,
|
||||||
|
AstNode::QuoteBlock(inner) => *inner,
|
||||||
|
AstNode::SpecialBlock(inner) => *inner,
|
||||||
|
AstNode::DynamicBlock(inner) => *inner,
|
||||||
|
AstNode::FootnoteDefinition(inner) => *inner,
|
||||||
|
AstNode::Comment(inner) => *inner,
|
||||||
|
AstNode::Drawer(inner) => *inner,
|
||||||
|
AstNode::PropertyDrawer(inner) => *inner,
|
||||||
|
AstNode::NodeProperty(inner) => *inner,
|
||||||
|
AstNode::Table(inner) => *inner,
|
||||||
|
AstNode::TableRow(inner) => *inner,
|
||||||
|
AstNode::VerseBlock(inner) => *inner,
|
||||||
|
AstNode::CommentBlock(inner) => *inner,
|
||||||
|
AstNode::ExampleBlock(inner) => *inner,
|
||||||
|
AstNode::ExportBlock(inner) => *inner,
|
||||||
|
AstNode::SrcBlock(inner) => *inner,
|
||||||
|
AstNode::Clock(inner) => *inner,
|
||||||
|
AstNode::DiarySexp(inner) => *inner,
|
||||||
|
AstNode::Planning(inner) => *inner,
|
||||||
|
AstNode::FixedWidthArea(inner) => *inner,
|
||||||
|
AstNode::HorizontalRule(inner) => *inner,
|
||||||
|
AstNode::Keyword(inner) => *inner,
|
||||||
|
AstNode::BabelCall(inner) => *inner,
|
||||||
|
AstNode::LatexEnvironment(inner) => *inner,
|
||||||
|
AstNode::Bold(inner) => *inner,
|
||||||
|
AstNode::Italic(inner) => *inner,
|
||||||
|
AstNode::Underline(inner) => *inner,
|
||||||
|
AstNode::StrikeThrough(inner) => *inner,
|
||||||
|
AstNode::Code(inner) => *inner,
|
||||||
|
AstNode::Verbatim(inner) => *inner,
|
||||||
|
AstNode::PlainText(inner) => *inner,
|
||||||
|
AstNode::RegularLink(inner) => *inner,
|
||||||
|
AstNode::RadioLink(inner) => *inner,
|
||||||
|
AstNode::RadioTarget(inner) => *inner,
|
||||||
|
AstNode::PlainLink(inner) => *inner,
|
||||||
|
AstNode::AngleLink(inner) => *inner,
|
||||||
|
AstNode::OrgMacro(inner) => *inner,
|
||||||
|
AstNode::Entity(inner) => *inner,
|
||||||
|
AstNode::LatexFragment(inner) => *inner,
|
||||||
|
AstNode::ExportSnippet(inner) => *inner,
|
||||||
|
AstNode::FootnoteReference(inner) => *inner,
|
||||||
|
AstNode::Citation(inner) => *inner,
|
||||||
|
AstNode::CitationReference(inner) => *inner,
|
||||||
|
AstNode::InlineBabelCall(inner) => *inner,
|
||||||
|
AstNode::InlineSourceBlock(inner) => *inner,
|
||||||
|
AstNode::LineBreak(inner) => *inner,
|
||||||
|
AstNode::Target(inner) => *inner,
|
||||||
|
AstNode::StatisticsCookie(inner) => *inner,
|
||||||
|
AstNode::Subscript(inner) => *inner,
|
||||||
|
AstNode::Superscript(inner) => *inner,
|
||||||
|
AstNode::TableCell(inner) => *inner,
|
||||||
|
AstNode::Timestamp(inner) => *inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,19 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::Element;
|
use super::Element;
|
||||||
|
use super::GetStandardProperties;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
use super::Source;
|
use super::StandardProperties;
|
||||||
|
use super::Timestamp;
|
||||||
|
|
||||||
pub type PriorityCookie = u8;
|
pub type PriorityCookie = u8;
|
||||||
|
pub type HeadlineLevel = u16;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Document<'s> {
|
pub struct Document<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub category: Option<String>,
|
||||||
|
pub path: Option<PathBuf>,
|
||||||
pub zeroth_section: Option<Section<'s>>,
|
pub zeroth_section: Option<Section<'s>>,
|
||||||
pub children: Vec<Heading<'s>>,
|
pub children: Vec<Heading<'s>>,
|
||||||
}
|
}
|
||||||
@@ -14,7 +21,7 @@ pub struct Document<'s> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Heading<'s> {
|
pub struct Heading<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub stars: usize,
|
pub level: HeadlineLevel,
|
||||||
pub todo_keyword: Option<(TodoKeywordType, &'s str)>,
|
pub todo_keyword: Option<(TodoKeywordType, &'s str)>,
|
||||||
pub priority_cookie: Option<PriorityCookie>,
|
pub priority_cookie: Option<PriorityCookie>,
|
||||||
pub title: Vec<Object<'s>>,
|
pub title: Vec<Object<'s>>,
|
||||||
@@ -22,6 +29,10 @@ pub struct Heading<'s> {
|
|||||||
pub children: Vec<DocumentElement<'s>>,
|
pub children: Vec<DocumentElement<'s>>,
|
||||||
pub is_comment: bool,
|
pub is_comment: bool,
|
||||||
pub is_archived: bool,
|
pub is_archived: bool,
|
||||||
|
pub is_footnote_section: bool,
|
||||||
|
pub scheduled: Option<Timestamp<'s>>,
|
||||||
|
pub deadline: Option<Timestamp<'s>>,
|
||||||
|
pub closed: Option<Timestamp<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -42,29 +53,41 @@ pub enum TodoKeywordType {
|
|||||||
Done,
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for Document<'s> {
|
impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
||||||
self.source
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> Source<'s> for DocumentElement<'s> {
|
|
||||||
fn get_source(&'s self) -> &'s str {
|
|
||||||
match self {
|
match self {
|
||||||
DocumentElement::Heading(obj) => obj.source,
|
DocumentElement::Heading(inner) => inner,
|
||||||
DocumentElement::Section(obj) => obj.source,
|
DocumentElement::Section(inner) => inner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for Section<'s> {
|
impl<'s> StandardProperties<'s> for Document<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for Heading<'s> {
|
impl<'s> StandardProperties<'s> for Section<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'s> StandardProperties<'s> for Heading<'s> {
|
||||||
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> Heading<'s> {
|
||||||
|
pub fn get_raw_value(&self) -> String {
|
||||||
|
// TODO: I think this could just return a string slice instead of an owned string.
|
||||||
|
let title_source: String = self
|
||||||
|
.title
|
||||||
|
.iter()
|
||||||
|
.map(|obj| obj.get_standard_properties().get_source())
|
||||||
|
.collect();
|
||||||
|
title_source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use super::greater_element::DynamicBlock;
|
use super::greater_element::DynamicBlock;
|
||||||
use super::greater_element::FootnoteDefinition;
|
use super::greater_element::FootnoteDefinition;
|
||||||
use super::greater_element::GreaterBlock;
|
|
||||||
use super::greater_element::PlainList;
|
use super::greater_element::PlainList;
|
||||||
use super::greater_element::PropertyDrawer;
|
use super::greater_element::PropertyDrawer;
|
||||||
use super::greater_element::Table;
|
use super::greater_element::Table;
|
||||||
|
use super::lesser_element::BabelCall;
|
||||||
use super::lesser_element::Clock;
|
use super::lesser_element::Clock;
|
||||||
use super::lesser_element::Comment;
|
use super::lesser_element::Comment;
|
||||||
use super::lesser_element::CommentBlock;
|
use super::lesser_element::CommentBlock;
|
||||||
@@ -18,15 +18,21 @@ use super::lesser_element::Paragraph;
|
|||||||
use super::lesser_element::Planning;
|
use super::lesser_element::Planning;
|
||||||
use super::lesser_element::SrcBlock;
|
use super::lesser_element::SrcBlock;
|
||||||
use super::lesser_element::VerseBlock;
|
use super::lesser_element::VerseBlock;
|
||||||
|
use super::CenterBlock;
|
||||||
use super::Drawer;
|
use super::Drawer;
|
||||||
|
use super::GetStandardProperties;
|
||||||
|
use super::QuoteBlock;
|
||||||
use super::SetSource;
|
use super::SetSource;
|
||||||
use super::Source;
|
use super::SpecialBlock;
|
||||||
|
use super::StandardProperties;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Element<'s> {
|
pub enum Element<'s> {
|
||||||
Paragraph(Paragraph<'s>),
|
Paragraph(Paragraph<'s>),
|
||||||
PlainList(PlainList<'s>),
|
PlainList(PlainList<'s>),
|
||||||
GreaterBlock(GreaterBlock<'s>),
|
CenterBlock(CenterBlock<'s>),
|
||||||
|
QuoteBlock(QuoteBlock<'s>),
|
||||||
|
SpecialBlock(SpecialBlock<'s>),
|
||||||
DynamicBlock(DynamicBlock<'s>),
|
DynamicBlock(DynamicBlock<'s>),
|
||||||
FootnoteDefinition(FootnoteDefinition<'s>),
|
FootnoteDefinition(FootnoteDefinition<'s>),
|
||||||
Comment(Comment<'s>),
|
Comment(Comment<'s>),
|
||||||
@@ -44,46 +50,19 @@ pub enum Element<'s> {
|
|||||||
FixedWidthArea(FixedWidthArea<'s>),
|
FixedWidthArea(FixedWidthArea<'s>),
|
||||||
HorizontalRule(HorizontalRule<'s>),
|
HorizontalRule(HorizontalRule<'s>),
|
||||||
Keyword(Keyword<'s>),
|
Keyword(Keyword<'s>),
|
||||||
BabelCall(Keyword<'s>),
|
BabelCall(BabelCall<'s>),
|
||||||
LatexEnvironment(LatexEnvironment<'s>),
|
LatexEnvironment(LatexEnvironment<'s>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for Element<'s> {
|
|
||||||
fn get_source(&'s self) -> &'s str {
|
|
||||||
match self {
|
|
||||||
Element::Paragraph(obj) => obj.get_source(),
|
|
||||||
Element::PlainList(obj) => obj.get_source(),
|
|
||||||
Element::GreaterBlock(obj) => obj.get_source(),
|
|
||||||
Element::DynamicBlock(obj) => obj.get_source(),
|
|
||||||
Element::FootnoteDefinition(obj) => obj.get_source(),
|
|
||||||
Element::Comment(obj) => obj.get_source(),
|
|
||||||
Element::Drawer(obj) => obj.get_source(),
|
|
||||||
Element::PropertyDrawer(obj) => obj.get_source(),
|
|
||||||
Element::Table(obj) => obj.get_source(),
|
|
||||||
Element::VerseBlock(obj) => obj.get_source(),
|
|
||||||
Element::CommentBlock(obj) => obj.get_source(),
|
|
||||||
Element::ExampleBlock(obj) => obj.get_source(),
|
|
||||||
Element::ExportBlock(obj) => obj.get_source(),
|
|
||||||
Element::SrcBlock(obj) => obj.get_source(),
|
|
||||||
Element::Clock(obj) => obj.get_source(),
|
|
||||||
Element::DiarySexp(obj) => obj.get_source(),
|
|
||||||
Element::Planning(obj) => obj.get_source(),
|
|
||||||
Element::FixedWidthArea(obj) => obj.get_source(),
|
|
||||||
Element::HorizontalRule(obj) => obj.get_source(),
|
|
||||||
Element::Keyword(obj) => obj.get_source(),
|
|
||||||
Element::BabelCall(obj) => obj.get_source(),
|
|
||||||
Element::LatexEnvironment(obj) => obj.get_source(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'s> SetSource<'s> for Element<'s> {
|
impl<'s> SetSource<'s> for Element<'s> {
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
fn set_source(&mut self, source: &'s str) {
|
fn set_source(&mut self, source: &'s str) {
|
||||||
match self {
|
match self {
|
||||||
Element::Paragraph(obj) => obj.source = source,
|
Element::Paragraph(obj) => obj.source = source,
|
||||||
Element::PlainList(obj) => obj.source = source,
|
Element::PlainList(obj) => obj.source = source,
|
||||||
Element::GreaterBlock(obj) => obj.source = source,
|
Element::CenterBlock(obj) => obj.source = source,
|
||||||
|
Element::QuoteBlock(obj) => obj.source = source,
|
||||||
|
Element::SpecialBlock(obj) => obj.source = source,
|
||||||
Element::DynamicBlock(obj) => obj.source = source,
|
Element::DynamicBlock(obj) => obj.source = source,
|
||||||
Element::FootnoteDefinition(obj) => obj.source = source,
|
Element::FootnoteDefinition(obj) => obj.source = source,
|
||||||
Element::Comment(obj) => obj.source = source,
|
Element::Comment(obj) => obj.source = source,
|
||||||
@@ -106,3 +85,34 @@ impl<'s> SetSource<'s> for Element<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'s> GetStandardProperties<'s> for Element<'s> {
|
||||||
|
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
||||||
|
match self {
|
||||||
|
Element::Paragraph(inner) => inner,
|
||||||
|
Element::PlainList(inner) => inner,
|
||||||
|
Element::CenterBlock(inner) => inner,
|
||||||
|
Element::QuoteBlock(inner) => inner,
|
||||||
|
Element::SpecialBlock(inner) => inner,
|
||||||
|
Element::DynamicBlock(inner) => inner,
|
||||||
|
Element::FootnoteDefinition(inner) => inner,
|
||||||
|
Element::Comment(inner) => inner,
|
||||||
|
Element::Drawer(inner) => inner,
|
||||||
|
Element::PropertyDrawer(inner) => inner,
|
||||||
|
Element::Table(inner) => inner,
|
||||||
|
Element::VerseBlock(inner) => inner,
|
||||||
|
Element::CommentBlock(inner) => inner,
|
||||||
|
Element::ExampleBlock(inner) => inner,
|
||||||
|
Element::ExportBlock(inner) => inner,
|
||||||
|
Element::SrcBlock(inner) => inner,
|
||||||
|
Element::Clock(inner) => inner,
|
||||||
|
Element::DiarySexp(inner) => inner,
|
||||||
|
Element::Planning(inner) => inner,
|
||||||
|
Element::FixedWidthArea(inner) => inner,
|
||||||
|
Element::HorizontalRule(inner) => inner,
|
||||||
|
Element::Keyword(inner) => inner,
|
||||||
|
Element::BabelCall(inner) => inner,
|
||||||
|
Element::LatexEnvironment(inner) => inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
12
src/types/get_standard_properties.rs
Normal file
12
src/types/get_standard_properties.rs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
use super::StandardProperties;
|
||||||
|
|
||||||
|
pub trait GetStandardProperties<'s> {
|
||||||
|
// TODO: Can I eliminate this dynamic dispatch, perhaps using nominal generic structs? Low prioritiy since this is not used during parsing.
|
||||||
|
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s, I: StandardProperties<'s>> GetStandardProperties<'s> for I {
|
||||||
|
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,25 +2,61 @@ use super::element::Element;
|
|||||||
use super::lesser_element::TableCell;
|
use super::lesser_element::TableCell;
|
||||||
use super::Keyword;
|
use super::Keyword;
|
||||||
use super::Object;
|
use super::Object;
|
||||||
use super::Source;
|
use super::StandardProperties;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PlainList<'s> {
|
pub struct PlainList<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub list_type: PlainListType,
|
||||||
pub children: Vec<PlainListItem<'s>>,
|
pub children: Vec<PlainListItem<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum PlainListType {
|
||||||
|
Unordered,
|
||||||
|
Ordered,
|
||||||
|
Descriptive,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The width that something is indented. For example, a single tab character could be a value of 4 or 8.
|
||||||
|
pub type IndentationLevel = u16;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PlainListItem<'s> {
|
pub struct PlainListItem<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub indentation: usize,
|
pub indentation: IndentationLevel,
|
||||||
pub bullet: &'s str,
|
pub bullet: &'s str,
|
||||||
|
pub counter: Option<PlainListItemCounter>,
|
||||||
|
pub checkbox: Option<(CheckboxType, &'s str)>,
|
||||||
pub tag: Vec<Object<'s>>,
|
pub tag: Vec<Object<'s>>,
|
||||||
|
pub pre_blank: PlainListItemPreBlank,
|
||||||
|
pub children: Vec<Element<'s>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type PlainListItemCounter = u16;
|
||||||
|
pub type PlainListItemPreBlank = u8;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CheckboxType {
|
||||||
|
On,
|
||||||
|
Trans,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CenterBlock<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GreaterBlock<'s> {
|
pub struct QuoteBlock<'s> {
|
||||||
|
pub source: &'s str,
|
||||||
|
pub children: Vec<Element<'s>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SpecialBlock<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub name: &'s str,
|
pub name: &'s str,
|
||||||
pub parameters: Option<&'s str>,
|
pub parameters: Option<&'s str>,
|
||||||
@@ -58,6 +94,7 @@ pub struct PropertyDrawer<'s> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NodeProperty<'s> {
|
pub struct NodeProperty<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
pub name: &'s str,
|
||||||
pub value: Option<&'s str>,
|
pub value: Option<&'s str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,62 +111,102 @@ pub struct TableRow<'s> {
|
|||||||
pub children: Vec<TableCell<'s>>,
|
pub children: Vec<TableCell<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for PlainList<'s> {
|
#[derive(Debug)]
|
||||||
fn get_source(&'s self) -> &'s str {
|
pub enum TableRowType {
|
||||||
|
Standard,
|
||||||
|
Rule,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> StandardProperties<'s> for PlainList<'s> {
|
||||||
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for PlainListItem<'s> {
|
impl<'s> StandardProperties<'s> for PlainListItem<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for GreaterBlock<'s> {
|
impl<'s> StandardProperties<'s> for CenterBlock<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for DynamicBlock<'s> {
|
impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for FootnoteDefinition<'s> {
|
impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for Drawer<'s> {
|
impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for PropertyDrawer<'s> {
|
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for NodeProperty<'s> {
|
impl<'s> StandardProperties<'s> for Drawer<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for Table<'s> {
|
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Source<'s> for TableRow<'s> {
|
impl<'s> StandardProperties<'s> for NodeProperty<'s> {
|
||||||
fn get_source(&'s self) -> &'s str {
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
self.source
|
self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'s> StandardProperties<'s> for Table<'s> {
|
||||||
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> StandardProperties<'s> for TableRow<'s> {
|
||||||
|
fn get_source<'b>(&'b self) -> &'s str {
|
||||||
|
self.source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> PlainListItem<'s> {
|
||||||
|
pub fn get_checkbox(&self) -> Option<&'s str> {
|
||||||
|
self.checkbox.as_ref().map(|(_, checkbox)| *checkbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_checkbox_type(&self) -> Option<&CheckboxType> {
|
||||||
|
self.checkbox
|
||||||
|
.as_ref()
|
||||||
|
.map(|(checkbox_type, _)| checkbox_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> TableRow<'s> {
|
||||||
|
pub fn get_type(&self) -> TableRowType {
|
||||||
|
if self.children.is_empty() {
|
||||||
|
TableRowType::Rule
|
||||||
|
} else {
|
||||||
|
TableRowType::Standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user