64 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
42 changed files with 1153 additions and 545 deletions

View File

@@ -137,7 +137,7 @@ spec:
value: []
- name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-all
- name: run-image-tracing-compare
taskRef:
name: run-docker-image
workspaces:
@@ -152,6 +152,46 @@ spec:
value: ["--no-default-features", "--features", "tracing,compare"]
- name: docker-image
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:
- name: report-success
when:

View File

@@ -1,6 +1,6 @@
[package]
name = "organic"
version = "0.1.9"
version = "0.1.10"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021"
@@ -31,7 +31,14 @@ path = "src/lib.rs"
path = "src/bin_compare.rs"
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]
futures = { version = "0.3.28", optional = true }
nom = "7.1.1"
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
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-opentelemetry = { version = "0.20.0", optional = true }
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
walkdir = { version = "2.3.3", optional = true }
[build-dependencies]
walkdir = "2.3.3"
[features]
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"]
# 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 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
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.
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
- We aim to provide perfect parity with the emacs org-mode parser. In that regard, any document that parses differently between Emacs and Organic is considered a bug.
- The parser should be fast. We're not doing anything special, but since this is written in Rust and natively compiled we should be able to beat the existing parsers.
- The parser should have minimal dependencies. This should reduce effort w.r.t.: security audits, legal compliance, portability.
- The parser should be usable everywhere. In the interest of getting org-mode used in as many places as possible, this parser should be usable by everyone everywhere. This means:
- It must have a permissive license for use in proprietary code bases.
- 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 a C library for native linking to other code. This is more of a maybe-goal for the project.
### Project Non-Goals

View File

@@ -14,7 +14,7 @@ RUN make DESTDIR="/root/dist" install
FROM build AS build-org-mode
ARG ORG_VERSION=f32f5982a76217629dad8b8fd82d53212576aee6
ARG ORG_VERSION=abf5156096c06ee5aa05795c3dc5a065f76ada97
COPY --from=build-emacs /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.
@@ -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=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
COPY foreign_document_test_entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]

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,3 @@
1. foo
#+NAME: bar
2. baz

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,12 @@ mod init_tracing;
#[cfg(not(feature = "tracing"))]
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")]
@@ -22,7 +27,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
let result = rt.block_on(async {
init_telemetry()?;
let main_body_result = main_body();
let main_body_result = main_body().await;
shutdown_telemetry()?;
main_body_result
});
@@ -30,14 +35,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
#[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);
if args.is_empty() {
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 {
for arg in args {
run_compare_on_file(arg)?
if run_compare_on_file(arg).await? {
} else {
Err("Diff results do not match.")?;
}
}
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_with_settings;
pub fn run_anonymous_compare<P: AsRef<str>>(
pub async fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P,
) -> Result<(), Box<dyn std::error::Error>> {
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default())
) -> Result<bool, Box<dyn std::error::Error>> {
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>> {
run_compare_on_file_with_settings(org_path, &GlobalSettings::default())
pub async fn run_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(), false).await
}
pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
pub async fn silent_anonymous_compare<P: AsRef<str>>(
org_contents: P,
global_settings: &GlobalSettings,
) -> Result<(), Box<dyn std::error::Error>> {
) -> Result<bool, 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.
let org_contents = org_contents.as_ref().replace("\r\n", "\n");
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 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())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
if !silent {
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
}
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if !silent {
diff_result.print(org_contents)?;
}
if diff_result.is_bad() {
Err("Diff results do not match.")?;
} else {
return Ok(false);
} else if !silent {
println!(
"{color}Entire document passes.{reset}",
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,
global_settings: &GlobalSettings,
) -> Result<(), Box<dyn std::error::Error>> {
global_settings: &GlobalSettings<'g, 's>,
silent: bool,
) -> Result<bool, Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
print_versions()?;
if !silent {
print_versions().await?;
}
let parent_directory = org_path
.parent()
.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
};
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())?;
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
if !silent {
println!("{}\n\n\n", org_contents);
println!("{}", org_sexp);
println!("{:#?}", rust_parsed);
}
// We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
diff_result.print(org_contents)?;
if !silent {
diff_result.print(org_contents)?;
}
if diff_result.is_bad() {
Err("Diff results do not match.")?;
} else {
return Ok(false);
} else if !silent {
println!(
"{color}Entire document passes.{reset}",
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>> {
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
async fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("Using emacs version: {}", get_emacs_version().await?.trim());
eprintln!(
"Using org-mode version: {}",
get_org_mode_version().await?.trim()
);
Ok(())
}

View File

@@ -416,14 +416,15 @@ where
///
/// 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 that seems to always have 1 element.
/// - Inner list of the objects from each #+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<Vec<Object<'s>>>>,
RG: Fn(R) -> Option<&'b Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>>,
>(
source: &'s str,
emacs: &'b Token<'s>,
@@ -459,32 +460,76 @@ pub(crate) fn compare_property_list_of_list_of_list_of_ast_nodes<
(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_value) in value.iter().zip(rust_value.iter()) {
// Assert the middle list is a length of 1 because I've never seen it any other way.
let value = value.as_list()?;
if value.len() != 1 {
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!(
"{} mismatch (emacs != rust) {:?} != {:?}",
emacs_field, value, rust_value
"{} mandatory value length mismatch (emacs != rust) {} != {} | {:?}",
emacs_field,
mandatory_value.len(),
rust_value.len(),
rust_value
));
return Ok(ComparePropertiesResult::SelfChange(this_status, message));
}
// Drill past the middle list to the inner list.
let value = value
.first()
.expect("The above if-statement asserts this exists.");
let value = value.as_list()?;
// Compare inner lists
let mut child_status: Vec<DiffEntry<'b, 's>> = Vec::with_capacity(rust_value.len());
for (e, r) in value.iter().zip(rust_value) {
for (e, r) in mandatory_value.iter().zip(rust_value) {
child_status.push(compare_ast_node(source, e, r.into())?);
}
let diff_scope = artificial_owned_diff_scope(emacs_field, child_status)?;
return Ok(ComparePropertiesResult::DiffEntry(diff_scope));
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))
}
Ok(ComparePropertiesResult::NoChange)
}
pub(crate) fn compare_property_number_lines<

