78 Commits

Author SHA1 Message Date
Tom Alexander
26f41b83aa Publish version 0.1.7.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-12 16:15:53 -04:00
Tom Alexander
e4c0e32536 Change public interface to return boxed dynamic error instead of String.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-12 15:52:01 -04:00
Tom Alexander
37e85158ea Reduce the exposed functions when compare feature is enabled. 2023-09-12 15:48:37 -04:00
Tom Alexander
6589a755a6 Merge branch 'reduce_pub'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 15:37:33 -04:00
Tom Alexander
a651b79e77 Move sexp into compare. 2023-09-11 15:37:20 -04:00
Tom Alexander
98de5e4ec5 Remove unused sexp parser entry point. 2023-09-11 15:07:52 -04:00
Tom Alexander
cf383fa394 Only include sexp module if compare feature is enabled.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 14:59:24 -04:00
Tom Alexander
84953c1669 Put back in needed pubs. 2023-09-11 14:59:23 -04:00
Tom Alexander
7650a9edff Remove all pub. 2023-09-11 13:11:08 -04:00
Tom Alexander
a74319d381 Add TODO. 2023-09-11 13:09:46 -04:00
Tom Alexander
7e57285ea7 Merge branch 'additional_detects'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 12:59:18 -04:00
Tom Alexander
f103d168d5 Update diary sexp parser to match org-mode's behavior. 2023-09-11 12:34:57 -04:00
Tom Alexander
f54081437a Add detect element functions for all elements that can be reasonably detected more efficiently than just parsing normally. 2023-09-11 12:28:15 -04:00
Tom Alexander
aa5988bc2f Re-enable a test.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 11:18:25 -04:00
Tom Alexander
76ca2b9762 Merge branch 'description_list_colon_in_tag'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 10:42:25 -04:00
Tom Alexander
1561e1e580 Update plain list item tag parser to allow double colon as long as its not the last one on that line. 2023-09-11 10:29:15 -04:00
Tom Alexander
1f11bfa2ec Join the plain list item tag end matchers again. 2023-09-11 10:13:22 -04:00
Tom Alexander
8440a3b256 Update test to show that we are not parsing description lists with double colon inside the tag correctly. 2023-09-11 10:01:50 -04:00
Tom Alexander
de7ad182b3 Make parse and compare their own binaries.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
This ensures that both code paths are capable of being parsed by rust-analyzer simultaneously and I think it will be less confusing to newcomers.
2023-09-09 04:21:34 -04:00
Tom Alexander
b75d9f5c91 Remove an outdated comment. 2023-09-09 00:00:12 -04:00
Tom Alexander
612744ebd0 Publish version 0.1.6.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-08 23:28:59 -04:00
Tom Alexander
1b4b8b4bdb Merge branch 'howard_abrams'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-08 23:19:19 -04:00
Tom Alexander
5587e19f16 Cleanup. 2023-09-08 23:19:08 -04:00
Tom Alexander
80f7098f9b Compare table formulas. 2023-09-08 23:05:04 -04:00
Tom Alexander
84d2babda9 Parse table formulas. 2023-09-08 22:47:07 -04:00
Tom Alexander
cc56b79683 Add a test showing we're not handling table formulas. 2023-09-08 22:42:24 -04:00
Tom Alexander
0105b49d0d Handle empty statistics cookies. 2023-09-08 22:21:49 -04:00
Tom Alexander
d79035e14d Add a test showing we are not handling empty statistics cookies. 2023-09-08 22:21:19 -04:00
Tom Alexander
7545fb7e1a Support keywords with colons in the key and without a space between the colon and value. 2023-09-08 22:17:10 -04:00
Tom Alexander
f30069efe7 Add a test showing we're not handling colons in keyword keys correctly. 2023-09-08 21:59:02 -04:00
Tom Alexander
d1fe2f6b09 Update the rest of the scripts to work with relative paths. 2023-09-08 21:50:32 -04:00
Tom Alexander
21c60d1036 Do not consume trailing whitespace on the footnote definition's final element. 2023-09-08 21:30:03 -04:00
Tom Alexander
6a1bdd5fee Support blank lines before content in footnote definitions. 2023-09-08 21:11:47 -04:00
Tom Alexander
5d20d3e99b Add a test showing we are not handling empty space for footnote definitions correctly. 2023-09-08 20:28:21 -04:00
Tom Alexander
a8fbf01124 Handle tabs for plain list descriptions.
This bug probably exists in hundreds of places across the code base. I am going to have to write a "fuzzer" that replaces random whitespace with tabs to find them all.
2023-09-08 20:02:49 -04:00
Tom Alexander
344ef04453 Add tests showing we are not handling tabs appropriately for description list tags. 2023-09-08 19:53:58 -04:00
Tom Alexander
ceb722e476 Check exit matcher after each space consumed for object trailing whitespace.
Since description list tags need to end with a space unconsumed for " ::", we need to check the exit matcher after each space consumed.
2023-09-08 19:38:49 -04:00
Tom Alexander
b04341882c Add test showing that we are not handling trailing spaces in description list tags correctly. 2023-09-08 19:24:03 -04:00
Tom Alexander
494fe5cceb Handle contentless list items mid-document. 2023-09-08 19:01:46 -04:00
Tom Alexander
0110d23387 Update empty list test to show that we're not handling trailing whitespace for empty list items properly. 2023-09-08 18:41:57 -04:00
Tom Alexander
0d7a15bfeb Handle spaces after statistics cookies. 2023-09-08 18:35:33 -04:00
Tom Alexander
352c20d1d8 Fix run_docker_compare_bisect with relative paths. 2023-09-08 18:05:10 -04:00
Tom Alexander
f82d2aada1 Fix run_docker_compare with relative paths. 2023-09-08 18:03:50 -04:00
Tom Alexander
669da4073e Accept the end condition as a parameter to the plain text parser so it can adapt to the context. 2023-09-08 17:54:49 -04:00
Tom Alexander
0056657b65 Add a test showing the plain text parser is not handling subsets of objects like inside a table cell. 2023-09-08 17:27:02 -04:00
Tom Alexander
8780976c15 Consume trailing whitespace after planning. 2023-09-08 16:30:40 -04:00
Tom Alexander
dc8b8d08ab Add test showing we break on empty sections that contain a planning. 2023-09-08 16:25:18 -04:00
Tom Alexander
93d3d9471f Compare priority, archived, and commented in headlines. 2023-09-08 16:00:16 -04:00
Tom Alexander
c7c0deed74 Parse priority cookie and COMMENT from headlines. 2023-09-08 16:00:16 -04:00
Tom Alexander
b32c21eb1d Add a test for a comment heading. 2023-09-08 16:00:16 -04:00
Tom Alexander
2e6e6fdd2b Move sections to their own source file. 2023-09-08 15:08:16 -04:00
Tom Alexander
3cc2294387 Move headlines into their own file. 2023-09-08 15:05:42 -04:00
Tom Alexander
40f22034da Make the item tag exit matcher a lower class than all all others.
This is to allow for " :: " inside a description list item's tag if it is nested inside another object.
2023-09-08 14:37:30 -04:00
Tom Alexander
ab612f293f Update org-mode version. 2023-09-08 13:11:58 -04:00
Tom Alexander
57c2922e4a Add test showing problem is description list parser. 2023-09-08 12:50:51 -04:00
Tom Alexander
c2eb1f51c8 Support blank lines between nested headlines. 2023-09-08 12:41:48 -04:00
Tom Alexander
b0930df788 Support zero skipped text in OrgSource slicing. 2023-09-07 04:16:00 -04:00
Tom Alexander
69512f559a Fix end conditions for subscript and superscript. 2023-09-07 04:16:00 -04:00
Tom Alexander
76a81b73ac Add a detect object function similar to the detect element function. 2023-09-07 04:16:00 -04:00
Tom Alexander
ba291c6776 Unify two places checking if text was preceded by whitespace. 2023-09-07 04:16:00 -04:00
Tom Alexander
6b82b46e09 Prevent nesting of text markup of the same type.
This greatly reduces the amount of detect element calls that are occurring.
2023-09-07 04:15:59 -04:00
Tom Alexander
6676012eb1 Change footnote reference class to Gamma. 2023-09-07 04:15:59 -04:00
Tom Alexander
facbe716e9 Cleanup 2023-09-07 01:23:26 -04:00
Tom Alexander
827f3e1c98 Add the rest of the relevant howard abrams repos. 2023-09-06 21:37:09 -04:00
Tom Alexander
fcea7e5a4b Add howard abrams demo-it and the upstreeam doomemacs repo to compare. 2023-09-06 21:11:46 -04:00
Tom Alexander
dda2b1e69f Compare howard abrams hamacs. 2023-09-06 20:56:36 -04:00
Tom Alexander
f79d07a7c8 Compare howard abrams dotfiles. 2023-09-06 19:49:04 -04:00
Tom Alexander
45283b48d9 Update performance scripts to support taking input file parameters.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-06 19:31:09 -04:00
Tom Alexander
08e4c646e5 Merge branch 'full_foreign_compare'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
2023-09-06 19:17:35 -04:00
Tom Alexander
f8b99ed235 Support counter set in plain list items. 2023-09-06 19:09:20 -04:00
Tom Alexander
6fc607cfe0 Compare node properties. 2023-09-06 18:54:47 -04:00
Tom Alexander
49afcf0db6 Support property nodes with colons in their key. 2023-09-06 18:54:01 -04:00
Tom Alexander
c4d7e646fc Support spaces after the end of a lesser block. 2023-09-06 18:54:01 -04:00
Tom Alexander
3fc3a5d1ef Add support for empty lesser blocks. 2023-09-06 18:11:57 -04:00
Tom Alexander
6e2fc362ea Add support for babel-call keywords. 2023-09-06 18:04:53 -04:00
Tom Alexander
90fa48661c Prefer the longer version of affiliated keywords. 2023-09-06 17:41:00 -04:00
Tom Alexander
5cefcd5fac Add in the emacs repository org-mode documents. 2023-09-06 17:06:17 -04:00
Tom Alexander
b83a103c17 Update the foreign document test to use all org-mode documents from the org-mode repository. 2023-09-06 17:02:56 -04:00
102 changed files with 1753 additions and 959 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.5" version = "0.1.7"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser." description = "An org-mode parser."
edition = "2021" edition = "2021"
@@ -25,6 +25,12 @@ path = "src/lib.rs"
name = "parse" name = "parse"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the library.
name = "compare"
path = "src/bin_compare.rs"
required-features = ["compare"]
[dependencies] [dependencies]
nom = "7.1.1" nom = "7.1.1"
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] } opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }

View File

@@ -71,10 +71,6 @@ fn write_header(test_file: &mut File) {
test_file, test_file,
r#" r#"
#[feature(exit_status_error)] #[feature(exit_status_error)]
use organic::compare_document;
use organic::parser::parse;
use organic::emacs_parse_anonymous_org_document;
use organic::parser::sexp::sexp_with_padding;
"# "#
) )
@@ -86,7 +82,6 @@ fn is_expect_fail(name: &str) -> Option<&str> {
match name { match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."), "autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."), "autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
_ => None, _ => None,
} }
} }

View File

@@ -14,7 +14,7 @@ RUN make DESTDIR="/root/dist" install
FROM build AS build-org-mode FROM build AS build-org-mode
ARG ORG_VERSION=7bdec435ff5d86220d13c431e799c5ed44a57da1 ARG ORG_VERSION=163bafb43dcc2bc94a2c7ccaa77d3d1dd488f1af
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.
@@ -36,13 +36,66 @@ ENTRYPOINT ["cargo", "test"]
FROM build as foreign-document-gather FROM build as foreign-document-gather
ARG HOWARD_ABRAMS_DOT_FILES_VERSION=1b54fe75d74670dc7bcbb6b01ea560c45528c628
ARG HOWARD_ABRAMS_DOT_FILES_PATH=/foreign_documents/howardabrams/dot-files
ARG HOWARD_ABRAMS_DOT_FILES_REPO=https://github.com/howardabrams/dot-files.git
RUN mkdir /foreign_documents RUN mkdir /foreign_documents
RUN mkdir -p $HOWARD_ABRAMS_DOT_FILES_PATH && git -C $HOWARD_ABRAMS_DOT_FILES_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DOT_FILES_PATH remote add origin $HOWARD_ABRAMS_DOT_FILES_REPO && git -C $HOWARD_ABRAMS_DOT_FILES_PATH fetch origin $HOWARD_ABRAMS_DOT_FILES_VERSION && git -C $HOWARD_ABRAMS_DOT_FILES_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_HAMACS_VERSION=da51188cc195d41882175d412fe40a8bc5730c5c
ARG HOWARD_ABRAMS_HAMACS_PATH=/foreign_documents/howardabrams/hamacs
ARG HOWARD_ABRAMS_HAMACS_REPO=https://github.com/howardabrams/hamacs.git
RUN mkdir -p $HOWARD_ABRAMS_HAMACS_PATH && git -C $HOWARD_ABRAMS_HAMACS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_HAMACS_PATH remote add origin $HOWARD_ABRAMS_HAMACS_REPO && git -C $HOWARD_ABRAMS_HAMACS_PATH fetch origin $HOWARD_ABRAMS_HAMACS_VERSION && git -C $HOWARD_ABRAMS_HAMACS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_DEMO_IT_VERSION=e399fd7ceb73caeae7cb50b247359bafcaee2a3f
ARG HOWARD_ABRAMS_DEMO_IT_PATH=/foreign_documents/howardabrams/demo-it
ARG HOWARD_ABRAMS_DEMO_IT_REPO=https://github.com/howardabrams/demo-it.git
RUN mkdir -p $HOWARD_ABRAMS_DEMO_IT_PATH && git -C $HOWARD_ABRAMS_DEMO_IT_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_DEMO_IT_PATH remote add origin $HOWARD_ABRAMS_DEMO_IT_REPO && git -C $HOWARD_ABRAMS_DEMO_IT_PATH fetch origin $HOWARD_ABRAMS_DEMO_IT_VERSION && git -C $HOWARD_ABRAMS_DEMO_IT_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_MAGIT_DEMO_VERSION=59e82f6bc7c18f550478d86a8f680c3f2da66985
ARG HOWARD_ABRAMS_MAGIT_DEMO_PATH=/foreign_documents/howardabrams/magit-demo
ARG HOWARD_ABRAMS_MAGIT_DEMO_REPO=https://github.com/howardabrams/magit-demo.git
RUN mkdir -p $HOWARD_ABRAMS_MAGIT_DEMO_PATH && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH remote add origin $HOWARD_ABRAMS_MAGIT_DEMO_REPO && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH fetch origin $HOWARD_ABRAMS_MAGIT_DEMO_VERSION && git -C $HOWARD_ABRAMS_MAGIT_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION=bfb7bd640fdf0ce3def21f9fc591ed35d776b26d
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH=/foreign_documents/howardabrams/pdx-emacs-hackers
ARG HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO=https://github.com/howardabrams/pdx-emacs-hackers.git
RUN mkdir -p $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH remote add origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_REPO && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH fetch origin $HOWARD_ABRAMS_PDX_EMACS_HACKERS_VERSION && git -C $HOWARD_ABRAMS_PDX_EMACS_HACKERS_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION=50de13068722b9e3878f8598b749b7ccd14e7f8e
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_PATH=/foreign_documents/howardabrams/flora-simulator
ARG HOWARD_ABRAMS_FLORA_SIMULATOR_REPO=https://github.com/howardabrams/flora-simulator.git
RUN mkdir -p $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH remote add origin $HOWARD_ABRAMS_FLORA_SIMULATOR_REPO && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH fetch origin $HOWARD_ABRAMS_FLORA_SIMULATOR_VERSION && git -C $HOWARD_ABRAMS_FLORA_SIMULATOR_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION=2d7a5e41001a1adf7ec24aeb6acc8525a72d7892
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH=/foreign_documents/howardabrams/literate-devops-demo
ARG HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO=https://github.com/howardabrams/literate-devops-demo.git
RUN mkdir -p $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH remote add origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_REPO && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH fetch origin $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_VERSION && git -C $HOWARD_ABRAMS_LITERATE_DEVOPS_DEMO_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION=b651c7f8b47b2710e99fce9652980902bbc1c6c9
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH=/foreign_documents/howardabrams/clojure-yesql-xp
ARG HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO=https://github.com/howardabrams/clojure-yesql-xp.git
RUN mkdir -p $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH remote add origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_REPO && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH fetch origin $HOWARD_ABRAMS_CLOJURE_YESQL_XP_VERSION && git -C $HOWARD_ABRAMS_CLOJURE_YESQL_XP_PATH checkout FETCH_HEAD
ARG HOWARD_ABRAMS_VEEP_VERSION=e37fcf63a5c4a526255735ee34955528b3b280ae
ARG HOWARD_ABRAMS_VEEP_PATH=/foreign_documents/howardabrams/veep
ARG HOWARD_ABRAMS_VEEP_REPO=https://github.com/howardabrams/veep.git
RUN mkdir -p $HOWARD_ABRAMS_VEEP_PATH && git -C $HOWARD_ABRAMS_VEEP_PATH init --initial-branch=main && git -C $HOWARD_ABRAMS_VEEP_PATH remote add origin $HOWARD_ABRAMS_VEEP_REPO && git -C $HOWARD_ABRAMS_VEEP_PATH fetch origin $HOWARD_ABRAMS_VEEP_VERSION && git -C $HOWARD_ABRAMS_VEEP_PATH checkout FETCH_HEAD
ARG DOOMEMACS_VERSION=42d5fd83504f8aa80f3248036006fbcd49222943
ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
FROM tester as foreign-document-test FROM tester as foreign-document-test
RUN apk add --no-cache bash RUN apk add --no-cache bash coreutils
RUN mkdir /foreign_documents RUN mkdir /foreign_documents
COPY --from=build-org-mode /root/org-mode/doc /foreign_documents/org-mode 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/doomemacs /foreign_documents/doomemacs
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"]

