Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de87b7df93 | ||
|
|
a267d13fd7 | ||
|
|
a29973a110 | ||
|
|
31c782499e | ||
|
|
b7c7057095 | ||
|
|
49e3c90a3a | ||
|
|
129228c5c5 | ||
|
|
f0a7493a89 | ||
|
|
dc5695ec9f | ||
|
|
4ff62fbfae | ||
|
|
c892d406c3 | ||
|
|
1a41cfc6c7 | ||
|
|
4f34ab9089 | ||
|
|
9b2348c0ef | ||
|
|
5716cbccea | ||
|
|
124cd50243 | ||
|
|
bac5d6e1d9 | ||
|
|
ba15999534 | ||
|
|
61c3e6c10e | ||
|
|
a7e130838d | ||
|
|
853adadf91 | ||
|
|
7b61329889 | ||
|
|
9bcfb2f1da | ||
|
|
4c8d9a3063 | ||
|
|
48cb3c4a02 | ||
|
|
9e60ff6683 | ||
|
|
c1de001786 | ||
|
|
716af5bb45 | ||
|
|
6137a46231 | ||
|
|
bdd04f4d5c | ||
|
|
36bdc54703 | ||
|
|
3031b6edd4 | ||
|
|
1a704dd312 | ||
|
|
a74ea730f4 | ||
|
|
8450785186 | ||
|
|
d443dbd468 | ||
|
|
c9ce32c881 | ||
|
|
85454a0a27 | ||
|
|
fdebf6dec5 | ||
|
|
444d6758aa | ||
|
|
6c7203410e | ||
|
|
bfe67b1f75 | ||
|
|
fd41ad9c29 | ||
|
|
7f751d4f28 | ||
|
|
52a4dab67c | ||
|
|
3d86e75059 | ||
|
|
ca6fdf1924 | ||
|
|
66d16d89ed | ||
|
|
ee5e0698b1 | ||
|
|
22681b6a58 | ||
|
|
876d33239e | ||
|
|
87941271a4 | ||
|
|
32b19d68d0 | ||
|
|
830097b0a9 | ||
|
|
44e9f708c9 | ||
|
|
fc4ff97c14 | ||
|
|
33372429dd | ||
|
|
ac0db64081 | ||
|
|
b8a4876779 | ||
|
|
925c42c8fb | ||
|
|
7d4100d956 | ||
|
|
53d90a2949 |
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "organic"
|
name = "organic"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
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)
|
||||||
|
|||||||
55
README.md
55
README.md
@@ -2,12 +2,63 @@
|
|||||||
|
|
||||||
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 a personal learning project to grow my experience in [rust](https://www.rust-lang.org/). It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README.
|
||||||
|
|
||||||
|
## Using this library
|
||||||
|
TODO: Add section on using Organic as a library (which is the intended use for this project).
|
||||||
|
|
||||||
|
### The parse binary
|
||||||
|
This program takes org-mode input either streamed in on stdin or as paths to files passed in as arguments. It then parses them using Organic and dumps the result to stdout. This program is intended solely as a development tool. Examples:
|
||||||
|
```bash
|
||||||
|
cat /foo/bar.org | cargo run --bin parse
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
cargo build --profile release-lto
|
||||||
|
./target/release-lto/parse /foo/bar.org /lorem/ipsum.org
|
||||||
|
```
|
||||||
|
|
||||||
|
### The compare binary
|
||||||
|
This program takes org-mode input either streamed in on stdin or as paths to files passed in as arguments. It then parses them using Organic and the official Emacs Org-mode parser and compares the parse result. This program is intended solely as a development tool. Since org-mode is a moving target, it is recommended that you run this through docker since we pin the version of org-mode to a specific revision. Examples:
|
||||||
|
```bash
|
||||||
|
cat /foo/bar.org | ./scripts/run_docker_compare.bash
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
./scripts/run_docker_compare.bash /foo/bar.org /lorem/ipsum.org
|
||||||
|
```
|
||||||
|
|
||||||
|
Not recommended since it is not through docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /foo/bar.org | cargo run --features compare --bin compare
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
cargo build --profile release-lto --features compare
|
||||||
|
./target/release-lto/compare /foo/bar.org /lorem/ipsum.org
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
There are three levels of tests for this repository: the standard tests, the autogenerated tests, and the foreign document tests.
|
||||||
|
|
||||||
|
### The standard tests
|
||||||
|
These are regular hand-written rust tests. These can be run with:
|
||||||
|
```bash
|
||||||
|
make unittest
|
||||||
|
```
|
||||||
|
|
||||||
|
### The auto-generated tests
|
||||||
|
These tests are automatically generated from the files in the `org_mode_samples` directory and they are still integrated with the rust/cargo testing framework. For each org-mode document in that folder, a test is generated that will parse the document with both Organic and the official Emacs Org-mode parser and then it will compare the parse results. Any deviation is considered a failure. Since org-mode is a moving target, it is recommended that you run these tests inside docker since the `organic-test` docker image is pinned to a specific revision of org-mode. These can be run with:
|
||||||
|
```bash
|
||||||
|
make dockertest
|
||||||
|
```
|
||||||
|
|
||||||
|
### The foreign document tests
|
||||||
|
These tests function the same as the auto-generated tests except they are **not** integrated with the rust/cargo testing framework and they involve comparing the parse of org-mode documents that live outside this repository. This allows us to test against a far greater variety of org-mode input documents without pulling massive sets of org-mode documents into this repository. The recommended way to run these tests is still through docker because it pins org-mode and the test documents to specific git revisions. These can be run with:
|
||||||
|
```bash
|
||||||
|
make foreign_document_test
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## 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.
|
||||||
|
|||||||
3
build.rs
3
build.rs
@@ -16,6 +16,9 @@ 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();
|
||||||
|
|
||||||
|
// Re-generate the tests if any org-mode files change
|
||||||
|
println!("cargo:rerun-if-changed=org_mode_samples");
|
||||||
|
|
||||||
write_header(&mut test_file);
|
write_header(&mut test_file);
|
||||||
|
|
||||||
let test_files = WalkDir::new("org_mode_samples")
|
let test_files = WalkDir::new("org_mode_samples")
|
||||||
|
|||||||
@@ -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=0c8d5679b536af450b61812246a3e02b8103f4b8
|
||||||
|
ARG WORG_PATH=/foreign_documents/worg
|
||||||
|
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
|
||||||
|
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
|
||||||
|
|
||||||
|
|
||||||
FROM tester as foreign-document-test
|
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.
|
||||||
|
|||||||
@@ -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 @@
|
|||||||
|
# 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,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:
|
||||||
@@ -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.
|
||||||
2
org_mode_samples/sections_and_headings/empty_heading.org
Normal file
2
org_mode_samples/sections_and_headings/empty_heading.org
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
* DONE
|
||||||
|
*
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
#+TODO: TODO(t) INPROGRESS(i/!) | DONE(d!) CANCELED(c@/!)
|
||||||
|
# ! : Log changes leading to this state.
|
||||||
|
# @ : Log changes leading to this state and prompt for a comment to include.
|
||||||
|
# /! : Log changes leaving this state if and only if to a state that does not log. This can be combined with the above like WAIT(w!/!) or DELAYED(d@/!)
|
||||||
|
* INPROGRESS
|
||||||
|
- State "TODO" from "INPROGRESS" [2023-09-14 Thu 02:13]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#+STARTUP: odd
|
||||||
|
* Foo
|
||||||
|
***** Bar
|
||||||
|
* Baz
|
||||||
|
*** Lorem
|
||||||
|
* Ipsum
|
||||||
|
**** Dolar
|
||||||
|
***** Cat
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ 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();
|
// TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings.
|
||||||
|
let org_contents = org_contents.as_ref().replace("\r\n", "\n");
|
||||||
|
let org_contents = org_contents.as_str();
|
||||||
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
||||||
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
||||||
let rust_parsed = parse(org_contents)?;
|
let rust_parsed = parse(org_contents)?;
|
||||||
@@ -44,6 +46,8 @@ pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn st
|
|||||||
.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()),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use super::util::assert_name;
|
|||||||
use super::util::get_property;
|
use super::util::get_property;
|
||||||
use crate::types::AngleLink;
|
use crate::types::AngleLink;
|
||||||
use crate::types::Bold;
|
use crate::types::Bold;
|
||||||
|
use crate::types::CheckboxType;
|
||||||
use crate::types::Citation;
|
use crate::types::Citation;
|
||||||
use crate::types::CitationReference;
|
use crate::types::CitationReference;
|
||||||
use crate::types::Clock;
|
use crate::types::Clock;
|
||||||
@@ -489,11 +490,11 @@ fn compare_heading<'s>(
|
|||||||
let level = get_property(emacs, ":level")?
|
let level = get_property(emacs, ":level")?
|
||||||
.ok_or("Level should not be nil")?
|
.ok_or("Level should not be nil")?
|
||||||
.as_atom()?;
|
.as_atom()?;
|
||||||
if rust.stars.to_string() != level {
|
if rust.level.to_string() != level {
|
||||||
this_status = DiffStatus::Bad;
|
this_status = DiffStatus::Bad;
|
||||||
message = Some(format!(
|
message = Some(format!(
|
||||||
"Headline level do not match (emacs != rust): {} != {}",
|
"Headline level do not match (emacs != rust): {} != {}",
|
||||||
level, rust.stars
|
level, rust.level
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,14 +547,26 @@ fn compare_heading<'s>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Compare title
|
// Compare title
|
||||||
let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?;
|
let title = get_property(emacs, ":title")?;
|
||||||
let title_status = title
|
match (title, rust.title.len()) {
|
||||||
.as_list()?
|
(None, 0) => {}
|
||||||
.iter()
|
(None, _) => {
|
||||||
.zip(rust.title.iter())
|
this_status = DiffStatus::Bad;
|
||||||
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
|
message = Some(format!(
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
"Titles do not match (emacs != rust): {:?} != {:?}",
|
||||||
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
|
title, rust.title
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(Some(title), _) => {
|
||||||
|
let title_status = title
|
||||||
|
.as_list()?
|
||||||
|
.iter()
|
||||||
|
.zip(rust.title.iter())
|
||||||
|
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Compare priority
|
// Compare priority
|
||||||
let priority = get_property(emacs, ":priority")?;
|
let priority = get_property(emacs, ":priority")?;
|
||||||
@@ -719,6 +732,10 @@ fn compare_plain_list<'s>(
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO compare :type
|
||||||
|
//
|
||||||
|
// :type is an unquoted atom of either descriptive, ordered, or unordered
|
||||||
|
|
||||||
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
||||||
child_status.push(compare_plain_list_item(source, emacs_child, rust_child)?);
|
child_status.push(compare_plain_list_item(source, emacs_child, rust_child)?);
|
||||||
}
|
}
|
||||||
@@ -787,7 +804,26 @@ fn compare_plain_list_item<'s>(
|
|||||||
contents_status,
|
contents_status,
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
// TODO: compare :bullet :checkbox :counter :pre-blank
|
// TODO: compare :bullet :counter :pre-blank
|
||||||
|
|
||||||
|
// Compare checkbox
|
||||||
|
let checkbox = get_property(emacs, ":checkbox")?
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.map_or(Ok(None), |r| r.map(Some))?
|
||||||
|
.unwrap_or("nil");
|
||||||
|
match (checkbox, &rust.checkbox) {
|
||||||
|
("nil", None) => {}
|
||||||
|
("off", Some((CheckboxType::Off, _))) => {}
|
||||||
|
("trans", Some((CheckboxType::Trans, _))) => {}
|
||||||
|
("on", Some((CheckboxType::On, _))) => {}
|
||||||
|
_ => {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
message = Some(format!(
|
||||||
|
"Checkbox mismatch (emacs != rust) {:?} != {:?}",
|
||||||
|
checkbox, rust.checkbox
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(DiffResult {
|
Ok(DiffResult {
|
||||||
status: this_status,
|
status: this_status,
|
||||||
@@ -862,6 +898,8 @@ fn compare_dynamic_block<'s>(
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Compare :block-name :arguments
|
||||||
|
|
||||||
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
||||||
child_status.push(compare_element(source, emacs_child, rust_child)?);
|
child_status.push(compare_element(source, emacs_child, rust_child)?);
|
||||||
}
|
}
|
||||||
@@ -1914,6 +1952,8 @@ fn compare_regular_link<'s>(
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Compare :type :path :format :raw-link :application :search-option
|
||||||
|
|
||||||
Ok(DiffResult {
|
Ok(DiffResult {
|
||||||
status: this_status,
|
status: this_status,
|
||||||
name: emacs_name.to_owned(),
|
name: emacs_name.to_owned(),
|
||||||
@@ -2441,6 +2481,8 @@ fn compare_subscript<'s>(
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO compare :use-brackets-p
|
||||||
|
|
||||||
Ok(DiffResult {
|
Ok(DiffResult {
|
||||||
status: this_status,
|
status: this_status,
|
||||||
name: emacs_name.to_owned(),
|
name: emacs_name.to_owned(),
|
||||||
@@ -2472,6 +2514,8 @@ fn compare_superscript<'s>(
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO compare :use-brackets-p
|
||||||
|
|
||||||
Ok(DiffResult {
|
Ok(DiffResult {
|
||||||
status: this_status,
|
status: this_status,
|
||||||
name: emacs_name.to_owned(),
|
name: emacs_name.to_owned(),
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ 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}")
|
||||||
(org-mode)
|
(org-mode)
|
||||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||||
@@ -42,6 +44,8 @@ where
|
|||||||
))?;
|
))?;
|
||||||
let elisp_script = format!(
|
let elisp_script = format!(
|
||||||
r#"(progn
|
r#"(progn
|
||||||
|
(require 'org)
|
||||||
|
(defun org-table-align () t)
|
||||||
(org-mode)
|
(org-mode)
|
||||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||||
)"#
|
)"#
|
||||||
|
|||||||
@@ -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,6 +13,20 @@ 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 org_list_allow_alphabetical: bool,
|
||||||
|
|
||||||
|
/// How many spaces a tab should be equal to.
|
||||||
|
///
|
||||||
|
/// Corresponds to the tab-width elisp variable.
|
||||||
|
pub tab_width: IndentationLevel,
|
||||||
|
|
||||||
|
/// Whether to only allow odd headline levels.
|
||||||
|
///
|
||||||
|
/// Corresponds to org-odd-levels-only elisp variable.
|
||||||
|
pub odd_levels_only: HeadlineLevelFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'g, 's> GlobalSettings<'g, 's> {
|
impl<'g, 's> GlobalSettings<'g, 's> {
|
||||||
@@ -23,6 +38,9 @@ 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(),
|
||||||
|
org_list_allow_alphabetical: false,
|
||||||
|
tab_width: 8,
|
||||||
|
odd_levels_only: HeadlineLevelFilter::OddEven,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,3 +50,9 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
|
|||||||
GlobalSettings::new()
|
GlobalSettings::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum HeadlineLevelFilter {
|
||||||
|
Odd,
|
||||||
|
OddEven,
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,5 +25,6 @@ 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(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;
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
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::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::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;
|
||||||
@@ -67,24 +71,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);
|
||||||
@@ -117,7 +120,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))
|
||||||
|
|||||||
@@ -141,7 +141,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"))]
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ use nom::bytes::complete::tag_no_case;
|
|||||||
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::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;
|
||||||
@@ -80,25 +83,23 @@ pub(crate) fn greater_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);
|
||||||
// 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
|
||||||
@@ -126,7 +127,6 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
|
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,26 @@
|
|||||||
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::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;
|
||||||
@@ -32,30 +34,39 @@ 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::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>> {
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
not(|i| context.check_exit_matcher(i))(input)?;
|
||||||
let (
|
let (
|
||||||
remaining,
|
remaining,
|
||||||
(star_count, maybe_todo_keyword, maybe_priority, maybe_comment, title, heading_tags),
|
(
|
||||||
) = headline(context, input, parent_stars)?;
|
headline_level,
|
||||||
|
star_count,
|
||||||
|
maybe_todo_keyword,
|
||||||
|
maybe_priority,
|
||||||
|
maybe_comment,
|
||||||
|
title,
|
||||||
|
heading_tags,
|
||||||
|
),
|
||||||
|
) = headline(context, input, parent_star_count)?;
|
||||||
let section_matcher = parser_with_context!(section)(context);
|
let section_matcher = parser_with_context!(section)(context);
|
||||||
let heading_matcher = parser_with_context!(heading(star_count))(context);
|
let heading_matcher = parser_with_context!(heading(star_count))(context);
|
||||||
let (remaining, maybe_section) =
|
let (remaining, maybe_section) =
|
||||||
@@ -80,11 +91,11 @@ fn _heading<'b, 'g, 'r, 's>(
|
|||||||
remaining,
|
remaining,
|
||||||
Heading {
|
Heading {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
stars: star_count,
|
level: headline_level,
|
||||||
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
|
todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
|
||||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||||
}),
|
}),
|
||||||
priority_cookie: maybe_priority.map(|(priority, _)| priority),
|
priority_cookie: maybe_priority.map(|(_, priority)| priority),
|
||||||
title,
|
title,
|
||||||
tags: heading_tags,
|
tags: heading_tags,
|
||||||
children,
|
children,
|
||||||
@@ -104,14 +115,15 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
|
|||||||
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>,
|
OrgSource<'s>,
|
||||||
(
|
(
|
||||||
usize,
|
HeadlineLevel,
|
||||||
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
|
HeadlineLevel,
|
||||||
Option<(PriorityCookie, OrgSource<'s>)>,
|
Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||||
Option<(OrgSource<'s>, OrgSource<'s>)>,
|
Option<(OrgSource<'s>, PriorityCookie)>,
|
||||||
|
Option<OrgSource<'s>>,
|
||||||
Vec<Object<'s>>,
|
Vec<Object<'s>>,
|
||||||
Vec<&'s str>,
|
Vec<&'s str>,
|
||||||
),
|
),
|
||||||
@@ -122,45 +134,47 @@ fn headline<'b, 'g, 'r, 's>(
|
|||||||
});
|
});
|
||||||
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,
|
||||||
|
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
||||||
|
)))(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
|
||||||
|
|
||||||
|
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
remaining,
|
remaining,
|
||||||
(
|
(
|
||||||
|
headline_level,
|
||||||
star_count,
|
star_count,
|
||||||
maybe_todo_keyword,
|
maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||||
maybe_priority,
|
maybe_priority,
|
||||||
maybe_comment,
|
maybe_comment.map(|(_, comment, _)| comment),
|
||||||
title,
|
maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
|
||||||
maybe_tags
|
maybe_tags
|
||||||
.map(|(_ws, tags)| {
|
.map(|(_ws, tags)| {
|
||||||
tags.into_iter()
|
tags.into_iter()
|
||||||
@@ -177,10 +191,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 +267,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,13 +1,17 @@
|
|||||||
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::character::complete::anychar;
|
||||||
|
use nom::character::complete::space1;
|
||||||
use nom::combinator::map;
|
use nom::combinator::map;
|
||||||
use nom::multi::many0;
|
use nom::multi::many0;
|
||||||
use nom::multi::many_till;
|
use nom::multi::many_till;
|
||||||
|
use nom::multi::separated_list0;
|
||||||
|
|
||||||
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::Res;
|
use crate::error::Res;
|
||||||
use crate::types::Keyword;
|
use crate::types::Keyword;
|
||||||
use crate::GlobalSettings;
|
use crate::GlobalSettings;
|
||||||
@@ -50,6 +54,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 +70,21 @@ 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -116,7 +117,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 {
|
||||||
|
|||||||
@@ -59,6 +59,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,21 +37,27 @@ 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;
|
||||||
|
|
||||||
#[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,
|
space0,
|
||||||
bullet,
|
parser_with_context!(bullet)(context),
|
||||||
alt((space1, line_ending, eof)),
|
alt((space1, line_ending, eof)),
|
||||||
)),
|
)),
|
||||||
|(_start, indent, bull, _after_whitespace)| {
|
|(_start, indent, (_bullet_type, bull), _after_whitespace)| {
|
||||||
Into::<&str>::into(bull) != "*" || indent.len() > 0
|
Into::<&str>::into(bull) != "*" || indent.len() > 0
|
||||||
},
|
},
|
||||||
)(input)
|
)(input)
|
||||||
@@ -81,7 +89,7 @@ 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 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:
|
||||||
@@ -142,44 +150,27 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, PlainListItem<'s>> {
|
) -> Res<OrgSource<'s>, 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) != "*" || indent_level > 0,
|
||||||
Into::<&str>::into(bull) != "*" || indent_level > 0
|
)(remaining)?;
|
||||||
})(remaining)?;
|
|
||||||
|
|
||||||
let (remaining, _maybe_counter_set) =
|
let (remaining, _maybe_counter_set) = opt(tuple((
|
||||||
opt(tuple((space1, tag("[@"), counter, tag("]"))))(remaining)?;
|
space1,
|
||||||
|
tag("[@"),
|
||||||
|
parser_with_context!(counter)(context),
|
||||||
|
tag("]"),
|
||||||
|
)))(remaining)?;
|
||||||
|
|
||||||
// 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 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 +182,35 @@ 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,
|
||||||
|
PlainListItem {
|
||||||
|
source: source.into(),
|
||||||
|
indentation: indent_level,
|
||||||
|
bullet: bull.into(),
|
||||||
|
checkbox: None,
|
||||||
|
tag: maybe_tag
|
||||||
|
.map(|(_ws, item_tag)| item_tag)
|
||||||
|
.unwrap_or(Vec::new()),
|
||||||
|
children: Vec::new(),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(_) => {}
|
||||||
|
};
|
||||||
|
let (remaining, _ws) = item_tag_post_gap(&parser_context, remaining)?;
|
||||||
|
|
||||||
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),
|
||||||
@@ -219,6 +239,8 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
source: source.into(),
|
source: source.into(),
|
||||||
indentation: indent_level,
|
indentation: indent_level,
|
||||||
bullet: bull.into(),
|
bullet: bull.into(),
|
||||||
|
checkbox: maybe_checkbox
|
||||||
|
.map(|(_, (checkbox_type, source))| (checkbox_type, Into::<&str>::into(source))),
|
||||||
tag: maybe_tag
|
tag: maybe_tag
|
||||||
.map(|(_ws, item_tag)| item_tag)
|
.map(|(_ws, item_tag)| item_tag)
|
||||||
.unwrap_or(Vec::new()),
|
.unwrap_or(Vec::new()),
|
||||||
@@ -227,19 +249,46 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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>)> {
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
))(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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().org_list_allow_alphabetical {
|
||||||
|
alt((
|
||||||
|
recognize(one_of(
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||||
|
)),
|
||||||
|
digit1,
|
||||||
|
))(input)
|
||||||
|
} else {
|
||||||
|
digit1(input)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
@@ -255,7 +304,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 +327,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 +376,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 +415,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>,
|
||||||
@@ -558,21 +622,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 +653,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)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
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::multi::many1;
|
||||||
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::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;
|
||||||
@@ -24,8 +24,9 @@ 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)?;
|
||||||
@@ -40,15 +41,17 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[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>(
|
||||||
|
context: RefContext<'b, 'g, 'r, 's>,
|
||||||
|
input: OrgSource<'s>,
|
||||||
|
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
let (remaining, _planning_type) = alt((
|
let (remaining, _planning_type) = alt((
|
||||||
tag_no_case("DEADLINE"),
|
tag_no_case("DEADLINE"),
|
||||||
tag_no_case("SCHEDULED"),
|
tag_no_case("SCHEDULED"),
|
||||||
tag_no_case("CLOSED"),
|
tag_no_case("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)?;
|
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
Ok((remaining, source))
|
Ok((remaining, source))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use nom::branch::alt;
|
use nom::branch::alt;
|
||||||
use nom::bytes::complete::tag;
|
use nom::bytes::complete::tag;
|
||||||
use nom::character::complete::anychar;
|
use nom::character::complete::anychar;
|
||||||
|
use nom::character::complete::digit0;
|
||||||
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;
|
||||||
@@ -414,7 +415,7 @@ fn repeater<'b, 'g, 'r, 's>(
|
|||||||
// ++ for catch-up type
|
// ++ for catch-up type
|
||||||
// .+ for restart type
|
// .+ for restart type
|
||||||
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
|
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
|
||||||
let (remaining, _value) = digit1(remaining)?;
|
let (remaining, _value) = digit0(remaining)?;
|
||||||
// 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) = recognize(one_of("hdwmy"))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
@@ -429,7 +430,7 @@ fn warning_delay<'b, 'g, 'r, 's>(
|
|||||||
// - for all type
|
// - for all type
|
||||||
// -- for first type
|
// -- for first type
|
||||||
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
|
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
|
||||||
let (remaining, _value) = digit1(remaining)?;
|
let (remaining, _value) = digit0(remaining)?;
|
||||||
// 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) = recognize(one_of("hdwmy"))(remaining)?;
|
||||||
let source = get_consumed(input, remaining);
|
let source = get_consumed(input, remaining);
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use super::Object;
|
|||||||
use super::Source;
|
use super::Source;
|
||||||
|
|
||||||
pub type PriorityCookie = u8;
|
pub type PriorityCookie = u8;
|
||||||
|
pub type HeadlineLevel = u16;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Document<'s> {
|
pub struct Document<'s> {
|
||||||
@@ -14,7 +15,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>>,
|
||||||
|
|||||||
@@ -10,15 +10,26 @@ pub struct PlainList<'s> {
|
|||||||
pub children: Vec<PlainListItem<'s>>,
|
pub children: Vec<PlainListItem<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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 checkbox: Option<(CheckboxType, &'s str)>,
|
||||||
pub tag: Vec<Object<'s>>,
|
pub tag: Vec<Object<'s>>,
|
||||||
pub children: Vec<Element<'s>>,
|
pub children: Vec<Element<'s>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CheckboxType {
|
||||||
|
On,
|
||||||
|
Trans,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GreaterBlock<'s> {
|
pub struct GreaterBlock<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ mod source;
|
|||||||
pub use document::Document;
|
pub use document::Document;
|
||||||
pub use document::DocumentElement;
|
pub use document::DocumentElement;
|
||||||
pub use document::Heading;
|
pub use document::Heading;
|
||||||
|
pub use document::HeadlineLevel;
|
||||||
pub use document::PriorityCookie;
|
pub use document::PriorityCookie;
|
||||||
pub use document::Section;
|
pub use document::Section;
|
||||||
pub use document::TodoKeywordType;
|
pub use document::TodoKeywordType;
|
||||||
pub use element::Element;
|
pub use element::Element;
|
||||||
|
pub use greater_element::CheckboxType;
|
||||||
pub use greater_element::Drawer;
|
pub use greater_element::Drawer;
|
||||||
pub use greater_element::DynamicBlock;
|
pub use greater_element::DynamicBlock;
|
||||||
pub use greater_element::FootnoteDefinition;
|
pub use greater_element::FootnoteDefinition;
|
||||||
pub use greater_element::GreaterBlock;
|
pub use greater_element::GreaterBlock;
|
||||||
|
pub use greater_element::IndentationLevel;
|
||||||
pub use greater_element::NodeProperty;
|
pub use greater_element::NodeProperty;
|
||||||
pub use greater_element::PlainList;
|
pub use greater_element::PlainList;
|
||||||
pub use greater_element::PlainListItem;
|
pub use greater_element::PlainListItem;
|
||||||
|
|||||||
Reference in New Issue
Block a user