103 Commits

Author SHA1 Message Date
Tom Alexander
4b54f95087 Publish version 0.1.10.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-14 19:31:41 -04:00
Tom Alexander
17c2e9fefe Add a build of compare and foreign document test without tracing. 2023-10-14 19:31:41 -04:00
Tom Alexander
d2d8c1ffcf Fix build for compare with tracing. 2023-10-14 19:20:49 -04:00
Tom Alexander
b9c638c280 Fix tracing.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-14 19:02:35 -04:00
Tom Alexander
8ac8f9fe6e Merge branch 'foreign_document_test'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-14 18:53:52 -04:00
Tom Alexander
ddb3144e66 Fix the tests.
Some checks failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-14 18:25:26 -04:00
Tom Alexander
ad5efc4b0f Only require sync on FileAccessInterface when compiling for compare utilities.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
Otherwise async compatibility would impact sync users of the plain library.
2023-10-14 18:09:50 -04:00
Tom Alexander
2de33b8150 Enable release-lto profile on foreign document test. 2023-10-14 18:00:20 -04:00
Tom Alexander
e1fde88a60 Switch to using tokio too invoke emacs async. 2023-10-14 17:58:50 -04:00
Tom Alexander
123da9cca3 Make the compare functions async. 2023-10-14 17:51:12 -04:00
Tom Alexander
c20e7b5f2f Make compare an always-async program.
This program is used as a development tool, so it is more valuable that we make it simple by only supporting one mode of operation (async) than making it widely compatible by supporting both.
2023-10-14 17:50:53 -04:00
Tom Alexander
74a3512038 Report whether all the tests passed. 2023-10-14 17:50:53 -04:00
Tom Alexander
ff04c4a131 Sort files in a compare_all_org_document. 2023-10-14 17:50:53 -04:00
Tom Alexander
00611e05c2 Remove old bash script. 2023-10-14 15:32:36 -04:00
Tom Alexander
3e7e54a1bd Add the foreign_document_test to the build tests. 2023-10-14 15:26:04 -04:00
Tom Alexander
aa35d1dc03 Copy over the rest of the foreign document test config. 2023-10-14 15:18:57 -04:00
Tom Alexander
92afdc0ea6 Strip prefix from file path. 2023-10-14 15:18:57 -04:00
Tom Alexander
f43920fc7c Add a silent mode for running the diff. 2023-10-14 15:18:57 -04:00
Tom Alexander
dde4bc7920 Add code for structured printing of test results. 2023-10-14 14:30:24 -04:00
Tom Alexander
3d68e1fd00 Switch the docker container over to invoking the rust-based foreign document test. 2023-10-13 13:28:24 -04:00
Tom Alexander
8271f6b44a Start invoking the tests. 2023-10-13 13:20:21 -04:00
Tom Alexander
8a26965e14 Write a function to compare all org-mode files in a directory. 2023-10-13 13:02:17 -04:00
Tom Alexander
3927889e66 Change test status to pass/fail. 2023-10-13 12:14:46 -04:00
Tom Alexander
5ecd7b8bef Launch tests in parallel. 2023-10-13 12:07:52 -04:00
Tom Alexander
b0b795d13b Limit concurrency of running tests. 2023-10-13 11:41:49 -04:00
Tom Alexander
182c2737cd Add futures. 2023-10-13 11:27:43 -04:00
Tom Alexander
5f93cabff5 Hit recursive async. 2023-10-13 11:27:42 -04:00
Tom Alexander
a1f8cbe079 Working on invoking the tests. 2023-10-13 11:27:42 -04:00
Tom Alexander
d7e870cba1 Starting to make result structs. 2023-10-13 11:27:42 -04:00
Tom Alexander
591b5ed382 Starting to define a TestConfig enum. 2023-10-13 11:27:42 -04:00
Tom Alexander
fd141762f0 Start a rust-based foreign document test.
Instead of using a simple bash script, this will use async rust to run multiple tests in parallel.
2023-10-13 11:27:42 -04:00
Tom Alexander
d59bbfa7d2 Update org-mode.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-13 11:26:41 -04:00
Tom Alexander
1d9f91cdd2 Merge branch 'paragraph_without_affiliated_keyword'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-12 17:46:56 -04:00
Tom Alexander
68f8e04ee8 Add tests for affiliated keywords before elements that cannot have affiliated keywords. 2023-10-12 17:46:25 -04:00
Tom Alexander
c039e0d62c Keywords without affiliated keywords should be lower priority than paragraphs with affiliated keywords. 2023-10-12 17:32:29 -04:00
Tom Alexander
e6e3783ec6 Support parsing affiliated keywords as keywords. 2023-10-12 17:23:54 -04:00
Tom Alexander
5ae19e455d Remove unused imports. 2023-10-12 17:14:18 -04:00
Tom Alexander
f475754a71 Port the rest of the element parsers over. 2023-10-12 17:12:55 -04:00
Tom Alexander
767f44f94d Port comment. 2023-10-12 16:52:49 -04:00
Tom Alexander
1411aca7b5 Port footnote definition. 2023-10-12 16:48:50 -04:00
Tom Alexander
9ccdcaac24 Port dynamic block. 2023-10-12 16:46:15 -04:00
Tom Alexander
d1223dcdb7 Port paragraph parser to use ak_element. 2023-10-12 16:37:31 -04:00
Tom Alexander
59448a4f2c Port greater blocker over to ak_element!(). 2023-10-12 16:27:57 -04:00
Tom Alexander
5136880532 Fix tests. 2023-10-12 16:14:23 -04:00
Tom Alexander
8654cf5507 Fully port over the first parser. 2023-10-12 16:09:35 -04:00
Tom Alexander
6ca4dc8ffc Add a macro for calling parsers that take in the affiliated keywords. 2023-10-12 16:06:59 -04:00
Tom Alexander
a6f36ba679 Create a template for new element functions that will take in the affiliated keywords instead of re-parsing them multiple times. 2023-10-12 15:47:06 -04:00
Tom Alexander
176e37874e Remove the parser to parse affiliated keywords as regular keywords. 2023-10-12 15:25:26 -04:00
Tom Alexander
3208a04f7a Update project status in the README.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-12 14:13:54 -04:00
Tom Alexander
0d579263cb Remove affiliated keyword non-exit for paragraph. 2023-10-12 14:07:57 -04:00
Tom Alexander
c022b30110 Update org-mode. 2023-10-12 13:38:11 -04:00
Tom Alexander
7da4e4a29b Merge branch 'optional_value'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-11 19:14:46 -04:00
Tom Alexander
8bc942a26f Create artificial scopes for the optional value and mandatory value. 2023-10-11 19:14:32 -04:00
Tom Alexander
dff7550038 Remove prints. 2023-10-11 19:09:51 -04:00
Tom Alexander
c4edcb8c24 Fix parsing the optional value. 2023-10-11 19:07:47 -04:00
Tom Alexander
4a44d88461 Better error messages. 2023-10-11 19:05:39 -04:00
Tom Alexander
efc6bd11d9 Do not exit on first loop. 2023-10-11 18:59:13 -04:00
Tom Alexander
51429e3155 Handle optval when translating names. 2023-10-11 18:50:22 -04:00
Tom Alexander
b1a0fa4acf Compare optional value. 2023-10-11 18:44:21 -04:00
Tom Alexander
aeb2b6fe68 Parse out the optional value objects. 2023-10-11 18:29:07 -04:00
Tom Alexander
6679db98a8 Add comments. 2023-10-11 17:53:32 -04:00
Tom Alexander
5363324bbf Add a test showing we are not handling optional values properly. 2023-10-11 17:37:39 -04:00
Tom Alexander
dc9188dffc Better error logging when emacs errors out.
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-11 17:22:01 -04:00
Tom Alexander
bd620ccd0d Format. 2023-10-11 16:32:08 -04:00
Tom Alexander
1947ae9f22 Merge branch 'document_properties'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-10-11 16:28:53 -04:00
Tom Alexander
3fcf1b3864 Compare Document additional properties. 2023-10-11 16:27:25 -04:00
Tom Alexander
d965dd6fd1 Implement get_additional_properties for Document. 2023-10-11 16:14:03 -04:00
Tom Alexander
42dcd41e48 Add tests for document properties. 2023-10-11 16:02:44 -04:00
Tom Alexander
68fac7cfe8 Merge branch 'affiliated_keywords_properties'
Some checks failed
rustfmt Build rustfmt has failed
rust-build Build rust-build has failed
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-11 15:31:23 -04:00
Tom Alexander
a9f067e25a Update name tests to use all the affiliated keywords. 2023-10-11 15:25:09 -04:00
Tom Alexander
41b4b1015e Update all comparisons to handle affiliated keywords. 2023-10-11 14:57:32 -04:00
Tom Alexander
9523365090 Update all elements to the new AffiliatedKeywords. 2023-10-11 14:44:25 -04:00
Tom Alexander
aa33fe42a8 Update compare_properties to handle affiliated keywords. 2023-10-11 14:14:33 -04:00
Tom Alexander
78a9b93f22 Nevermind, it did work. 2023-10-11 14:03:42 -04:00
Tom Alexander
a89339e472 Using owned string did not solve it. 2023-10-11 14:00:18 -04:00
Tom Alexander
f3307a8159 Lifetime issue. 2023-10-11 13:55:43 -04:00
Tom Alexander
4bfa25dcb3 Comparing lists of quoted strings. 2023-10-11 13:04:33 -04:00
Tom Alexander
441a240c33 Comparing single string values. 2023-10-11 13:00:21 -04:00
Tom Alexander
e767892dd5 Add capitalization. 2023-10-11 12:42:42 -04:00
Tom Alexander
8e0a7dea0f A very simple GetAffiliatedKeywords trait. 2023-10-11 12:19:27 -04:00
Tom Alexander
c4cc40f5e1 Parsing the affiliated keywords. 2023-10-11 12:16:35 -04:00
Tom Alexander
a5129199c7 I need to parse the affiliated keywords during parsing because it relies on the global settings. 2023-10-11 12:16:35 -04:00
Tom Alexander
c0e462944d Comment out the broken stuff. 2023-10-11 12:16:35 -04:00
Tom Alexander
63614841e8 Trait lifetime issues. 2023-10-11 12:16:35 -04:00
Tom Alexander
bc4c09c546 Add constants for affiliated keyword settings. 2023-10-11 12:16:35 -04:00
Tom Alexander
0d1ab0e75a Add a debug assert for org entities being sorted. 2023-10-11 12:16:35 -04:00
Tom Alexander
6e39029f91 Store the affiliated keywords on the plainlist. 2023-10-10 18:10:34 -04:00
Tom Alexander
d550966516 Add a test with all the affiliated keyword types. 2023-10-10 18:02:23 -04:00
Tom Alexander
32d6b3d1ee Merge branch 'compare_additional_properties'
Some checks failed
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 failed
2023-10-10 17:40:40 -04:00
Tom Alexander
7cb71a5a0a Compare the additional properties on headlines. 2023-10-10 17:40:27 -04:00
Tom Alexander
6b90a9bfa8 Add TODO. 2023-10-10 17:15:51 -04:00
Tom Alexander
83d939bdc0 Update org-mode. 2023-10-10 17:00:13 -04:00
Tom Alexander
0e1ace78f4 Merge branch 'headline_extra_properties'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-10 16:57:14 -04:00
Tom Alexander
6d4db60688 Support ("") as being equal to None in compare_property_list_of_ast_nodes. 2023-10-10 16:35:16 -04:00
Tom Alexander
3aca01891d Remove the old implementation of comparing headlines. 2023-10-10 16:12:29 -04:00
Tom Alexander
275ed87c3f Add a compare_properties variant with support for additional properties. 2023-10-10 16:10:12 -04:00
Tom Alexander
b385270d7b Implement a function to get the additional properties on a headline. 2023-10-10 15:54:44 -04:00
Tom Alexander
fb412bc2cd Merge branch 'compare_properties_document'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-10-10 15:31:21 -04:00
Tom Alexander
585b1d2b74 compare_properties for document. 2023-10-10 15:30:27 -04:00
Tom Alexander
c578bb45af Implement ExactSizeIterator for the other node types. 2023-10-10 15:27:03 -04:00
Tom Alexander
b7f7876706 Impl ExactSizeIterator for multi_field_iter. 2023-10-10 15:23:04 -04:00
Tom Alexander
ee5ed17c20 Implement ExactSizeIterator for DocumentIter. 2023-10-10 15:19:42 -04:00
Tom Alexander
a873794068 Starting a compare properties document function. 2023-10-10 14:58:18 -04:00
105 changed files with 3572 additions and 1323 deletions

View File

@@ -137,7 +137,7 @@ spec:
value: [] value: []
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-all - name: run-image-tracing-compare
taskRef: taskRef:
name: run-docker-image name: run-docker-image
workspaces: workspaces:
@@ -152,6 +152,46 @@ spec:
value: ["--no-default-features", "--features", "tracing,compare"] value: ["--no-default-features", "--features", "tracing,compare"]
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-compare-foreign
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-tracing-compare
params:
- name: args
value:
[
"--no-default-features",
"--features",
"compare,foreign_document_test",
]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-all
taskRef:
name: run-docker-image
workspaces:
- name: source
workspace: git-source
- name: cargo-cache
workspace: cargo-cache
runAfter:
- run-image-compare-foreign
params:
- name: args
value:
[
"--no-default-features",
"--features",
"tracing,compare,foreign_document_test",
]
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
finally: finally:
- name: report-success - name: report-success
when: when:

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.9" version = "0.1.10"
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"
@@ -31,7 +31,14 @@ path = "src/lib.rs"
path = "src/bin_compare.rs" path = "src/bin_compare.rs"
required-features = ["compare"] required-features = ["compare"]
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the library.
name = "foreign_document_test"
path = "src/bin_foreign_document_test.rs"
required-features = ["foreign_document_test"]
[dependencies] [dependencies]
futures = { version = "0.3.28", optional = true }
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"] }
opentelemetry-otlp = { version = "0.13.0", optional = true } opentelemetry-otlp = { version = "0.13.0", optional = true }
@@ -40,13 +47,15 @@ tokio = { version = "1.30.0", optional = true, default-features = false, feature
tracing = { version = "0.1.37", optional = true } tracing = { version = "0.1.37", optional = true }
tracing-opentelemetry = { version = "0.20.0", optional = true } tracing-opentelemetry = { version = "0.20.0", optional = true }
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
walkdir = { version = "2.3.3", optional = true }
[build-dependencies] [build-dependencies]
walkdir = "2.3.3" walkdir = "2.3.3"
[features] [features]
default = [] default = []
compare = [] compare = ["tokio/process", "tokio/macros"]
foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"]
tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"] tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"]
# Optimized build for any sort of release. # Optimized build for any sort of release.

View File

@@ -52,6 +52,8 @@ buildtest:
> cargo build --no-default-features --features compare > cargo build --no-default-features --features compare
> cargo build --no-default-features --features tracing > cargo build --no-default-features --features tracing
> cargo build --no-default-features --features compare,tracing > cargo build --no-default-features --features compare,tracing
> cargo build --no-default-features --features compare,foreign_document_test
> cargo build --no-default-features --features compare,tracing,foreign_document_test
.PHONY: foreign_document_test .PHONY: foreign_document_test
foreign_document_test: foreign_document_test:

View File