View File

@@ -5,6 +5,8 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
function log { function log {
(>&2 echo "${@}") (>&2 echo "${@}")
} }
@@ -21,25 +23,27 @@ function main {
if [ "${CARGO_TARGET_DIR:-}" = "" ]; then if [ "${CARGO_TARGET_DIR:-}" = "" ]; then
CARGO_TARGET_DIR=$(realpath target/) CARGO_TARGET_DIR=$(realpath target/)
fi fi
PARSE="${CARGO_TARGET_DIR}/release-lto/parse" PARSE="${CARGO_TARGET_DIR}/release-lto/compare"
run_compare "org-mode/org-guide.org" "/foreign_documents/org-mode/org-guide.org" local all_status=0
run_compare "org-mode/org-manual.org" "/foreign_documents/org-mode/org-manual.org"
}
function run_compare {
local name="$1"
local target_document="$2"
set +e set +e
$PARSE "$target_document" &> /dev/null
local status=$? (run_compare_function "org-mode" compare_all_org_document "/foreign_documents/org-mode")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "howard_abrams" compare_howard_abrams)
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e set -e
if [ "$status" -eq 0 ]; then if [ "$all_status" -ne 0 ]; then
echo "$(green_text "GOOD") $name" echo "$(red_text "Some tests failed.")"
else else
echo "$(red_text "FAIL") $name" echo "$(green_text "All tests passed.")"
return 1
fi fi
return "$all_status"
} }
function green_text { function green_text {
@@ -54,4 +58,88 @@ function yellow_text {
(IFS=' '; printf '\x1b[38;2;255;255;0m%s\x1b[0m' "${*}") (IFS=' '; printf '\x1b[38;2;255;255;0m%s\x1b[0m' "${*}")
} }
function indent {
local depth="$1"
local scaled_depth=$((depth * 2))
shift 1
local prefix=$(printf -- "%${scaled_depth}s")
while read l; do
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
done
}
function run_compare_function {
local name="$1"
local stdoutput
shift 1
set +e
stdoutput=$("${@}")
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$(green_text "GOOD") $name"
indent 1 <<<"$stdoutput"
else
echo "$(red_text "FAIL") $name"
indent 1 <<<"$stdoutput"
return 1
fi
}
function compare_all_org_document {
local root_dir="$1"
local target_document
local all_status=0
while read target_document; do
local relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
set +e
(run_compare "$relative_path" "$target_document")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
done<<<$(find "$root_dir" -type f -iname '*.org')
return "$all_status"
}
function run_compare {
local name="$1"
local target_document="$2"
set +e
($PARSE "$target_document" &> /dev/null)
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$(green_text "GOOD") $name"
else
echo "$(red_text "FAIL") $name"
return 1
fi
}
function compare_howard_abrams {
local all_status=0
set +e
(run_compare_function "dot-files" compare_all_org_document "/foreign_documents/howardabrams/dot-files")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "hamacs" compare_all_org_document "/foreign_documents/howardabrams/hamacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "demo-it" compare_all_org_document "/foreign_documents/howardabrams/demo-it")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "magit-demo" compare_all_org_document "/foreign_documents/howardabrams/magit-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "pdx-emacs-hackers" compare_all_org_document "/foreign_documents/howardabrams/pdx-emacs-hackers")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "flora-simulator" compare_all_org_document "/foreign_documents/howardabrams/flora-simulator")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "literate-devops-demo" compare_all_org_document "/foreign_documents/howardabrams/literate-devops-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "clojure-yesql-xp" compare_all_org_document "/foreign_documents/howardabrams/clojure-yesql-xp")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "veep" compare_all_org_document "/foreign_documents/howardabrams/veep")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
return "$all_status"
}
main "${@}" main "${@}"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
* [0/4] foo

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
: ${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
cd "$DIR/../"
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)
@@ -39,7 +38,7 @@ function launch_container {
if [ "$SHELL" != "YES" ]; then if [ "$SHELL" != "YES" ]; then
local features_joined=$(IFS=","; echo "${features[*]}") local features_joined=$(IFS=","; echo "${features[*]}")
additional_args+=(cargo run --no-default-features --features "$features_joined") 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_args+=(/bin/sh)
@@ -56,10 +55,10 @@ function launch_container {
local full_path=$($REALPATH "$path") local full_path=$($REALPATH "$path")
local containing_folder=$(dirname "$full_path") local containing_folder=$(dirname "$full_path")
local file_name=$(basename "$full_path") local file_name=$(basename "$full_path")
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "${containing_folder}:/input:ro" -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" -- "/input/$file_name" 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"
done done
else else
docker run "${additional_flags[@]}" --init --rm -i --mount type=tmpfs,destination=/tmp -v "$($REALPATH ./):/source:ro" --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source --entrypoint "" organic-test "${additional_args[@]}" 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[@]}"
fi fi
} }

View File

@@ -5,7 +5,6 @@ set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR/../"
REALPATH=$(command -v uu-realpath || command -v realpath) REALPATH=$(command -v uu-realpath || command -v realpath)
############## Setup ######################### ############## Setup #########################

View File

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

View File

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

View File

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

52
src/bin_compare.rs Normal file
View File

@@ -0,0 +1,52 @@
#![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
use std::io::Read;
use organic::compare::run_anonymous_compare;
use organic::compare::run_compare_on_file;
#[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry;
#[cfg(feature = "tracing")]
use crate::init_tracing::shutdown_telemetry;
#[cfg(feature = "tracing")]
mod init_tracing;
#[cfg(not(feature = "tracing"))]
fn main() -> Result<(), Box<dyn std::error::Error>> {
main_body()
}
#[cfg(feature = "tracing")]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
init_telemetry()?;
let main_body_result = main_body();
shutdown_telemetry()?;
main_body_result
});
result
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args().skip(1);
if args.is_empty() {
let org_contents = read_stdin_to_string()?;
run_anonymous_compare(org_contents)
} else {
for arg in args {
run_compare_on_file(arg)?
}
Ok(())
}
}
fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
let mut stdin_contents = String::new();
std::io::stdin()
.lock()
.read_to_string(&mut stdin_contents)?;
Ok(stdin_contents)
}

73
src/compare/compare.rs Normal file
View File

@@ -0,0 +1,73 @@
use std::path::Path;
use crate::compare::diff::compare_document;
use crate::compare::parse::emacs_parse_anonymous_org_document;
use crate::compare::parse::emacs_parse_file_org_document;
use crate::compare::parse::get_emacs_version;
use crate::compare::parse::get_org_mode_version;
use crate::compare::sexp::sexp;
use crate::parser::parse;
use crate::parser::parse_with_settings;
use crate::GlobalSettings;
use crate::LocalFileAccessInterface;
pub fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P,
) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = org_contents.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let rust_parsed = parse(org_contents)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
let org_contents = org_contents.as_str();
let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
let org_sexp = emacs_parse_file_org_document(org_path)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}

View File

