Merge branch 'worg'
This commit is contained in:
commit
a267d13fd7
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)
|
||||||
|
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,7 +547,17 @@ 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")?;
|
||||||
|
match (title, rust.title.len()) {
|
||||||
|
(None, 0) => {}
|
||||||
|
(None, _) => {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
message = Some(format!(
|
||||||
|
"Titles do not match (emacs != rust): {:?} != {:?}",
|
||||||
|
title, rust.title
|
||||||
|
))
|
||||||
|
}
|
||||||
|
(Some(title), _) => {
|
||||||
let title_status = title
|
let title_status = title
|
||||||
.as_list()?
|
.as_list()?
|
||||||
.iter()
|
.iter()
|
||||||
@ -554,6 +565,8 @@ fn compare_heading<'s>(
|
|||||||
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
|
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
|
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)?;
|
||||||
(remaining, children)
|
if let Some(lines) = leading_blank_lines {
|
||||||
|
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)?;
|
||||||
(remaining, children)
|
if let Some(lines) = leading_blank_lines {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
recognize(tuple((
|
recognize(tuple((
|
||||||
item_tag_divider,
|
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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user