@@ -6,14 +6,13 @@ Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) p
This project is still under HEAVY development. While the version remains v0.1.x the API will be changing often. Once we hit v0.2.x we will start following semver. This project is still under HEAVY development. While the version remains v0.1.x the API will be changing often. Once we hit v0.2.x we will start following semver.
Currently, the parser is able to correctly identify the start/end bounds of all the org-mode objects and elements (except table.el tables, org-mode tables are supported) but many of the interior properties are not yet populated. Currently, Organic parses most documents the same as the official org-mode parser. Most of the development right now is finding documents where the parsers differ and fixing those issues.
### Project Goals ### Project Goals
- We aim to provide perfect parity with the emacs org-mode parser. In that regard, any document that parses differently between Emacs and Organic is considered a bug. - We aim to provide perfect parity with the emacs org-mode parser. In that regard, any document that parses differently between Emacs and Organic is considered a bug.
- The parser should be fast. We're not doing anything special, but since this is written in Rust and natively compiled we should be able to beat the existing parsers.
- The parser should have minimal dependencies. This should reduce effort w.r.t.: security audits, legal compliance, portability. - The parser should have minimal dependencies. This should reduce effort w.r.t.: security audits, legal compliance, portability.
- The parser should be usable everywhere. In the interest of getting org-mode used in as many places as possible, this parser should be usable by everyone everywhere. This means: - The parser should be usable everywhere. In the interest of getting org-mode used in as many places as possible, this parser should be usable by everyone everywhere. This means:
- It must have a permissive license for use in proprietary code bases. - It must have a permissive license.
- We will investigate compiling to WASM. This is an important goal of the project and will definitely happen, but only after the parser has a more stable API. - We will investigate compiling to WASM. This is an important goal of the project and will definitely happen, but only after the parser has a more stable API.
- We will investigate compiling to a C library for native linking to other code. This is more of a maybe-goal for the project. - We will investigate compiling to a C library for native linking to other code. This is more of a maybe-goal for the project.
### Project Non-Goals ### Project Non-Goals

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=c703541ffcc14965e3567f928de1683a1c1e33f6 ARG ORG_VERSION=abf5156096c06ee5aa05795c3dc5a065f76ada97
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.
@@ -102,6 +102,4 @@ COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_docume
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs COPY --from=build-emacs /root/emacs /foreign_documents/emacs
COPY foreign_document_test_entrypoint.sh /entrypoint.sh ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -1,149 +0,0 @@
#!/usr/bin/env bash
#
# Run the Organic compare script against a series of documents sourced from exterior places.
set -euo pipefail
IFS=$'\n\t'
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
REALPATH=$(command -v uu-realpath || command -v realpath)
function log {
(>&2 echo "${@}")
}
function die {
local status_code="$1"
shift
(>&2 echo "${@}")
exit "$status_code"
}
function main {
cargo build --no-default-features --features compare --profile release-lto
if [ "${CARGO_TARGET_DIR:-}" = "" ]; then
CARGO_TARGET_DIR=$(realpath target/)
fi
PARSE="${CARGO_TARGET_DIR}/release-lto/compare"
local all_status=0
set +e
(run_compare_function "org-mode" compare_all_org_document "/foreign_documents/org-mode")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "worg" compare_all_org_document "/foreign_documents/worg")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "howard_abrams" compare_howard_abrams)
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
if [ "$all_status" -ne 0 ]; then
red_text "Some tests failed."
else
green_text "All tests passed."
fi
return "$all_status"
}
function green_text {
(IFS=' '; printf '\x1b[38;2;0;255;0m%s\x1b[0m' "${*}")
}
function red_text {
(IFS=' '; printf '\x1b[38;2;255;0;0m%s\x1b[0m' "${*}")
}
function yellow_text {
(IFS=' '; printf '\x1b[38;2;255;255;0m%s\x1b[0m' "${*}")
}
function indent {
local depth="$1"
local scaled_depth=$((depth * 2))
shift 1
local prefix
prefix=$(printf -- "%${scaled_depth}s")
while read -r l; do
(IFS=' '; printf -- '%s%s\n' "$prefix" "$l")
done
}
function run_compare_function {
local name="$1"
local stdoutput
shift 1
set +e
stdoutput=$("${@}")
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$(green_text "GOOD") $name"
indent 1 <<<"$stdoutput"
else
echo "$(red_text "FAIL") $name"
indent 1 <<<"$stdoutput"
return 1
fi
}
function compare_all_org_document {
local root_dir="$1"
local target_document
local all_status=0
while read target_document; do
local relative_path
relative_path=$($REALPATH --relative-to "$root_dir" "$target_document")
set +e
(run_compare "$relative_path" "$target_document")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
done<<<"$(find "$root_dir" -type f -iname '*.org' | sort)"
return "$all_status"
}
function run_compare {
local name="$1"
local target_document="$2"
set +e
($PARSE "$target_document" &> /dev/null)
local status=$?
set -e
if [ "$status" -eq 0 ]; then
echo "$(green_text "GOOD") $name"
else
echo "$(red_text "FAIL") $name"
return 1
fi
}
function compare_howard_abrams {
local all_status=0
set +e
(run_compare_function "dot-files" compare_all_org_document "/foreign_documents/howardabrams/dot-files")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "hamacs" compare_all_org_document "/foreign_documents/howardabrams/hamacs")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "demo-it" compare_all_org_document "/foreign_documents/howardabrams/demo-it")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "magit-demo" compare_all_org_document "/foreign_documents/howardabrams/magit-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "pdx-emacs-hackers" compare_all_org_document "/foreign_documents/howardabrams/pdx-emacs-hackers")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "flora-simulator" compare_all_org_document "/foreign_documents/howardabrams/flora-simulator")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "literate-devops-demo" compare_all_org_document "/foreign_documents/howardabrams/literate-devops-demo")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "clojure-yesql-xp" compare_all_org_document "/foreign_documents/howardabrams/clojure-yesql-xp")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "veep" compare_all_org_document "/foreign_documents/howardabrams/veep")
if [ "$?" -ne 0 ]; then all_status=1; fi
set -e
return "$all_status"
}
main "${@}"

View File

@@ -0,0 +1,8 @@
#+CAPTION[foo]: *bar*
#+CAPTION[*lorem* ipsum]: dolar
1. baz
#+CAPTION[foo]: *bar*
#+CAPTION[*lorem* ipsum]: dolar
# Comments cannot have affiliated keywords so those become regular keywords.

View File

@@ -0,0 +1,74 @@
* Headline
before
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
:candle:
inside
the drawer
:end:
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
:candle:
inside
the drawer
:end:
after

View File

@@ -1,10 +0,0 @@
* Headline
before
#+NAME: foo
:candle:
inside
the drawer
:end:
after

View File

@@ -0,0 +1,72 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+BEGIN: clocktable :scope file :maxlevel 2
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
| Headline | Time |
|--------------+--------|
| *Total time* | *0:00* |
#+END:
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+BEGIN: clocktable :scope file :maxlevel 2
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
| Headline | Time |
|--------------+--------|
| *Total time* | *0:00* |
#+END:

View File

@@ -1,7 +0,0 @@
#+NAME: foo
#+BEGIN: clocktable :scope file :maxlevel 2
#+CAPTION: Clock summary at [2023-08-25 Fri 05:34]
| Headline | Time |
|--------------+--------|
| *Total time* | *0:00* |
#+END:

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
[fn:1] A footnote.
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
[fn:1] A footnote.

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_center
#+end_center
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_center
#+end_center

View File

@@ -1,4 +0,0 @@
#+NAME: foo
#+begin_center
#+end_center

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_quote
#+end_quote
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_quote
#+end_quote

View File

@@ -1,4 +0,0 @@
#+NAME: foo
#+begin_quote
#+end_quote

View File

@@ -0,0 +1,71 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_defun
foo
{{{bar(baz)}}}
#+end_defun
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_defun
foo
{{{bar(baz)}}}
#+end_defun

View File

@@ -1,6 +0,0 @@
#+NAME: foo
#+begin_defun
foo
{{{bar(baz)}}}
#+end_defun

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
1. bar
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
1. BAR

View File

@@ -0,0 +1,3 @@
1. foo
#+NAME: bar
2. baz

View File

@@ -1,2 +0,0 @@
#+NAME: foo
1. bar

View File

@@ -0,0 +1,5 @@
* baz
# lorem
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,6 @@
* baz
# lorem
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,4 @@
* baz
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,5 @@
* baz
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,13 @@
* baz
# lorem
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,19 @@
:PROPERTIES:
:foo: bar
:foo+: baz
:cat: dog
:END:
# Even though these are inheriting the properties and overwriting and/or appending to them, this is not represented in the parser AST so Organic does not do any special handling of this.
* Overwrite
:PROPERTIES:
:foo: lorem
:bat: car
:END:
* Append
:PROPERTIES:
:foo+: ipsum
:cake: lie
:END:

View File

@@ -0,0 +1,5 @@
* Overwrite
#+NAME: foo
:PROPERTIES:
:header-args: :var foo="lorem"
:END:

View File

@@ -1,25 +0,0 @@
# Blank lines and comments can come before property drawers in the zeroth section
:PROPERTIES:
:FOO: bar
:END:
* Spaces turn property drawers into regular drawers
:PROPERTIES:
:FOO: bar
:END:
* Comments turn property drawers into regular drawers
# Comment
:PROPERTIES:
:FOO: bar
:END:
* Baseline
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,4 @@
# lorem
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,5 @@
# lorem
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,3 @@
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,4 @@
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,12 @@
# lorem
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -1,12 +0,0 @@
# Blank lines and comments can come before property drawers in the zeroth section
:PROPERTIES:
:FOO: bar
:END:

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
| foo | bar |
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
| foo | bar |

View File

@@ -1,2 +0,0 @@
#+NAME: foo
| foo | bar |

View File

@@ -0,0 +1,3 @@
|foo|
#+NAME: bar
|baz |

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+call: foo(bar="baz")
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+call: foo(bar="baz")

View File

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

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
%%(foo)
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
%%(foo)

View File

@@ -1,2 +0,0 @@
#+NAME: foo
%%(foo)

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
: bar
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
: bar

View File

@@ -1,2 +0,0 @@
#+NAME: foo
: bar

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
-----
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
-----

View File

@@ -1,2 +0,0 @@
#+NAME: foo
-----

View File

@@ -1,8 +1,63 @@
#+name: foo #+NAME: a
#+caption: bar #+caption: b *lorem* ipsum
#+caption: baz #+results: c
[[file:lorem/ipsum.png]] #+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+foo: bar
#+name: cat
#+caption: dog
[[file:lorem/ipsum.png]]
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+foo: bar

View File

@@ -1,7 +0,0 @@
# 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

@@ -1,2 +0,0 @@
#+NAME: foo
#+FOO: BAR

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
\begin{foo}
bar
\end{foo}
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
\begin{foo}
bar
\end{foo}

View File

@@ -1,4 +0,0 @@
#+NAME: foo
\begin{foo}
bar
\end{foo}

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_comment text
bar
#+end_comment
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_comment text
bar
#+end_comment

View File

@@ -1,4 +0,0 @@
#+NAME: foo
#+begin_comment text
bar
#+end_comment

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_example text
bar
#+end_example
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_example text
bar
#+end_example

View File

@@ -1,4 +0,0 @@
#+NAME: foo
#+begin_example text
bar
#+end_example

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_export text
bar
#+end_export
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_export text
bar
#+end_export

View File

@@ -1,4 +0,0 @@
#+NAME: foo
#+begin_export text
bar
#+end_export

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_src text
bar
#+end_src
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_src text
bar
#+end_src

View File

@@ -1,4 +0,0 @@
#+NAME: foo
#+begin_src text
bar
#+end_src

View File

@@ -0,0 +1,67 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
#+begin_verse text
bar
#+end_verse
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
#+begin_verse text
bar
#+end_verse

View File

@@ -1,4 +0,0 @@
#+NAME: foo
#+begin_verse text
bar
#+end_verse

View File

@@ -0,0 +1,63 @@
#+NAME: a
#+caption: b *lorem* ipsum
#+results: c
#+headers: d
#+header: e
#+label: f
#+plot: g
#+resname: h
#+result: i
#+source: j
#+srcname: k
#+tblname: l
#+attr_latex: m
#+attr_html: n
#+NAME: aa
#+caption: bb *lorem* ipsum
#+results: cc
#+headers: dd
#+header: ee
#+label: ff
#+plot: gg
#+resname: hh
#+result: ii
#+source: jj
#+srcname: kk
#+tblname: ll
#+attr_latex: mm
#+attr_html: nn
bar
#+NAME: A
#+CAPTION: B *LOREM* IPSUM
#+RESULTS: C
#+HEADERS: D
#+HEADER: E
#+LABEL: F
#+PLOT: G
#+RESNAME: H
#+RESULT: I
#+SOURCE: J
#+SRCNAME: K
#+TBLNAME: L
#+ATTR_LATEX: M
#+ATTR_HTML: N
#+NAME: AA
#+CAPTION: BB *LOREM* IPSUM
#+RESULTS: CC
#+HEADERS: DD
#+HEADER: EE
#+LABEL: FF
#+PLOT: GG
#+RESNAME: HH
#+RESULT: II
#+SOURCE: JJ
#+SRCNAME: KK
#+TBLNAME: LL
#+ATTR_LATEX: MM
#+ATTR_HTML: NN
bar

View File

@@ -1,2 +0,0 @@
#+NAME: foo
bar

View File

@@ -0,0 +1,3 @@
#+NAME: foo
* bar
#+NAME: baz

View File