View File

@@ -10,3 +10,5 @@ pub use compare::run_anonymous_compare;
pub use compare::run_anonymous_compare_with_settings;
pub use compare::run_compare_on_file;
pub use compare::run_compare_on_file_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::process::Command;
use tokio::process::Command;
use crate::context::HeadlineLevelFilter;
use crate::settings::GlobalSettings;
@@ -25,9 +26,9 @@ fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
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,
global_settings: &GlobalSettings,
global_settings: &GlobalSettings<'g, 's>,
) -> Result<String, Box<dyn std::error::Error>>
where
C: AsRef<str>,
@@ -54,15 +55,24 @@ where
.arg("--batch")
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
out.status.exit_ok()?;
let out = cmd.output().await?;
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;
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,
global_settings: &GlobalSettings,
global_settings: &GlobalSettings<'g, 's>,
) -> Result<String, Box<dyn std::error::Error>>
where
P: AsRef<Path>,
@@ -97,8 +107,17 @@ where
.arg("--batch")
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
out.status.exit_ok()?;
let out = cmd.output().await?;
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;
Ok(String::from_utf8(org_sexp)?)
}
@@ -125,7 +144,7 @@ where
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
(message "%s" (version))
)"#;
@@ -138,12 +157,12 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;
let out = cmd.output().await?;
out.status.exit_ok()?;
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
(org-mode)
(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(elisp_script);
let out = cmd.output()?;
let out = cmd.output().await?;
out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?)
}

View File