@@ -1,10 +1,11 @@
use std::collections::BTreeSet;
use std::collections::HashSet; use std::collections::HashSet;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::assert_bounds; use super::util::assert_bounds;
use super::util::assert_name; use super::util::assert_name;
use super::util::get_property; use super::util::get_property;
use crate::parser::sexp::unquote;
use crate::parser::sexp::Token;
use crate::types::AngleLink; use crate::types::AngleLink;
use crate::types::Bold; use crate::types::Bold;
use crate::types::Citation; use crate::types::Citation;
@@ -36,6 +37,7 @@ use crate::types::Keyword;
use crate::types::LatexEnvironment; use crate::types::LatexEnvironment;
use crate::types::LatexFragment; use crate::types::LatexFragment;
use crate::types::LineBreak; use crate::types::LineBreak;
use crate::types::NodeProperty;
use crate::types::Object; use crate::types::Object;
use crate::types::OrgMacro; use crate::types::OrgMacro;
use crate::types::Paragraph; use crate::types::Paragraph;
@@ -44,6 +46,7 @@ use crate::types::PlainList;
use crate::types::PlainListItem; use crate::types::PlainListItem;
use crate::types::PlainText; use crate::types::PlainText;
use crate::types::Planning; use crate::types::Planning;
use crate::types::PriorityCookie;
use crate::types::PropertyDrawer; use crate::types::PropertyDrawer;
use crate::types::RadioLink; use crate::types::RadioLink;
use crate::types::RadioTarget; use crate::types::RadioTarget;
@@ -83,7 +86,7 @@ pub struct DiffResult<'s> {
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DiffStatus { enum DiffStatus {
Good, Good,
Bad, Bad,
} }
@@ -306,6 +309,7 @@ fn compare_element<'s>(
Element::FixedWidthArea(obj) => compare_fixed_width_area(source, emacs, obj), Element::FixedWidthArea(obj) => compare_fixed_width_area(source, emacs, obj),
Element::HorizontalRule(obj) => compare_horizontal_rule(source, emacs, obj), Element::HorizontalRule(obj) => compare_horizontal_rule(source, emacs, obj),
Element::Keyword(obj) => compare_keyword(source, emacs, obj), Element::Keyword(obj) => compare_keyword(source, emacs, obj),
Element::BabelCall(obj) => compare_babel_call(source, emacs, obj),
Element::LatexEnvironment(obj) => compare_latex_environment(source, emacs, obj), Element::LatexEnvironment(obj) => compare_latex_environment(source, emacs, obj),
}; };
match compare_result { match compare_result {
@@ -488,7 +492,7 @@ fn compare_heading<'s>(
if rust.stars.to_string() != level { if rust.stars.to_string() != level {
this_status = DiffStatus::Bad; this_status = DiffStatus::Bad;
message = Some(format!( message = Some(format!(
"Headline level do not much (emacs != rust): {} != {}", "Headline level do not match (emacs != rust): {} != {}",
level, rust.stars level, rust.stars
)) ))
} }
@@ -551,7 +555,57 @@ fn compare_heading<'s>(
.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)?);
// TODO: Compare priority, :footnote-section-p, :archivedp, :commentedp // Compare priority
let priority = get_property(emacs, ":priority")?;
match (priority, rust.priority_cookie) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
priority, rust.priority_cookie
));
}
(Some(emacs_priority_cookie), Some(rust_priority_cookie)) => {
let emacs_priority_cookie =
emacs_priority_cookie.as_atom()?.parse::<PriorityCookie>()?;
if emacs_priority_cookie != rust_priority_cookie {
this_status = DiffStatus::Bad;
message = Some(format!(
"Priority cookie mismatch (emacs != rust) {:?} != {:?}",
emacs_priority_cookie, rust_priority_cookie
));
}
}
}
// Compare archived
let archived = get_property(emacs, ":archivedp")?;
match (archived, rust.is_archived) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"archived mismatch (emacs != rust) {:?} != {:?}",
archived, rust.is_archived
));
}
(None, false) | (Some(_), true) => {}
}
// Compare commented
let commented = get_property(emacs, ":commentedp")?;
match (commented, rust.is_comment) {
(None, true) | (Some(_), false) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"commented mismatch (emacs != rust) {:?} != {:?}",
commented, rust.is_comment
));
}
(None, false) | (Some(_), true) => {}
}
// TODO: Compare :footnote-section-p
// Compare section // Compare section
let section_status = children let section_status = children
@@ -733,6 +787,8 @@ fn compare_plain_list_item<'s>(
contents_status, contents_status,
)?); )?);
// TODO: compare :bullet :checkbox :counter :pre-blank
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),
@@ -933,7 +989,7 @@ fn compare_property_drawer<'s>(
rust: &'s PropertyDrawer<'s>, rust: &'s PropertyDrawer<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> { ) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?; let children = emacs.as_list()?;
let child_status = Vec::new(); let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut message = None; let mut message = None;
let emacs_name = "property-drawer"; let emacs_name = "property-drawer";
@@ -949,9 +1005,8 @@ fn compare_property_drawer<'s>(
Ok(_) => {} Ok(_) => {}
}; };
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()) {
// TODO: What are node properties and are they the only legal child of property drawers? child_status.push(compare_node_property(source, emacs_child, rust_child)?);
// child_status.push(compare_element(source, emacs_child, rust_child)?);
} }
Ok(DiffResult { Ok(DiffResult {
@@ -965,6 +1020,40 @@ fn compare_property_drawer<'s>(
.into()) .into())
} }
fn compare_node_property<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s NodeProperty<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "node-property";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
match assert_bounds(source, emacs, rust) {
Err(err) => {
this_status = DiffStatus::Bad;
message = Some(err.to_string())
}
Ok(_) => {}
};
// TODO: Compare :key :value
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message,
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into())
}
fn compare_table<'s>( fn compare_table<'s>(
source: &'s str, source: &'s str,
emacs: &'s Token<'s>, emacs: &'s Token<'s>,
@@ -987,6 +1076,44 @@ fn compare_table<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// Compare formulas
//
// :tblfm is either nil or a list () filled with quoted strings containing the value for any tblfm keywords at the end of the table.
let emacs_formulas = get_property(emacs, ":tblfm")?;
if let Some(emacs_formulas) = emacs_formulas {
let emacs_formulas = emacs_formulas.as_list()?;
if emacs_formulas.len() != rust.formulas.len() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
} else {
let atoms = emacs_formulas
.into_iter()
.map(Token::as_atom)
.collect::<Result<Vec<_>, _>>()?;
let unquoted = atoms
.into_iter()
.map(unquote)
.collect::<Result<BTreeSet<_>, _>>()?;
for kw in &rust.formulas {
if !unquoted.contains(kw.value) {
this_status = DiffStatus::Bad;
message = Some(format!("Could not find formula in emacs: {}", kw.value))
}
}
}
} else {
if !rust.formulas.is_empty() {
this_status = DiffStatus::Bad;
message = Some(format!(
"Formulas do not match (emacs != rust): {:?} != {:?}",
emacs_formulas, rust.formulas
))
}
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_row(source, emacs_child, rust_child)?); child_status.push(compare_table_row(source, emacs_child, rust_child)?);
} }
@@ -1024,6 +1151,10 @@ fn compare_table_row<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// TODO: Compare :type
//
// :type is an unquoted atom of either standard or rule
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
child_status.push(compare_table_cell(source, emacs_child, rust_child)?); child_status.push(compare_table_cell(source, emacs_child, rust_child)?);
} }
@@ -1222,6 +1353,8 @@ fn compare_src_block<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// TODO: Compare :language :switches :parameters :number-lines :preserve-indent :retain-labels :use-labels :label-fmt :value
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),
@@ -1447,6 +1580,52 @@ fn compare_keyword<'s>(
.into()) .into())
} }
fn compare_babel_call<'s>(
source: &'s str,
emacs: &'s Token<'s>,
rust: &'s Keyword<'s>,
) -> Result<DiffEntry<'s>, Box<dyn std::error::Error>> {
let child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
let emacs_name = "babel-call";
if assert_name(emacs, emacs_name).is_err() {
this_status = DiffStatus::Bad;
}
match assert_bounds(source, emacs, rust) {
Err(err) => {
this_status = DiffStatus::Bad;
message = Some(err.to_string())
}
Ok(_) => {}
};
// TODO: compare :call :inside-header :arguments :end-header
let value = unquote(
get_property(emacs, ":value")?
.ok_or("Emacs keywords should have a :value")?
.as_atom()?,
)?;
if value != rust.value {
this_status = DiffStatus::Bad;
message = Some(format!(
"Mismatchs keyword values (emacs != rust) {:?} != {:?}",
value, rust.value
))
}
Ok(DiffResult {
status: this_status,
name: emacs_name.to_owned(),
message,
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into())
}
fn compare_latex_environment<'s>( fn compare_latex_environment<'s>(
source: &'s str, source: &'s str,
emacs: &'s Token<'s>, emacs: &'s Token<'s>,

View File

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

View File

@@ -16,9 +16,6 @@ use nom::sequence::delimited;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::convert_error;
use super::org_source::OrgSource;
use super::util::get_consumed;
use crate::error::Res; use crate::error::Res;
#[derive(Debug)] #[derive(Debug)]
@@ -31,8 +28,8 @@ pub enum Token<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct TextWithProperties<'s> { pub struct TextWithProperties<'s> {
pub text: &'s str, pub(crate) text: &'s str,
pub properties: Vec<Token<'s>>, pub(crate) properties: Vec<Token<'s>>,
} }
enum ParseState { enum ParseState {
@@ -41,35 +38,39 @@ enum ParseState {
} }
impl<'s> Token<'s> { impl<'s> Token<'s> {
pub fn as_vector<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> { pub(crate) fn as_vector<'p>(
&'p self,
) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::Vector(children) => Ok(children), Token::Vector(children) => Ok(children),
_ => Err(format!("wrong token type, expected vector: {:?}", self)), _ => Err(format!("wrong token type, expected vector: {:?}", self)),
}?) }?)
} }
pub fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> { pub(crate) fn as_list<'p>(&'p self) -> Result<&'p Vec<Token<'s>>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::List(children) => Ok(children), Token::List(children) => Ok(children),
_ => Err(format!("wrong token type, expected list: {:?}", self)), _ => Err(format!("wrong token type, expected list: {:?}", self)),
}?) }?)
} }
pub fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> { pub(crate) fn as_atom<'p>(&'p self) -> Result<&'s str, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::Atom(body) => Ok(*body), Token::Atom(body) => Ok(*body),
_ => Err(format!("wrong token type, expected atom: {:?}", self)), _ => Err(format!("wrong token type, expected atom: {:?}", self)),
}?) }?)
} }
pub fn as_text<'p>(&'p self) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> { pub(crate) fn as_text<'p>(
&'p self,
) -> Result<&'p TextWithProperties<'s>, Box<dyn std::error::Error>> {
Ok(match self { Ok(match self {
Token::TextWithProperties(body) => Ok(body), Token::TextWithProperties(body) => Ok(body),
_ => Err(format!("wrong token type, expected text: {:?}", self)), _ => Err(format!("wrong token type, expected text: {:?}", self)),
}?) }?)
} }
pub fn as_map<'p>( pub(crate) fn as_map<'p>(
&'p self, &'p self,
) -> Result<HashMap<&'s str, &'p Token<'s>>, Box<dyn std::error::Error>> { ) -> Result<HashMap<&'s str, &'p Token<'s>>, Box<dyn std::error::Error>> {
let mut hashmap = HashMap::new(); let mut hashmap = HashMap::new();
@@ -95,7 +96,26 @@ impl<'s> Token<'s> {
} }
} }
pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> { /// Check if the child string slice is a slice of the parent string slice.
fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize;
let parent_end = parent_start + parent.len();
let child_start = child.as_ptr() as usize;
let child_end = child_start + child.len();
child_start >= parent_start && child_end <= parent_end
}
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
assert!(is_slice_of(input, remaining));
let source = {
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
&input[..offset]
};
source.into()
}
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut out = String::with_capacity(text.len()); let mut out = String::with_capacity(text.len());
if !text.starts_with(r#"""#) { if !text.starts_with(r#"""#) {
return Err("Quoted text does not start with quote.".into()); return Err("Quoted text does not start with quote.".into());
@@ -132,29 +152,20 @@ pub fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn sexp_with_padding<'s>(input: &'s str) -> Res<&'s str, Token<'s>> { pub fn sexp<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = multispace0(input)?; let (remaining, _) = multispace0(input)?;
let remaining = OrgSource::new(remaining); let (remaining, tkn) = token(remaining).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
let (remaining, tkn) = token(remaining)
.map(|(rem, out)| (Into::<&str>::into(rem), out))
.map_err(convert_error)?;
let (remaining, _) = multispace0(remaining)?; let (remaining, _) = multispace0(remaining)?;
Ok((remaining, tkn)) Ok((remaining, tkn))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn sexp<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn token<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, tkn) = token(input)?;
Ok((remaining, tkn))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn token<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
alt((list, vector, atom))(input) alt((list, vector, atom))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn list<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("(")(input)?; let (remaining, _) = tag("(")(input)?;
let (remaining, children) = delimited( let (remaining, children) = delimited(
multispace0, multispace0,
@@ -166,7 +177,7 @@ fn list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn vector<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("[")(input)?; let (remaining, _) = tag("[")(input)?;
let (remaining, children) = delimited( let (remaining, children) = delimited(
multispace0, multispace0,
@@ -178,7 +189,7 @@ fn vector<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
not(peek(one_of(")]")))(input)?; not(peek(one_of(")]")))(input)?;
alt(( alt((
text_with_properties, text_with_properties,
@@ -189,7 +200,7 @@ fn atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, body) = take_till1(|c| match c { let (remaining, body) = take_till1(|c| match c {
' ' | '\t' | '\r' | '\n' | ')' | ']' => true, ' ' | '\t' | '\r' | '\n' | ')' | ']' => true,
_ => false, _ => false,
@@ -198,7 +209,7 @@ fn unquoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag(r#"""#)(input)?; let (remaining, _) = tag(r#"""#)(input)?;
let (remaining, _) = escaped( let (remaining, _) = escaped(
take_till1(|c| match c { take_till1(|c| match c {
@@ -214,7 +225,7 @@ fn quoted_atom<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn hash_notation<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("#<")(input)?; let (remaining, _) = tag("#<")(input)?;
let (remaining, _body) = take_till1(|c| match c { let (remaining, _body) = take_till1(|c| match c {
'>' => true, '>' => true,
@@ -225,7 +236,7 @@ fn hash_notation<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> {
Ok((remaining, Token::Atom(source.into()))) Ok((remaining, Token::Atom(source.into())))
} }
fn text_with_properties<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Token<'s>> { fn text_with_properties<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag("#(")(input)?; let (remaining, _) = tag("#(")(input)?;
let (remaining, (text, props)) = delimited( let (remaining, (text, props)) = delimited(
multispace0, multispace0,
@@ -255,7 +266,7 @@ mod tests {
#[test] #[test]
fn simple() { fn simple() {
let input = " (foo bar baz ) "; let input = " (foo bar baz ) ";
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::Atom(_) => false, Token::Atom(_) => false,
@@ -268,7 +279,7 @@ mod tests {
#[test] #[test]
fn quoted() { fn quoted() {
let input = r#" ("foo" bar baz ) "#; let input = r#" ("foo" bar baz ) "#;
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::Atom(_) => false, Token::Atom(_) => false,
@@ -292,7 +303,7 @@ mod tests {
#[test] #[test]
fn quoted_containing_paren() { fn quoted_containing_paren() {
let input = r#" (foo "b(a)r" baz ) "#; let input = r#" (foo "b(a)r" baz ) "#;
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::List(_) => true, Token::List(_) => true,
@@ -328,7 +339,7 @@ mod tests {
#[test] #[test]
fn string_containing_escaped_characters() { fn string_containing_escaped_characters() {
let input = r#" (foo "\\( x=2 \\)" bar) "#; let input = r#" (foo "\\( x=2 \\)" bar) "#;
let (remaining, parsed) = sexp_with_padding(input).expect("Parse the input"); let (remaining, parsed) = sexp(input).expect("Parse the input");
assert_eq!(remaining, ""); assert_eq!(remaining, "");
assert!(match parsed { assert!(match parsed {
Token::Atom(_) => false, Token::Atom(_) => false,

View File

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

View File

@@ -14,24 +14,28 @@ use crate::error::Res;
use crate::parser::OrgSource; use crate::parser::OrgSource;
#[derive(Debug)] #[derive(Debug)]
pub enum ContextElement<'r, 's> { pub(crate) enum ContextElement<'r, 's> {
/// Stores a parser that indicates that children should exit upon matching an exit matcher. /// Stores a parser that indicates that children should exit upon matching an exit matcher.
ExitMatcherNode(ExitMatcherNode<'r>), ExitMatcherNode(ExitMatcherNode<'r>),
/// Stores the name of the current element to prevent directly nesting elements of the same type. /// Stores the name of the current element to prevent directly nesting elements of the same type.
Context(&'r str), Context(&'r str),
/// Stores the name of the current object to prevent directly nesting elements of the same type.
ContextObject(&'r str),
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool), ConsumeTrailingWhitespace(bool),
/// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement. /// This is just here to use the 's lifetime until I'm sure we can eliminate it from ContextElement.
#[allow(dead_code)]
Placeholder(PhantomData<&'s str>), Placeholder(PhantomData<&'s str>),
} }
pub struct ExitMatcherNode<'r> { pub(crate) struct ExitMatcherNode<'r> {
// TODO: Should this be "&'r DynContextMatcher<'c>" ? // TODO: Should this be "&'r DynContextMatcher<'c>" ?
pub exit_matcher: &'r DynContextMatcher<'r>, pub(crate) exit_matcher: &'r DynContextMatcher<'r>,
pub class: ExitClass, pub(crate) class: ExitClass,
} }
impl<'r> std::fmt::Debug for ExitMatcherNode<'r> { impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
@@ -43,13 +47,13 @@ impl<'r> std::fmt::Debug for ExitMatcherNode<'r> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Context<'g, 'r, 's> { pub(crate) struct Context<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>, tree: List<'r, &'r ContextElement<'r, 's>>,
} }
impl<'g, 'r, 's> Context<'g, 'r, 's> { impl<'g, 'r, 's> Context<'g, 'r, 's> {
pub fn new( pub(crate) fn new(
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
tree: List<'r, &'r ContextElement<'r, 's>>, tree: List<'r, &'r ContextElement<'r, 's>>,
) -> Self { ) -> Self {
@@ -59,38 +63,38 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
} }
} }
pub fn with_additional_node(&'r self, new_element: &'r ContextElement<'r, 's>) -> Self { pub(crate) fn with_additional_node(&'r self, new_element: &'r ContextElement<'r, 's>) -> Self {
let new_tree = self.tree.push(new_element); let new_tree = self.tree.push(new_element);
Self::new(self.global_settings, new_tree) Self::new(self.global_settings, new_tree)
} }
pub fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> { pub(crate) fn iter(&'r self) -> super::list::Iter<'r, &'r ContextElement<'r, 's>> {
self.tree.iter() self.tree.iter()
} }
pub fn iter_context(&'r self) -> Iter<'g, 'r, 's> { fn iter_context(&'r self) -> Iter<'g, 'r, 's> {
Iter { Iter {
next: self.tree.iter_list(), next: self.tree.iter_list(),
global_settings: self.global_settings, global_settings: self.global_settings,
} }
} }
pub fn get_parent(&'r self) -> Option<Self> { pub(crate) fn get_parent(&'r self) -> Option<Self> {
self.tree.get_parent().map(|parent_tree| Self { self.tree.get_parent().map(|parent_tree| Self {
global_settings: self.global_settings, global_settings: self.global_settings,
tree: parent_tree.clone(), tree: parent_tree.clone(),
}) })
} }
pub fn get_data(&self) -> &ContextElement<'r, 's> { fn get_data(&self) -> &ContextElement<'r, 's> {
self.tree.get_data() self.tree.get_data()
} }
pub fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> { pub(crate) fn get_global_settings(&self) -> &'g GlobalSettings<'g, 's> {
self.global_settings self.global_settings
} }
pub fn with_global_settings<'gg>( pub(crate) fn with_global_settings<'gg>(
&self, &self,
new_settings: &'gg GlobalSettings<'gg, 's>, new_settings: &'gg GlobalSettings<'gg, 's>,
) -> Context<'gg, 'r, 's> { ) -> Context<'gg, 'r, 's> {
@@ -101,7 +105,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn check_exit_matcher( pub(crate) fn check_exit_matcher(
&'r self, &'r self,
i: OrgSource<'s>, i: OrgSource<'s>,
) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> { ) -> IResult<OrgSource<'s>, OrgSource<'s>, CustomError<OrgSource<'s>>> {
@@ -130,7 +134,7 @@ impl<'g, 'r, 's> Context<'g, 'r, 's> {
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.
/// ///
/// Defaults to true. /// Defaults to true.
pub fn should_consume_trailing_whitespace(&self) -> bool { pub(crate) fn should_consume_trailing_whitespace(&self) -> bool {
self._should_consume_trailing_whitespace().unwrap_or(true) self._should_consume_trailing_whitespace().unwrap_or(true)
} }
@@ -155,7 +159,7 @@ fn document_end<'b, 'g, 'r, 's>(
eof(input) eof(input)
} }
pub struct Iter<'g, 'r, 's> { struct Iter<'g, 'r, 's> {
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
next: super::list::IterList<'r, &'r ContextElement<'r, 's>>, next: super::list::IterList<'r, &'r ContextElement<'r, 's>>,
} }
@@ -172,7 +176,7 @@ impl<'g, 'r, 's> Iterator for Iter<'g, 'r, 's> {
} }
impl<'r, 's> ContextElement<'r, 's> { impl<'r, 's> ContextElement<'r, 's> {
pub fn document_context() -> Self { pub(crate) fn document_context() -> Self {
Self::ExitMatcherNode(ExitMatcherNode { Self::ExitMatcherNode(ExitMatcherNode {
exit_matcher: &document_end, exit_matcher: &document_end,
class: ExitClass::Document, class: ExitClass::Document,

View File

@@ -1,16 +1,9 @@
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum ExitClass { pub(crate) enum ExitClass {
/// Headlines and sections.
Document = 1, Document = 1,
Alpha = 2,
/// Elements who take priority over beta elements when matching. Beta = 3,
Alpha = 20, Gamma = 4,
/// Elements who cede priority to alpha elements when matching.
Beta = 300,
/// Elements who cede priority to alpha and beta elements when matching.
Gamma = 4000,
} }
impl std::fmt::Display for ExitClass { impl std::fmt::Display for ExitClass {

View File

@@ -15,7 +15,7 @@ pub struct GlobalSettings<'g, 's> {
} }
impl<'g, 's> GlobalSettings<'g, 's> { impl<'g, 's> GlobalSettings<'g, 's> {
pub fn new() -> GlobalSettings<'g, 's> { fn new() -> GlobalSettings<'g, 's> {
GlobalSettings { GlobalSettings {
radio_targets: Vec::new(), radio_targets: Vec::new(),
file_access: &LocalFileAccessInterface { file_access: &LocalFileAccessInterface {

View File

@@ -1,7 +1,7 @@
use std::fmt::Debug; use std::fmt::Debug;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct List<'parent, T> { pub(crate) struct List<'parent, T> {
data: T, data: T,
parent: Link<'parent, T>, parent: Link<'parent, T>,
} }
@@ -9,30 +9,30 @@ pub struct List<'parent, T> {
type Link<'parent, T> = Option<&'parent List<'parent, T>>; type Link<'parent, T> = Option<&'parent List<'parent, T>>;
impl<'parent, T> List<'parent, T> { impl<'parent, T> List<'parent, T> {
pub fn new(first_item: T) -> Self { pub(crate) fn new(first_item: T) -> Self {
Self { Self {
data: first_item, data: first_item,
parent: None, parent: None,
} }
} }
pub fn get_data(&self) -> &T { pub(crate) fn get_data(&self) -> &T {
&self.data &self.data
} }
pub fn get_parent(&'parent self) -> Link<'parent, T> { pub(crate) fn get_parent(&'parent self) -> Link<'parent, T> {
self.parent self.parent
} }
pub fn iter(&self) -> Iter<'_, T> { pub(crate) fn iter(&self) -> Iter<'_, T> {
Iter { next: Some(self) } Iter { next: Some(self) }
} }
pub fn iter_list(&self) -> IterList<'_, T> { pub(crate) fn iter_list(&self) -> IterList<'_, T> {
IterList { next: Some(self) } IterList { next: Some(self) }
} }
pub fn push(&'parent self, item: T) -> Self { pub(crate) fn push(&'parent self, item: T) -> Self {
Self { Self {
data: item, data: item,
parent: Some(self), parent: Some(self),
@@ -40,7 +40,7 @@ impl<'parent, T> List<'parent, T> {
} }
} }
pub struct Iter<'a, T> { pub(crate) struct Iter<'a, T> {
next: Link<'a, T>, next: Link<'a, T>,
} }
@@ -54,7 +54,7 @@ impl<'a, T> Iterator for Iter<'a, T> {
} }
} }
pub struct IterList<'a, T> { pub(crate) struct IterList<'a, T> {
next: Link<'a, T>, next: Link<'a, T>,
} }

View File

@@ -8,22 +8,22 @@ mod global_settings;
mod list; mod list;
mod parser_with_context; mod parser_with_context;
pub type RefContext<'b, 'g, 'r, 's> = &'b Context<'g, 'r, 's>; pub(crate) type RefContext<'b, 'g, 'r, 's> = &'b Context<'g, 'r, 's>;
pub trait ContextMatcher = for<'b, 'g, 'r, 's> Fn( pub(crate) trait ContextMatcher = for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>, OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>>; ) -> Res<OrgSource<'s>, OrgSource<'s>>;
pub type DynContextMatcher<'c> = dyn ContextMatcher + 'c; type DynContextMatcher<'c> = dyn ContextMatcher + 'c;
pub trait Matcher = for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>; pub(crate) trait Matcher = for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>;
#[allow(dead_code)] #[allow(dead_code)]
pub type DynMatcher<'c> = dyn Matcher + 'c; type DynMatcher<'c> = dyn Matcher + 'c;
pub use context::Context; pub(crate) use context::Context;
pub use context::ContextElement; pub(crate) use context::ContextElement;
pub use context::ExitMatcherNode; pub(crate) use context::ExitMatcherNode;
pub use exiting::ExitClass; 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 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;

View File

@@ -2,7 +2,7 @@ use nom::error::ErrorKind;
use nom::error::ParseError; use nom::error::ParseError;
use nom::IResult; use nom::IResult;
pub type Res<T, U> = IResult<T, U, CustomError<T>>; pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>;
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now. // TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
#[derive(Debug)] #[derive(Debug)]
@@ -13,7 +13,7 @@ pub enum CustomError<I> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct MyError<I>(pub I); pub struct MyError<I>(pub(crate) I);
impl<I> ParseError<I> for CustomError<I> { impl<I> ParseError<I> for CustomError<I> {
fn from_error_kind(input: I, kind: ErrorKind) -> Self { fn from_error_kind(input: I, kind: ErrorKind) -> Self {

View File

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

View File

@@ -10,7 +10,7 @@ const SERVICE_NAME: &'static str = "organic";
// Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits. // Despite the obvious verbosity that fully-qualifying everything causes, in these functions I am fully-qualifying everything relating to tracing. This is because the tracing feature involves multiple libraries working together and so I think it is beneficial to see which libraries contribute which bits.
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
// by default it will hit http://localhost:4317 with a gRPC payload // by default it will hit http://localhost:4317 with a gRPC payload
// TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned. // TODO: I think the endpoint can be controlled by the OTEL_EXPORTER_OTLP_TRACES_ENDPOINT env variable instead of hard-coded into this code base. Regardless, I am the only developer right now so I am not too concerned.
let exporter = opentelemetry_otlp::new_exporter() let exporter = opentelemetry_otlp::new_exporter()
@@ -55,17 +55,17 @@ pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
} }
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
opentelemetry::global::shutdown_tracer_provider(); opentelemetry::global::shutdown_tracer_provider();
Ok(()) Ok(())
} }
#[cfg(not(feature = "tracing"))] #[cfg(not(feature = "tracing"))]
pub fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn init_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }
#[cfg(not(feature = "tracing"))] #[cfg(not(feature = "tracing"))]
pub fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> { pub(crate) fn shutdown_telemetry() -> Result<(), Box<dyn std::error::Error>> {
Ok(()) Ok(())
} }

View File

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

View File

@@ -4,19 +4,7 @@ use std::io::Read;
use std::path::Path; use std::path::Path;
use ::organic::parser::parse; use ::organic::parser::parse;
#[cfg(feature = "compare")]
use organic::compare_document;
#[cfg(feature = "compare")]
use organic::emacs_parse_anonymous_org_document;
#[cfg(feature = "compare")]
use organic::emacs_parse_file_org_document;
#[cfg(feature = "compare")]
use organic::get_emacs_version;
#[cfg(feature = "compare")]
use organic::get_org_mode_version;
use organic::parser::parse_with_settings; use organic::parser::parse_with_settings;
#[cfg(feature = "compare")]
use organic::parser::sexp::sexp_with_padding;
use organic::GlobalSettings; use organic::GlobalSettings;
use organic::LocalFileAccessInterface; use organic::LocalFileAccessInterface;
@@ -66,85 +54,14 @@ fn read_stdin_to_string() -> Result<String, Box<dyn std::error::Error>> {
Ok(stdin_contents) Ok(stdin_contents)
} }
#[cfg(feature = "compare")]
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> { fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = org_contents.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let rust_parsed = parse(org_contents)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?;
let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}
#[cfg(not(feature = "compare"))]
fn run_anonymous_parse<P: AsRef<str>>(org_contents: P) -> Result<(), Box<dyn std::error::Error>> {
eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let rust_parsed = parse(org_contents.as_ref())?; let rust_parsed = parse(org_contents.as_ref())?;
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
Ok(()) Ok(())
} }
#[cfg(feature = "compare")]
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> { fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref(); let org_path = org_path.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?;
let org_contents = org_contents.as_str();
let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()),
};
let global_settings = {
let mut global_settings = GlobalSettings::default();
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
let org_sexp = emacs_parse_file_org_document(org_path)?;
let (_remaining, parsed_sexp) =
sexp_with_padding(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if diff_result.is_bad() {
Err("Diff results do not match.")?;
}
Ok(())
}
#[cfg(not(feature = "compare"))]
fn run_parse_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!(
"This program was built with compare disabled. Only parsing with organic, not comparing."
);
let parent_directory = org_path let parent_directory = org_path
.parent() .parent()
.ok_or("Should be contained inside a directory.")?; .ok_or("Should be contained inside a directory.")?;

View File

@@ -18,7 +18,7 @@ use crate::parser::util::get_consumed;
use crate::types::AngleLink; use crate::types::AngleLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn angle_link<'b, 'g, 'r, 's>( pub(crate) fn angle_link<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, AngleLink<'s>> { ) -> Res<OrgSource<'s>, AngleLink<'s>> {

View File

@@ -31,7 +31,7 @@ use crate::types::Citation;
use crate::types::Object; use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation<'b, 'g, 'r, 's>( pub(crate) fn citation<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Citation<'s>> { ) -> Res<OrgSource<'s>, Citation<'s>> {

View File

@@ -29,7 +29,7 @@ use crate::types::CitationReference;
use crate::types::Object; use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference<'b, 'g, 'r, 's>( pub(crate) fn citation_reference<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CitationReference<'s>> { ) -> Res<OrgSource<'s>, CitationReference<'s>> {
@@ -49,7 +49,7 @@ pub fn citation_reference<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn citation_reference_key<'b, 'g, 'r, 's>( pub(crate) fn citation_reference_key<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -161,7 +161,7 @@ fn _key_suffix_end<'b, 'g, 'r, 's>(
tag(";")(input) tag(";")(input)
} }
pub fn must_balance_bracket<'s, F, O>( pub(crate) fn must_balance_bracket<'s, F, O>(
mut inner: F, mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O> ) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>
where where

View File

@@ -20,7 +20,7 @@ use crate::parser::util::start_of_line;
use crate::types::Clock; use crate::types::Clock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn clock<'b, 'g, 'r, 's>( pub(crate) fn clock<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Clock<'s>> { ) -> Res<OrgSource<'s>, Clock<'s>> {

View File

@@ -25,7 +25,7 @@ use crate::parser::util::start_of_line;
use crate::types::Comment; use crate::types::Comment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment<'b, 'g, 'r, 's>( pub(crate) fn comment<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Comment<'s>> { ) -> Res<OrgSource<'s>, Comment<'s>> {
@@ -67,6 +67,17 @@ fn comment_line<'b, 'g, 'r, 's>(
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_comment<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((
start_of_line,
space0,
tag("#"),
alt((tag(" "), line_ending, eof)),
))(input)?;
Ok((input, ()))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

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

View File

@@ -1,63 +1,34 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::all_consuming; use nom::combinator::all_consuming;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many1_count;
use nom::multi::many_till;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings; use super::in_buffer_settings::apply_in_buffer_settings;
use super::in_buffer_settings::scan_for_in_buffer_settings; use super::in_buffer_settings::scan_for_in_buffer_settings;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::section::zeroth_section;
use super::token::AllTokensIterator; use super::token::AllTokensIterator;
use super::token::Token; use super::token::Token;
use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::start_of_line;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::Context; use crate::context::Context;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::context::RefContext; 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::parser::comment::comment;
use crate::parser::element_parser::element;
use crate::parser::object_parser::standard_set_object;
use crate::parser::org_source::convert_error; use crate::parser::org_source::convert_error;
use crate::parser::planning::planning;
use crate::parser::property_drawer::property_drawer;
use crate::parser::util::blank_line; use crate::parser::util::blank_line;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::types::Document; use crate::types::Document;
use crate::types::DocumentElement;
use crate::types::Element;
use crate::types::Heading;
use crate::types::Object; use crate::types::Object;
use crate::types::Section;
use crate::types::TodoKeywordType;
/// Parse a full org-mode document. /// Parse a full org-mode document.
/// ///
/// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document. /// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document.
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, String> { pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
parse_with_settings(input, &GlobalSettings::default()) parse_with_settings(input, &GlobalSettings::default())
} }
@@ -70,7 +41,7 @@ pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, String> {
pub fn parse_with_settings<'g, 's>( pub fn parse_with_settings<'g, 's>(
input: &'s str, input: &'s str,
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
) -> Result<Document<'s>, String> { ) -> Result<Document<'s>, Box<dyn std::error::Error>> {
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context)); let initial_context = Context::new(global_settings, List::new(&initial_context));
let wrapped_input = OrgSource::new(input); let wrapped_input = OrgSource::new(input);
@@ -78,7 +49,7 @@ pub fn parse_with_settings<'g, 's>(
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input) all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
.map_err(|err| err.to_string()) .map_err(|err| err.to_string())
.map(|(_remaining, parsed_document)| parsed_document); .map(|(_remaining, parsed_document)| parsed_document);
ret Ok(ret?)
} }
/// Parse a full org-mode document. /// Parse a full org-mode document.
@@ -87,7 +58,7 @@ pub fn parse_with_settings<'g, 's>(
/// ///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO". /// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)] #[allow(dead_code)]
pub fn document<'b, 'g, 'r, 's>( fn document<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: &'s str, input: &'s str,
) -> Res<&'s str, Document<'s>> { ) -> Res<&'s str, Document<'s>> {
@@ -185,314 +156,8 @@ fn _document<'b, 'g, 'r, 's>(
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn zeroth_section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let without_consuming_whitespace_context = ContextElement::ConsumeTrailingWhitespace(false);
let without_consuming_whitespace_context =
parser_context.with_additional_node(&without_consuming_whitespace_context);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, comment_and_property_drawer_element) = opt(tuple((
opt(parser_with_context!(comment)(
&without_consuming_whitespace_context,
)),
parser_with_context!(property_drawer)(context),
many0(blank_line),
)))(input)?;
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some()
},
)(remaining)?;
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
children.insert(0, Element::PropertyDrawer(property_drawer));
comment
.map(Element::Comment)
.map(|ele| children.insert(0, ele));
});
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (planning_element, property_drawer_element)) = tuple((
opt(parser_with_context!(planning)(&parser_context)),
opt(parser_with_context!(property_drawer)(&parser_context)),
))(input)?;
if planning_element.is_none() && property_drawer_element.is_none() {
let (remain, _ws) = many0(blank_line)(remaining)?;
remaining = remain;
input = remain;
}
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
},
)(remaining)?;
property_drawer_element
.map(Element::PropertyDrawer)
.map(|ele| children.insert(0, ele));
planning_element
.map(Element::Planning)
.map(|ele| children.insert(0, ele));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input)
}
const fn heading(
parent_stars: usize,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> {
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
title,
tags: heading_tags,
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
OrgSource<'s>,
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (
remaining,
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
many1(parser_with_context!(standard_set_object)(&parser_context)),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?;
Ok((
remaining,
(
star_count,
ws,
maybe_todo_keyword,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
let global_settings = context.get_global_settings();
if global_settings.in_progress_todo_keywords.is_empty()
&& global_settings.complete_todo_keywords.is_empty()
{
alt((
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
))(input)
} else {
for todo_keyword in global_settings
.in_progress_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
Err(_) => {}
}
}
for todo_keyword in global_settings
.complete_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoTodoKeyword".into(),
))))
}
}
impl<'s> Document<'s> { impl<'s> Document<'s> {
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> { fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
AllTokensIterator::new(Token::Document(self)) AllTokensIterator::new(Token::Document(self))
} }
} }

View File

@@ -32,7 +32,7 @@ use crate::types::Paragraph;
use crate::types::SetSource; use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn drawer<'b, 'g, 'r, 's>( pub(crate) fn drawer<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Drawer<'s>> { ) -> Res<OrgSource<'s>, Drawer<'s>> {

View File

@@ -32,7 +32,7 @@ use crate::types::Paragraph;
use crate::types::SetSource; use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn dynamic_block<'b, 'g, 'r, 's>( pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DynamicBlock<'s>> { ) -> Res<OrgSource<'s>, DynamicBlock<'s>> {

View File

@@ -4,14 +4,19 @@ use nom::multi::many0;
use super::clock::clock; use super::clock::clock;
use super::comment::comment; use super::comment::comment;
use super::comment::detect_comment;
use super::diary_sexp::detect_diary_sexp;
use super::diary_sexp::diary_sexp; use super::diary_sexp::diary_sexp;
use super::drawer::drawer; use super::drawer::drawer;
use super::dynamic_block::dynamic_block; use super::dynamic_block::dynamic_block;
use super::fixed_width_area::detect_fixed_width_area;
use super::fixed_width_area::fixed_width_area; use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::detect_footnote_definition;
use super::footnote_definition::footnote_definition; use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block; use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule; use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
use super::keyword::babel_call_keyword;
use super::keyword::keyword; use super::keyword::keyword;
use super::latex_environment::latex_environment; use super::latex_environment::latex_environment;
use super::lesser_block::comment_block; use super::lesser_block::comment_block;
@@ -23,6 +28,7 @@ use super::org_source::OrgSource;
use super::paragraph::paragraph; use super::paragraph::paragraph;
use super::plain_list::detect_plain_list; use super::plain_list::detect_plain_list;
use super::plain_list::plain_list; use super::plain_list::plain_list;
use super::table::detect_table;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; use crate::context::parser_with_context;
@@ -34,7 +40,7 @@ use crate::parser::table::org_mode_table;
use crate::types::Element; use crate::types::Element;
use crate::types::SetSource; use crate::types::SetSource;
pub const fn element( pub(crate) const fn element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
@@ -67,6 +73,7 @@ fn _element<'b, 'g, 'r, 's>(
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context); let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context); let keyword_matcher = parser_with_context!(keyword)(context);
let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context); let affiliated_keyword_matcher = parser_with_context!(affiliated_keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call_keyword)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context); let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context); let latex_environment_matcher = parser_with_context!(latex_environment)(context);
@@ -90,6 +97,7 @@ fn _element<'b, 'g, 'r, 's>(
map(fixed_width_area_matcher, Element::FixedWidthArea), map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule), map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment), map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword), map(keyword_matcher, Element::Keyword),
))(remaining) ))(remaining)
{ {
@@ -119,7 +127,7 @@ fn _element<'b, 'g, 'r, 's>(
Ok((remaining, element)) Ok((remaining, element))
} }
pub const fn detect_element( pub(crate) const fn detect_element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> ) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()>
{ {
@@ -132,7 +140,16 @@ fn _detect_element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(input).is_ok() { if alt((
detect_plain_list,
detect_footnote_definition,
detect_diary_sexp,
detect_comment,
detect_fixed_width_area,
detect_table,
))(input)
.is_ok()
{
return Ok((input, ())); return Ok((input, ()));
} }
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {

View File

@@ -433,7 +433,7 @@ const ORG_ENTITIES: [&'static str; 413] = [
]; ];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn entity<'b, 'g, 'r, 's>( pub(crate) fn entity<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Entity<'s>> { ) -> Res<OrgSource<'s>, Entity<'s>> {

View File

@@ -20,7 +20,7 @@ use crate::parser::util::get_consumed;
use crate::types::ExportSnippet; use crate::types::ExportSnippet;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_snippet<'b, 'g, 'r, 's>( pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportSnippet<'s>> { ) -> Res<OrgSource<'s>, ExportSnippet<'s>> {

View File

@@ -21,7 +21,7 @@ use crate::parser::util::start_of_line;
use crate::types::FixedWidthArea; use crate::types::FixedWidthArea;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fixed_width_area<'b, 'g, 'r, 's>( pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> { ) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
@@ -47,7 +47,7 @@ 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, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple(( let (remaining, (_colon, _leading_whitespace_and_content, _line_ending)) = tuple((
tag(":"), tag(":"),
opt(tuple((space1, is_not("\r\n")))), opt(tuple((space1, is_not("\r\n")))),
alt((line_ending, eof)), alt((line_ending, eof)),
@@ -55,3 +55,14 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((
start_of_line,
space0,
tag(":"),
alt((tag(" "), line_ending, eof)),
))(input)?;
Ok((input, ()))
}

View File

@@ -4,13 +4,16 @@ 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::digit1;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::opt;
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::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::include_input;
use super::util::WORD_CONSTITUENT_CHARACTERS; use super::util::WORD_CONSTITUENT_CHARACTERS;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -30,7 +33,7 @@ use crate::parser::util::start_of_line;
use crate::types::FootnoteDefinition; use crate::types::FootnoteDefinition;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_definition<'b, 'g, 'r, 's>( pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> { ) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
@@ -41,8 +44,15 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
} }
start_of_line(input)?; start_of_line(input)?;
// Cannot be indented. // Cannot be indented.
let (remaining, (_lead_in, lbl, _lead_out, _ws)) = let (remaining, (_, lbl, _, _, _)) = tuple((
tuple((tag_no_case("[fn:"), label, tag("]"), space0))(input)?; tag_no_case("[fn:"),
label,
tag("]"),
space0,
opt(verify(many0(blank_line), |lines: &Vec<OrgSource<'_>>| {
lines.len() <= 2
})),
))(input)?;
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("footnote definition"), ContextElement::Context("footnote definition"),
@@ -54,11 +64,22 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]); let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]); let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
// TODO: The problem is we are not accounting for trailing whitespace like we do in section. Maybe it would be easier if we passed down whether or not to parse trailing whitespace into the element matcher similar to how tag takes in parameters.
let element_matcher = parser_with_context!(element(true))(&parser_context); let 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, _exit_contents)) = let (mut remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(include_input(element_matcher), exit_matcher)(remaining)?;
// Re-parse the last element of the footnote definition with consume trailing whitespace off because the trailing whitespace needs to belong to the footnote definition, not the contents.
if context.should_consume_trailing_whitespace() {
if let Some((final_item_input, _)) = children.pop() {
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
let final_item_context = parser_context.with_additional_node(&final_item_context);
let (remain, reparsed_final_item) =
parser_with_context!(element(true))(&final_item_context)(final_item_input)?;
children.push((final_item_input, reparsed_final_item));
remaining = remain;
}
}
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -66,13 +87,13 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
FootnoteDefinition { FootnoteDefinition {
source: source.into(), source: source.into(),
label: lbl.into(), label: lbl.into(),
children, children: children.into_iter().map(|(_, item)| item).collect(),
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub 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(( alt((
digit1, digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)), take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
@@ -101,7 +122,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?; tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ())) Ok((input, ()))
} }

View File

@@ -23,7 +23,7 @@ use crate::parser::util::get_consumed;
use crate::types::FootnoteReference; use crate::types::FootnoteReference;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_reference<'b, 'g, 'r, 's>( pub(crate) fn footnote_reference<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteReference<'s>> { ) -> Res<OrgSource<'s>, FootnoteReference<'s>> {
@@ -42,7 +42,7 @@ fn anonymous_footnote<'b, 'g, 'r, 's>(
let (remaining, _) = tag_no_case("[fn::")(input)?; let (remaining, _) = tag_no_case("[fn::")(input)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth()); let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
@@ -78,7 +78,7 @@ fn inline_footnote<'b, 'g, 'r, 's>(
let (remaining, _) = tag(":")(remaining)?; let (remaining, _) = tag(":")(remaining)?;
let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth()); let exit_with_depth = footnote_definition_end(remaining.get_bracket_depth());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Beta, class: ExitClass::Gamma,
exit_matcher: &exit_with_depth, exit_matcher: &exit_with_depth,
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);

View File

@@ -33,7 +33,7 @@ use crate::types::Paragraph;
use crate::types::SetSource; use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn greater_block<'b, 'g, 'r, 's>( pub(crate) fn greater_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, GreaterBlock<'s>> { ) -> Res<OrgSource<'s>, GreaterBlock<'s>> {

258
src/parser/headline.rs Normal file
View File

@@ -0,0 +1,258 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many1_count;
use nom::multi::separated_list1;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::section::section;
use super::util::get_consumed;
use super::util::start_of_line;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line;
use crate::types::DocumentElement;
use crate::types::Heading;
use crate::types::Object;
use crate::types::PriorityCookie;
use crate::types::TodoKeywordType;
pub(crate) const fn heading(
parent_stars: usize,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Heading<'s>> {
move |context, input: OrgSource<'_>| _heading(context, input, parent_stars)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _heading<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<OrgSource<'s>, Heading<'s>> {
not(|i| context.check_exit_matcher(i))(input)?;
let (
remaining,
(star_count, maybe_todo_keyword, maybe_priority, maybe_comment, title, heading_tags),
) = headline(context, input, parent_stars)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
children.insert(0, section);
}
let remaining = if children.is_empty() {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
} else {
remaining
};
let is_archived = heading_tags.contains(&"ARCHIVE");
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
priority_cookie: maybe_priority.map(|(priority, _)| priority),
title,
tags: heading_tags,
children,
is_comment: maybe_comment.is_some(),
is_archived,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_stars: usize,
) -> Res<
OrgSource<'s>,
(
usize,
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>,
Option<(PriorityCookie, OrgSource<'s>)>,
Option<(OrgSource<'s>, OrgSource<'s>)>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
});
let parser_context = context.with_additional_node(&parser_context);
let (
remaining,
(
_,
star_count,
_,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags,
_,
_,
),
) = tuple((
start_of_line,
verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars
}),
space1,
opt(tuple((
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
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)?;
Ok((
remaining,
(
star_count,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline_title_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple((
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn tags<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Vec<OrgSource<'s>>> {
let (remaining, (_open, tags, _close)) =
tuple((tag(":"), separated_list1(tag(":"), single_tag), tag(":")))(input)?;
Ok((remaining, tags))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(many1(verify(anychar, |c| {
c.is_alphanumeric() || "_@#%".contains(*c)
})))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn heading_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (TodoKeywordType, OrgSource<'s>)> {
let global_settings = context.get_global_settings();
if global_settings.in_progress_todo_keywords.is_empty()
&& global_settings.complete_todo_keywords.is_empty()
{
alt((
map(tag("TODO"), |capture| (TodoKeywordType::Todo, capture)),
map(tag("DONE"), |capture| (TodoKeywordType::Done, capture)),
))(input)
} else {
for todo_keyword in global_settings
.in_progress_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Todo, ent)));
}
Err(_) => {}
}
}
for todo_keyword in global_settings
.complete_todo_keywords
.iter()
.map(String::as_str)
{
let result = tag::<_, _, CustomError<_>>(todo_keyword)(input);
match result {
Ok((remaining, ent)) => {
return Ok((remaining, (TodoKeywordType::Done, ent)));
}
Err(_) => {}
}
}
Err(nom::Err::Error(CustomError::MyError(MyError(
"NoTodoKeyword".into(),
))))
}
}
fn priority_cookie<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PriorityCookie> {
let (remaining, (_, priority_character, _)) = tuple((
tag("[#"),
verify(anychar, |c| c.is_alphanumeric()),
tag("]"),
))(input)?;
let cookie = PriorityCookie::try_from(priority_character).map_err(|_| {
nom::Err::Error(CustomError::MyError(MyError(
"Failed to cast priority cookie to number.".into(),
)))
})?;
Ok((remaining, cookie))
}

View File

@@ -15,7 +15,7 @@ use crate::parser::util::start_of_line;
use crate::types::HorizontalRule; use crate::types::HorizontalRule;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn horizontal_rule<'b, 'g, 'r, 's>( pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, HorizontalRule<'s>> { ) -> Res<OrgSource<'s>, HorizontalRule<'s>> {

View File

@@ -13,7 +13,7 @@ use crate::types::Keyword;
use crate::GlobalSettings; use crate::GlobalSettings;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn scan_for_in_buffer_settings<'s>( pub(crate) fn scan_for_in_buffer_settings<'s>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> { ) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
// TODO: Optimization idea: since this is slicing the OrgSource at each character, it might be more efficient to do a parser that uses a search function like take_until, and wrap it in a function similar to consumed but returning the input along with the normal output, then pass all of that into a verify that confirms we were at the start of a line using the input we just returned. // TODO: Optimization idea: since this is slicing the OrgSource at each character, it might be more efficient to do a parser that uses a search function like take_until, and wrap it in a function similar to consumed but returning the input along with the normal output, then pass all of that into a verify that confirms we were at the start of a line using the input we just returned.
@@ -44,7 +44,7 @@ fn in_buffer_settings_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSou
))(input) ))(input)
} }
pub fn apply_in_buffer_settings<'g, 's, 'sf>( pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
keywords: Vec<Keyword<'sf>>, keywords: Vec<Keyword<'sf>>,
original_settings: &'g GlobalSettings<'g, 's>, original_settings: &'g GlobalSettings<'g, 's>,
) -> Result<GlobalSettings<'g, 's>, String> { ) -> Result<GlobalSettings<'g, 's>, String> {

View File

@@ -26,7 +26,7 @@ use crate::parser::util::get_consumed;
use crate::types::InlineBabelCall; use crate::types::InlineBabelCall;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_babel_call<'b, 'g, 'r, 's>( pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, InlineBabelCall<'s>> { ) -> Res<OrgSource<'s>, InlineBabelCall<'s>> {

View File

@@ -28,7 +28,7 @@ use crate::parser::util::get_consumed;
use crate::types::InlineSourceBlock; use crate::types::InlineSourceBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn inline_source_block<'b, 'g, 'r, 's>( pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> { ) -> Res<OrgSource<'s>, InlineSourceBlock<'s>> {

View File

@@ -5,8 +5,8 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while1; use nom::bytes::complete::take_while1;
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::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed; use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
@@ -26,12 +26,12 @@ use crate::parser::util::start_of_line;
use crate::types::Keyword; use crate::types::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [ const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
"caption", "data", "header", "headers", "label", "name", "plot", "resname", "result", "caption", "data", "headers", "header", "label", "name", "plot", "resname", "results",
"results", "source", "srcname", "tblname", "result", "source", "srcname", "tblname",
]; ];
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"]; const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
pub fn filtered_keyword<F: Matcher>( pub(crate) fn filtered_keyword<F: Matcher>(
key_parser: F, key_parser: F,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
move |input| _filtered_keyword(&key_parser, input) move |input| _filtered_keyword(&key_parser, input)
@@ -66,7 +66,7 @@ fn _filtered_keyword<'s, F: Matcher>(
} }
Err(_) => {} Err(_) => {}
}; };
let (remaining, _ws) = space1(remaining)?; let (remaining, _ws) = space0(remaining)?;
let (remaining, parsed_value) = recognize(many_till( let (remaining, parsed_value) = recognize(many_till(
anychar, anychar,
peek(tuple((space0, alt((line_ending, eof))))), peek(tuple((space0, alt((line_ending, eof))))),
@@ -83,7 +83,7 @@ fn _filtered_keyword<'s, F: Matcher>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn keyword<'b, 'g, 'r, 's>( pub(crate) fn keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
@@ -91,20 +91,50 @@ pub fn keyword<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn affiliated_keyword<'b, 'g, 'r, 's>( pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(affiliated_key)(input) filtered_keyword(affiliated_key)(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(babel_call_key)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag_no_case("call")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn table_formula_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(table_formula_key)(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_formula_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
tag_no_case("tblfm")(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn regular_keyword_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( not(peek(alt((tag_no_case("call"), tag_no_case("begin")))))(input)?;
not(peek(tag_no_case("call"))), recognize(many_till(
not(peek(tag_no_case("begin"))), anychar,
is_not(" \t\r\n:"), peek(alt((
)))(input) recognize(one_of(" \t\r\n")), // Give up if we hit whitespace
recognize(tuple((tag(":"), one_of(" \t\r\n")))), // Stop if we see a colon followed by whitespace
recognize(tuple((tag(":"), is_not(" \t\r\n:"), not(tag(":"))))), // Stop if we see a colon that is the last colon before whitespace. This is for keywords like "#+foo:bar:baz: lorem: ipsum" which would have the key "foo:bar:baz".
))),
))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -19,7 +19,7 @@ use crate::error::Res;
/// Parses the text in the value of a #+TODO keyword. /// Parses the text in the value of a #+TODO keyword.
/// ///
/// Example input: "foo bar baz | lorem ipsum" /// Example input: "foo bar baz | lorem ipsum"
pub fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> { pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?; let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
let (remaining, after_pipe_words) = opt(tuple(( let (remaining, after_pipe_words) = opt(tuple((
tuple((space0, tag("|"), space0)), tuple((space0, tag("|"), space0)),

View File

@@ -25,7 +25,7 @@ use crate::parser::util::start_of_line;
use crate::types::LatexEnvironment; use crate::types::LatexEnvironment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_environment<'b, 'g, 'r, 's>( pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> { ) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {

View File

@@ -24,7 +24,7 @@ use crate::parser::util::get_consumed;
use crate::types::LatexFragment; use crate::types::LatexFragment;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn latex_fragment<'b, 'g, 'r, 's>( pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexFragment<'s>> { ) -> Res<OrgSource<'s>, LatexFragment<'s>> {
@@ -174,7 +174,7 @@ fn dollar_char_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'b, 'g, 'r, 's>( fn pre<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
@@ -188,7 +188,7 @@ pub fn pre<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'b, 'g, 'r, 's>( fn post<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
@@ -226,7 +226,7 @@ fn bordered_dollar_fragment<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn open_border<'b, 'g, 'r, 's>( fn open_border<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -234,7 +234,7 @@ pub fn open_border<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn close_border<'b, 'g, 'r, 's>( fn close_border<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {

View File

@@ -7,6 +7,7 @@ use nom::character::complete::space1;
use nom::combinator::consumed; use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
@@ -34,13 +35,13 @@ use crate::types::SrcBlock;
use crate::types::VerseBlock; use crate::types::VerseBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verse_block<'b, 'g, 'r, 's>( pub(crate) fn verse_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, VerseBlock<'s>> { ) -> Res<OrgSource<'s>, VerseBlock<'s>> {
let (remaining, name) = lesser_block_begin("verse")(context, input)?; let (remaining, name) = lesser_block_begin("verse")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("verse"); let lesser_block_end_specialized = lesser_block_end("verse");
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
@@ -89,13 +90,13 @@ pub fn verse_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment_block<'b, 'g, 'r, 's>( pub(crate) fn comment_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CommentBlock<'s>> { ) -> Res<OrgSource<'s>, CommentBlock<'s>> {
let (remaining, name) = lesser_block_begin("comment")(context, input)?; let (remaining, name) = lesser_block_begin("comment")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("comment"); let lesser_block_end_specialized = lesser_block_end("comment");
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
@@ -129,13 +130,13 @@ pub fn comment_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn example_block<'b, 'g, 'r, 's>( pub(crate) fn example_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExampleBlock<'s>> { ) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
let (remaining, _name) = lesser_block_begin("example")(context, input)?; let (remaining, _name) = lesser_block_begin("example")(context, input)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("example"); let lesser_block_end_specialized = lesser_block_end("example");
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
@@ -169,14 +170,14 @@ pub fn example_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_block<'b, 'g, 'r, 's>( pub(crate) fn export_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportBlock<'s>> { ) -> Res<OrgSource<'s>, ExportBlock<'s>> {
let (remaining, name) = lesser_block_begin("export")(context, input)?; let (remaining, name) = lesser_block_begin("export")(context, input)?;
// https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block. // https://orgmode.org/worg/org-syntax.html#Blocks claims that export blocks must have a single word for data but testing shows no data and multi-word data still parses as an export block.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("export"); let lesser_block_end_specialized = lesser_block_end("export");
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
@@ -210,14 +211,14 @@ pub fn export_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn src_block<'b, 'g, 'r, 's>( pub(crate) fn src_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> { ) -> Res<OrgSource<'s>, SrcBlock<'s>> {
let (remaining, name) = lesser_block_begin("src")(context, input)?; let (remaining, name) = lesser_block_begin("src")(context, input)?;
// https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block. // https://orgmode.org/worg/org-syntax.html#Blocks claims that data is mandatory and must follow the LANGUAGE SWITCHES ARGUMENTS pattern but testing has shown that no data and incorrect data here will still parse to a src block.
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
let lesser_block_end_specialized = lesser_block_end("src"); let lesser_block_end_specialized = lesser_block_end("src");
let contexts = [ let contexts = [
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
@@ -275,9 +276,10 @@ fn _lesser_block_end<'b, 'g, 'r, 's, 'c>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, _name, _ws)) = tuple(( let (remaining, (_begin, _name, _ws, _ending)) = tuple((
tag_no_case("#+end_"), tag_no_case("#+end_"),
tag_no_case(current_name_lower), tag_no_case(current_name_lower),
space0,
alt((eof, line_ending)), alt((eof, line_ending)),
))(remaining)?; ))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);

View File

@@ -13,7 +13,7 @@ use crate::parser::util::get_consumed;
use crate::types::LineBreak; use crate::types::LineBreak;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn line_break<'b, 'g, 'r, 's>( pub(crate) fn line_break<'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>, LineBreak<'s>> { ) -> Res<OrgSource<'s>, LineBreak<'s>> {

View File

@@ -14,6 +14,7 @@ mod fixed_width_area;
mod footnote_definition; mod footnote_definition;
mod footnote_reference; mod footnote_reference;
mod greater_block; mod greater_block;
mod headline;
mod horizontal_rule; mod horizontal_rule;
mod in_buffer_settings; mod in_buffer_settings;
mod inline_babel_call; mod inline_babel_call;
@@ -35,7 +36,7 @@ mod planning;
mod property_drawer; mod property_drawer;
mod radio_link; mod radio_link;
mod regular_link; mod regular_link;
pub mod sexp; mod section;
mod statistics_cookie; mod statistics_cookie;
mod subscript_and_superscript; mod subscript_and_superscript;
mod table; mod table;
@@ -44,7 +45,6 @@ mod text_markup;
mod timestamp; mod timestamp;
mod token; mod token;
mod util; mod util;
pub use document::document;
pub use document::parse; pub use document::parse;
pub use document::parse_with_settings; pub use document::parse_with_settings;
pub use org_source::OrgSource; pub(crate) use org_source::OrgSource;

View File

@@ -4,8 +4,11 @@ use nom::combinator::map;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::plain_text::plain_text; use super::plain_text::plain_text;
use super::regular_link::regular_link; use super::regular_link::regular_link;
use super::subscript_and_superscript::detect_subscript_or_superscript;
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::parser::angle_link::angle_link; use crate::parser::angle_link::angle_link;
use crate::parser::citation::citation; use crate::parser::citation::citation;
@@ -29,87 +32,37 @@ use crate::parser::timestamp::timestamp;
use crate::types::Object; use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn standard_set_object<'b, 'g, 'r, 's>( pub(crate) fn standard_set_object<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
map(parser_with_context!(timestamp)(context), Object::Timestamp), parser_with_context!(standard_set_object_sans_plain_text)(context),
map(parser_with_context!(subscript)(context), Object::Subscript),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(plain_text(detect_standard_set_object_sans_plain_text))(context),
Object::Superscript, Object::PlainText,
), ),
map(
parser_with_context!(statistics_cookie)(context),
Object::StatisticsCookie,
),
map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(line_break)(context), Object::LineBreak),
map(
parser_with_context!(inline_source_block)(context),
Object::InlineSourceBlock,
),
map(
parser_with_context!(inline_babel_call)(context),
Object::InlineBabelCall,
),
map(parser_with_context!(citation)(context), Object::Citation),
map(
parser_with_context!(footnote_reference)(context),
Object::FootnoteReference,
),
map(
parser_with_context!(export_snippet)(context),
Object::ExportSnippet,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
map(parser_with_context!(radio_link)(context), Object::RadioLink),
map(
parser_with_context!(radio_target)(context),
Object::RadioTarget,
),
parser_with_context!(text_markup)(context),
map(
parser_with_context!(regular_link)(context),
Object::RegularLink,
),
map(parser_with_context!(plain_link)(context), Object::PlainLink),
map(parser_with_context!(angle_link)(context), Object::AngleLink),
map(parser_with_context!(org_macro)(context), Object::OrgMacro),
map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn minimal_set_object<'b, 'g, 'r, 's>( pub(crate) fn minimal_set_object<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt(( let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript), parser_with_context!(minimal_set_object_sans_plain_text)(context),
map( map(
parser_with_context!(superscript)(context), parser_with_context!(plain_text(detect_minimal_set_object_sans_plain_text))(context),
Object::Superscript, Object::PlainText,
), ),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
map(parser_with_context!(plain_text)(context), Object::PlainText),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn any_object_except_plain_text<'b, 'g, 'r, 's>( fn standard_set_object_sans_plain_text<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -166,7 +119,80 @@ pub fn any_object_except_plain_text<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_description_object_set<'b, 'g, 'r, 's>( fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
map(parser_with_context!(subscript)(context), Object::Subscript),
map(
parser_with_context!(superscript)(context),
Object::Superscript,
),
map(parser_with_context!(entity)(context), Object::Entity),
map(
parser_with_context!(latex_fragment)(context),
Object::LatexFragment,
),
parser_with_context!(text_markup)(context),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if standard_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if minimal_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
// TODO: It can also contain another link, but only when it is a plain or angle link. It can contain square brackets, but not ]]
let (remaining, object) = alt((
parser_with_context!(regular_link_description_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(
detect_regular_link_description_set_object_sans_plain_text
))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn regular_link_description_set_object_sans_plain_text<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -189,13 +215,45 @@ pub fn regular_link_description_object_set<'b, 'g, 'r, 's>(
Object::InlineBabelCall, Object::InlineBabelCall,
), ),
map(parser_with_context!(org_macro)(context), Object::OrgMacro), map(parser_with_context!(org_macro)(context), Object::OrgMacro),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'b, 'g, 'r, 's>( fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if regular_link_description_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn table_cell_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> {
let (remaining, object) = alt((
parser_with_context!(table_cell_set_object_sans_plain_text)(context),
map(
parser_with_context!(plain_text(detect_table_cell_set_object_sans_plain_text))(context),
Object::PlainText,
),
))(input)?;
Ok((remaining, object))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_cell_set_object_sans_plain_text<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -223,7 +281,24 @@ pub fn table_cell_set_object<'b, 'g, 'r, 's>(
), ),
map(parser_with_context!(target)(context), Object::Target), map(parser_with_context!(target)(context), Object::Target),
map(parser_with_context!(timestamp)(context), Object::Timestamp), map(parser_with_context!(timestamp)(context), Object::Timestamp),
parser_with_context!(minimal_set_object)(context), parser_with_context!(minimal_set_object_sans_plain_text)(context),
))(input)?; ))(input)?;
Ok((remaining, object)) Ok((remaining, object))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if detect_subscript_or_superscript(input).is_ok() {
return Ok((input, ()));
}
if table_cell_set_object_sans_plain_text(context, input).is_ok() {
return Ok((input, ()));
}
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No object detected.".into(),
))));
}

View File

@@ -18,7 +18,7 @@ use crate::parser::util::get_consumed;
use crate::types::OrgMacro; use crate::types::OrgMacro;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_macro<'b, 'g, 'r, 's>( pub(crate) fn org_macro<'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>, OrgMacro<'s>> { ) -> Res<OrgSource<'s>, OrgMacro<'s>> {

View File

@@ -11,10 +11,10 @@ use nom::Slice;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
pub type BracketDepth = i16; pub(crate) type BracketDepth = i16;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct OrgSource<'s> { pub(crate) struct OrgSource<'s> {
full_source: &'s str, full_source: &'s str,
start: usize, start: usize,
end: usize, // exclusive end: usize, // exclusive
@@ -37,7 +37,7 @@ impl<'s> OrgSource<'s> {
/// Returns a wrapped string that keeps track of values we need for parsing org-mode. /// Returns a wrapped string that keeps track of values we need for parsing org-mode.
/// ///
/// Only call this on the full original string. Calling this on a substring can result in invalid values. /// Only call this on the full original string. Calling this on a substring can result in invalid values.
pub fn new(input: &'s str) -> Self { pub(crate) fn new(input: &'s str) -> Self {
OrgSource { OrgSource {
full_source: input, full_source: input,
start: 0, start: 0,
@@ -51,37 +51,37 @@ impl<'s> OrgSource<'s> {
} }
/// Get the text since the line break preceding the start of this WrappedInput. /// Get the text since the line break preceding the start of this WrappedInput.
pub fn text_since_line_break(&self) -> &'s str { pub(crate) fn text_since_line_break(&self) -> &'s str {
&self.full_source[self.start_of_line..self.start] &self.full_source[self.start_of_line..self.start]
} }
pub fn len(&self) -> usize { pub(crate) fn len(&self) -> usize {
self.end - self.start self.end - self.start
} }
pub fn get_preceding_character(&self) -> Option<char> { pub(crate) fn get_preceding_character(&self) -> Option<char> {
self.preceding_character self.preceding_character
} }
pub fn is_at_start_of_line(&self) -> bool { pub(crate) fn is_at_start_of_line(&self) -> bool {
self.start == self.start_of_line self.start == self.start_of_line
} }
pub fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> { pub(crate) fn get_until(&self, other: OrgSource<'s>) -> OrgSource<'s> {
assert!(other.start >= self.start); assert!(other.start >= self.start);
assert!(other.end <= self.end); assert!(other.end <= self.end);
self.slice(..(other.start - self.start)) self.slice(..(other.start - self.start))
} }
pub fn get_bracket_depth(&self) -> BracketDepth { pub(crate) fn get_bracket_depth(&self) -> BracketDepth {
self.bracket_depth self.bracket_depth
} }
pub fn get_brace_depth(&self) -> BracketDepth { pub(crate) fn get_brace_depth(&self) -> BracketDepth {
self.brace_depth self.brace_depth
} }
pub fn get_parenthesis_depth(&self) -> BracketDepth { pub(crate) fn get_parenthesis_depth(&self) -> BracketDepth {
self.parenthesis_depth self.parenthesis_depth
} }
} }
@@ -145,6 +145,9 @@ where
if new_end > self.end { if new_end > self.end {
panic!("Attempted to extend past the end of the WrappedInput.") panic!("Attempted to extend past the end of the WrappedInput.")
} }
if new_start == self.start && new_end == self.end {
return self.clone();
}
let skipped_text = &self.full_source[self.start..new_start]; let skipped_text = &self.full_source[self.start..new_start];
let mut start_of_line = self.start_of_line; let mut start_of_line = self.start_of_line;
@@ -183,7 +186,7 @@ where
start: new_start, start: new_start,
end: new_end, end: new_end,
start_of_line, start_of_line,
preceding_character: skipped_text.chars().last(), preceding_character: skipped_text.chars().last().or(self.preceding_character),
bracket_depth, bracket_depth,
brace_depth, brace_depth,
parenthesis_depth, parenthesis_depth,
@@ -303,7 +306,7 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
} }
} }
pub fn convert_error<'a, I: Into<CustomError<&'a str>>>( pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
err: nom::Err<I>, err: nom::Err<I>,
) -> nom::Err<CustomError<&'a str>> { ) -> nom::Err<CustomError<&'a str>> {
match err { match err {

View File

@@ -22,7 +22,7 @@ use crate::parser::util::start_of_line;
use crate::types::Paragraph; use crate::types::Paragraph;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn paragraph<'b, 'g, 'r, 's>( pub(crate) fn paragraph<'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>, Paragraph<'s>> { ) -> Res<OrgSource<'s>, Paragraph<'s>> {

View File

@@ -53,7 +53,7 @@ const ORG_LINK_PARAMETERS: [&'static str; 23] = [
]; ];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_link<'b, 'g, 'r, 's>( pub(crate) fn plain_link<'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>, PlainLink<'s>> { ) -> Res<OrgSource<'s>, PlainLink<'s>> {
@@ -105,7 +105,7 @@ fn post<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn protocol<'b, 'g, 'r, 's>( pub(crate) fn protocol<'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>> {

View File

@@ -1,5 +1,6 @@
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::digit1; use nom::character::complete::digit1;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
@@ -19,6 +20,7 @@ use nom::sequence::tuple;
use super::element_parser::element; 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::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;
@@ -39,7 +41,7 @@ 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 fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
if verify( if verify(
tuple(( tuple((
start_of_line, start_of_line,
@@ -61,7 +63,7 @@ pub fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_list<'b, 'g, 'r, 's>( pub(crate) fn plain_list<'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>, PlainList<'s>> { ) -> Res<OrgSource<'s>, PlainList<'s>> {
@@ -135,7 +137,7 @@ pub fn plain_list<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_list_item<'b, 'g, 'r, 's>( fn plain_list_item<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainListItem<'s>> { ) -> Res<OrgSource<'s>, PlainListItem<'s>> {
@@ -147,13 +149,17 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
Into::<&str>::into(bull) != "*" || indent_level > 0 Into::<&str>::into(bull) != "*" || indent_level > 0
})(remaining)?; })(remaining)?;
let (remaining, maybe_tag) = opt(tuple(( let (remaining, _maybe_counter_set) =
space1, opt(tuple((space1, tag("[@"), counter, tag("]"))))(remaining)?;
parser_with_context!(item_tag)(context),
tag(" ::"), // TODO: parse checkbox
)))(remaining)?;
let maybe_contentless_item: Res<OrgSource<'_>, OrgSource<'_>> = let (remaining, maybe_tag) =
peek(recognize(tuple((many0(blank_line), eof))))(remaining); opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?;
let maybe_contentless_item: Res<OrgSource<'_>, ()> = peek(parser_with_context!(
detect_contentless_item_contents
)(context))(remaining);
match maybe_contentless_item { match maybe_contentless_item {
Ok((_rem, _ws)) => { Ok((_rem, _ws)) => {
let (remaining, _trailing_ws) = opt(blank_line)(remaining)?; let (remaining, _trailing_ws) = opt(blank_line)(remaining)?;
@@ -165,7 +171,7 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
children: Vec::new(), children: Vec::new(),
}, },
@@ -214,25 +220,13 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag, _divider)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
children: children.into_iter().map(|(_start, item)| item).collect(), children: children.into_iter().map(|(_start, item)| item).collect(),
}, },
)); ));
} }
fn include_input<'s, F, O>(
mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
where
F: FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>,
{
move |input: OrgSource<'_>| {
let (remaining, output) = inner(input)?;
Ok((remaining, (input, output)))
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
@@ -321,6 +315,7 @@ fn item_tag<'b, 'g, 'r, 's>(
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
)(input)?; )(input)?;
let (remaining, _) = item_tag_divider(remaining)?;
Ok((remaining, children)) Ok((remaining, children))
} }
@@ -329,11 +324,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>> {
recognize(alt(( alt((
recognize(tuple((
item_tag_divider,
opt(tuple((
peek(one_of(" \t")),
many_till(anychar, peek(alt((item_tag_divider, line_ending, eof)))),
))),
alt((line_ending, eof)),
))),
line_ending, line_ending,
tag(" :: "), ))(input)
recognize(tuple((tag(" ::"), alt((line_ending, eof))))), }
)))(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"))]
@@ -357,6 +363,18 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
)(input) )(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let (remaining, _) = recognize(many_till(
blank_line,
parser_with_context!(exit_matcher_parser)(context),
))(input)?;
Ok((remaining, ()))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -7,7 +7,6 @@ use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::object_parser::any_object_except_plain_text;
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;
@@ -17,17 +16,42 @@ use crate::error::Res;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainText; use crate::types::PlainText;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] pub(crate) fn plain_text<F>(
pub fn plain_text<'b, 'g, 'r, 's>( end_condition: F,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainText<'s>>
where
F: for<'bb, 'gg, 'rr, 'ss> Fn(
RefContext<'bb, 'gg, 'rr, 'ss>,
OrgSource<'ss>,
) -> Res<OrgSource<'ss>, ()>,
{
move |context, input| _plain_text(&end_condition, context, input)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(end_condition))
)]
fn _plain_text<'b, 'g, 'r, 's, F>(
end_condition: F,
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainText<'s>> { ) -> Res<OrgSource<'s>, PlainText<'s>>
where
F: for<'bb, 'gg, 'rr, 'ss> Fn(
RefContext<'bb, 'gg, 'rr, 'ss>,
OrgSource<'ss>,
) -> Res<OrgSource<'ss>, ()>,
{
let (remaining, source) = recognize(verify( let (remaining, source) = recognize(verify(
many_till( many_till(
anychar, anychar,
peek(alt(( peek(alt((
parser_with_context!(exit_matcher_parser)(context), parser_with_context!(exit_matcher_parser)(context),
parser_with_context!(plain_text_end)(context), recognize(parser_with_context!(end_condition)(context)),
))), ))),
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
@@ -41,14 +65,6 @@ pub fn plain_text<'b, 'g, 'r, 's>(
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn plain_text_end<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(parser_with_context!(any_object_except_plain_text)(context))(input)
}
impl<'x> RematchObject<'x> for PlainText<'x> { impl<'x> RematchObject<'x> for PlainText<'x> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn rematch_object<'b, 'g, 'r, 's>( fn rematch_object<'b, 'g, 'r, 's>(
@@ -73,6 +89,7 @@ mod tests {
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::GlobalSettings; use crate::context::GlobalSettings;
use crate::context::List; use crate::context::List;
use crate::parser::object_parser::detect_standard_set_object_sans_plain_text;
use crate::types::Source; use crate::types::Source;
#[test] #[test]
@@ -81,7 +98,9 @@ mod tests {
let global_settings = GlobalSettings::default(); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context)); let initial_context = Context::new(&global_settings, List::new(&initial_context));
let plain_text_matcher = parser_with_context!(plain_text)(&initial_context); let plain_text_matcher = parser_with_context!(plain_text(
detect_standard_set_object_sans_plain_text
))(&initial_context);
let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap(); let (remaining, result) = map(plain_text_matcher, Object::PlainText)(input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_source(), Into::<&str>::into(input)); assert_eq!(result.get_source(), Into::<&str>::into(input));

View File

@@ -10,6 +10,7 @@ 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::util::maybe_consume_trailing_whitespace_if_not_exiting;
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;
@@ -17,8 +18,8 @@ use crate::parser::util::start_of_line;
use crate::types::Planning; use crate::types::Planning;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn planning<'b, 'g, 'r, 's>( pub(crate) fn planning<'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>, Planning<'s>> { ) -> Res<OrgSource<'s>, Planning<'s>> {
start_of_line(input)?; start_of_line(input)?;
@@ -26,6 +27,8 @@ pub fn planning<'b, 'g, 'r, 's>(
let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?; let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?; let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((

View File

@@ -31,7 +31,7 @@ use crate::types::NodeProperty;
use crate::types::PropertyDrawer; use crate::types::PropertyDrawer;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn property_drawer<'b, 'g, 'r, 's>( pub(crate) fn property_drawer<'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>, PropertyDrawer<'s>> { ) -> Res<OrgSource<'s>, PropertyDrawer<'s>> {
@@ -169,5 +169,8 @@ fn node_property_name_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((tag("+:"), tag(":")))(input) recognize(tuple((
alt((tag("+:"), tag(":"))),
alt((space1, line_ending, eof)),
)))(input)
} }

View File

@@ -23,7 +23,7 @@ use crate::types::RadioLink;
use crate::types::RadioTarget; use crate::types::RadioTarget;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn radio_link<'b, 'g, 'r, 's>( pub(crate) fn radio_link<'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>, RadioLink<'s>> { ) -> Res<OrgSource<'s>, RadioLink<'s>> {
@@ -47,7 +47,7 @@ pub fn radio_link<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn rematch_target<'x, 'b, 'g, 'r, 's>( pub(crate) fn rematch_target<'x, 'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
target: &'x Vec<Object<'x>>, target: &'x Vec<Object<'x>>,
input: OrgSource<'s>, input: OrgSource<'s>,
@@ -78,7 +78,7 @@ pub fn rematch_target<'x, 'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn radio_target<'b, 'g, 'r, 's>( pub(crate) fn radio_target<'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>, RadioTarget<'s>> { ) -> Res<OrgSource<'s>, RadioTarget<'s>> {
@@ -118,7 +118,7 @@ fn radio_target_end<'b, 'g, 'r, 's>(
alt((tag("<"), tag(">"), line_ending))(input) alt((tag("<"), tag(">"), line_ending))(input)
} }
pub trait RematchObject<'x> { pub(crate) trait RematchObject<'x> {
fn rematch_object<'b, 'g, 'r, 's>( fn rematch_object<'b, 'g, 'r, 's>(
&'x self, &'x self,
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,

View File

@@ -6,7 +6,7 @@ use nom::character::complete::one_of;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use super::object_parser::regular_link_description_object_set; use super::object_parser::regular_link_description_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::get_consumed; use super::util::get_consumed;
@@ -21,7 +21,7 @@ use crate::types::Object;
use crate::types::RegularLink; use crate::types::RegularLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link<'b, 'g, 'r, 's>( pub(crate) fn regular_link<'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>, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
@@ -32,7 +32,7 @@ pub fn regular_link<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_without_description<'b, 'g, 'r, 's>( fn regular_link_without_description<'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>, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
@@ -51,7 +51,7 @@ pub fn regular_link_without_description<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_with_description<'b, 'g, 'r, 's>( fn regular_link_with_description<'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>, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
@@ -72,7 +72,7 @@ pub fn regular_link_with_description<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pathreg<'b, 'g, 'r, 's>( fn pathreg<'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>> {
@@ -88,7 +88,7 @@ pub fn pathreg<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn description<'b, 'g, 'r, 's>( fn description<'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>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
@@ -99,7 +99,7 @@ pub fn description<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
parser_with_context!(regular_link_description_object_set)(&parser_context), parser_with_context!(regular_link_description_set_object)(&parser_context),
parser_with_context!(exit_matcher_parser)(&parser_context), parser_with_context!(exit_matcher_parser)(&parser_context),
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),

144
src/parser/section.rs Normal file
View File

@@ -0,0 +1,144 @@
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::headline::detect_headline;
use super::org_source::OrgSource;
use super::util::exit_matcher_parser;
use super::util::get_consumed;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::comment::comment;
use crate::parser::element_parser::element;
use crate::parser::planning::planning;
use crate::parser::property_drawer::property_drawer;
use crate::parser::util::blank_line;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::types::Element;
use crate::types::Section;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let without_consuming_whitespace_context = ContextElement::ConsumeTrailingWhitespace(false);
let without_consuming_whitespace_context =
parser_context.with_additional_node(&without_consuming_whitespace_context);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, comment_and_property_drawer_element) = opt(tuple((
opt(parser_with_context!(comment)(
&without_consuming_whitespace_context,
)),
parser_with_context!(property_drawer)(context),
many0(blank_line),
)))(input)?;
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some()
},
)(remaining)?;
comment_and_property_drawer_element.map(|(comment, property_drawer, _ws)| {
children.insert(0, Element::PropertyDrawer(property_drawer));
comment
.map(Element::Comment)
.map(|ele| children.insert(0, ele));
});
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> {
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context("section"),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &section_end,
}),
];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (mut remaining, (planning_element, property_drawer_element)) = tuple((
opt(parser_with_context!(planning)(&parser_context)),
opt(parser_with_context!(property_drawer)(&parser_context)),
))(input)?;
if planning_element.is_none() && property_drawer_element.is_none() {
let (remain, _ws) = many0(blank_line)(remaining)?;
remaining = remain;
input = remain;
}
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
},
)(remaining)?;
property_drawer_element
.map(Element::PropertyDrawer)
.map(|ele| children.insert(0, ele));
planning_element
.map(Element::Planning)
.map(|ele| children.insert(0, ele));
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
Section {
source: source.into(),
children,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn section_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(detect_headline)(input)
}

View File

@@ -1,9 +1,11 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::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::RefContext; use crate::context::RefContext;
@@ -11,7 +13,7 @@ use crate::error::Res;
use crate::types::StatisticsCookie; use crate::types::StatisticsCookie;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn statistics_cookie<'b, 'g, 'r, 's>( pub(crate) fn statistics_cookie<'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>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
@@ -22,14 +24,18 @@ pub fn statistics_cookie<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn percent_statistics_cookie<'b, 'g, 'r, 's>( fn percent_statistics_cookie<'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>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = let (remaining, _) = recognize(tuple((
recognize(tuple((tag("["), nom::character::complete::u64, tag("%]"))))(input)?; tag("["),
opt(nom::character::complete::u64),
tag("%]"),
)))(input)?;
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)?;
let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
StatisticsCookie { StatisticsCookie {
@@ -39,19 +45,20 @@ pub fn percent_statistics_cookie<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fraction_statistics_cookie<'b, 'g, 'r, 's>( fn fraction_statistics_cookie<'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>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
let (remaining, source) = recognize(tuple(( let (remaining, _) = recognize(tuple((
tag("["), tag("["),
nom::character::complete::u64, opt(nom::character::complete::u64),
tag("/"), tag("/"),
nom::character::complete::u64, opt(nom::character::complete::u64),
tag("]"), tag("]"),
)))(input)?; )))(input)?;
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)?;
let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
StatisticsCookie { StatisticsCookie {

View File

@@ -1,5 +1,6 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::take_while;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::combinator::map; use nom::combinator::map;
@@ -9,12 +10,14 @@ use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
use super::util::preceded_by_whitespace;
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;
@@ -30,13 +33,26 @@ use crate::types::Subscript;
use crate::types::Superscript; use crate::types::Superscript;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn subscript<'b, 'g, 'r, 's>( pub(crate) fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
// This does not have to detect all valid subscript/superscript but all that it detects must be valid.
let (remaining, _) = one_of("_^")(input)?;
pre(input)?;
if tag::<_, _, CustomError<_>>("*")(remaining).is_ok() {
return Ok((input, ()));
}
let (remaining, _) = opt(one_of("+-"))(remaining)?;
let (_remaining, _) = verify(anychar, |c| c.is_alphanumeric())(remaining)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn subscript<'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>, Subscript<'s>> { ) -> Res<OrgSource<'s>, Subscript<'s>> {
// We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily. // We check for the underscore first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("_")(input)?; let (remaining, _) = tag("_")(input)?;
pre(context, input)?; pre(input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -50,13 +66,13 @@ pub fn subscript<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn superscript<'b, 'g, 'r, 's>( pub(crate) fn superscript<'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>, Superscript<'s>> { ) -> Res<OrgSource<'s>, Superscript<'s>> {
// We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily. // We check for the circumflex first before checking the pre-character as a minor optimization to avoid walking up the context tree to find the document root unnecessarily.
let (remaining, _) = tag("^")(input)?; let (remaining, _) = tag("^")(input)?;
pre(context, input)?; pre(input)?;
let (remaining, _body) = script_body(context, remaining)?; let (remaining, _body) = script_body(context, remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -70,19 +86,8 @@ pub fn superscript<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn pre<'b, 'g, 'r, 's>( fn pre<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
_context: RefContext<'b, 'g, 'r, 's>, not(preceded_by_whitespace(true))(input)?;
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character();
match preceding_character {
Some(c) if !c.is_whitespace() => {}
_ => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Must be preceded by a non-whitespace character.".into(),
))));
}
};
Ok((input, ())) Ok((input, ()))
} }
@@ -120,36 +125,29 @@ fn script_asterisk<'b, 'g, 'r, 's>(
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum<'b, 'g, 'r, 's>( fn script_alphanum<'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>> {
let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?; let (remaining, _sign) = opt(recognize(one_of("+-")))(input)?;
let (remaining, _script) = many_till( let (remaining, _script) =
parser_with_context!(script_alphanum_character)(context), many_till(script_alphanum_character, end_script_alphanum_character)(remaining)?;
parser_with_context!(end_script_alphanum_character)(context),
)(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn script_alphanum_character<'b, 'g, 'r, 's>( fn script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(verify(anychar, |c| { recognize(verify(anychar, |c| {
c.is_alphanumeric() || r#",.\"#.contains(*c) c.is_alphanumeric() || r#",.\"#.contains(*c)
}))(input) }))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn end_script_alphanum_character<'b, 'g, 'r, 's>( fn end_script_alphanum_character<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?; let (remaining, final_char) = recognize(verify(anychar, |c| c.is_alphanumeric()))(input)?;
peek(not(parser_with_context!(script_alphanum_character)( peek(tuple((
context, take_while(|c| r#",.\"#.contains(c)),
not(script_alphanum_character),
)))(remaining)?; )))(remaining)?;
Ok((remaining, final_char)) Ok((remaining, final_char))
} }

View File

@@ -8,10 +8,12 @@ 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::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object; use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
@@ -31,7 +33,7 @@ use crate::types::TableRow;
/// ///
/// This is not the table.el style. /// This is not the table.el style.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table<'b, 'g, 'r, 's>( pub(crate) fn org_mode_table<'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>, Table<'s>> { ) -> Res<OrgSource<'s>, Table<'s>> {
@@ -56,18 +58,27 @@ pub fn org_mode_table<'b, 'g, 'r, 's>(
let (remaining, (children, _exit_contents)) = let (remaining, (children, _exit_contents)) =
many_till(org_mode_table_row_matcher, exit_matcher)(input)?; many_till(org_mode_table_row_matcher, exit_matcher)(input)?;
// TODO: Consume trailing formulas let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
remaining, remaining,
Table { Table {
source: source.into(), source: source.into(),
formulas,
children, children,
}, },
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, space0, tag("|")))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_end<'b, 'g, 'r, 's>( fn table_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
@@ -78,7 +89,7 @@ fn table_end<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row<'b, 'g, 'r, 's>( fn org_mode_table_row<'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>, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
@@ -89,7 +100,7 @@ pub fn org_mode_table_row<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row_rule<'b, 'g, 'r, 's>( fn org_mode_table_row_rule<'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>, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
@@ -106,7 +117,7 @@ pub fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row_regular<'b, 'g, 'r, 's>( fn org_mode_table_row_regular<'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>, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
@@ -126,7 +137,7 @@ pub fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_cell<'b, 'g, 'r, 's>( fn org_mode_table_cell<'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>, TableCell<'s>> { ) -> Res<OrgSource<'s>, TableCell<'s>> {

View File

@@ -21,7 +21,7 @@ use crate::parser::util::get_consumed;
use crate::types::Target; use crate::types::Target;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn target<'b, 'g, 'r, 's>( pub(crate) fn target<'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>, Target<'s>> { ) -> Res<OrgSource<'s>, Target<'s>> {

View File

@@ -18,6 +18,7 @@ use tracing::span;
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::radio_link::RematchObject; use super::radio_link::RematchObject;
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 crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -41,7 +42,7 @@ use crate::types::Underline;
use crate::types::Verbatim; use crate::types::Verbatim;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn text_markup<'b, 'g, 'r, 's>( pub(crate) fn text_markup<'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>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -59,7 +60,7 @@ pub fn text_markup<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn bold<'b, 'g, 'r, 's>( 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>> {
@@ -76,7 +77,7 @@ pub fn bold<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn italic<'b, 'g, 'r, 's>( 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>> {
@@ -93,7 +94,7 @@ pub fn italic<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn underline<'b, 'g, 'r, 's>( 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>> {
@@ -110,7 +111,7 @@ pub fn underline<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn strike_through<'b, 'g, 'r, 's>( 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>> {
@@ -127,7 +128,7 @@ pub fn strike_through<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verbatim<'b, 'g, 'r, 's>( 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>> {
@@ -144,7 +145,7 @@ pub fn verbatim<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn code<'b, 'g, 'r, 's>( 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>> {
@@ -177,15 +178,26 @@ fn _text_markup_object<'b, 'g, 'r, 's, 'c>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
if in_object_section(context, marker_symbol) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same type".into(),
))));
}
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(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());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let contexts = [
ContextElement::ContextObject(marker_symbol),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &text_markup_end_specialized, exit_matcher: &text_markup_end_specialized,
}); }),
let parser_context = context.with_additional_node(&parser_context); ];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
@@ -229,16 +241,25 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
if in_object_section(context, marker_symbol) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same type".into(),
))));
}
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) = 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());
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode { let contexts = [
ContextElement::ContextObject(marker_symbol),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &text_markup_end_specialized, exit_matcher: &text_markup_end_specialized,
}); }),
let parser_context = context.with_additional_node(&parser_context); ];
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, contents) = recognize(verify( let (remaining, contents) = recognize(verify(
many_till( many_till(
@@ -267,7 +288,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'b, 'g, 'r, 's>( fn pre<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
@@ -277,7 +298,6 @@ pub fn pre<'b, 'g, 'r, 's>(
None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(') None | Some('\r') | Some('\n') | Some(' ') | Some('\t') | Some('-') | Some('(')
| Some('{') | Some('\'') | Some('"') | Some('<') => {} | Some('{') | Some('\'') | Some('"') | Some('<') => {}
Some(_) => { Some(_) => {
// Not at start of line, cannot be a heading
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(),
)))); ))));
@@ -287,7 +307,7 @@ pub fn pre<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'b, 'g, 'r, 's>( fn post<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
@@ -305,7 +325,7 @@ fn _text_markup_end<'b, 'g, 'r, 's, 'c>(
input: OrgSource<'s>, input: OrgSource<'s>,
marker_symbol: &'c str, marker_symbol: &'c str,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
not(preceded_by_whitespace)(input)?; not(preceded_by_whitespace(false))(input)?;
let (remaining, _marker) = terminated( let (remaining, _marker) = terminated(
tag(marker_symbol), tag(marker_symbol),
peek(parser_with_context!(post)(context)), peek(parser_with_context!(post)(context)),

View File

@@ -23,7 +23,7 @@ use crate::parser::util::get_consumed;
use crate::types::Timestamp; use crate::types::Timestamp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn timestamp<'b, 'g, 'r, 's>( pub(crate) fn timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {

View File

@@ -4,13 +4,14 @@ use crate::types::Document;
use crate::types::DocumentElement; use crate::types::DocumentElement;
use crate::types::Element; use crate::types::Element;
use crate::types::Heading; use crate::types::Heading;
use crate::types::NodeProperty;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainListItem; use crate::types::PlainListItem;
use crate::types::Section; use crate::types::Section;
use crate::types::TableCell; use crate::types::TableCell;
use crate::types::TableRow; use crate::types::TableRow;
pub enum Token<'r, 's> { pub(crate) enum Token<'r, 's> {
Document(&'r Document<'s>), Document(&'r Document<'s>),
Heading(&'r Heading<'s>), Heading(&'r Heading<'s>),
Section(&'r Section<'s>), Section(&'r Section<'s>),
@@ -19,10 +20,11 @@ pub enum Token<'r, 's> {
PlainListItem(&'r PlainListItem<'s>), PlainListItem(&'r PlainListItem<'s>),
TableRow(&'r TableRow<'s>), TableRow(&'r TableRow<'s>),
TableCell(&'r TableCell<'s>), TableCell(&'r TableCell<'s>),
NodeProperty(&'r NodeProperty<'s>),
} }
impl<'r, 's> Token<'r, 's> { impl<'r, 's> Token<'r, 's> {
pub fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> { fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> {
match self { match self {
Token::Document(document) => Box::new( Token::Document(document) => Box::new(
document document
@@ -81,7 +83,9 @@ impl<'r, 's> Token<'r, 's> {
} }
Element::Comment(_) => Box::new(std::iter::empty()), Element::Comment(_) => Box::new(std::iter::empty()),
Element::Drawer(inner) => Box::new(inner.children.iter().map(Token::Element)), Element::Drawer(inner) => Box::new(inner.children.iter().map(Token::Element)),
Element::PropertyDrawer(_) => Box::new(std::iter::empty()), Element::PropertyDrawer(inner) => {
Box::new(inner.children.iter().map(Token::NodeProperty))
}
Element::Table(inner) => Box::new(inner.children.iter().map(Token::TableRow)), Element::Table(inner) => Box::new(inner.children.iter().map(Token::TableRow)),
Element::VerseBlock(inner) => Box::new(inner.children.iter().map(Token::Object)), Element::VerseBlock(inner) => Box::new(inner.children.iter().map(Token::Object)),
Element::CommentBlock(_) => Box::new(std::iter::empty()), Element::CommentBlock(_) => Box::new(std::iter::empty()),
@@ -94,21 +98,23 @@ impl<'r, 's> Token<'r, 's> {
Element::FixedWidthArea(_) => Box::new(std::iter::empty()), Element::FixedWidthArea(_) => Box::new(std::iter::empty()),
Element::HorizontalRule(_) => Box::new(std::iter::empty()), Element::HorizontalRule(_) => Box::new(std::iter::empty()),
Element::Keyword(_) => Box::new(std::iter::empty()), Element::Keyword(_) => Box::new(std::iter::empty()),
Element::BabelCall(_) => Box::new(std::iter::empty()),
Element::LatexEnvironment(_) => Box::new(std::iter::empty()), Element::LatexEnvironment(_) => Box::new(std::iter::empty()),
}, },
Token::PlainListItem(elem) => Box::new(elem.children.iter().map(Token::Element)), Token::PlainListItem(elem) => Box::new(elem.children.iter().map(Token::Element)),
Token::TableRow(elem) => Box::new(elem.children.iter().map(Token::TableCell)), Token::TableRow(elem) => Box::new(elem.children.iter().map(Token::TableCell)),
Token::TableCell(elem) => Box::new(elem.children.iter().map(Token::Object)), Token::TableCell(elem) => Box::new(elem.children.iter().map(Token::Object)),
Token::NodeProperty(_) => Box::new(std::iter::empty()),
} }
} }
} }
pub struct AllTokensIterator<'r, 's> { pub(crate) struct AllTokensIterator<'r, 's> {
queued_tokens: VecDeque<Token<'r, 's>>, queued_tokens: VecDeque<Token<'r, 's>>,
} }
impl<'r, 's> AllTokensIterator<'r, 's> { impl<'r, 's> AllTokensIterator<'r, 's> {
pub fn new(tkn: Token<'r, 's>) -> Self { pub(crate) fn new(tkn: Token<'r, 's>) -> Self {
let mut queued_tokens = VecDeque::new(); let mut queued_tokens = VecDeque::new();
queued_tokens.push_back(tkn); queued_tokens.push_back(tkn);
AllTokensIterator { queued_tokens } AllTokensIterator { queued_tokens }

View File

@@ -2,13 +2,13 @@ use nom::branch::alt;
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;
use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
@@ -21,12 +21,11 @@ use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
pub const WORD_CONSTITUENT_CHARACTERS: &str = pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/// Check if we are below a section of the given section type regardless of depth /// Check if we are below a section of the given section type regardless of depth
#[allow(dead_code)] pub(crate) fn in_section<'b, 'g, 'r, 's, 'x>(
pub fn in_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str, section_name: &'x str,
) -> bool { ) -> bool {
@@ -40,7 +39,7 @@ pub fn in_section<'b, 'g, 'r, 's, 'x>(
} }
/// Checks if we are currently an immediate child of the given section type /// Checks if we are currently an immediate child of the given section type
pub fn immediate_in_section<'b, 'g, 'r, 's, 'x>( pub(crate) fn immediate_in_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str, section_name: &'x str,
) -> bool { ) -> bool {
@@ -54,8 +53,22 @@ pub fn immediate_in_section<'b, 'g, 'r, 's, 'x>(
false false
} }
/// Check if we are below a section of the given section type regardless of depth
pub(crate) fn in_object_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str,
) -> bool {
for thing in context.iter() {
match thing {
ContextElement::ContextObject(name) if *name == section_name => return true,
_ => {}
}
}
false
}
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser. /// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> { pub(crate) fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> {
input.get_until(remaining) input.get_until(remaining)
} }
@@ -63,31 +76,35 @@ pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSo
/// ///
/// It is up to the caller to ensure this is called at the start of a line. /// It is up to the caller to ensure this is called at the start of a line.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn blank_line(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> { pub(crate) fn blank_line(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> {
not(eof)(input)?; not(eof)(input)?;
recognize(tuple((space0, alt((line_ending, eof)))))(input) recognize(tuple((space0, alt((line_ending, eof)))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
alt((eof, recognize(many0(blank_line))))(input) alt((eof, recognize(many0(blank_line))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>( pub(crate) fn maybe_consume_object_trailing_whitespace_if_not_exiting<'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>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
if exit_matcher_parser(context, input).is_err() { // We have to check exit matcher after each character because description list tags need to end with a space unconsumed (" ::").
opt(space0)(input) let (remaining, _) = many_till(
} else { one_of(" \t"),
Ok((input, None)) alt((
} peek(recognize(none_of(" \t"))),
parser_with_context!(exit_matcher_parser)(context),
)),
)(input)?;
Ok((remaining, None))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>( pub(crate) fn maybe_consume_trailing_whitespace_if_not_exiting<'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>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
@@ -100,7 +117,7 @@ pub fn maybe_consume_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace<'b, 'g, 'r, 's>( pub(crate) fn maybe_consume_trailing_whitespace<'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>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
@@ -113,7 +130,7 @@ pub fn maybe_consume_trailing_whitespace<'b, 'g, 'r, 's>(
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
if input.is_at_start_of_line() { if input.is_at_start_of_line() {
Ok((input, ())) Ok((input, ()))
} else { } else {
@@ -123,16 +140,25 @@ pub fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
} }
} }
pub(crate) fn preceded_by_whitespace(
allow_start_of_file: bool,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
move |input| _preceded_by_whitespace(allow_start_of_file, input)
}
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn preceded_by_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { fn _preceded_by_whitespace<'s>(
allow_start_of_file: bool,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
let preceding_character = input.get_preceding_character(); let preceding_character = input.get_preceding_character();
if !preceding_character if !preceding_character
.map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space .map(|c| c.is_whitespace() || c == '\u{200B}') // 200B = Zero-width space
.unwrap_or(false) .unwrap_or(allow_start_of_file)
{ {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not preceded by whitespace.".into(), "Must be preceded by a whitespace character.".into(),
)))); ))));
} }
Ok((input, ())) Ok((input, ()))
@@ -142,13 +168,13 @@ pub fn preceded_by_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
/// ///
/// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace. /// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn non_whitespace_character(input: OrgSource<'_>) -> Res<OrgSource<'_>, char> { pub(crate) fn non_whitespace_character(input: OrgSource<'_>) -> Res<OrgSource<'_>, char> {
none_of(" \t\r\n")(input) none_of(" \t\r\n")(input)
} }
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn exit_matcher_parser<'b, 'g, 'r, 's>( pub(crate) fn exit_matcher_parser<'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>> {
@@ -156,18 +182,18 @@ pub fn exit_matcher_parser<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn text_until_exit<'b, 'g, 'r, 's>( pub(crate) fn text_until_exit<'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(verify( recognize(many_till(
many_till(anychar, parser_with_context!(exit_matcher_parser)(context)), anychar,
|(children, _exit_contents)| !children.is_empty(), parser_with_context!(exit_matcher_parser)(context),
))(input) ))(input)
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> { fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not implemented yet.".into(), "Not implemented yet.".into(),
)))); ))));
@@ -178,10 +204,22 @@ pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
/// Text from the current point until the next line break or end of file /// Text from the current point until the next line break or end of file
/// ///
/// Useful for debugging. /// Useful for debugging.
pub fn text_until_eol<'r, 's>( fn text_until_eol<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> { ) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input) let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
.map(|(_remaining, line)| Into::<&str>::into(line))?; .map(|(_remaining, line)| Into::<&str>::into(line))?;
Ok(line.trim()) Ok(line.trim())
} }
pub(crate) fn include_input<'s, F, O>(
mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
where
F: FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, O>,
{
move |input: OrgSource<'_>| {
let (remaining, output) = inner(input)?;
Ok((remaining, (input, output)))
}
}

View File

@@ -2,6 +2,8 @@ use super::Element;
use super::Object; use super::Object;
use super::Source; use super::Source;
pub type PriorityCookie = u8;
#[derive(Debug)] #[derive(Debug)]
pub struct Document<'s> { pub struct Document<'s> {
pub source: &'s str, pub source: &'s str,
@@ -14,10 +16,12 @@ pub struct Heading<'s> {
pub source: &'s str, pub source: &'s str,
pub stars: usize, pub stars: usize,
pub todo_keyword: Option<(TodoKeywordType, &'s str)>, pub todo_keyword: Option<(TodoKeywordType, &'s str)>,
// TODO: add todo-type enum pub priority_cookie: Option<PriorityCookie>,
pub title: Vec<Object<'s>>, pub title: Vec<Object<'s>>,
pub tags: Vec<&'s str>, pub tags: Vec<&'s str>,
pub children: Vec<DocumentElement<'s>>, pub children: Vec<DocumentElement<'s>>,
pub is_comment: bool,
pub is_archived: bool,
} }
#[derive(Debug)] #[derive(Debug)]

View File

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

View File

@@ -1,5 +1,6 @@
use super::element::Element; use super::element::Element;
use super::lesser_element::TableCell; use super::lesser_element::TableCell;
use super::Keyword;
use super::Object; use super::Object;
use super::Source; use super::Source;
@@ -63,6 +64,7 @@ pub struct NodeProperty<'s> {
#[derive(Debug)] #[derive(Debug)]
pub struct Table<'s> { pub struct Table<'s> {
pub source: &'s str, pub source: &'s str,
pub formulas: Vec<Keyword<'s>>,
pub children: Vec<TableRow<'s>>, pub children: Vec<TableRow<'s>>,
} }
@@ -114,6 +116,12 @@ impl<'s> Source<'s> for PropertyDrawer<'s> {
} }
} }
impl<'s> Source<'s> for NodeProperty<'s> {
fn get_source(&'s self) -> &'s str {
self.source
}
}
impl<'s> Source<'s> for Table<'s> { impl<'s> Source<'s> for Table<'s> {
fn get_source(&'s self) -> &'s str { fn get_source(&'s self) -> &'s str {
self.source self.source

View File

@@ -97,7 +97,7 @@ pub struct LatexEnvironment<'s> {
} }
impl<'s> Paragraph<'s> { impl<'s> Paragraph<'s> {
pub fn of_text(input: &'s str) -> Self { pub(crate) fn of_text(input: &'s str) -> Self {
let mut objects = Vec::with_capacity(1); let mut objects = Vec::with_capacity(1);
objects.push(Object::PlainText(PlainText { source: input })); objects.push(Object::PlainText(PlainText { source: input }));
Paragraph { Paragraph {

View File

@@ -7,6 +7,7 @@ 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::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;
@@ -63,5 +64,5 @@ pub use object::Target;
pub use object::Timestamp; pub use object::Timestamp;
pub use object::Underline; pub use object::Underline;
pub use object::Verbatim; pub use object::Verbatim;
pub use source::SetSource; pub(crate) use source::SetSource;
pub use source::Source; pub use source::Source;

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