@@ -14,7 +14,12 @@ mod init_tracing;
#[cfg(not(feature = "tracing"))] #[cfg(not(feature = "tracing"))]
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
main_body() let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
let main_body_result = main_body().await;
main_body_result
});
result
} }
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@@ -22,7 +27,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?; let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async { let result = rt.block_on(async {
init_telemetry()?; init_telemetry()?;
let main_body_result = main_body(); let main_body_result = main_body().await;
shutdown_telemetry()?; shutdown_telemetry()?;
main_body_result main_body_result
}); });
@@ -30,14 +35,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn main_body() -> Result<(), Box<dyn std::error::Error>> { async fn main_body() -> Result<(), Box<dyn std::error::Error>> {
let args = std::env::args().skip(1); let args = std::env::args().skip(1);
if args.is_empty() { if args.is_empty() {
let org_contents = read_stdin_to_string()?; let org_contents = read_stdin_to_string()?;
run_anonymous_compare(org_contents) if run_anonymous_compare(org_contents).await? {
} else {
Err("Diff results do not match.")?;
}
Ok(())
} else { } else {
for arg in args { for arg in args {
run_compare_on_file(arg)? if run_compare_on_file(arg).await? {
} else {
Err("Diff results do not match.")?;
}
} }
Ok(()) Ok(())
} }

View File

@@ -0,0 +1,399 @@
#![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
use std::path::Path;
use std::path::PathBuf;
use std::process::ExitCode;
use futures::future::BoxFuture;
use futures::future::FutureExt;
use organic::compare::silent_compare_on_file;
use tokio::sync::Semaphore;
use tokio::task::JoinError;
use walkdir::WalkDir;
#[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<ExitCode, Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
let main_body_result = main_body().await;
main_body_result
});
result
}
#[cfg(feature = "tracing")]
fn main() -> Result<ExitCode, 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().await;
shutdown_telemetry()?;
main_body_result
});
result
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
let layer = compare_group("org-mode", || {
compare_all_org_document("/foreign_documents/org-mode")
});
let layer = layer.chain(compare_group("emacs", || {
compare_all_org_document("/foreign_documents/emacs")
}));
let layer = layer.chain(compare_group("worg", || {
compare_all_org_document("/foreign_documents/worg")
}));
let layer = layer.chain(compare_group("howard_abrams", compare_howard_abrams));
let layer = layer.chain(compare_group("doomemacs", || {
compare_all_org_document("/foreign_documents/doomemacs")
}));
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
let mut any_failed = false;
for test in running_tests.into_iter() {
let test_result = test.await??;
if test_result.is_immediately_bad() || test_result.has_bad_children() {
any_failed = true;
}
test_result.print();
}
if any_failed {
println!(
"{color}Some tests failed.{reset}",
color = TestResult::foreground_color(255, 0, 0),
reset = TestResult::reset_color(),
);
Ok(ExitCode::FAILURE)
} else {
println!(
"{color}All tests passed.{reset}",
color = TestResult::foreground_color(0, 255, 0),
reset = TestResult::reset_color(),
);
Ok(ExitCode::SUCCESS)
}
}
fn compare_howard_abrams() -> impl Iterator<Item = TestConfig> {
let layer = compare_group("dot-files", || {
compare_all_org_document("/foreign_documents/howardabrams/dot-files")
});
let layer = layer.chain(compare_group("hamacs", || {
compare_all_org_document("/foreign_documents/howardabrams/hamacs")
}));
let layer = layer.chain(compare_group("demo-it", || {
compare_all_org_document("/foreign_documents/howardabrams/demo-it")
}));
let layer = layer.chain(compare_group("magit-demo", || {
compare_all_org_document("/foreign_documents/howardabrams/magit-demo")
}));
let layer = layer.chain(compare_group("pdx-emacs-hackers", || {
compare_all_org_document("/foreign_documents/howardabrams/pdx-emacs-hackers")
}));
let layer = layer.chain(compare_group("flora-simulator", || {
compare_all_org_document("/foreign_documents/howardabrams/flora-simulator")
}));
let layer = layer.chain(compare_group("literate-devops-demo", || {
compare_all_org_document("/foreign_documents/howardabrams/literate-devops-demo")
}));
let layer = layer.chain(compare_group("clojure-yesql-xp", || {
compare_all_org_document("/foreign_documents/howardabrams/clojure-yesql-xp")
}));
let layer = layer.chain(compare_group("veep", || {
compare_all_org_document("/foreign_documents/howardabrams/veep")
}));
layer
}
fn compare_group<N: Into<String>, F: Fn() -> I, I: Iterator<Item = TestConfig>>(
name: N,
inner: F,
) -> impl Iterator<Item = TestConfig> {
std::iter::once(TestConfig::TestLayer(TestLayer {
name: name.into(),
children: inner().collect(),
}))
}
fn compare_all_org_document<P: AsRef<Path>>(root_dir: P) -> impl Iterator<Item = TestConfig> {
let root_dir = root_dir.as_ref();
let mut test_files = WalkDir::new(root_dir)
.into_iter()
.filter(|e| match e {
Ok(dir_entry) => {
dir_entry.file_type().is_file()
&& Path::new(dir_entry.file_name())
.extension()
.map(|ext| ext.to_ascii_lowercase() == "org")
.unwrap_or(false)
}
Err(_) => true,
})
.collect::<Result<Vec<_>, _>>()
.unwrap();
test_files.sort_by_cached_key(|test_file| PathBuf::from(test_file.path()));
let test_configs: Vec<_> = test_files
.into_iter()
.map(|test_file| {
let name = test_file
.path()
.strip_prefix(root_dir)
.expect("Result is from walkdir so it must be below the root directory.")
.as_os_str()
.to_string_lossy()
.into_owned();
TestConfig::SingleFile(SingleFile {
name,
file_path: test_file.into_path(),
})
})
.collect();
test_configs.into_iter()
}
static TEST_PERMITS: Semaphore = Semaphore::const_new(8);
#[derive(Debug)]
enum TestConfig {
TestLayer(TestLayer),
SingleFile(SingleFile),
}
#[derive(Debug)]
struct TestLayer {
name: String,
children: Vec<TestConfig>,
}
#[derive(Debug)]
struct SingleFile {
name: String,
file_path: PathBuf,
}
#[derive(Debug)]
enum TestResult {
ResultLayer(ResultLayer),
SingleFileResult(SingleFileResult),
}
#[derive(Debug)]
struct ResultLayer {
name: String,
children: Vec<TestResult>,
}
#[derive(Debug)]
struct SingleFileResult {
name: String,
file_path: PathBuf,
status: TestStatus,
}
#[derive(Debug)]
pub(crate) enum TestStatus {
Pass,
Fail,
}
impl TestConfig {
fn run_test(self) -> BoxFuture<'static, Result<TestResult, JoinError>> {
async move {
match self {
TestConfig::TestLayer(test) => Ok(TestResult::ResultLayer(test.run_test().await?)),
TestConfig::SingleFile(test) => {
Ok(TestResult::SingleFileResult(test.run_test().await?))
}
}
}
.boxed()
}
}
impl SingleFile {
async fn run_test(self) -> Result<SingleFileResult, JoinError> {
let _permit = TEST_PERMITS.acquire().await.unwrap();
let result = silent_compare_on_file(&self.file_path).await;
Ok(SingleFileResult {
name: self.name,
file_path: self.file_path,
status: if let Ok(true) = result {
TestStatus::Pass
} else {
TestStatus::Fail
},
})
}
}
impl TestLayer {
async fn run_test(self) -> Result<ResultLayer, JoinError> {
let running_children: Vec<_> = self
.children
.into_iter()
.map(|c| tokio::spawn(c.run_test()))
.collect();
let mut children = Vec::with_capacity(running_children.len());
for c in running_children {
children.push(c.await??);
}
Ok(ResultLayer {
name: self.name,
children,
})
}
}
impl TestResult {
pub fn print(&self) {
self.print_indented(0);
}
fn print_indented(&self, indentation: usize) {
match self {
TestResult::ResultLayer(result) => result.print_indented(indentation),
TestResult::SingleFileResult(result) => result.print_indented(indentation),
}
}
fn has_bad_children(&self) -> bool {
match self {
TestResult::ResultLayer(result) => result.has_bad_children(),
TestResult::SingleFileResult(result) => result.has_bad_children(),
}
}
fn is_immediately_bad(&self) -> bool {
match self {
TestResult::ResultLayer(result) => result.is_immediately_bad(),
TestResult::SingleFileResult(result) => result.is_immediately_bad(),
}
}
pub(crate) fn foreground_color(red: u8, green: u8, blue: u8) -> String {
if TestResult::should_use_color() {
format!(
"\x1b[38;2;{red};{green};{blue}m",
red = red,
green = green,
blue = blue
)
} else {
String::new()
}
}
#[allow(dead_code)]
pub(crate) fn background_color(red: u8, green: u8, blue: u8) -> String {
if TestResult::should_use_color() {
format!(
"\x1b[48;2;{red};{green};{blue}m",
red = red,
green = green,
blue = blue
)
} else {
String::new()
}
}
pub(crate) fn reset_color() -> &'static str {
if TestResult::should_use_color() {
"\x1b[0m"
} else {
""
}
}
fn should_use_color() -> bool {
!std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
}
}
impl SingleFileResult {
fn print_indented(&self, indentation: usize) {
match self.status {
TestStatus::Pass => {
println!(
"{indentation}{color}PASS{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(0, 255, 0),
reset = TestResult::reset_color(),
name = self.name
);
}
TestStatus::Fail => {
println!(
"{indentation}{color}FAIL{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(255, 0, 0),
reset = TestResult::reset_color(),
name = self.name
);
}
}
}
fn has_bad_children(&self) -> bool {
false
}
fn is_immediately_bad(&self) -> bool {
match self.status {
TestStatus::Pass => false,
TestStatus::Fail => true,
}
}
}
impl ResultLayer {
fn print_indented(&self, indentation: usize) {
if self.is_immediately_bad() {
println!(
"{indentation}{color}FAIL{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(255, 0, 0),
reset = TestResult::reset_color(),
name = self.name
);
} else if self.has_bad_children() {
println!(
"{indentation}{color}BADCHILD{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(255, 255, 0),
reset = TestResult::reset_color(),
name = self.name
);
} else {
println!(
"{indentation}{color}PASS{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(0, 255, 0),
reset = TestResult::reset_color(),
name = self.name
);
}
self.children
.iter()
.for_each(|result| result.print_indented(indentation + 1));
}
fn has_bad_children(&self) -> bool {
self.children
.iter()
.any(|result| result.is_immediately_bad() || result.has_bad_children())
}
fn is_immediately_bad(&self) -> bool {
false
}
}

View File

@@ -12,39 +12,60 @@ use crate::context::LocalFileAccessInterface;
use crate::parser::parse_file_with_settings; use crate::parser::parse_file_with_settings;
use crate::parser::parse_with_settings; use crate::parser::parse_with_settings;
pub fn run_anonymous_compare<P: AsRef<str>>( pub async fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P, org_contents: P,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<bool, Box<dyn std::error::Error>> {
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default()) run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), false).await
} }
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> { pub async fn run_compare_on_file<P: AsRef<Path>>(
run_compare_on_file_with_settings(org_path, &GlobalSettings::default()) org_path: P,
) -> Result<bool, Box<dyn std::error::Error>> {
run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), false).await
} }
pub fn run_anonymous_compare_with_settings<P: AsRef<str>>( pub async fn silent_anonymous_compare<P: AsRef<str>>(
org_contents: P, org_contents: P,
global_settings: &GlobalSettings, ) -> Result<bool, Box<dyn std::error::Error>> {
) -> Result<(), Box<dyn std::error::Error>> { run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), true).await
}
pub async fn silent_compare_on_file<P: AsRef<Path>>(
org_path: P,
) -> Result<bool, Box<dyn std::error::Error>> {
run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), true).await
}
pub async fn run_anonymous_compare_with_settings<'g, 's, P: AsRef<str>>(
org_contents: P,
global_settings: &GlobalSettings<'g, 's>,
silent: bool,
) -> Result<bool, Box<dyn std::error::Error>> {
// TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings. // TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings.
let org_contents = org_contents.as_ref().replace("\r\n", "\n"); let org_contents = org_contents.as_ref().replace("\r\n", "\n");
let org_contents = org_contents.as_str(); let org_contents = org_contents.as_str();
print_versions()?; if !silent {
print_versions().await?;
}
let rust_parsed = parse_with_settings(org_contents, global_settings)?; let rust_parsed = parse_with_settings(org_contents, global_settings)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings)?; let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings).await?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?; let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents); if !silent {
println!("{}", org_sexp); println!("{}\n\n\n", org_contents);
println!("{:#?}", rust_parsed); println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
}
// We do the diffing after printing out both parsed forms in case the diffing panics // We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?; if !silent {
diff_result.print(org_contents)?;
}
if diff_result.is_bad() { if diff_result.is_bad() {
Err("Diff results do not match.")?; return Ok(false);
} else { } else if !silent {
println!( println!(
"{color}Entire document passes.{reset}", "{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0), color = DiffResult::foreground_color(0, 255, 0),
@@ -52,15 +73,18 @@ pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
); );
} }
Ok(()) Ok(true)
} }
pub fn run_compare_on_file_with_settings<P: AsRef<Path>>( pub async fn run_compare_on_file_with_settings<'g, 's, P: AsRef<Path>>(
org_path: P, org_path: P,
global_settings: &GlobalSettings, global_settings: &GlobalSettings<'g, 's>,
) -> Result<(), Box<dyn std::error::Error>> { silent: bool,
) -> Result<bool, Box<dyn std::error::Error>> {
let org_path = org_path.as_ref(); let org_path = org_path.as_ref();
print_versions()?; if !silent {
print_versions().await?;
}
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.")?;
@@ -77,20 +101,24 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
global_settings global_settings
}; };
let rust_parsed = parse_file_with_settings(org_contents, &global_settings, Some(org_path))?; let rust_parsed = parse_file_with_settings(org_contents, &global_settings, Some(org_path))?;
let org_sexp = emacs_parse_file_org_document(org_path, &global_settings)?; let org_sexp = emacs_parse_file_org_document(org_path, &global_settings).await?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?; let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
println!("{}\n\n\n", org_contents); if !silent {
println!("{}", org_sexp); println!("{}\n\n\n", org_contents);
println!("{:#?}", rust_parsed); println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
}
// We do the diffing after printing out both parsed forms in case the diffing panics // We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?; if !silent {
diff_result.print(org_contents)?;
}
if diff_result.is_bad() { if diff_result.is_bad() {
Err("Diff results do not match.")?; return Ok(false);
} else { } else if !silent {
println!( println!(
"{color}Entire document passes.{reset}", "{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0), color = DiffResult::foreground_color(0, 255, 0),
@@ -98,11 +126,14 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
); );
} }
Ok(()) Ok(true)
} }
fn print_versions() -> Result<(), Box<dyn std::error::Error>> { async fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("Using emacs version: {}", get_emacs_version()?.trim()); eprintln!("Using emacs version: {}", get_emacs_version().await?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim()); eprintln!(
"Using org-mode version: {}",
get_org_mode_version().await?.trim()
);
Ok(()) Ok(())
} }

View File