@@ -12,7 +12,6 @@ use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::OrgSource;
use crate::types::Keyword;
#[derive(Debug)]
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.
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.
#[allow(dead_code)]
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> {
// TODO: Should this be "&'r DynContextMatcher<'c>" ?
pub(crate) exit_matcher: &'r DynContextMatcher<'r>,

View File

@@ -1,6 +1,12 @@
use std::fmt::Debug;
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 {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
}

View File

@@ -95,7 +95,9 @@ pub struct EntityDefinition<'a> {
impl<'g, 's> 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())));
debug_assert!(
DEFAULT_ORG_ENTITIES.is_sorted_by(|a, b| b.name.len().partial_cmp(&a.name.len()))
);
GlobalSettings {
radio_targets: Vec::new(),
file_access: &LocalFileAccessInterface {

View File

@@ -22,7 +22,6 @@ type DynMatcher<'c> = dyn Matcher + 'c;
pub(crate) use context::Context;
pub(crate) use context::ContextElement;
pub(crate) use context::ExitMatcherNode;
pub(crate) use context::HasAffiliatedKeywordInner;
pub(crate) use exiting::ExitClass;
pub use file_access_interface::FileAccessInterface;
pub use file_access_interface::LocalFileAccessInterface;

View File

@@ -1,9 +1,21 @@
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;
@@ -13,12 +25,15 @@ use crate::types::AffiliatedKeywordValue;
use crate::types::AffiliatedKeywords;
use crate::types::Keyword;
pub(crate) fn parse_affiliated_keywords<'g, 's>(
pub(crate) fn parse_affiliated_keywords<'g, 's, AK>(
global_settings: &'g GlobalSettings<'g, 's>,
input: Vec<Keyword<'s>>,
) -> AffiliatedKeywords<'s> {
input: AK,
) -> AffiliatedKeywords<'s>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let mut ret = BTreeMap::new();
for kw in input.into_iter() {
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(
@@ -45,6 +60,25 @@ pub(crate) fn parse_affiliated_keywords<'g, 's>(
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
@@ -55,7 +89,7 @@ pub(crate) fn parse_affiliated_keywords<'g, 's>(
});
match list_of_lists {
AffiliatedKeywordValue::ListOfListsOfObjects(list_of_lists) => {
list_of_lists.push(objects);
list_of_lists.push((optional_objects, objects));
}
_ => panic!("Invalid AffiliatedKeywordValue type."),
}
@@ -75,12 +109,16 @@ pub(crate) fn parse_affiliated_keywords<'g, 's>(
}
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.eq_ignore_ascii_case(src) {
if name_until_optval.eq_ignore_ascii_case(src) {
return dst.to_lowercase();
}
}
name.to_lowercase()
name_until_optval.to_lowercase()
}
fn is_single_string_keyword<'g, 's>(

View File

@@ -9,13 +9,11 @@ use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::BracketDepth;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::start_of_line;
@@ -28,17 +26,21 @@ use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::org_line_ending;
use crate::types::BabelCall;
use crate::types::Keyword;
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCall<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, BabelCall<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?;
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;

View File

@@ -14,16 +14,21 @@ use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::DiarySexp;
use crate::types::Keyword;
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, DiarySexp<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, DiarySexp<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?;
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
let (remaining, _eol) = org_line_ending(remaining)?;

View File

@@ -7,12 +7,10 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
@@ -32,23 +30,28 @@ use crate::parser::util::start_of_line;
use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::Drawer;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(
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>,
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") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
}
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;
let (remaining, (_open_colon, drawer_name, _close_colon, _new_line)) = tuple((

View File

@@ -18,7 +18,6 @@ use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
@@ -37,23 +36,28 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::start_of_line;
use crate::types::DynamicBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::SetSource;
#[cfg_attr(
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>,
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") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
}
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;

View File

@@ -1,10 +1,5 @@
use nom::branch::alt;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::sequence::tuple;
#[cfg(feature = "tracing")]
use tracing::span;
use nom::multi::many0;
use super::babel_call::babel_call;
use super::clock::clock;
@@ -21,7 +16,6 @@ use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule;
use super::keyword::affiliated_keyword;
use super::keyword::affiliated_keyword_as_regular_keyword;
use super::keyword::keyword;
use super::latex_environment::latex_environment;
use super::lesser_block::comment_block;
@@ -39,6 +33,8 @@ use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::macros::ak_element;
use crate::parser::macros::element;
use crate::parser::table::org_mode_table;
use crate::types::Element;
@@ -60,117 +56,204 @@ fn _element<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
can_be_paragraph: bool,
) -> Res<OrgSource<'s>, Element<'s>> {
let plain_list_matcher = parser_with_context!(plain_list)(context);
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 (post_affiliated_keywords_input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let (mut remaining, mut maybe_element) = {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Main element block");
#[cfg(feature = "tracing")]
let _enter = span.enter();
let mut affiliated_keywords = affiliated_keywords.into_iter();
opt(alt((
map(plain_list_matcher, Element::PlainList),
greater_block_matcher,
map(dynamic_block_matcher, Element::DynamicBlock),
map(footnote_definition_matcher, Element::FootnoteDefinition),
map(comment_matcher, Element::Comment),
map(drawer_matcher, Element::Drawer),
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)?
};
ak_element!(
plain_list,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::PlainList
);
if maybe_element.is_none() && can_be_paragraph {
#[cfg(feature = "tracing")]
let span = span!(tracing::Level::DEBUG, "Paragraph with affiliated keyword.");
#[cfg(feature = "tracing")]
let _enter = span.enter();
ak_element!(
greater_block,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input
);
let (remain, paragraph_with_affiliated_keyword) = opt(map(
tuple((
peek(affiliated_keyword),
map(paragraph_matcher, Element::Paragraph),
)),
|(_, paragraph)| paragraph,
))(remaining)?;
if paragraph_with_affiliated_keyword.is_some() {
remaining = remain;
maybe_element = paragraph_with_affiliated_keyword;
}
}
ak_element!(
dynamic_block,
&mut affiliated_keywords,
post_affiliated_keywords_input,
context,
input,
Element::DynamicBlock
);
if maybe_element.is_none() {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Affiliated keyword as regular keyword."
ak_element!(
footnote_definition,
&mut affiliated_keywords,
post_affiliated_keywords_input,
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 {
#[cfg(feature = "tracing")]
let span = span!(
tracing::Level::DEBUG,
"Paragraph without affiliated keyword."
// Keyword without affiliated keywords
ak_element!(
keyword,
std::iter::empty(),
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() {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"No element.",
))));
}
let element = maybe_element.expect("The above if-statement ensures this is Some().");
Ok((remaining, element))
Err(nom::Err::Error(CustomError::MyError(MyError(
"No element.",
))))
}
pub(crate) const fn detect_element(

View File

@@ -21,16 +21,21 @@ use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::FixedWidthArea;
use crate::types::Keyword;
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, FixedWidthArea<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
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 (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;

View File

@@ -33,21 +33,26 @@ use crate::parser::util::immediate_in_section;
use crate::parser::util::maybe_consume_trailing_whitespace;
use crate::parser::util::start_of_line;
use crate::types::FootnoteDefinition;
use crate::types::Keyword;
#[cfg_attr(
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>,
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") {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
}
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
start_of_line(remaining)?;
// Cannot be indented.
let (remaining, (_, lbl, _, _, _)) = tuple((

View File

@@ -18,7 +18,6 @@ use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::in_section;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
@@ -46,14 +45,17 @@ use crate::types::SpecialBlock;
#[cfg_attr(
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>(
context: RefContext<'b, 'g, 'r, 's>,
pub(crate) fn greater_block<'b, 'g, 'r, 's, AK>(
affiliated_keywords: AK,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
let pre_affiliated_keywords_input = input;
let (input, affiliated_keywords) = many0(affiliated_keyword)(input)?;
context: RefContext<'b, 'g, 'r, 's>,
pre_affiliated_keywords_input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name)) = tuple((
@@ -91,14 +93,17 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>,
affiliated_keywords: Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
affiliated_keywords: AK,
) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
context,
input,
@@ -121,14 +126,17 @@ fn center_block<'b, 'g, 'r, 's>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>,
affiliated_keywords: Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
affiliated_keywords: AK,
) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
context,
input,
@@ -149,15 +157,18 @@ fn quote_block<'b, 'g, 'r, 's>(
))
}
fn special_block<'s>(
fn special_block<'s, AK>(
name: &'s str,
) -> impl for<'b, 'g, 'r> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
OrgSource<'s>,
Vec<Keyword<'s>>,
AK,
) -> Res<OrgSource<'s>, Element<'s>>
+ 's {
+ 's
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let context_name = format!("special block {}", name);
move |context, input, pre_affiliated_keywords_input, affiliated_keywords| {
_special_block(
@@ -173,16 +184,19 @@ fn special_block<'s>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
pre_affiliated_keywords_input: OrgSource<'s>,
name: &'s str,
context_name: &'c str,
affiliated_keywords: Vec<Keyword<'s>>,
) -> Res<OrgSource<'s>, Element<'s>> {
affiliated_keywords: AK,
) -> Res<OrgSource<'s>, Element<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) = greater_block_body(
context,

View File

@@ -5,12 +5,10 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1_count;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
@@ -18,16 +16,21 @@ use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::start_of_line;
use crate::types::HorizontalRule;
use crate::types::Keyword;
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, HorizontalRule<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, HorizontalRule<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?;
let (remaining, _rule) = recognize(tuple((
space0,

View File

@@ -13,7 +13,6 @@ use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
@@ -92,13 +91,17 @@ fn _filtered_keyword<'s, F: Matcher>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, Keyword<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -109,22 +112,6 @@ pub(crate) fn keyword<'b, 'g, 'r, 's>(
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);
Ok((remaining, kw))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn affiliated_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
filtered_keyword(affiliated_key)(input)

View File

@@ -8,12 +8,10 @@ use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
@@ -26,17 +24,22 @@ use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line;
use crate::types::Keyword;
use crate::types::LatexEnvironment;
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, LatexEnvironment<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, LatexEnvironment<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let value_start = remaining;
start_of_line(remaining)?;
let (remaining, _leading_whitespace) = space0(remaining)?;

View File

@@ -13,13 +13,11 @@ use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
@@ -41,6 +39,7 @@ use crate::types::CharOffsetInLine;
use crate::types::CommentBlock;
use crate::types::ExampleBlock;
use crate::types::ExportBlock;
use crate::types::Keyword;
use crate::types::LineNumber;
use crate::types::Object;
use crate::types::PlainText;
@@ -51,13 +50,17 @@ use crate::types::VerseBlock;
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, VerseBlock<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, VerseBlock<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("verse")(context, remaining)?;
let (remaining, parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
@@ -115,13 +118,17 @@ pub(crate) fn verse_block<'b, 'g, 'r, 's>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CommentBlock<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, CommentBlock<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("comment")(context, remaining)?;
let (remaining, _parameters) = opt(tuple((space1, data)))(remaining)?;
let (remaining, _nl) = recognize(tuple((space0, line_ending)))(remaining)?;
@@ -159,13 +166,17 @@ pub(crate) fn comment_block<'b, 'g, 'r, 's>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, ExampleBlock<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, _) = lesser_block_begin("example")(context, remaining)?;
let (remaining, parameters) = opt(alt((
map(tuple((space1, example_switches)), |(_, switches)| switches),
@@ -236,13 +247,17 @@ pub(crate) fn example_block<'b, 'g, 'r, 's>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportBlock<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, ExportBlock<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
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.
let (remaining, export_type) = opt(map(
@@ -288,13 +303,17 @@ pub(crate) fn export_block<'b, 'g, 'r, 's>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, SrcBlock<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
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.
let (remaining, language) =

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

@@ -27,6 +27,7 @@ mod latex_environment;
mod latex_fragment;
mod lesser_block;
mod line_break;
mod macros;
mod object_parser;
mod org_macro;
mod org_source;

View File

@@ -2,54 +2,46 @@ use nom::branch::alt;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many1;
use nom::multi::many_till;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::element_parser::detect_element;
use super::keyword::affiliated_keyword;
use super::org_source::OrgSource;
use super::util::blank_line;
use super::util::get_consumed;
use super::util::get_has_affiliated_keyword;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
use crate::context::ExitMatcherNode;
use crate::context::HasAffiliatedKeywordInner;
use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::start_of_line;
use crate::types::Keyword;
use crate::types::Paragraph;
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
let contexts = [
ContextElement::HasAffiliatedKeyword(HasAffiliatedKeywordInner {
start_after_affiliated_keywords: remaining,
keywords: &affiliated_keywords,
}),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &paragraph_end,
}),
];
) -> Res<OrgSource<'s>, Paragraph<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let contexts = [ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &paragraph_end,
})];
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 exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
@@ -89,15 +81,6 @@ fn paragraph_end<'b, 'g, 'r, 's>(
if regular_end.is_ok() {
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((
recognize(parser_with_context!(detect_element(false))(context)),
eof,

View File

@@ -43,6 +43,7 @@ use crate::parser::util::org_space;
use crate::parser::util::start_of_line;
use crate::types::CheckboxType;
use crate::types::IndentationLevel;
use crate::types::Keyword;
use crate::types::Object;
use crate::types::PlainList;
use crate::types::PlainListItem;
@@ -81,14 +82,17 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainList<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, PlainList<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let contexts = [
ContextElement::Context("plain list"),
ContextElement::ConsumeTrailingWhitespace(true),
@@ -580,8 +584,8 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_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) = plain_list_matcher(input).unwrap();
let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1.");
}
@@ -592,8 +596,8 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_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) = plain_list_matcher(input).unwrap();
let (remaining, result) =
plain_list(std::iter::empty(), input, &initial_context, input).unwrap();
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
}
@@ -605,8 +609,7 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_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_matcher(input);
let result = plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_err());
}
@@ -617,8 +620,7 @@ mod tests {
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_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_matcher(input);
let result = plain_list(std::iter::empty(), input, &initial_context, input);
assert!(result.is_ok());
}

View File

@@ -29,6 +29,7 @@ use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::Keyword;
use crate::types::Table;
use crate::types::TableCell;
use crate::types::TableRow;
@@ -38,13 +39,17 @@ use crate::types::TableRow;
/// This is not the table.el style.
#[cfg_attr(
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>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Table<'s>> {
let (remaining, affiliated_keywords) = many0(affiliated_keyword)(input)?;
) -> Res<OrgSource<'s>, Table<'s>>
where
AK: IntoIterator<Item = Keyword<'s>>,
{
start_of_line(remaining)?;
peek(tuple((space0, tag("|"))))(remaining)?;

View File

@@ -271,24 +271,6 @@ pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
Ok((remaining, (indentation_level, leading_whitespace)))
}
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.
///
/// This is important for making start-of-document, end-of-document, and other context-dependent tests succeed.

View File

@@ -6,7 +6,7 @@ use super::Object;
pub enum AffiliatedKeywordValue<'s> {
SingleString(&'s str),
ListOfStrings(Vec<&'s str>),
ListOfListsOfObjects(Vec<Vec<Object<'s>>>),
ListOfListsOfObjects(Vec<(Option<Vec<Object<'s>>>, Vec<Object<'s>>)>),
}
#[derive(Debug)]

View File

@@ -1,17 +1,17 @@
// TODO: Investigate writing a proc macro to make specifying these combinations easier. For example, currently I am only setting 1 setting per test to keep the repetition reasonable when I should be mixing all the different combinations.
{expect_fail}
#[test]
fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
#[tokio::test]
async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
organic::compare::run_anonymous_compare(org_contents.as_str())?;
organic::compare::run_anonymous_compare(org_contents.as_str()).await?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
#[tokio::test]
async fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
@@ -19,13 +19,13 @@ fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.list_allow_alphabetical = true;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
#[tokio::test]
async fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
@@ -33,13 +33,13 @@ fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.tab_width = 1;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
#[tokio::test]
async fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
@@ -47,13 +47,13 @@ fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.tab_width = 16;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
Ok(())
}}
{expect_fail}
#[test]
fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
#[tokio::test]
async fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{
@@ -61,6 +61,6 @@ fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd;
global_settings
}};
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?;
Ok(())
}}