@@ -3,6 +3,7 @@ use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
use super::diff::artificial_diff_scope; use super::diff::artificial_diff_scope;
use super::diff::artificial_owned_diff_scope;
use super::diff::compare_ast_node; use super::diff::compare_ast_node;
use super::diff::DiffEntry; use super::diff::DiffEntry;
use super::diff::DiffStatus; use super::diff::DiffStatus;
@@ -15,6 +16,7 @@ use super::util::get_property_unquoted_atom;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::CharOffsetInLine; use crate::types::CharOffsetInLine;
use crate::types::LineNumber; use crate::types::LineNumber;
use crate::types::Object;
use crate::types::RetainLabels; use crate::types::RetainLabels;
use crate::types::SwitchNumberLines; use crate::types::SwitchNumberLines;
@@ -32,6 +34,26 @@ pub(crate) enum ComparePropertiesResult<'b, 's> {
DiffEntry(DiffEntry<'b, 's>), DiffEntry(DiffEntry<'b, 's>),
} }
impl<'b, 's> ComparePropertiesResult<'b, 's> {
pub(crate) fn apply(
self,
child_status: &mut Vec<DiffEntry<'b, 's>>,
this_status: &mut DiffStatus,
message: &mut Option<String>,
) {
match self {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
*this_status = new_status;
*message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
// foo
}
}
/// Do no comparison. /// Do no comparison.
/// ///
/// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values. /// This is for when you want to acknowledge that a field exists in the emacs token, but you do not have any validation for it when using the compare_properties!() macro. Ideally, this should be kept to a minimum since this represents untested values.
@@ -353,6 +375,15 @@ where
let rust_value: Option<Vec<RV>> = rust_value.map(|it| it.collect()); let rust_value: Option<Vec<RV>> = rust_value.map(|it| it.collect());
match (value, rust_value) { match (value, rust_value) {
(None, None) => {} (None, None) => {}
(Some(el), None)
if el.len() == 1
&& el.into_iter().all(|t| {
if let Ok(r#""""#) = t.as_atom() {
true
} else {
false
}
}) => {}
(None, rv @ Some(_)) | (Some(_), rv @ None) => { (None, rv @ Some(_)) | (Some(_), rv @ None) => {
let this_status = DiffStatus::Bad; let this_status = DiffStatus::Bad;
let message = Some(format!( let message = Some(format!(
@@ -381,6 +412,126 @@ where
Ok(ComparePropertiesResult::NoChange) Ok(ComparePropertiesResult::NoChange)
} }
/// Special compare used for affiliate keywords that are parsed as objects.
///
/// Org-mode seems to store these as a 3-deep list:
/// - Outer list with 1 element per #+caption keyword (or other parsed keyword).
/// - Middle list which has:
/// - first element is a list of objects representing the value after the colon.
/// - every additional element is a list of objects from inside the square brackets (the optional value).
pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
'b,
's,
'x,
R,
RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>,
>(
source: &'s str,
emacs: &'b Token<'s>,
rust_node: R,
emacs_field: &'x str,
rust_value_getter: RG,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>> {
// TODO: Replace Object<'s> with generics. I hard-coded Object in to make lifetimes easier.
let rust_value = rust_value_getter(rust_node);
let value = get_property(emacs, emacs_field)?
.map(Token::as_list)
.map_or(Ok(None), |r| r.map(Some))?;
let (value, rust_value) = match (value, rust_value) {
(None, None) => {
return Ok(ComparePropertiesResult::NoChange);
}
(None, Some(_)) | (Some(_), None) => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(value), Some(rust_value)) if value.len() != rust_value.len() => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
(Some(value), Some(rust_value)) => (value, rust_value),
};
let mut full_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
// Iterate the outer lists
for (value, (rust_optional, rust_value)) in value.iter().zip(rust_value.iter()) {
let mut middle_value = value.as_list()?.iter();
// First element of middle list is the mandatory value (the value past the colon).
let mandatory_value = middle_value.next();
let mandatory_value = match mandatory_value {
Some(mandatory_value) => mandatory_value,
None => {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
};
// Compare optional value
if let Some(rust_optional) = rust_optional {
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
if rust_optional.len() != middle_value.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} optional value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
middle_value.len(),
rust_optional.len(),
rust_optional
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in middle_value.zip(rust_optional) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
if !child_status.is_empty() {
let diff_scope = artificial_diff_scope("optional value", child_status)?;
full_status.push(diff_scope);
}
}
// Compare mandatory value
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
let mandatory_value = mandatory_value.as_list()?;
if rust_value.len() != mandatory_value.len() {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
mandatory_value.len(),
rust_value.len(),
rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
for (e, r) in mandatory_value.iter().zip(rust_value) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
if !child_status.is_empty() {
let diff_scope = artificial_diff_scope("mandatory value", child_status)?;
full_status.push(diff_scope);
}
}
if full_status.is_empty() {
Ok(ComparePropertiesResult::NoChange)
} else {
let diff_scope = artificial_owned_diff_scope(emacs_field, full_status)?;
Ok(ComparePropertiesResult::DiffEntry(diff_scope))
}
}
pub(crate) fn compare_property_number_lines< pub(crate) fn compare_property_number_lines<
'b, 'b,
's, 's,

View File

@@ -2,7 +2,6 @@ use std::borrow::Cow;
// TODO: Update all to use the macro to assert there are no unexpected keys. // TODO: Update all to use the macro to assert there are no unexpected keys.
// TODO: Check all compare funtions for whether they correctly iterate children. // TODO: Check all compare funtions for whether they correctly iterate children.
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::collections::HashSet;
use super::compare_field::compare_identity; use super::compare_field::compare_identity;
use super::compare_field::compare_noop; use super::compare_field::compare_noop;
@@ -21,12 +20,13 @@ use super::elisp_fact::ElispFact;
use super::elisp_fact::GetElispFact; use super::elisp_fact::GetElispFact;
use super::sexp::unquote; use super::sexp::unquote;
use super::sexp::Token; use super::sexp::Token;
use super::util::affiliated_keywords_names;
use super::util::assert_no_children; use super::util::assert_no_children;
use super::util::compare_additional_properties;
use super::util::compare_affiliated_keywords;
use super::util::compare_children; use super::util::compare_children;
use super::util::compare_children_iter;
use super::util::compare_standard_properties; use super::util::compare_standard_properties;
use super::util::get_property;
use super::util::get_property_boolean;
use super::util::get_property_quoted_string;
use crate::compare::compare_field::ComparePropertiesResult; use crate::compare::compare_field::ComparePropertiesResult;
use crate::compare::compare_field::EmacsField; use crate::compare::compare_field::EmacsField;
use crate::compare::macros::compare_properties; use crate::compare::macros::compare_properties;
@@ -47,7 +47,6 @@ use crate::types::Date;
use crate::types::DayOfMonth; use crate::types::DayOfMonth;
use crate::types::DiarySexp; use crate::types::DiarySexp;
use crate::types::Document; use crate::types::Document;
use crate::types::DocumentElement;
use crate::types::Drawer; use crate::types::Drawer;
use crate::types::DynamicBlock; use crate::types::DynamicBlock;
use crate::types::Entity; use crate::types::Entity;
@@ -81,7 +80,6 @@ use crate::types::PlainListItem;
use crate::types::PlainListType; use crate::types::PlainListType;
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::QuoteBlock; use crate::types::QuoteBlock;
use crate::types::RadioLink; use crate::types::RadioLink;
@@ -332,6 +330,17 @@ pub(crate) fn artificial_diff_scope<'b, 's>(
.into()) .into())
} }
pub(crate) fn artificial_owned_diff_scope<'b, 's, 'x>(
name: &'x str,
children: Vec<DiffEntry<'b, 's>>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
Ok(DiffLayer {
name: name.to_string().into(),
children,
}
.into())
}
pub(crate) fn compare_ast_node<'b, 's>( pub(crate) fn compare_ast_node<'b, 's>(
source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
@@ -438,99 +447,70 @@ pub fn compare_document<'b, 's>(
} }
fn _compare_document<'b, 's>( fn _compare_document<'b, 's>(
_source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust: &'b Document<'s>, rust: &'b Document<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> { ) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None; let mut message = None;
// Compare :path let additional_property_names: Vec<String> = rust
// :path is a quoted string to the absolute path of the document. .get_additional_properties()
let document_path = get_property_quoted_string(emacs, ":path")?; .map(|node_property| format!(":{}", node_property.property_name.to_uppercase()))
let rust_document_path = rust.path.as_ref().map(|p| p.to_str()).flatten(); .collect();
match (
document_path.as_ref().map(|s| s.as_str()),
rust_document_path,
) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Path mismatch (emacs != rust) {:?} != {:?}",
document_path, rust_document_path
));
}
(Some(e), Some(r)) if e != r => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Path mismatch (emacs != rust) {:?} != {:?}",
document_path, rust_document_path
));
}
(Some(_), Some(_)) => {}
};
// Compare category let additional_properties: Vec<(String, &str)> = rust
// :CATEGORY is specified either from "#+CATEGORY:" or it is the file name without the ".org" extension. .get_additional_properties()
let category = get_property_quoted_string(emacs, ":CATEGORY")?; .map(|node_property| {
match (category.as_ref(), rust.category.as_ref()) { (
(None, None) => {} format!(":{}", node_property.property_name.to_uppercase()),
(None, Some(_)) | (Some(_), None) => { node_property.value.unwrap_or(""),
this_status = DiffStatus::Bad;
message = Some(format!(
"Category mismatch (emacs != rust) {:?} != {:?}",
category, rust.category
));
}
(Some(e), Some(r)) if e != r => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Category mismatch (emacs != rust) {:?} != {:?}",
category, rust.category
));
}
(Some(_), Some(_)) => {}
};
// Skipping "org-data" and its properties
for (i, token) in children.iter().skip(2).enumerate() {
let section_or_headline = token.as_list()?;
let first_cell = section_or_headline
.first()
.ok_or("Should have at least one child.")?
.as_atom()?;
if first_cell == "section" {
if i != 0 {
return Err("Section cannot be after the first child of document.".into());
}
child_status.push(compare_ast_node(
rust.source,
token,
rust.zeroth_section
.as_ref()
.ok_or("No corresponding zeroth-section")?
.into(),
)?);
} else if first_cell == "headline" {
let corresponding_heading = rust
.children
.iter()
.nth(i - rust.zeroth_section.as_ref().map(|_| 1).unwrap_or(0))
.ok_or("Should have a corresponding heading.")?;
child_status.push(compare_ast_node(
rust.source,
token,
corresponding_heading.into(),
)?);
} else {
return Err(format!(
"Document should only contain sections and headlines, found: {}",
first_cell
) )
.into()); })
.collect();
compare_additional_properties(emacs, additional_properties.into_iter())?.apply(
&mut child_status,
&mut this_status,
&mut message,
);
compare_children_iter(
source,
emacs,
rust.into_iter(),
&mut child_status,
&mut this_status,
&mut message,
)?;
for diff in compare_properties!(
source,
emacs,
rust,
additional_property_names
.iter()
.map(String::as_str)
.map(EmacsField::Required),
(
EmacsField::Required(":path"),
|r| r.path.as_ref().map(|p| p.to_str()).flatten(),
compare_property_quoted_string
),
(
EmacsField::Required(":CATEGORY"),
|r| r.category.as_ref(),
compare_property_quoted_string
)
) {
match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
} }
} }
@@ -586,7 +566,7 @@ fn compare_section<'b, 's>(
} }
#[allow(dead_code)] #[allow(dead_code)]
fn new_compare_heading<'b, 's>( fn compare_heading<'b, 's>(
source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust: &'b Heading<'s>, rust: &'b Heading<'s>,
@@ -595,7 +575,26 @@ fn new_compare_heading<'b, 's>(
let mut child_status = Vec::new(); let mut child_status = Vec::new();
let mut message = None; let mut message = None;
// TODO: This needs to support additional properties from the property drawer let additional_property_names: Vec<String> = rust
.get_additional_properties()
.map(|node_property| format!(":{}", node_property.property_name.to_uppercase()))
.collect();
let additional_properties: Vec<(String, &str)> = rust
.get_additional_properties()
.map(|node_property| {
(
format!(":{}", node_property.property_name.to_uppercase()),
node_property.value.unwrap_or(""),
)
})
.collect();
compare_additional_properties(emacs, additional_properties.into_iter())?.apply(
&mut child_status,
&mut this_status,
&mut message,
);
compare_children( compare_children(
source, source,
@@ -610,6 +609,10 @@ fn new_compare_heading<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
additional_property_names
.iter()
.map(String::as_str)
.map(EmacsField::Required),
( (
EmacsField::Required(":level"), EmacsField::Required(":level"),
|r| Some(r.level), |r| Some(r.level),
@@ -717,278 +720,6 @@ fn new_compare_heading<'b, 's>(
.into()) .into())
} }
fn compare_heading<'b, 's>(
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b Heading<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good;
let mut message = None;
// Compare level
let level = get_property(emacs, ":level")?
.ok_or("Level should not be nil")?
.as_atom()?;
if rust.level.to_string() != level {
this_status = DiffStatus::Bad;
message = Some(format!(
"Headline level do not match (emacs != rust): {} != {}",
level, rust.level
))
}
// Compare tags
let emacs_tags = get_tags_from_heading(emacs)?;
let emacs_tags: HashSet<_> = emacs_tags.iter().map(|val| val.as_str()).collect();
let rust_tags: HashSet<&str> = rust.tags.iter().map(|val| *val).collect();
let difference: Vec<&str> = emacs_tags
.symmetric_difference(&rust_tags)
.map(|val| *val)
.collect();
if !difference.is_empty() {
this_status = DiffStatus::Bad;
message = Some(format!("Mismatched tags: {}", difference.join(", ")));
}
// Compare todo-keyword
let todo_keyword = get_property(emacs, ":todo-keyword")?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
match (todo_keyword, &rust.todo_keyword, unquote(todo_keyword)) {
("nil", None, _) => {}
(_, Some((_rust_todo_type, rust_todo)), Ok(emacs_todo)) if emacs_todo == *rust_todo => {}
(emacs_todo, rust_todo, _) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"(emacs != rust) {:?} != {:?}",
emacs_todo, rust_todo
));
}
};
// Compare todo-type
let todo_type = get_property(emacs, ":todo-type")?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
// todo-type is an unquoted string either todo, done, or nil
match (todo_type, &rust.todo_keyword) {
("nil", None) => {}
("todo", Some((TodoKeywordType::Todo, _))) => {}
("done", Some((TodoKeywordType::Done, _))) => {}
(emacs_todo, rust_todo) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"(emacs != rust) {:?} != {:?}",
emacs_todo, rust_todo
));
}
};
// Compare title
let title = get_property(emacs, ":title")?;
match (title, rust.title.len()) {
(None, 0) => {}
(None, _) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Titles do not match (emacs != rust): {:?} != {:?}",
title, rust.title
))
}
(Some(title), _) => {
let title_status = title
.as_list()?
.iter()
.zip(rust.title.iter())
.map(|(emacs_child, rust_child)| {
compare_ast_node(source, emacs_child, rust_child.into())
})
.collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope("title", title_status)?);
}
};
// 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) => {}
}
// Compare raw-value
let raw_value = get_property_quoted_string(emacs, ":raw-value")?
.ok_or("Headlines should have :raw-value.")?;
let rust_raw_value = rust.get_raw_value();
if raw_value != rust_raw_value {
this_status = DiffStatus::Bad;
message = Some(format!(
"raw-value mismatch (emacs != rust) {:?} != {:?}",
raw_value, rust_raw_value
));
}
// Compare footnote-section-p
let footnote_section = get_property_boolean(emacs, ":footnote-section-p")?;
if footnote_section != rust.is_footnote_section {
this_status = DiffStatus::Bad;
message = Some(format!(
"footnote section mismatch (emacs != rust) {:?} != {:?}",
footnote_section, rust.is_footnote_section
));
}
// Compare scheduled
let scheduled = get_property(emacs, ":scheduled")?;
match (scheduled, &rust.scheduled) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Scheduled mismatch (emacs != rust) {:?} != {:?}",
scheduled, rust.scheduled
));
}
(Some(emacs_child), Some(rust_child)) => {
let result = compare_ast_node(source, emacs_child, rust_child.into())?;
child_status.push(artificial_diff_scope("scheduled", vec![result])?);
}
}
// Compare deadline
let deadline = get_property(emacs, ":deadline")?;
match (deadline, &rust.deadline) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Deadline mismatch (emacs != rust) {:?} != {:?}",
deadline, rust.deadline
));
}
(Some(emacs_child), Some(rust_child)) => {
let result = compare_ast_node(source, emacs_child, rust_child.into())?;
child_status.push(artificial_diff_scope("deadline", vec![result])?);
}
}
// Compare closed
let closed = get_property(emacs, ":closed")?;
match (closed, &rust.closed) {
(None, None) => {}
(None, Some(_)) | (Some(_), None) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Closed mismatch (emacs != rust) {:?} != {:?}",
closed, rust.closed
));
}
(Some(emacs_child), Some(rust_child)) => {
let result = compare_ast_node(source, emacs_child, rust_child.into())?;
child_status.push(artificial_diff_scope("closed", vec![result])?);
}
}
// TODO: Compare :pre-blank
// Compare section
let section_status = children
.iter()
.skip(2)
.zip(rust.children.iter())
.map(|(emacs_child, rust_child)| match rust_child {
DocumentElement::Heading(rust_heading) => {
compare_ast_node(source, emacs_child, rust_heading.into())
}
DocumentElement::Section(rust_section) => {
compare_ast_node(source, emacs_child, rust_section.into())
}
})
.collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope("section", section_status)?);
Ok(DiffResult {
status: this_status,
name: rust.get_elisp_name(),
message,
children: child_status,
rust_source: rust.get_source(),
emacs_token: emacs,
}
.into())
}
fn get_tags_from_heading<'b, 's>(
emacs: &'b Token<'s>,
) -> Result<HashSet<String>, Box<dyn std::error::Error>> {
let tags = match get_property(emacs, ":tags")? {
Some(prop) => prop,
None => return Ok(HashSet::new()),
};
match tags.as_atom() {
Ok(val) => panic!("Unexpected value for tags: {:?}", val),
Err(_) => {}
};
let tags = {
let tags = tags.as_list()?;
let strings = tags
.iter()
.map(Token::as_atom)
.collect::<Result<Vec<&str>, _>>()?;
strings
.into_iter()
.map(unquote)
.collect::<Result<HashSet<String>, _>>()?
};
Ok(tags)
}
fn compare_paragraph<'b, 's>( fn compare_paragraph<'b, 's>(
source: &'s str, source: &'s str,
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
@@ -1007,21 +738,7 @@ fn compare_paragraph<'b, 's>(
&mut message, &mut message,
)?; )?;
for diff in compare_properties!( for diff in compare_properties!(source, emacs, rust, [],) {
source,
emacs,
rust,
(
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
)
) {
match diff { match diff {
ComparePropertiesResult::NoChange => {} ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => { ComparePropertiesResult::SelfChange(new_status, new_message) => {
@@ -1065,16 +782,7 @@ fn compare_plain_list<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":type"), EmacsField::Required(":type"),
|r| Some(match r.list_type { |r| Some(match r.list_type {
@@ -1192,24 +900,28 @@ fn compare_center_block<'b, 's>(
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
rust: &'b CenterBlock<'s>, rust: &'b CenterBlock<'s>,
) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> { ) -> Result<DiffEntry<'b, 's>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let mut child_status = Vec::new();
let mut this_status = DiffStatus::Good; let mut this_status = DiffStatus::Good;
let mut child_status = Vec::new();
let mut message = None; let mut message = None;
// TODO: Compare :caption compare_children(
// Compare name source,
let name = get_property_quoted_string(emacs, ":name")?; emacs,
if name.as_ref().map(String::as_str) != rust.name { &rust.children,
this_status = DiffStatus::Bad; &mut child_status,
message = Some(format!( &mut this_status,
"Name mismatch (emacs != rust) {:?} != {:?}", &mut message,
name, rust.name )?;
));
}
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { for diff in compare_properties!(source, emacs, rust, [],) {
child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?); match diff {
ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => {
this_status = new_status;
message = new_message
}
ComparePropertiesResult::DiffEntry(diff_entry) => child_status.push(diff_entry),
}
} }
Ok(DiffResult { Ok(DiffResult {
@@ -1241,21 +953,7 @@ fn compare_quote_block<'b, 's>(
&mut message, &mut message,
)?; )?;
for diff in compare_properties!( for diff in compare_properties!(source, emacs, rust, [],) {
source,
emacs,
rust,
(
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
)
) {
match diff { match diff {
ComparePropertiesResult::NoChange => {} ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => { ComparePropertiesResult::SelfChange(new_status, new_message) => {
@@ -1299,16 +997,7 @@ fn compare_special_block<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":type"), EmacsField::Required(":type"),
|r| Some(r.block_type), |r| Some(r.block_type),
@@ -1368,16 +1057,7 @@ fn compare_dynamic_block<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":block-name"), EmacsField::Required(":block-name"),
|r| Some(r.block_name), |r| Some(r.block_name),
@@ -1432,16 +1112,7 @@ fn compare_footnote_definition<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":label"), EmacsField::Required(":label"),
|r| Some(r.label), |r| Some(r.label),
@@ -1538,16 +1209,7 @@ fn compare_drawer<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":drawer-name"), EmacsField::Required(":drawer-name"),
|r| Some(r.drawer_name), |r| Some(r.drawer_name),
@@ -1684,16 +1346,7 @@ fn compare_table<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":tblfm"), EmacsField::Required(":tblfm"),
|r| if r.formulas.is_empty() { |r| if r.formulas.is_empty() {
@@ -1845,21 +1498,7 @@ fn compare_verse_block<'b, 's>(
&mut message, &mut message,
)?; )?;
for diff in compare_properties!( for diff in compare_properties!(source, emacs, rust, [],) {
source,
emacs,
rust,
(
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
)
) {
match diff { match diff {
ComparePropertiesResult::NoChange => {} ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => { ComparePropertiesResult::SelfChange(new_status, new_message) => {
@@ -1896,16 +1535,7 @@ fn compare_comment_block<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents), |r| Some(r.contents),
@@ -1948,16 +1578,7 @@ fn compare_example_block<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.contents.as_str()), |r| Some(r.contents.as_str()),
@@ -2030,16 +1651,7 @@ fn compare_export_block<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":type"), EmacsField::Required(":type"),
|r| r.get_export_type(), |r| r.get_export_type(),
@@ -2087,16 +1699,7 @@ fn compare_src_block<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":language"), EmacsField::Required(":language"),
|r| r.language, |r| r.language,
@@ -2234,16 +1837,7 @@ fn compare_diary_sexp<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.value), |r| Some(r.value),
@@ -2338,16 +1932,7 @@ fn compare_fixed_width_area<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.get_value()), |r| Some(r.get_value()),
@@ -2386,21 +1971,7 @@ fn compare_horizontal_rule<'b, 's>(
assert_no_children(emacs, &mut this_status, &mut message)?; assert_no_children(emacs, &mut this_status, &mut message)?;
for diff in compare_properties!( for diff in compare_properties!(source, emacs, rust, [],) {
source,
emacs,
rust,
(
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
)
) {
match diff { match diff {
ComparePropertiesResult::NoChange => {} ComparePropertiesResult::NoChange => {}
ComparePropertiesResult::SelfChange(new_status, new_message) => { ComparePropertiesResult::SelfChange(new_status, new_message) => {
@@ -2437,16 +2008,7 @@ fn compare_keyword<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":key"), EmacsField::Required(":key"),
|r| Some(r.key.to_uppercase()), |r| Some(r.key.to_uppercase()),
@@ -2494,16 +2056,7 @@ fn compare_babel_call<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.value), |r| Some(r.value),
@@ -2566,16 +2119,7 @@ fn compare_latex_environment<'b, 's>(
source, source,
emacs, emacs,
rust, rust,
( [],
EmacsField::Optional(":name"),
|r| r.name,
compare_property_quoted_string
),
(
EmacsField::Optional(":caption"),
compare_identity,
compare_noop
),
( (
EmacsField::Required(":value"), EmacsField::Required(":value"),
|r| Some(r.value), |r| Some(r.value),

View File

@@ -30,7 +30,7 @@
/// } /// }
/// ``` /// ```
macro_rules! compare_properties { macro_rules! compare_properties {
($source:expr, $emacs:expr, $rust:expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),+) => { ($source:expr, $emacs:expr, $rust:expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),*) => {
{ {
let mut new_status = Vec::new(); let mut new_status = Vec::new();
let children = $emacs.as_list()?; let children = $emacs.as_list()?;
@@ -63,7 +63,7 @@ macro_rules! compare_properties {
}, },
EmacsField::Optional(_name) => {}, EmacsField::Optional(_name) => {},
} }
)+ )*
if !emacs_keys.is_empty() { if !emacs_keys.is_empty() {
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect(); let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
@@ -91,7 +91,180 @@ macro_rules! compare_properties {
new_status.push(result); new_status.push(result);
} }
} }
)+ )*
new_status
}
};
// For elements with affiliated keywords
($source:expr, $emacs:expr, $rust:expr, [], $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),*) => {
{
let mut new_status = Vec::new();
let children = $emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let mut emacs_keys: BTreeSet<&str> = attributes_map.keys().map(|s| *s).collect();
if emacs_keys.contains(":standard-properties") {
emacs_keys.remove(":standard-properties");
} else {
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks :standard-properties field.",
))));
}
let affiliated_keywords_names: Vec<String> = affiliated_keywords_names($rust).collect();
for additional_field in affiliated_keywords_names.iter().map(String::as_str).map(EmacsField::Required) {
match additional_field {
EmacsField::Required(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Optional(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Required(name) => {
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks required field: {}",
name
))));
},
EmacsField::Optional(_name) => {},
}
}
$(
match $emacs_field {
EmacsField::Required(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Optional(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Required(name) => {
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks required field: {}",
name
))));
},
EmacsField::Optional(_name) => {},
}
)*
if !emacs_keys.is_empty() {
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
let unexpected_keys = unexpected_keys.join(", ");
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token had extra field(s): {}",
unexpected_keys
))));
}
for diff in compare_affiliated_keywords($source, $emacs, $rust)? {
new_status.push(diff);
}
$(
let emacs_name = match $emacs_field {
EmacsField::Required(name) => {
name
},
EmacsField::Optional(name) => {
name
},
};
let result = $compare_fn($source, $emacs, $rust, emacs_name, $rust_value_getter)?;
match result {
ComparePropertiesResult::SelfChange(DiffStatus::Good, _) => unreachable!("No comparison functions should return SelfChange() when DiffStatus is good."),
ComparePropertiesResult::NoChange => {},
result => {
new_status.push(result);
}
}
)*
new_status
}
};
// Specifies additional properties
($source:expr, $emacs:expr, $rust:expr, $additionalproperties: expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),*) => {
{
let mut new_status = Vec::new();
let children = $emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let mut emacs_keys: BTreeSet<&str> = attributes_map.keys().map(|s| *s).collect();
if emacs_keys.contains(":standard-properties") {
emacs_keys.remove(":standard-properties");
} else {
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks :standard-properties field.",
))));
}
for additional_field in $additionalproperties {
match additional_field {
EmacsField::Required(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Optional(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Required(name) => {
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks required field: {}",
name
))));
},
EmacsField::Optional(_name) => {},
}
}
$(
match $emacs_field {
EmacsField::Required(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Optional(name) if emacs_keys.contains(name) => {
emacs_keys.remove(name);
},
EmacsField::Required(name) => {
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token lacks required field: {}",
name
))));
},
EmacsField::Optional(_name) => {},
}
)*
if !emacs_keys.is_empty() {
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
let unexpected_keys = unexpected_keys.join(", ");
new_status.push(ComparePropertiesResult::SelfChange(DiffStatus::Bad, Some(format!(
"Emacs token had extra field(s): {}",
unexpected_keys
))));
}
$(
let emacs_name = match $emacs_field {
EmacsField::Required(name) => {
name
},
EmacsField::Optional(name) => {
name
},
};
let result = $compare_fn($source, $emacs, $rust, emacs_name, $rust_value_getter)?;
match result {
ComparePropertiesResult::SelfChange(DiffStatus::Good, _) => unreachable!("No comparison functions should return SelfChange() when DiffStatus is good."),
ComparePropertiesResult::NoChange => {},
result => {
new_status.push(result);
}
}
)*
new_status new_status
} }

View File

@@ -10,3 +10,5 @@ pub use compare::run_anonymous_compare;
pub use compare::run_anonymous_compare_with_settings; pub use compare::run_anonymous_compare_with_settings;
pub use compare::run_compare_on_file; pub use compare::run_compare_on_file;
pub use compare::run_compare_on_file_with_settings; pub use compare::run_compare_on_file_with_settings;
pub use compare::silent_anonymous_compare;
pub use compare::silent_compare_on_file;

View File

@@ -1,5 +1,6 @@
use std::path::Path; use std::path::Path;
use std::process::Command;
use tokio::process::Command;
use crate::context::HeadlineLevelFilter; use crate::context::HeadlineLevelFilter;
use crate::settings::GlobalSettings; use crate::settings::GlobalSettings;
@@ -25,9 +26,9 @@ fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
ret ret
} }
pub(crate) fn emacs_parse_anonymous_org_document<C>( pub(crate) async fn emacs_parse_anonymous_org_document<'g, 's, C>(
file_contents: C, file_contents: C,
global_settings: &GlobalSettings, global_settings: &GlobalSettings<'g, 's>,
) -> Result<String, Box<dyn std::error::Error>> ) -> Result<String, Box<dyn std::error::Error>>
where where
C: AsRef<str>, C: AsRef<str>,
@@ -54,15 +55,24 @@ where
.arg("--batch") .arg("--batch")
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
out.status.exit_ok()?; let status = out.status.exit_ok();
if status.is_err() {
eprintln!(
"Emacs errored out: {}\n{}",
String::from_utf8(out.stdout)?,
String::from_utf8(out.stderr)?
);
status?;
unreachable!();
}
let org_sexp = out.stderr; let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?) Ok(String::from_utf8(org_sexp)?)
} }
pub(crate) fn emacs_parse_file_org_document<P>( pub(crate) async fn emacs_parse_file_org_document<'g, 's, P>(
file_path: P, file_path: P,
global_settings: &GlobalSettings, global_settings: &GlobalSettings<'g, 's>,
) -> Result<String, Box<dyn std::error::Error>> ) -> Result<String, Box<dyn std::error::Error>>
where where
P: AsRef<Path>, P: AsRef<Path>,
@@ -97,8 +107,17 @@ where
.arg("--batch") .arg("--batch")
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
out.status.exit_ok()?; let status = out.status.exit_ok();
if status.is_err() {
eprintln!(
"Emacs errored out: {}\n{}",
String::from_utf8(out.stdout)?,
String::from_utf8(out.stderr)?
);
status?;
unreachable!();
}
let org_sexp = out.stderr; let org_sexp = out.stderr;
Ok(String::from_utf8(org_sexp)?) Ok(String::from_utf8(org_sexp)?)
} }
@@ -125,7 +144,7 @@ where
output output
} }
pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> { pub async fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
let elisp_script = r#"(progn let elisp_script = r#"(progn
(message "%s" (version)) (message "%s" (version))
)"#; )"#;
@@ -138,12 +157,12 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) Ok(String::from_utf8(out.stderr)?)
} }
pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> { pub async fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
let elisp_script = r#"(progn let elisp_script = r#"(progn
(org-mode) (org-mode)
(message "%s" (org-version nil t nil)) (message "%s" (org-version nil t nil))
@@ -157,7 +176,7 @@ pub fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) Ok(String::from_utf8(out.stderr)?)
} }

View File

@@ -1,12 +1,18 @@
use std::str::FromStr; use std::str::FromStr;
use super::compare_field::compare_property_list_of_list_of_list_of_ast_nodes;
use super::compare_field::compare_property_list_of_quoted_string;
use super::compare_field::compare_property_quoted_string;
use super::compare_field::ComparePropertiesResult;
use super::diff::DiffEntry; use super::diff::DiffEntry;
use super::diff::DiffStatus; use super::diff::DiffStatus;
use super::elisp_fact::GetElispFact; use super::elisp_fact::GetElispFact;
use super::sexp::Token; use super::sexp::Token;
use crate::compare::diff::compare_ast_node; use crate::compare::diff::compare_ast_node;
use crate::compare::sexp::unquote; use crate::compare::sexp::unquote;
use crate::types::AffiliatedKeywordValue;
use crate::types::AstNode; use crate::types::AstNode;
use crate::types::GetAffiliatedKeywords;
use crate::types::GetStandardProperties; use crate::types::GetStandardProperties;
use crate::types::StandardProperties; use crate::types::StandardProperties;
@@ -221,22 +227,6 @@ pub(crate) fn get_property_quoted_string<'b, 's, 'x>(
.map_or(Ok(None), |r| r.map(Some))?) .map_or(Ok(None), |r| r.map(Some))?)
} }
/// Get a named property containing a boolean value.
///
/// This uses the elisp convention of nil == false, non-nil == true.
///
/// Returns false if key is not found.
pub(crate) fn get_property_boolean<'b, 's, 'x>(
emacs: &'b Token<'s>,
key: &'x str,
) -> Result<bool, Box<dyn std::error::Error>> {
Ok(get_property(emacs, key)?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil")
!= "nil")
}
/// Get a named property containing an unquoted numeric value. /// Get a named property containing an unquoted numeric value.
/// ///
/// Returns None if key is not found. /// Returns None if key is not found.
@@ -284,6 +274,33 @@ where
Ok(()) Ok(())
} }
pub(crate) fn compare_children_iter<'b, 's, RC, RI: Iterator<Item = RC> + ExactSizeIterator>(
source: &'s str,
emacs: &'b Token<'s>,
rust_children: RI,
child_status: &mut Vec<DiffEntry<'b, 's>>,
this_status: &mut DiffStatus,
message: &mut Option<String>,
) -> Result<(), Box<dyn std::error::Error>>
where
AstNode<'b, 's>: From<RC>,
{
let emacs_children = emacs.as_list()?;
let emacs_children_length = emacs_children.len() - 2;
if emacs_children_length != rust_children.len() {
*this_status = DiffStatus::Bad;
*message = Some(format!(
"Child length mismatch (emacs != rust) {:?} != {:?}",
emacs_children_length,
rust_children.len()
));
}
for (emacs_child, rust_child) in emacs_children.iter().skip(2).zip(rust_children) {
child_status.push(compare_ast_node(source, emacs_child, rust_child.into())?);
}
Ok(())
}
pub(crate) fn assert_no_children<'b, 's>( pub(crate) fn assert_no_children<'b, 's>(
emacs: &'b Token<'s>, emacs: &'b Token<'s>,
this_status: &mut DiffStatus, this_status: &mut DiffStatus,
@@ -300,3 +317,86 @@ pub(crate) fn assert_no_children<'b, 's>(
} }
Ok(()) Ok(())
} }
pub(crate) fn compare_additional_properties<'b, 's, RK, RV, RI>(
emacs: &'b Token<'s>,
rust_children: RI,
) -> Result<ComparePropertiesResult<'b, 's>, Box<dyn std::error::Error>>
where
RK: AsRef<str>,
RV: AsRef<str>,
RI: Iterator<Item = (RK, RV)> + ExactSizeIterator,
{
for (rust_key, rust_value) in rust_children {
let rust_key = rust_key.as_ref();
let rust_value = rust_value.as_ref();
let emacs_value = get_property_quoted_string(emacs, rust_key)?;
if Some(rust_value) != emacs_value.as_ref().map(String::as_str) {
let this_status = DiffStatus::Bad;
let message = Some(format!(
"{} mismatch (emacs != rust) {:?} != {:?}",
rust_key, emacs_value, rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
}
Ok(ComparePropertiesResult::NoChange)
}
pub(crate) fn compare_affiliated_keywords<'b, 's, GAK>(
source: &'s str,
emacs: &'b Token<'s>,
rust: &'b GAK,
) -> Result<Vec<ComparePropertiesResult<'b, 's>>, Box<dyn std::error::Error>>
where
GAK: GetAffiliatedKeywords<'s>,
{
let mut ret = Vec::new();
let affiliated_keywords = rust.get_affiliated_keywords();
for (rust_name, rust_value) in affiliated_keywords.keywords.iter() {
let emacs_property_name = format!(":{}", rust_name);
match rust_value {
AffiliatedKeywordValue::SingleString(rust_value) => {
let diff = compare_property_quoted_string(
source,
emacs,
rust,
emacs_property_name.as_str(),
|_| Some(*rust_value),
)?;
ret.push(diff);
}
AffiliatedKeywordValue::ListOfStrings(rust_value) => {
let diff = compare_property_list_of_quoted_string(
source,
emacs,
rust,
emacs_property_name.as_str(),
|_| Some(rust_value.iter()),
)?;
ret.push(diff);
}
AffiliatedKeywordValue::ListOfListsOfObjects(rust_value) => {
let diff = compare_property_list_of_list_of_list_of_ast_nodes(
source,
emacs,
rust,
emacs_property_name.as_str(),
|_| Some(rust_value),
)?;
ret.push(diff);
}
};
}
Ok(ret)
}
pub(crate) fn affiliated_keywords_names<'s, GAK>(rust: &'s GAK) -> impl Iterator<Item = String> + 's
where
GAK: GetAffiliatedKeywords<'s>,
{
rust.get_affiliated_keywords()
.keywords
.keys()
.map(|k| format!(":{}", k))
}

View File

@@ -1,5 +1,25 @@
use super::global_settings::EntityDefinition; use super::global_settings::EntityDefinition;
pub(crate) const DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS: [&'static str; 1] = ["CAPTION"];
pub(crate) const DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["CAPTION", "RESULTS"];
pub(crate) const DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
"CAPTION", "DATA", "HEADER", "HEADERS", "LABEL", "NAME", "PLOT", "RESNAME", "RESULT",
"RESULTS", "SOURCE", "SRCNAME", "TBLNAME",
];
pub(crate) const DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST: [(&'static str, &'static str); 8] = [
("DATA", "NAME"),
("LABEL", "NAME"),
("RESNAME", "NAME"),
("SOURCE", "NAME"),
("SRCNAME", "NAME"),
("TBLNAME", "NAME"),
("RESULT", "RESULTS"),
("HEADERS", "HEADER"),
];
pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&'static str; 23] = [ pub(crate) const DEFAULT_ORG_LINK_PARAMETERS: [&'static str; 23] = [
"id", "id",
"eww", "eww",

View File

@@ -12,7 +12,6 @@ use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::OrgSource; use crate::parser::OrgSource;
use crate::types::Keyword;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum ContextElement<'r, 's> { pub(crate) enum ContextElement<'r, 's> {
@@ -25,22 +24,11 @@ pub(crate) enum ContextElement<'r, 's> {
/// Indicates if elements should consume the whitespace after them. /// Indicates if elements should consume the whitespace after them.
ConsumeTrailingWhitespace(bool), ConsumeTrailingWhitespace(bool),
/// Indicate that we are parsing a paragraph that already has affiliated keywords.
///
/// The value stored is the start of the element after the affiliated keywords. In this way, we can ensure that we do not exit an element immediately after the affiliated keyword had been consumed.
HasAffiliatedKeyword(HasAffiliatedKeywordInner<'r, 's>),
/// 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)] #[allow(dead_code)]
Placeholder(PhantomData<&'s str>), Placeholder(PhantomData<&'s str>),
} }
#[derive(Debug, Clone)]
pub(crate) struct HasAffiliatedKeywordInner<'r, 's> {
pub(crate) start_after_affiliated_keywords: OrgSource<'s>,
pub(crate) keywords: &'r Vec<Keyword<'s>>,
}
pub(crate) struct ExitMatcherNode<'r> { pub(crate) struct ExitMatcherNode<'r> {
// TODO: Should this be "&'r DynContextMatcher<'c>" ? // TODO: Should this be "&'r DynContextMatcher<'c>" ?
pub(crate) exit_matcher: &'r DynContextMatcher<'r>, pub(crate) exit_matcher: &'r DynContextMatcher<'r>,

View File

@@ -1,6 +1,12 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::path::PathBuf; use std::path::PathBuf;
#[cfg(any(feature = "compare", feature = "foreign_document_test"))]
pub trait FileAccessInterface: Sync + Debug {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
}
#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))]
pub trait FileAccessInterface: Debug { pub trait FileAccessInterface: Debug {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>; fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
} }

View File

@@ -5,6 +5,10 @@ use super::constants::DEFAULT_ORG_ENTITIES;
use super::constants::DEFAULT_ORG_LINK_PARAMETERS; use super::constants::DEFAULT_ORG_LINK_PARAMETERS;
use super::FileAccessInterface; use super::FileAccessInterface;
use super::LocalFileAccessInterface; use super::LocalFileAccessInterface;
use crate::context::constants::DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS;
use crate::context::constants::DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST;
use crate::context::constants::DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS;
use crate::types::IndentationLevel; use crate::types::IndentationLevel;
use crate::types::Object; use crate::types::Object;
@@ -54,6 +58,26 @@ pub struct GlobalSettings<'g, 's> {
/// ///
/// Corresponds to org-entities elisp variable. /// Corresponds to org-entities elisp variable.
pub entities: &'g [EntityDefinition<'s>], pub entities: &'g [EntityDefinition<'s>],
/// Keywords that contain the standard set of objects (excluding footnote references).
///
/// Corresponds to org-element-parsed-keywords elisp variable.
pub element_parsed_keywords: &'g [&'s str],
/// Keywords that can have a secondary value in square brackets.
///
/// Corresponds to org-element-dual-keywords elisp variable.
pub element_dual_keywords: &'g [&'s str],
/// Keywords that can be affiliated with an element.
///
/// Corresponds to org-element-affiliated-keywords elisp variable.
pub element_affiliated_keywords: &'g [&'s str],
/// Mapping of keyword names.
///
/// Corresponds to org-element-keyword-translation-alist elisp variable.
pub element_keyword_translation_alist: &'g [(&'s str, &'s str)],
} }
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8; pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@@ -71,6 +95,9 @@ pub struct EntityDefinition<'a> {
impl<'g, 's> GlobalSettings<'g, 's> { impl<'g, 's> GlobalSettings<'g, 's> {
fn new() -> GlobalSettings<'g, 's> { fn new() -> GlobalSettings<'g, 's> {
debug_assert!(
DEFAULT_ORG_ENTITIES.is_sorted_by(|a, b| b.name.len().partial_cmp(&a.name.len()))
);
GlobalSettings { GlobalSettings {
radio_targets: Vec::new(), radio_targets: Vec::new(),
file_access: &LocalFileAccessInterface { file_access: &LocalFileAccessInterface {
@@ -85,6 +112,10 @@ impl<'g, 's> GlobalSettings<'g, 's> {
link_parameters: &DEFAULT_ORG_LINK_PARAMETERS, link_parameters: &DEFAULT_ORG_LINK_PARAMETERS,
link_templates: BTreeMap::new(), link_templates: BTreeMap::new(),
entities: &DEFAULT_ORG_ENTITIES, entities: &DEFAULT_ORG_ENTITIES,
element_parsed_keywords: &DEFAULT_ORG_ELEMENT_PARSED_KEYWORDS,
element_dual_keywords: &DEFAULT_ORG_ELEMENT_DUAL_KEYWORDS,
element_affiliated_keywords: &DEFAULT_ORG_ELEMENT_AFFILIATED_KEYWORDS,
element_keyword_translation_alist: &DEFAULT_ORG_ELEMENT_KEYWORD_TRANSLATION_ALIST,
} }
} }
} }

View File

@@ -22,7 +22,6 @@ type DynMatcher<'c> = dyn Matcher + 'c;
pub(crate) use context::Context; pub(crate) use context::Context;
pub(crate) use context::ContextElement; pub(crate) use context::ContextElement;
pub(crate) use context::ExitMatcherNode; pub(crate) use context::ExitMatcherNode;
pub(crate) use context::HasAffiliatedKeywordInner;
pub(crate) 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;

View File

@@ -11,8 +11,15 @@ macro_rules! children_iter {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.next.next().map(Into::<AstNode>::into) self.next.next().map(Into::<AstNode>::into)
} }
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.next.len();
(size, Some(size))
}
} }
impl<'r, 's> ExactSizeIterator for $itertype<'r, 's> {}
impl<'r, 's> IntoIterator for &'r $astnodetype { impl<'r, 's> IntoIterator for &'r $astnodetype {
type Item = AstNode<'r, 's>; type Item = AstNode<'r, 's>;
@@ -42,8 +49,14 @@ macro_rules! empty_iter {
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
None None
} }
fn size_hint(&self) -> (usize, Option<usize>) {
(0, Some(0))
}
} }
impl<'r, 's> ExactSizeIterator for $itertype<'r, 's> {}
impl<'r, 's> IntoIterator for &'r $astnodetype { impl<'r, 's> IntoIterator for &'r $astnodetype {
type Item = AstNode<'r, 's>; type Item = AstNode<'r, 's>;
@@ -79,8 +92,15 @@ $fieldname: $innertype,
.or_else(|| self.$fieldname.next().map(Into::<AstNode>::into)) .or_else(|| self.$fieldname.next().map(Into::<AstNode>::into))
),* ),*
} }
fn size_hint(&self) -> (usize, Option<usize>) {
let size = self.$firstfieldname.len()$( + self.$fieldname.len() ),*;
(size, Some(size))
}
} }
impl<'r, 's> ExactSizeIterator for $itertype<'r, 's> {}
impl<'r, 's> IntoIterator for &'r $astnodetype { impl<'r, 's> IntoIterator for &'r $astnodetype {
type Item = AstNode<'r, 's>; type Item = AstNode<'r, 's>;

View File

@@ -1,6 +1,7 @@
#![feature(exit_status_error)] #![feature(exit_status_error)]
#![feature(trait_alias)] #![feature(trait_alias)]
#![feature(path_file_prefix)] #![feature(path_file_prefix)]
#![feature(is_sorted)]
// TODO: #![warn(missing_docs)] // TODO: #![warn(missing_docs)]
#[cfg(feature = "compare")] #[cfg(feature = "compare")]

View File

@@ -0,0 +1,160 @@
use std::collections::BTreeMap;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_until;
use nom::character::complete::anychar;
use nom::combinator::all_consuming;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::map_parser;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::object_parser::standard_set_object;
use super::util::confine_context;
use crate::context::parser_with_context;
use crate::context::Context;
use crate::context::ContextElement;
use crate::context::GlobalSettings;
use crate::context::List;
use crate::types::AffiliatedKeywordValue;
use crate::types::AffiliatedKeywords;
use crate::types::Keyword;
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
global_settings: &'g GlobalSettings<'g, 's>,
input: AK,
) -> AffiliatedKeywords<'s>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let mut ret = BTreeMap::new();
for kw in input {
let translated_name = translate_name(global_settings, kw.key);
if is_single_string_keyword(global_settings, translated_name.as_str()) {
ret.insert(
translated_name,
AffiliatedKeywordValue::SingleString(kw.value),
);
} else if is_list_of_single_string_keyword(global_settings, translated_name.as_str()) {
let list_of_strings = ret
.entry(translated_name)
.or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
match list_of_strings {
AffiliatedKeywordValue::ListOfStrings(list_of_strings)
if list_of_strings.is_empty() =>
{
list_of_strings.push(kw.value);
}
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.clear();
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
} else if is_list_of_objects_keyword(global_settings, translated_name.as_str()) {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
let (_remaining, optional_objects) = opt(all_consuming(map(
tuple((
take_until("["),
tag("["),
map_parser(
recognize(many_till(anychar, peek(tuple((tag("]"), eof))))),
confine_context(|i| {
all_consuming(many0(parser_with_context!(standard_set_object)(
&initial_context,
)))(i)
}),
),
tag("]"),
eof,
)),
|(_, _, objects, _, _)| objects,
)))(kw.key.into())
.expect("Object parser should always succeed.");
// TODO: This should be omitting footnote references
let (_remaining, objects) = all_consuming(many0(parser_with_context!(
standard_set_object
)(&initial_context)))(kw.value.into())
.expect("Object parser should always succeed.");
let list_of_lists = ret.entry(translated_name).or_insert_with(|| {
AffiliatedKeywordValue::ListOfListsOfObjects(Vec::with_capacity(1))
});
match list_of_lists {
AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => {
list_of_lists.push((optional_objects, objects));
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
} else {
let list_of_strings = ret
.entry(translated_name)
.or_insert_with(|| AffiliatedKeywordValue::ListOfStrings(Vec::with_capacity(1)));
match list_of_strings {
AffiliatedKeywordValue::ListOfStrings(list_of_strings) => {
list_of_strings.push(kw.value);
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
}
}
AffiliatedKeywords { keywords: ret }
}
fn translate_name<'g, 's>(global_settings: &'g GlobalSettings<'g, 's>, name: &'s str) -> String {
let name_until_optval = name
.split_once("[")
.map(|(before, _after)| before)
.unwrap_or(name);
for (src, dst) in global_settings.element_keyword_translation_alist {
if name_until_optval.eq_ignore_ascii_case(src) {
return dst.to_lowercase();
}
}
name_until_optval.to_lowercase()
}
fn is_single_string_keyword<'g, 's>(
_global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> bool {
// TODO: Is this defined by an elisp variable?
for single_string_name in ["plot", "name"] {
if name.eq_ignore_ascii_case(single_string_name) {
return true;
}
}
false
}
fn is_list_of_single_string_keyword<'g, 's>(
_global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> bool {
// TODO: Is this defined by an elisp variable?
for single_string_name in ["results"] {
if name.eq_ignore_ascii_case(single_string_name) {
return true;
}
}
false
}
fn is_list_of_objects_keyword<'g, 's>(
global_settings: &'g GlobalSettings<'g, 's>,
name: &'s str,
) -> bool {
for parsed_keyword in global_settings.element_parsed_keywords {
if name.eq_ignore_ascii_case(parsed_keyword) {
return true;
}
}
false
}

View File

@@ -9,14 +9,12 @@ use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use nom::InputTake; use nom::InputTake;
use super::keyword::affiliated_keyword; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::start_of_line; use super::util::start_of_line;
use super::OrgSource; use super::OrgSource;
@@ -28,17 +26,21 @@ use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::org_line_ending; use crate::parser::util::org_line_ending;
use crate::types::BabelCall; use crate::types::BabelCall;
use crate::types::Keyword;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn babel_call<'b, 'g, 'r, 's>( pub(crate) fn babel_call<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'s>,
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCall<'s>> { ) -> Res<OrgSource<'s>, BabelCall<'s>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?; let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
@@ -50,7 +52,10 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>(
remaining, remaining,
BabelCall { BabelCall {
source: Into::<&str>::into(source), source: Into::<&str>::into(source),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(line_break.take(0)), value: Into::<&str>::into(line_break.take(0)),
call: None, call: None,
inside_header: None, inside_header: None,
@@ -73,7 +78,10 @@ pub(crate) fn babel_call<'b, 'g, 'r, 's>(
remaining, remaining,
BabelCall { BabelCall {
source: Into::<&str>::into(source), source: Into::<&str>::into(source),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(value).trim_end(), value: Into::<&str>::into(value).trim_end(),
call: call.map(Into::<&str>::into), call: call.map(Into::<&str>::into),
inside_header: inside_header.map(Into::<&str>::into), inside_header: inside_header.map(Into::<&str>::into),

View File

@@ -4,9 +4,9 @@ use nom::combinator::recognize;
use nom::multi::many0; use nom::multi::many0;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending; use super::util::org_line_ending;
use crate::context::RefContext; use crate::context::RefContext;
@@ -14,16 +14,21 @@ use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::DiarySexp; use crate::types::DiarySexp;
use crate::types::Keyword;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn diary_sexp<'b, 'g, 'r, 's>( pub(crate) fn diary_sexp<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?; let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
let (remaining, _eol) = org_line_ending(remaining)?; let (remaining, _eol) = org_line_ending(remaining)?;
@@ -35,7 +40,10 @@ pub(crate) fn diary_sexp<'b, 'g, 'r, 's>(
remaining, remaining,
DiarySexp { DiarySexp {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(value), value: Into::<&str>::into(value),
}, },
)) ))

View File

@@ -7,13 +7,11 @@ use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::affiliated_keyword; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_name;
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;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -32,23 +30,28 @@ use crate::parser::util::start_of_line;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS; use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::Drawer; use crate::types::Drawer;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::SetSource; use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn drawer<'b, 'g, 'r, 's>( pub(crate) fn drawer<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
if immediate_in_section(context, "drawer") { if immediate_in_section(context, "drawer") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
} }
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?; let (remaining, _leading_whitespace) = space0(remaining)?;
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple(( let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((
@@ -100,7 +103,10 @@ pub(crate) fn drawer<'b, 'g, 'r, 's>(
remaining, remaining,
Drawer { Drawer {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
drawer_name: drawer_name.into(), drawer_name: drawer_name.into(),
children, children,
}, },

View File

@@ -17,9 +17,8 @@ use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::affiliated_keyword; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_name;
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;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -37,23 +36,28 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::DynamicBlock; use crate::types::DynamicBlock;
use crate::types::Element; use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
use crate::types::SetSource; use crate::types::SetSource;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn dynamic_block<'b, 'g, 'r, 's>( pub(crate) fn dynamic_block<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
if immediate_in_section(context, "dynamic block") { if immediate_in_section(context, "dynamic block") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
} }
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?; let (remaining, _leading_whitespace) = space0(remaining)?;
@@ -107,7 +111,10 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
remaining, remaining,
DynamicBlock { DynamicBlock {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
block_name: name.into(), block_name: name.into(),
parameters: parameters.map(|val| val.into()), parameters: parameters.map(|val| val.into()),
children, children,

View File

@@ -1,10 +1,5 @@
use nom::branch::alt; use nom::branch::alt;
use nom::combinator::map; use nom::multi::many0;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::sequence::tuple;
#[cfg(feature = "tracing")]
use tracing::span;
use super::babel_call::babel_call; use super::babel_call::babel_call;
use super::clock::clock; use super::clock::clock;
@@ -21,7 +16,6 @@ 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::affiliated_keyword_as_regular_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;
@@ -39,6 +33,8 @@ 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::macros::ak_element;
use crate::parser::macros::element;
use crate::parser::table::org_mode_table; use crate::parser::table::org_mode_table;
use crate::types::Element; use crate::types::Element;
@@ -60,117 +56,204 @@ fn _element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>> {
let plain_list_matcher = parser_with_context!(plain_list)(context); let (post_affiliated_keywords_input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let greater_block_matcher = parser_with_context!(greater_block)(context);
let dynamic_block_matcher = parser_with_context!(dynamic_block)(context);
let footnote_definition_matcher = parser_with_context!(footnote_definition)(context);
let comment_matcher = parser_with_context!(comment)(context);
let drawer_matcher = parser_with_context!(drawer)(context);
let table_matcher = parser_with_context!(org_mode_table)(context);
let verse_block_matcher = parser_with_context!(verse_block)(context);
let comment_block_matcher = parser_with_context!(comment_block)(context);
let example_block_matcher = parser_with_context!(example_block)(context);
let export_block_matcher = parser_with_context!(export_block)(context);
let src_block_matcher = parser_with_context!(src_block)(context);
let clock_matcher = parser_with_context!(clock)(context);
let diary_sexp_matcher = parser_with_context!(diary_sexp)(context);
let fixed_width_area_matcher = parser_with_context!(fixed_width_area)(context);
let horizontal_rule_matcher = parser_with_context!(horizontal_rule)(context);
let keyword_matcher = parser_with_context!(keyword)(context);
let babel_keyword_matcher = parser_with_context!(babel_call)(context);
let paragraph_matcher = parser_with_context!(paragraph)(context);
let latex_environment_matcher = parser_with_context!(latex_environment)(context);
let (mut remaining, mut maybe_element) = { let mut affiliated_keywords = affiliated_keywords.into_iter();
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Main element block");
#[cfg(feature = "tracing")]
let _enter = span.enter();
opt(alt(( ak_element!(
map(plain_list_matcher, Element::PlainList), plain_list,
greater_block_matcher, &mut affiliated_keywords,
map(dynamic_block_matcher, Element::DynamicBlock), post_affiliated_keywords_input,
map(footnote_definition_matcher, Element::FootnoteDefinition), context,
map(comment_matcher, Element::Comment), input,
map(drawer_matcher, Element::Drawer), Element::PlainList
map(table_matcher, Element::Table), );
map(verse_block_matcher, Element::VerseBlock),
map(comment_block_matcher, Element::CommentBlock),
map(example_block_matcher, Element::ExampleBlock),
map(export_block_matcher, Element::ExportBlock),
map(src_block_matcher, Element::SrcBlock),
map(clock_matcher, Element::Clock),
map(diary_sexp_matcher, Element::DiarySexp),
map(fixed_width_area_matcher, Element::FixedWidthArea),
map(horizontal_rule_matcher, Element::HorizontalRule),
map(latex_environment_matcher, Element::LatexEnvironment),
map(babel_keyword_matcher, Element::BabelCall),
map(keyword_matcher, Element::Keyword),
)))(input)?
};
if maybe_element.is_none() && can_be_paragraph { ak_element!(
#[cfg(feature = "tracing")] greater_block,
let span = span!(tracing::Level::DEBUG, "Paragraph with affiliated keyword."); &mut affiliated_keywords,
#[cfg(feature = "tracing")] post_affiliated_keywords_input,
let _enter = span.enter(); context,
input
);
let (remain, paragraph_with_affiliated_keyword) = opt(map( ak_element!(
tuple(( dynamic_block,
peek(affiliated_keyword), &mut affiliated_keywords,
map(paragraph_matcher, Element::Paragraph), post_affiliated_keywords_input,
)), context,
|(_, paragraph)| paragraph, input,
))(remaining)?; Element::DynamicBlock
if paragraph_with_affiliated_keyword.is_some() { );
remaining = remain;
maybe_element = paragraph_with_affiliated_keyword;
}
}
if maybe_element.is_none() { ak_element!(
#[cfg(feature = "tracing")] footnote_definition,
let span = span!( &mut affiliated_keywords,
tracing::Level::DEBUG, post_affiliated_keywords_input,
"Affiliated keyword as regular keyword." context,
input,
Element::FootnoteDefinition
);
element!(comment, context, input, Element::Comment);
ak_element!(
drawer,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::Drawer
);
ak_element!(
org_mode_table,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::Table
);
ak_element!(
verse_block,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::VerseBlock
);
ak_element!(
comment_block,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::CommentBlock
);
ak_element!(
example_block,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::ExampleBlock
);
ak_element!(
export_block,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::ExportBlock
);
ak_element!(
src_block,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::SrcBlock
);
element!(clock, context, input, Element::Clock);
ak_element!(
diary_sexp,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::DiarySexp
);
ak_element!(
fixed_width_area,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::FixedWidthArea
);
ak_element!(
horizontal_rule,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::HorizontalRule
);
ak_element!(
latex_environment,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::LatexEnvironment
);
ak_element!(
babel_call,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::BabelCall
);
// Keyword with affiliated keywords
ak_element!(
keyword,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::Keyword
);
if can_be_paragraph {
// Paragraph with affiliated keyword
ak_element!(
paragraph,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::Paragraph
); );
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, kw) = opt(map(
parser_with_context!(affiliated_keyword_as_regular_keyword)(context),
Element::Keyword,
))(remaining)?;
if kw.is_some() {
maybe_element = kw;
remaining = remain;
}
} }
if maybe_element.is_none() && can_be_paragraph { // Keyword without affiliated keywords
#[cfg(feature = "tracing")] ak_element!(
let span = span!( keyword,
tracing::Level::DEBUG, std::iter::empty(),
"Paragraph without affiliated keyword." input,
context,
input,
Element::Keyword
);
if can_be_paragraph {
// Paragraph without affiliated keyword
ak_element!(
paragraph,
std::iter::empty(),
input,
context,
input,
Element::Paragraph
); );
#[cfg(feature = "tracing")]
let _enter = span.enter();
let (remain, paragraph_without_affiliated_keyword) =
map(paragraph_matcher, Element::Paragraph)(remaining)?;
remaining = remain;
maybe_element = Some(paragraph_without_affiliated_keyword);
} }
if maybe_element.is_none() { Err(nom::Err::Error(CustomError::MyError(MyError(
return Err(nom::Err::Error(CustomError::MyError(MyError( "No element.",
"No element.", ))))
))));
}
let element = maybe_element.expect("The above if-statement ensures this is Some().");
Ok((remaining, element))
} }
pub(crate) const fn detect_element( pub(crate) const fn detect_element(

View File

@@ -9,9 +9,9 @@ use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending; use super::util::org_line_ending;
use crate::context::parser_with_context; use crate::context::parser_with_context;
@@ -21,16 +21,21 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::FixedWidthArea; use crate::types::FixedWidthArea;
use crate::types::Keyword;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>( pub(crate) fn fixed_width_area<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context); let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context); let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?; let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;
@@ -57,7 +62,10 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
remaining, remaining,
FixedWidthArea { FixedWidthArea {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value, value,
}, },
)) ))

View File

@@ -11,9 +11,9 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_name;
use super::util::include_input; use super::util::include_input;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::WORD_CONSTITUENT_CHARACTERS; use super::util::WORD_CONSTITUENT_CHARACTERS;
@@ -33,21 +33,26 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::maybe_consume_trailing_whitespace; use crate::parser::util::maybe_consume_trailing_whitespace;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::FootnoteDefinition; use crate::types::FootnoteDefinition;
use crate::types::Keyword;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn footnote_definition<'b, 'g, 'r, 's>( pub(crate) fn footnote_definition<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
if immediate_in_section(context, "footnote definition") { if immediate_in_section(context, "footnote definition") {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(), "Cannot nest objects of the same element".into(),
)))); ))));
} }
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?; start_of_line(remaining)?;
// Cannot be indented. // Cannot be indented.
let (remaining, (_, lbl, _, _, _)) = tuple(( let (remaining, (_, lbl, _, _, _)) = tuple((
@@ -94,7 +99,10 @@ pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
remaining, remaining,
FootnoteDefinition { FootnoteDefinition {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
label: lbl.into(), label: lbl.into(),
children: children.into_iter().map(|(_, item)| item).collect(), children: children.into_iter().map(|(_, item)| item).collect(),
}, },

View File

@@ -17,9 +17,8 @@ use nom::multi::many_till;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::affiliated_keyword; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_name;
use super::util::in_section; use super::util::in_section;
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;
@@ -46,14 +45,17 @@ use crate::types::SpecialBlock;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn greater_block<'b, 'g, 'r, 's>( pub(crate) fn greater_block<'b, 'g, 'r, 's, AK>(
context: RefContext<'b, 'g, 'r, 's>, affiliated_keywords: AK,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> { context: RefContext<'b, 'g, 'r, 's>,
let pre_affiliated_keywords_input = input; pre_affiliated_keywords_input: OrgSource<'s>,
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?; ) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'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)) = tuple(( let (remaining, (_begin, name)) = tuple((
@@ -71,19 +73,19 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
context, context,
remaining, remaining,
pre_affiliated_keywords_input, pre_affiliated_keywords_input,
&affiliated_keywords, affiliated_keywords,
)?, )?,
"quote" => quote_block( "quote" => quote_block(
context, context,
remaining, remaining,
pre_affiliated_keywords_input, pre_affiliated_keywords_input,
&affiliated_keywords, affiliated_keywords,
)?, )?,
_ => special_block(name)( _ => special_block(name)(
context, context,
remaining, remaining,
pre_affiliated_keywords_input, pre_affiliated_keywords_input,
&affiliated_keywords, affiliated_keywords,
)?, )?,
}; };
Ok((remaining, element)) Ok((remaining, element))
@@ -91,14 +93,17 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
fn center_block<'b, 'g, 'r, 's>( fn center_block<'b, 'g, 'r, 's, AK>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>, pre_affiliated_keywords_input: OrgSource<'s>,
affiliated_keywords: &Vec<Keyword<'s>>, affiliated_keywords: AK,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body( let (remaining, (source, children)) = greater_block_body(
context, context,
input, input,
@@ -110,7 +115,10 @@ fn center_block<'b, 'g, 'r, 's>(
remaining, remaining,
Element::CenterBlock(CenterBlock { Element::CenterBlock(CenterBlock {
source, source,
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children, children,
}), }),
)) ))
@@ -118,14 +126,17 @@ fn center_block<'b, 'g, 'r, 's>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
fn quote_block<'b, 'g, 'r, 's>( fn quote_block<'b, 'g, 'r, 's, AK>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>, pre_affiliated_keywords_input: OrgSource<'s>,
affiliated_keywords: &Vec<Keyword<'s>>, affiliated_keywords: AK,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body( let (remaining, (source, children)) = greater_block_body(
context, context,
input, input,
@@ -137,21 +148,27 @@ fn quote_block<'b, 'g, 'r, 's>(
remaining, remaining,
Element::QuoteBlock(QuoteBlock { Element::QuoteBlock(QuoteBlock {
source, source,
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children, children,
}), }),
)) ))
} }
fn special_block<'s>( fn special_block<'s, AK>(
name: &'s str, name: &'s str,
) -> impl for<'b, 'g, 'r> Fn( ) -> impl for<'b, 'g, 'r> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>, OrgSource<'s>,
OrgSource<'s>, OrgSource<'s>,
&Vec<Keyword<'s>>, AK,
) -> Res<OrgSource<'s>, Element<'s>> ) -> Res<OrgSource<'s>, Element<'s>>
+ 's { + 's
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let context_name = format!("special block {}", name); let context_name = format!("special block {}", name);
move |context, input, pre_affiliated_keywords_input, affiliated_keywords| { move |context, input, pre_affiliated_keywords_input, affiliated_keywords| {
_special_block( _special_block(
@@ -167,16 +184,19 @@ fn special_block<'s>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
fn _special_block<'c, 'b, 'g, 'r, 's>( fn _special_block<'c, 'b, 'g, 'r, 's, AK>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>, pre_affiliated_keywords_input: OrgSource<'s>,
name: &'s str, name: &'s str,
context_name: &'c str, context_name: &'c str,
affiliated_keywords: &Vec<Keyword<'s>>, affiliated_keywords: AK,
) -> Res<OrgSource<'s>, Element<'s>> { ) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?; let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) = greater_block_body( let (remaining, (source, children)) = greater_block_body(
context, context,
@@ -189,7 +209,10 @@ fn _special_block<'c, 'b, 'g, 'r, 's>(
remaining, remaining,
Element::SpecialBlock(SpecialBlock { Element::SpecialBlock(SpecialBlock {
source, source,
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children, children,
block_type: name, block_type: name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)), parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),

View File

@@ -5,29 +5,32 @@ use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1_count; use nom::multi::many1_count;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::affiliated_keyword; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; 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::start_of_line; use crate::parser::util::start_of_line;
use crate::types::HorizontalRule; use crate::types::HorizontalRule;
use crate::types::Keyword;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>( pub(crate) fn horizontal_rule<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _rule) = recognize(tuple(( let (remaining, _rule) = recognize(tuple((
space0, space0,
@@ -42,7 +45,10 @@ pub(crate) fn horizontal_rule<'b, 'g, 'r, 's>(
remaining, remaining,
HorizontalRule { HorizontalRule {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
}, },
)) ))
} }

View File

@@ -13,14 +13,13 @@ 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::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::BracketDepth; use super::org_source::BracketDepth;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::Matcher; use crate::context::Matcher;
use crate::context::RefContext; use crate::context::RefContext;
@@ -28,6 +27,7 @@ use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::AffiliatedKeywords;
use crate::types::Keyword; use crate::types::Keyword;
const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [ const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
@@ -64,7 +64,7 @@ fn _filtered_keyword<'s, F: Matcher>(
remaining, remaining,
Keyword { Keyword {
source: consumed_input.into(), source: consumed_input.into(),
name: None, // To be populated by the caller if this keyword is in a context to support affiliated keywords. affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(), key: parsed_key.into(),
value: "".into(), value: "".into(),
}, },
@@ -82,7 +82,7 @@ fn _filtered_keyword<'s, F: Matcher>(
remaining, remaining,
Keyword { Keyword {
source: consumed_input.into(), source: consumed_input.into(),
name: None, // To be populated by the caller if this keyword is in a context to support affiliated keywords. affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(), key: parsed_key.into(),
value: parsed_value.into(), value: parsed_value.into(),
}, },
@@ -91,34 +91,23 @@ fn _filtered_keyword<'s, F: Matcher>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn keyword<'b, 'g, 'r, 's>( pub(crate) fn keyword<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?; let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
kw.name = get_name(&affiliated_keywords); kw.affiliated_keywords =
kw.source = Into::<&str>::into(source); parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords);
Ok((remaining, kw))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn affiliated_keyword_as_regular_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
let (remaining, mut kw) = affiliated_keyword(input)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
kw.source = Into::<&str>::into(source); kw.source = Into::<&str>::into(source);
Ok((remaining, kw)) Ok((remaining, kw))
} }

View File

@@ -8,14 +8,12 @@ use nom::character::complete::space0;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::keyword::affiliated_keyword; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::get_name;
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;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -26,17 +24,22 @@ use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::Keyword;
use crate::types::LatexEnvironment; use crate::types::LatexEnvironment;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn latex_environment<'b, 'g, 'r, 's>( pub(crate) fn latex_environment<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let value_start = remaining; let value_start = remaining;
start_of_line(remaining)?; start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?; let (remaining, _leading_whitespace) = space0(remaining)?;
@@ -67,7 +70,10 @@ pub(crate) fn latex_environment<'b, 'g, 'r, 's>(
remaining, remaining,
LatexEnvironment { LatexEnvironment {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: value.into(), value: value.into(),
}, },
)) ))

View File

@@ -13,14 +13,12 @@ use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use nom::InputTake; use nom::InputTake;
use super::keyword::affiliated_keyword; use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::get_name;
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;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -41,6 +39,7 @@ use crate::types::CharOffsetInLine;
use crate::types::CommentBlock; use crate::types::CommentBlock;
use crate::types::ExampleBlock; use crate::types::ExampleBlock;
use crate::types::ExportBlock; use crate::types::ExportBlock;
use crate::types::Keyword;
use crate::types::LineNumber; use crate::types::LineNumber;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainText; use crate::types::PlainText;
@@ -51,13 +50,17 @@ use crate::types::VerseBlock;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn verse_block<'b, 'g, 'r, 's>( pub(crate) fn verse_block<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("verse")(context, remaining)?; let (remaining, _) = lesser_block_begin("verse")(context, remaining)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
@@ -103,7 +106,10 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
remaining, remaining,
VerseBlock { VerseBlock {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
data: parameters.map(|parameters| Into::<&str>::into(parameters)), data: parameters.map(|parameters| Into::<&str>::into(parameters)),
children, children,
}, },
@@ -112,13 +118,17 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn comment_block<'b, 'g, 'r, 's>( pub(crate) fn comment_block<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("comment")(context, remaining)?; let (remaining, _) = lesser_block_begin("comment")(context, remaining)?;
let (remaining, _parameters) = opt(tuple((space1, data)))(remaining)?; let (remaining, _parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?; let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
@@ -145,7 +155,10 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
remaining, remaining,
CommentBlock { CommentBlock {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
contents: contents.into(), contents: contents.into(),
}, },
)) ))
@@ -153,13 +166,17 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn example_block<'b, 'g, 'r, 's>( pub(crate) fn example_block<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("example")(context, remaining)?; let (remaining, _) = lesser_block_begin("example")(context, remaining)?;
let (remaining, parameters) = opt(alt(( let (remaining, parameters) = opt(alt((
map(tuple((space1, example_switches)), |(_, switches)| switches), map(tuple((space1, example_switches)), |(_, switches)| switches),
@@ -213,7 +230,10 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
remaining, remaining,
ExampleBlock { ExampleBlock {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
switches, switches,
number_lines, number_lines,
preserve_indent, preserve_indent,
@@ -227,13 +247,17 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn export_block<'b, 'g, 'r, 's>( pub(crate) fn export_block<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("export")(context, remaining)?; let (remaining, _) = lesser_block_begin("export")(context, remaining)?;
// 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, export_type) = opt(map( let (remaining, export_type) = opt(map(
@@ -266,7 +290,10 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
remaining, remaining,
ExportBlock { ExportBlock {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
export_type: export_type.map(Into::<&str>::into), export_type: export_type.map(Into::<&str>::into),
data: parameters.map(|parameters| Into::<&str>::into(parameters)), data: parameters.map(|parameters| Into::<&str>::into(parameters)),
contents, contents,
@@ -276,13 +303,17 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn src_block<'b, 'g, 'r, 's>( pub(crate) fn src_block<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("src")(context, remaining)?; let (remaining, _) = lesser_block_begin("src")(context, remaining)?;
// 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, language) = let (remaining, language) =
@@ -332,7 +363,10 @@ pub(crate) fn src_block<'b, 'g, 'r, 's>(
remaining, remaining,
SrcBlock { SrcBlock {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
language: language.map(Into::<&str>::into), language: language.map(Into::<&str>::into),
switches, switches,
parameters: parameters.map(Into::<&str>::into), parameters: parameters.map(Into::<&str>::into),

40
src/parser/macros.rs Normal file
View File

@@ -0,0 +1,40 @@
/// Parse an element that has affiliated keywords.
macro_rules! ak_element {
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr, $wrapper: expr) => {
if let Ok((remaining, ele)) = $parser(
$affiliated_keywords,
$post_affiliated_keywords_input,
$context,
$input,
) {
return Ok((remaining, $wrapper(ele)));
}
};
($parser:ident, $affiliated_keywords:expr, $post_affiliated_keywords_input: expr, $context: expr, $input: expr) => {
if let Ok((remaining, ele)) = $parser(
$affiliated_keywords,
$post_affiliated_keywords_input,
$context,
$input,
) {
return Ok((remaining, ele));
}
};
}
pub(crate) use ak_element;
macro_rules! element {
($parser:ident, $context: expr, $input: expr, $wrapper: expr) => {
if let Ok((remaining, ele)) = $parser($context, $input) {
return Ok((remaining, $wrapper(ele)));
}
};
($parser:ident, $context: expr, $input: expr) => {
if let Ok((remaining, ele)) = $parser($context, $input) {
return Ok((remaining, ele));
}
};
}
pub(crate) use element;

View File

@@ -1,3 +1,4 @@
mod affiliated_keyword;
mod angle_link; mod angle_link;
mod babel_call; mod babel_call;
mod citation; mod citation;
@@ -26,6 +27,7 @@ mod latex_environment;
mod latex_fragment; mod latex_fragment;
mod lesser_block; mod lesser_block;
mod line_break; mod line_break;
mod macros;
mod object_parser; mod object_parser;
mod org_macro; mod org_macro;
mod org_source; mod org_source;

View File

@@ -2,54 +2,46 @@ use nom::branch::alt;
use nom::combinator::eof; use nom::combinator::eof;
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::affiliated_keyword::parse_affiliated_keywords;
use super::element_parser::detect_element; use super::element_parser::detect_element;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::blank_line; use super::util::blank_line;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::get_has_affiliated_keyword;
use super::util::get_name;
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;
use crate::context::ContextElement; use crate::context::ContextElement;
use crate::context::ExitClass; use crate::context::ExitClass;
use crate::context::ExitMatcherNode; use crate::context::ExitMatcherNode;
use crate::context::HasAffiliatedKeywordInner;
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::object_parser::standard_set_object; use crate::parser::object_parser::standard_set_object;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::Keyword;
use crate::types::Paragraph; use crate::types::Paragraph;
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn paragraph<'b, 'g, 'r, 's>( pub(crate) fn paragraph<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
let contexts = [ AK: IntoIterator<Item = Keyword<'s>>,
ContextElement::HasAffiliatedKeyword(HasAffiliatedKeywordInner { {
start_after_affiliated_keywords: remaining, let contexts = [ContextElement::ExitMatcherNode(ExitMatcherNode {
keywords: &affiliated_keywords, class: ExitClass::Gamma,
}), exit_matcher: &paragraph_end,
ContextElement::ExitMatcherNode(ExitMatcherNode { })];
class: ExitClass::Gamma,
exit_matcher: &paragraph_end,
}),
];
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 standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context); let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -68,7 +60,10 @@ pub(crate) fn paragraph<'b, 'g, 'r, 's>(
remaining, remaining,
Paragraph { Paragraph {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children, children,
}, },
)) ))
@@ -86,15 +81,6 @@ fn paragraph_end<'b, 'g, 'r, 's>(
if regular_end.is_ok() { if regular_end.is_ok() {
return regular_end; return regular_end;
} }
match get_has_affiliated_keyword(context) {
Some(start_post_affiliated_keywords) if input == start_post_affiliated_keywords => {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No exit due to affiliated keywords.",
))));
}
_ => {}
}
// Check to see if input is the start of a HasAffiliatedKeyword
alt(( alt((
recognize(parser_with_context!(detect_element(false))(context)), recognize(parser_with_context!(detect_element(false))(context)),
eof, eof,

View File

@@ -18,11 +18,11 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::element_parser::element; use super::element_parser::element;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
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::get_name;
use super::util::include_input; use super::util::include_input;
use super::util::indentation_level; use super::util::indentation_level;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
@@ -43,6 +43,7 @@ use crate::parser::util::org_space;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::CheckboxType; use crate::types::CheckboxType;
use crate::types::IndentationLevel; use crate::types::IndentationLevel;
use crate::types::Keyword;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainList; use crate::types::PlainList;
use crate::types::PlainListItem; use crate::types::PlainListItem;
@@ -81,14 +82,17 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn plain_list<'b, 'g, 'r, 's>( pub(crate) fn plain_list<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
let contexts = [ let contexts = [
ContextElement::Context("plain list"), ContextElement::Context("plain list"),
ContextElement::ConsumeTrailingWhitespace(true), ContextElement::ConsumeTrailingWhitespace(true),
@@ -163,7 +167,10 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
remaining, remaining,
PlainList { PlainList {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
list_type: first_item_list_type.expect("Plain lists require at least one element."), list_type: first_item_list_type.expect("Plain lists require at least one element."),
children: children.into_iter().map(|(_start, item)| item).collect(), children: children.into_iter().map(|(_start, item)| item).collect(),
}, },
@@ -577,8 +584,8 @@ 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_list_matcher = parser_with_context!(plain_list)(&initial_context); let (remaining, result) =
let (remaining, result) = plain_list_matcher(input).unwrap(); plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1."); assert_eq!(result.get_standard_properties().get_source(), "1.");
} }
@@ -589,8 +596,8 @@ 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_list_matcher = parser_with_context!(plain_list)(&initial_context); let (remaining, result) =
let (remaining, result) = plain_list_matcher(input).unwrap(); plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), ""); assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo"); assert_eq!(result.get_standard_properties().get_source(), "1. foo");
} }
@@ -602,8 +609,7 @@ 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_list_matcher = parser_with_context!(plain_list)(&initial_context); let result = plain_list(std::iter::empty(), input, &initial_context, input);
let result = plain_list_matcher(input);
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -614,8 +620,7 @@ 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_list_matcher = parser_with_context!(plain_list)(&initial_context); let result = plain_list(std::iter::empty(), input, &initial_context, input);
let result = plain_list_matcher(input);
assert!(result.is_ok()); assert!(result.is_ok());
} }

View File

@@ -13,12 +13,12 @@ use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword; use super::keyword::affiliated_keyword;
use super::keyword::table_formula_keyword; use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object; use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::get_name;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending; use super::util::org_line_ending;
use crate::context::parser_with_context; use crate::context::parser_with_context;
@@ -29,6 +29,7 @@ use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::Keyword;
use crate::types::Table; use crate::types::Table;
use crate::types::TableCell; use crate::types::TableCell;
use crate::types::TableRow; use crate::types::TableRow;
@@ -38,13 +39,17 @@ use crate::types::TableRow;
/// This is not the table.el style. /// This is not the table.el style.
#[cfg_attr( #[cfg_attr(
feature = "tracing", feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context)) tracing::instrument(ret, level = "debug", skip(context, affiliated_keywords))
)] )]
pub(crate) fn org_mode_table<'b, 'g, 'r, 's>( pub(crate) fn org_mode_table<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
remaining: OrgSource<'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>>
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?; where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?; start_of_line(remaining)?;
peek(tuple((space0, tag("|"))))(remaining)?; peek(tuple((space0, tag("|"))))(remaining)?;
@@ -77,7 +82,10 @@ pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
remaining, remaining,
Table { Table {
source: source.into(), source: source.into(),
name: get_name(&affiliated_keywords), affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
formulas, formulas,
children, children,
}, },

View File

@@ -23,7 +23,6 @@ use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::IndentationLevel; use crate::types::IndentationLevel;
use crate::types::Keyword;
pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str = pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -272,39 +271,6 @@ pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
Ok((remaining, (indentation_level, leading_whitespace))) Ok((remaining, (indentation_level, leading_whitespace)))
} }
pub(crate) fn get_name<'s>(affiliated_keywords: &Vec<Keyword<'s>>) -> Option<&'s str> {
let name_keyword = affiliated_keywords
.iter()
.filter(|kw| {
kw.key.eq_ignore_ascii_case("name")
|| kw.key.eq_ignore_ascii_case("source")
|| kw.key.eq_ignore_ascii_case("tblname")
|| kw.key.eq_ignore_ascii_case("resname")
|| kw.key.eq_ignore_ascii_case("srcname")
|| kw.key.eq_ignore_ascii_case("label")
})
.last();
name_keyword.map(|kw| kw.value)
}
pub(crate) fn get_has_affiliated_keyword<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
) -> Option<OrgSource<'s>> {
for context in context.iter() {
match context {
ContextElement::HasAffiliatedKeyword(inner) => {
if !inner.keywords.is_empty() {
return Some(inner.start_after_affiliated_keywords);
} else {
return None;
}
}
_ => {}
}
}
None
}
/// Reset the input OrgSource as if it was starting a fresh document. /// Reset the input OrgSource as if it was starting a fresh document.
/// ///
/// This is important for making start-of-document, end-of-document, and other context-dependent tests succeed. /// This is important for making start-of-document, end-of-document, and other context-dependent tests succeed.

View File

@@ -0,0 +1,33 @@
use std::collections::BTreeMap;
use super::Object;
#[derive(Debug)]
pub enum AffiliatedKeywordValue<'s> {
SingleString(&'s str),
ListOfStrings(Vec<&'s str>),
ListOfListsOfObjects(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
}
#[derive(Debug)]
pub struct AffiliatedKeyword<'s> {
pub name: &'s str,
pub value: AffiliatedKeywordValue<'s>,
}
#[derive(Debug)]
pub struct AffiliatedKeywords<'s> {
pub(crate) keywords: BTreeMap<String, AffiliatedKeywordValue<'s>>,
}
pub trait GetAffiliatedKeywords<'s> {
fn get_affiliated_keywords<'a>(&'a self) -> &'a AffiliatedKeywords<'s>;
}
impl<'s> Default for AffiliatedKeywords<'s> {
fn default() -> Self {
AffiliatedKeywords {
keywords: BTreeMap::new(),
}
}
}

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