Compare commits
239 Commits
v0.1.8
...
5694f80f41
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5694f80f41 | ||
|
|
36327e92d7 | ||
|
|
c70eb69ed6 | ||
|
|
8d9ff77799 | ||
|
|
029791e0b2 | ||
|
|
8d621b32dc | ||
|
|
dfad7b7888 | ||
|
|
56348a6d54 | ||
|
|
65e142a215 | ||
|
|
aa0a0b890e | ||
|
|
ab33730830 | ||
|
|
4c8828b91b | ||
|
|
2ba5156ee1 | ||
|
|
ae3a6ff919 | ||
|
|
4716e1ce5b | ||
|
|
89fcf6cb54 | ||
|
|
f220fd63e5 | ||
|
|
7cf1f7b7bb | ||
|
|
135ca133ea | ||
|
|
d126488891 | ||
|
|
51748afd41 | ||
|
|
e84135985e | ||
|
|
448902bb05 | ||
|
|
65df18171a | ||
|
|
e2bc58a469 | ||
|
|
fbe3c76ab7 | ||
|
|
368c6a457e | ||
|
|
c7dbe596b3 | ||
|
|
3da52a0826 | ||
|
|
ae11e390d1 | ||
|
|
45dd38ac2d | ||
|
|
7af18e2312 | ||
|
|
1b603f3a05 | ||
|
|
d06e4de7b0 | ||
|
|
e686666ea0 | ||
|
|
672848d06a | ||
|
|
627c785e24 | ||
|
|
758e224e6d | ||
|
|
f79606047e | ||
|
|
dd3de67a8c | ||
|
|
823426a4f1 | ||
|
|
fa97124186 | ||
|
|
885fefd060 | ||
|
|
efac73798f | ||
|
|
68e392811e | ||
|
|
343af41f78 | ||
|
|
f49a1853ad | ||
|
|
6bd8d9efd7 | ||
|
|
18ad80e018 | ||
|
|
34a0858473 | ||
|
|
4ba9d7439a | ||
|
|
6f0439bb6d | ||
|
|
b478b6f5d7 | ||
|
|
02af3d0081 | ||
|
|
40685f05cc | ||
|
|
e21701b97c | ||
|
|
ef8a6884fe | ||
|
|
ac7125d9b6 | ||
|
|
58ca9569a6 | ||
|
|
1da521b08a | ||
|
|
386ad5091d | ||
|
|
5f84cd974d | ||
|
|
d8ea450a46 | ||
|
|
3742f4fa08 | ||
|
|
11a7234900 | ||
|
|
50a3631b79 | ||
|
|
da2d7535e8 | ||
|
|
1351577c5a | ||
|
|
65eda08843 | ||
|
|
b82d4c0eca | ||
|
|
93fe46e4e7 | ||
|
|
5b308ea76f | ||
|
|
ab4a0c1224 | ||
|
|
786521ad4a | ||
|
|
d8102b7bc2 | ||
|
|
a26640355c | ||
|
|
057c8a1387 | ||
|
|
4fc81e983a | ||
|
|
258e9485de | ||
|
|
87ac18e6b2 | ||
|
|
e1e4ac75e4 | ||
|
|
c877116540 | ||
|
|
8e70773b15 | ||
|
|
f046b16c11 | ||
|
|
1ab7d2f2d7 | ||
|
|
a548c7e170 | ||
|
|
b556f4617f | ||
|
|
13163f2468 | ||
|
|
da5dcd4c1b | ||
|
|
d059afef07 | ||
|
|
bcade66e68 | ||
|
|
301a6db83e | ||
|
|
32da06776c | ||
|
|
169bf69f5e | ||
|
|
7ee48ff65c | ||
|
|
afb43ff34f | ||
|
|
b56d847cfa | ||
|
|
1503054994 | ||
|
|
03028889bd | ||
|
|
317293f0f2 | ||
|
|
3d7f411cf9 | ||
|
|
650cbc17db | ||
|
|
1d7770e590 | ||
|
|
bf038db31c | ||
|
|
4cdf88a632 | ||
|
|
2eaef82fdb | ||
|
|
00dc7b636c | ||
|
|
e6c809ab03 | ||
|
|
e673aa862e | ||
|
|
3b6659c5fd | ||
|
|
68a3f8b87e | ||
|
|
b1244de1dc | ||
|
|
e5a402ee1b | ||
|
|
d4a2ad4a7f | ||
|
|
3d1b2713ed | ||
|
|
60bec4695b | ||
|
|
d992947ff1 | ||
|
|
76fb24d1d1 | ||
|
|
b56318fbe4 | ||
|
|
8169499de3 | ||
|
|
29d9e76545 | ||
|
|
4d356b855e | ||
|
|
ae66d1bd89 | ||
|
|
c551938904 | ||
|
|
0fb80e3fee | ||
|
|
590e7fba0e | ||
|
|
4a72747dc9 | ||
|
|
2352636672 | ||
|
|
36217f5704 | ||
|
|
0654b676f7 | ||
|
|
a80d171e4d | ||
|
|
2e1a946ac9 | ||
|
|
01c2f1bf66 | ||
|
|
be483110ef | ||
|
|
94401dcf00 | ||
|
|
2b5df83956 | ||
|
|
d53b9e1e1f | ||
|
|
5c929ffc13 | ||
|
|
bc3224be7a | ||
|
|
54c66fb4d6 | ||
|
|
6a8ae9d838 | ||
|
|
512432c5f0 | ||
|
|
890cd3e4fd | ||
|
|
9846cde2f0 | ||
|
|
dec3242e72 | ||
|
|
a8a34e2d9c | ||
|
|
c55fae86f8 | ||
|
|
e7ec23af3d | ||
|
|
10ae36a419 | ||
|
|
ecdfd7087f | ||
|
|
3ed9b552e2 | ||
|
|
d04c8c832c | ||
|
|
9575ef30ac | ||
|
|
06ecf41663 | ||
|
|
10d03fd432 | ||
|
|
a62c3fc522 | ||
|
|
25f664e69e | ||
|
|
52e0d305aa | ||
|
|
418c5c1ce8 | ||
|
|
ecd523fa8f | ||
|
|
c0555dec0b | ||
|
|
1b788f3f21 | ||
|
|
b3382c66cd | ||
|
|
2a003b85fd | ||
|
|
270ba53150 | ||
|
|
de5788d8f3 | ||
|
|
5a254392cb | ||
|
|
178894680b | ||
|
|
599b3b8f0a | ||
|
|
d78ce10a0b | ||
|
|
12ab9beada | ||
|
|
186201a4b5 | ||
|
|
d38b0a84f6 | ||
|
|
6ed35f4674 | ||
|
|
846a8b3729 | ||
|
|
896250836b | ||
|
|
6c77586960 | ||
|
|
fc7d4bd949 | ||
|
|
f1e35e317b | ||
|
|
3fb2b5d31c | ||
|
|
d1dac0b8de | ||
|
|
93f1bcd744 | ||
|
|
47674a6907 | ||
|
|
5d1582be4d | ||
|
|
dae10c2eef | ||
|
|
5e127fec11 | ||
|
|
064a4eeee7 | ||
|
|
7727b5ef47 | ||
|
|
967e74c147 | ||
|
|
13697df7ea | ||
|
|
07e11e359a | ||
|
|
0c363c8dd6 | ||
|
|
9a479b33e0 | ||
|
|
7a854838ef | ||
|
|
2012e5a6d5 | ||
|
|
f1261ddce8 | ||
|
|
3a422e6435 | ||
|
|
6670f8c768 | ||
|
|
d7a36c8aca | ||
|
|
f820e27b17 | ||
|
|
a4b1d462c3 | ||
|
|
1b7326eafe | ||
|
|
90433aa55f | ||
|
|
a5b4eb40f6 | ||
|
|
48d550e1fc | ||
|
|
9ce042d5b6 | ||
|
|
8784da5179 | ||
|
|
875a50ae46 | ||
|
|
c4ea3fbf88 | ||
|
|
95fa834420 | ||
|
|
32a7ce3f36 | ||
|
|
d8c52568db | ||
|
|
c5be75ee8d | ||
|
|
282417ee94 | ||
|
|
ab46a9e5c6 | ||
|
|
4359fc9266 | ||
|
|
7419b75d76 | ||
|
|
e4cfc296e5 | ||
|
|
9a1d91ae45 | ||
|
|
df5d699a39 | ||
|
|
9111408d83 | ||
|
|
35f058a354 | ||
|
|
dd91e506bd | ||
|
|
cd781a7dcf | ||
|
|
8cd0e4ec63 | ||
|
|
f9460b88d7 | ||
|
|
0b2a5f4fbf | ||
|
|
6097e4df18 | ||
|
|
d5b1014fe4 | ||
|
|
dd8a8207ce | ||
|
|
b4c985071c | ||
|
|
d4f27ef297 | ||
|
|
f25246556c | ||
|
|
3fe56e9aa3 | ||
|
|
f180412ff3 | ||
|
|
f0e28206ff | ||
|
|
1f64e289a2 | ||
|
|
f7690ff64b | ||
|
|
bd5e50d558 |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "organic"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
authors = ["Tom Alexander <tom@fizz.buzz>"]
|
||||
description = "An org-mode parser."
|
||||
edition = "2021"
|
||||
|
||||
7
Makefile
7
Makefile
@@ -46,6 +46,13 @@ dockertest:
|
||||
> $(MAKE) -C docker/organic_test
|
||||
> docker run --init --rm -i -t --read-only -v "$$(readlink -f ./):/source:ro" --mount type=tmpfs,destination=/tmp --mount source=cargo-cache,target=/usr/local/cargo/registry --mount source=rust-cache,target=/target --env CARGO_TARGET_DIR=/target -w /source organic-test --no-default-features --features compare --no-fail-fast --lib --test test_loader -- --test-threads $(TESTJOBS)
|
||||
|
||||
.PHONY: buildtest
|
||||
buildtest:
|
||||
> cargo build --no-default-features
|
||||
> cargo build --no-default-features --features compare
|
||||
> cargo build --no-default-features --features tracing
|
||||
> cargo build --no-default-features --features compare,tracing
|
||||
|
||||
.PHONY: foreign_document_test
|
||||
foreign_document_test:
|
||||
> $(MAKE) -C docker/organic_test run_foreign_document_test
|
||||
|
||||
25
README.md
25
README.md
@@ -4,10 +4,31 @@ Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) p
|
||||
|
||||
## Project Status
|
||||
|
||||
This project is a personal learning project to grow my experience in [rust](https://www.rust-lang.org/). It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README.
|
||||
This project is 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.
|
||||
|
||||
### 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.
|
||||
- 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
|
||||
- This project will not include an elisp engine since that would drastically increase the complexity of the code. Any features requiring an elisp engine will not be implemented (for example, Emacs supports embedded eval expressions in documents but this parser will never support that).
|
||||
- This project is exclusively an org-mode **parser**. This limits its scope to roughly the output of `(org-element-parse-buffer)`. It will not render org-mode documents in other formats like HTML or LaTeX.
|
||||
### Project Maybe-Goals
|
||||
- table.el support. Currently we support org-mode tables but org-mode also allows table.el tables. So far, their use in org-mode documents seems rather uncommon so this is a low-priority feature.
|
||||
- Document editing support. I do not anticipate any advanced editing features to make editing ergonomic, but it should be relatively easy to be able to parse an org-mode document and serialize it back into org-mode. This would enable cool features to be built on top of the library like auto-formatters. To accomplish this feature, We'd have to capture all of the various separators and whitespace that we are currently simply throwing away. This would add many additional fields to the parsed structs and it would add more noise to the parsers themselves, so I do not want to approach this feature until the parser is more complete since it would make modifications and refactoring more difficult.
|
||||
### Supported Versions
|
||||
This project targets the version of Emacs and Org-mode that are built into the [organic-test docker image](docker/organic_test/Dockerfile). This is newer than the version of Org-mode that shipped with Emacs 29.1. The parser itself does not depend on Emacs or Org-mode though, so this only matters for development purposes when running the automated tests that compare against upstream Org-mode.
|
||||
|
||||
## Using this library
|
||||
TODO: Add section on using Organic as a library (which is the intended use for this project).
|
||||
TODO: Add section on using Organic as a library (which is the intended use for this project). This will be added when we have a bit more API stability since currently the library is under heavy development.
|
||||
|
||||
## Development
|
||||
|
||||
### The parse binary
|
||||
This program takes org-mode input either streamed in on stdin or as paths to files passed in as arguments. It then parses them using Organic and dumps the result to stdout. This program is intended solely as a development tool. Examples:
|
||||
|
||||
27
build.rs
27
build.rs
@@ -19,8 +19,6 @@ fn main() {
|
||||
// Re-generate the tests if any org-mode files change
|
||||
println!("cargo:rerun-if-changed=org_mode_samples");
|
||||
|
||||
write_header(&mut test_file);
|
||||
|
||||
let test_files = WalkDir::new("org_mode_samples")
|
||||
.into_iter()
|
||||
.filter(|e| match e {
|
||||
@@ -54,28 +52,15 @@ fn write_test(test_file: &mut File, test: &walkdir::DirEntry) {
|
||||
.strip_suffix(".org")
|
||||
.expect("Should have .org extension")
|
||||
.replace("/", "_");
|
||||
let test_name = format!("autogen_{}", test_name);
|
||||
|
||||
if let Some(_reason) = is_expect_fail(test_name.as_str()) {
|
||||
write!(test_file, "#[ignore]\n").unwrap();
|
||||
}
|
||||
write!(
|
||||
test_file,
|
||||
include_str!("./tests/test_template"),
|
||||
name = test_name,
|
||||
path = test.path().display()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "compare")]
|
||||
fn write_header(test_file: &mut File) {
|
||||
write!(
|
||||
test_file,
|
||||
r#"
|
||||
#[feature(exit_status_error)]
|
||||
|
||||
"#
|
||||
path = test.path().display(),
|
||||
expect_fail = is_expect_fail(test_name.as_str())
|
||||
.map(|_| "#[ignore]\n")
|
||||
.unwrap_or("")
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -83,8 +68,8 @@ fn write_header(test_file: &mut File) {
|
||||
#[cfg(feature = "compare")]
|
||||
fn is_expect_fail(name: &str) -> Option<&str> {
|
||||
match name {
|
||||
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
|
||||
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
||||
"greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
|
||||
"element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
|
||||
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
|
||||
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
|
||||
|
||||
ARG WORG_VERSION=0c8d5679b536af450b61812246a3e02b8103f4b8
|
||||
ARG WORG_VERSION=ba6cda890f200d428a5d68e819eef15b5306055f
|
||||
ARG WORG_PATH=/foreign_documents/worg
|
||||
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
|
||||
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD
|
||||
|
||||
7
notes/test_names.org
Normal file
7
notes/test_names.org
Normal file
@@ -0,0 +1,7 @@
|
||||
* Autogen tests
|
||||
The autogen tests are the tests automatically generated to compare the output of Organic vs the upstream Emacs Org-mode parser using the sample documents in the =org_mode_samples= folder. They will have a prefix based on the settings for each test.
|
||||
|
||||
- default :: The test is run with the default settings (The upstream Emacs Org-mode determines the default settings)
|
||||
- la :: Short for "list alphabetic". Enables alphabetic plain lists.
|
||||
- t# :: Sets the tab-width to # (as in t4 sets the tab-width to 4).
|
||||
- odd :: Sets the org-odd-levels-only setting to true (meaning "odd" as opposed to "oddeven").
|
||||
17
org_mode_samples/affiliated_keyword/all_name.org
Normal file
17
org_mode_samples/affiliated_keyword/all_name.org
Normal file
@@ -0,0 +1,17 @@
|
||||
#+name: foo
|
||||
: bar
|
||||
|
||||
#+source: foo
|
||||
: bar
|
||||
|
||||
#+tblname: foo
|
||||
: bar
|
||||
|
||||
#+resname: foo
|
||||
: bar
|
||||
|
||||
#+srcname: foo
|
||||
: bar
|
||||
|
||||
#+label: foo
|
||||
: bar
|
||||
8
org_mode_samples/affiliated_keyword/case_insensitive.org
Normal file
8
org_mode_samples/affiliated_keyword/case_insensitive.org
Normal file
@@ -0,0 +1,8 @@
|
||||
#+NAME: foo
|
||||
bar
|
||||
|
||||
#+NaMe: baz
|
||||
cat
|
||||
|
||||
#+name: lorem
|
||||
ipsum
|
||||
6
org_mode_samples/affiliated_keyword/multiple_name.org
Normal file
6
org_mode_samples/affiliated_keyword/multiple_name.org
Normal file
@@ -0,0 +1,6 @@
|
||||
#+name: foo
|
||||
#+source: bar
|
||||
#+name: baz
|
||||
#+tblname: lorem
|
||||
#+label: ipsum
|
||||
: dolar
|
||||
@@ -0,0 +1,2 @@
|
||||
#+tblname: foo
|
||||
: bar
|
||||
1
org_mode_samples/document/category.org
Normal file
1
org_mode_samples/document/category.org
Normal file
@@ -0,0 +1 @@
|
||||
#+CATEGORY: theory
|
||||
5
org_mode_samples/document/category_multiple.org
Normal file
5
org_mode_samples/document/category_multiple.org
Normal file
@@ -0,0 +1,5 @@
|
||||
#+CATEGORY: foo
|
||||
#+CATEGORY: bar
|
||||
#+begin_src text
|
||||
#+CATEGORY: baz
|
||||
#+end_src
|
||||
10
org_mode_samples/greater_element/drawer/name.org
Normal file
10
org_mode_samples/greater_element/drawer/name.org
Normal file
@@ -0,0 +1,10 @@
|
||||
* Headline
|
||||
before
|
||||
#+NAME: foo
|
||||
:candle:
|
||||
inside
|
||||
|
||||
the drawer
|
||||
:end:
|
||||
|
||||
after
|
||||
7
org_mode_samples/greater_element/dynamic_block/name.org
Normal file
7
org_mode_samples/greater_element/dynamic_block/name.org
Normal file
@@ -0,0 +1,7 @@
|
||||
#+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:
|
||||
@@ -0,0 +1,8 @@
|
||||
#+begin_center
|
||||
|
||||
#+end_center
|
||||
|
||||
#+begin_center
|
||||
#+NAME: foo
|
||||
|
||||
#+end_center
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
#+begin_center
|
||||
|
||||
#+end_center
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
#+begin_quote
|
||||
|
||||
#+end_quote
|
||||
@@ -0,0 +1,6 @@
|
||||
#+NAME: foo
|
||||
#+begin_defun
|
||||
foo
|
||||
|
||||
{{{bar(baz)}}}
|
||||
#+end_defun
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_defun foo bar baz
|
||||
lorem
|
||||
#+end_defun
|
||||
@@ -0,0 +1,22 @@
|
||||
# An ordered list starting at 3
|
||||
1. [@3] foo
|
||||
|
||||
|
||||
# An ordered list starting at 11
|
||||
1. [@D] bar
|
||||
|
||||
|
||||
# An ordered list starting at 1 with the contents of "[@kk] baz"
|
||||
1. [@kk] baz
|
||||
|
||||
|
||||
# A paragraph when org-list-allow-alphabetical is nil
|
||||
m. lorem
|
||||
|
||||
|
||||
# A paragraph when org-list-allow-alphabetical is nil
|
||||
m. [@k] ipsum
|
||||
|
||||
|
||||
# An unordered list with :counter set to 3
|
||||
- [@3] dolar
|
||||
@@ -0,0 +1,30 @@
|
||||
# Alphabetic lists larger than 26 elements should become numbered. From M-x describe-variable org-list-allow-alphabetical:
|
||||
#
|
||||
# > Lists with more than 26 items will fallback to standard numbering.
|
||||
a. 1
|
||||
a. 2
|
||||
a. 3
|
||||
a. 4
|
||||
a. 5
|
||||
a. 6
|
||||
a. 7
|
||||
a. 8
|
||||
a. 9
|
||||
a. 10
|
||||
a. 11
|
||||
a. 12
|
||||
a. 13
|
||||
a. 14
|
||||
a. 15
|
||||
a. 16
|
||||
a. 17
|
||||
a. 18
|
||||
a. 19
|
||||
a. 20
|
||||
a. 21
|
||||
a. 22
|
||||
a. 23
|
||||
a. 24
|
||||
a. 25
|
||||
a. 26
|
||||
a. 27
|
||||
2
org_mode_samples/greater_element/plain_list/name.org
Normal file
2
org_mode_samples/greater_element/plain_list/name.org
Normal file
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
1. bar
|
||||
@@ -0,0 +1,5 @@
|
||||
# "lorem" is prefixed by a tab instead of spaces, so the editor's tab-width value determines whether lorem is a sibling of baz (tab-width 8), a sibling of bar (tab-width < 8), or a child of baz (tab-width > 8).
|
||||
1. foo
|
||||
1. bar
|
||||
1. baz
|
||||
1. lorem
|
||||
2
org_mode_samples/greater_element/table/name.org
Normal file
2
org_mode_samples/greater_element/table/name.org
Normal file
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
| foo | bar |
|
||||
3
org_mode_samples/lesser_element/babel_call/empty.org
Normal file
3
org_mode_samples/lesser_element/babel_call/empty.org
Normal file
@@ -0,0 +1,3 @@
|
||||
#+call:
|
||||
|
||||
#+call:
|
||||
@@ -0,0 +1,3 @@
|
||||
#+call: foo[inside](bar="baz")[outside]
|
||||
|
||||
#+call: foo[](bar="baz")[]
|
||||
@@ -1 +1,2 @@
|
||||
#+NAME: foo
|
||||
#+call: foo(bar="baz")
|
||||
@@ -0,0 +1,7 @@
|
||||
#+call: foo[inside](bar="baz")[outside]
|
||||
|
||||
#+call: foo[[inside]](bar="baz")[outside]
|
||||
|
||||
#+call: foo[inside]((bar="baz"))[outside]
|
||||
|
||||
#+call: foo[inside](bar="baz")[[outside]]
|
||||
@@ -0,0 +1 @@
|
||||
#+call: foo(bar="baz"
|
||||
7
org_mode_samples/lesser_element/babel_call/simple.org
Normal file
7
org_mode_samples/lesser_element/babel_call/simple.org
Normal file
@@ -0,0 +1,7 @@
|
||||
#+call: foo(bar="baz")
|
||||
|
||||
#+call: lorem ipsum
|
||||
|
||||
#+call: dolar cat(dog)
|
||||
|
||||
#+call: (bat)
|
||||
3
org_mode_samples/lesser_element/babel_call/spaces.org
Normal file
3
org_mode_samples/lesser_element/babel_call/spaces.org
Normal file
@@ -0,0 +1,3 @@
|
||||
#+call: foo [inside] (bar="baz") [outside]
|
||||
|
||||
#+call: foo (bar="baz") [outside]
|
||||
@@ -0,0 +1 @@
|
||||
CLOCK: [2023-04-21 Fri 19:32]--[2023-04-21 Fri 19:35]
|
||||
3
org_mode_samples/lesser_element/clock/name.org
Normal file
3
org_mode_samples/lesser_element/clock/name.org
Normal file
@@ -0,0 +1,3 @@
|
||||
CLOCK: [2023-04-21 Fri 19:32]--[2023-04-21 Fri 19:35] => 0:03
|
||||
#+NAME: foo
|
||||
CLOCK: [2023-04-21 Fri 19:43]
|
||||
@@ -0,0 +1 @@
|
||||
CLOCK: [2023-04-21 Fri 19:43] => 0:03
|
||||
2
org_mode_samples/lesser_element/clock/time_range.org
Normal file
2
org_mode_samples/lesser_element/clock/time_range.org
Normal file
@@ -0,0 +1,2 @@
|
||||
CLOCK: [1970-01-01 Thu 8:15-13:15otherrest +1w -1d] => 0:03
|
||||
CLOCK: [1970-01-01 Thu 8:15-13:15otherrest +1w -1d]
|
||||
@@ -1,4 +1,5 @@
|
||||
# Comment
|
||||
#
|
||||
# indented line
|
||||
# At the top of the file
|
||||
|
||||
|
||||
2
org_mode_samples/lesser_element/comment/name.org
Normal file
2
org_mode_samples/lesser_element/comment/name.org
Normal file
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
# Comments cannot have affiliated keywords.
|
||||
2
org_mode_samples/lesser_element/diary_sexp/name.org
Normal file
2
org_mode_samples/lesser_element/diary_sexp/name.org
Normal file
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
%%(foo)
|
||||
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
: bar
|
||||
2
org_mode_samples/lesser_element/horizontal_rule/name.org
Normal file
2
org_mode_samples/lesser_element/horizontal_rule/name.org
Normal file
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
-----
|
||||
2
org_mode_samples/lesser_element/keyword/name.org
Normal file
2
org_mode_samples/lesser_element/keyword/name.org
Normal file
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
#+FOO: BAR
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
\begin{foo}
|
||||
bar
|
||||
\end{foo}
|
||||
@@ -0,0 +1,10 @@
|
||||
#+begin_comment
|
||||
,* foo
|
||||
,,,** bar
|
||||
,*** baz
|
||||
lorem
|
||||
, ipsum
|
||||
,#+begin_src dolar
|
||||
|
||||
,#+end_src
|
||||
#+end_comment
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
#+begin_comment text
|
||||
bar
|
||||
#+end_comment
|
||||
@@ -0,0 +1,4 @@
|
||||
#+begin_comment
|
||||
This is a comment
|
||||
,* with an escaped line.
|
||||
#+end_comment
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_comment -n 20
|
||||
foo
|
||||
#+end_comment
|
||||
@@ -0,0 +1,10 @@
|
||||
#+begin_example
|
||||
,* foo
|
||||
,,,** bar
|
||||
,*** baz
|
||||
lorem
|
||||
, ipsum
|
||||
,#+begin_src dolar
|
||||
|
||||
,#+end_src
|
||||
#+end_example
|
||||
@@ -0,0 +1,7 @@
|
||||
#+begin_example python :exports results
|
||||
print("foo")
|
||||
#+end_example
|
||||
|
||||
#+begin_example python -n :exports results
|
||||
print("foo")
|
||||
#+end_example
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_example elisp -n 5
|
||||
foo
|
||||
#+end_example
|
||||
@@ -0,0 +1,7 @@
|
||||
#+begin_example -n -10
|
||||
foo
|
||||
#+end_example
|
||||
|
||||
#+begin_example +n -15
|
||||
bar
|
||||
#+end_example
|
||||
@@ -0,0 +1,7 @@
|
||||
#+begin_example -n 0
|
||||
foo
|
||||
#+end_example
|
||||
|
||||
#+begin_example +n 0
|
||||
bar
|
||||
#+end_example
|
||||
@@ -0,0 +1,8 @@
|
||||
#+begin_example -n 5
|
||||
foo
|
||||
#+end_example
|
||||
|
||||
# Line numbering starts at 15 for the example below since it uses +n.
|
||||
#+begin_example +n 10
|
||||
bar
|
||||
#+end_example
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
#+begin_example text
|
||||
bar
|
||||
#+end_example
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_example foo -k
|
||||
bar
|
||||
#+end_example
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_example foo -n bar -k baz
|
||||
|
||||
#+end_example
|
||||
@@ -0,0 +1,15 @@
|
||||
#+begin_example text -i
|
||||
foo
|
||||
#+end_example
|
||||
|
||||
#+begin_example text -n -i
|
||||
foo
|
||||
#+end_example
|
||||
|
||||
#+begin_example text
|
||||
foo
|
||||
#+end_example
|
||||
|
||||
#+begin_example text -n -r -k
|
||||
foo
|
||||
#+end_example
|
||||
@@ -0,0 +1,6 @@
|
||||
#+begin_example text
|
||||
foo
|
||||
bar (ref:here)
|
||||
baz
|
||||
#+end_example
|
||||
Link to the reference: [[(here)]]
|
||||
@@ -0,0 +1,2 @@
|
||||
#+begin_example
|
||||
#+end_example
|
||||
@@ -0,0 +1,15 @@
|
||||
#+BEGIN_EXAMPLE elisp -n -r -l "((%s))"
|
||||
foo
|
||||
#+END_EXAMPLE
|
||||
|
||||
#+BEGIN_EXAMPLE elisp -k -n -r -l "((%s))"
|
||||
foo
|
||||
#+END_EXAMPLE
|
||||
|
||||
#+BEGIN_EXAMPLE elisp -k 8 -n -r -l "((%s))"
|
||||
foo
|
||||
#+END_EXAMPLE
|
||||
|
||||
#+BEGIN_EXAMPLE elisp -n -r -k -l "((%s))"
|
||||
foo
|
||||
#+END_EXAMPLE
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_example +n 10
|
||||
foo
|
||||
#+end_example
|
||||
@@ -0,0 +1,10 @@
|
||||
#+begin_export html
|
||||
,* foo
|
||||
,,,** bar
|
||||
,*** baz
|
||||
lorem
|
||||
, ipsum
|
||||
,#+begin_src dolar
|
||||
|
||||
,#+end_src
|
||||
#+end_export
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
#+begin_export text
|
||||
bar
|
||||
#+end_export
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_export latex
|
||||
This would be LaTeX code.
|
||||
#+end_export
|
||||
6
org_mode_samples/lesser_element/lesser_block/nested.org
Normal file
6
org_mode_samples/lesser_element/lesser_block/nested.org
Normal file
@@ -0,0 +1,6 @@
|
||||
# Verse blocks are the only lesser blocks that contain objects
|
||||
#+begin_verse
|
||||
#+begin_comment
|
||||
This is a comment.
|
||||
#+end_comment
|
||||
#+end_verse
|
||||
@@ -0,0 +1,7 @@
|
||||
#+begin_src python :exports results
|
||||
print("foo")
|
||||
#+end_src
|
||||
|
||||
#+begin_src python -n :exports results
|
||||
print("foo")
|
||||
#+end_src
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
#+begin_src text
|
||||
bar
|
||||
#+end_src
|
||||
@@ -0,0 +1,7 @@
|
||||
#+begin_src python :exports results
|
||||
print("foo")
|
||||
#+end_src
|
||||
|
||||
#+begin_src python -n :exports results
|
||||
print("foo")
|
||||
#+end_src
|
||||
@@ -0,0 +1,3 @@
|
||||
#+begin_src foo -n bar -k baz
|
||||
|
||||
#+end_src
|
||||
@@ -0,0 +1,15 @@
|
||||
#+begin_src text -i
|
||||
foo
|
||||
#+end_src
|
||||
|
||||
#+begin_src text -n -i
|
||||
foo
|
||||
#+end_src
|
||||
|
||||
#+begin_src text
|
||||
foo
|
||||
#+end_src
|
||||
|
||||
#+begin_src text -n -r -k
|
||||
foo
|
||||
#+end_src
|
||||
@@ -0,0 +1,6 @@
|
||||
#+begin_src text
|
||||
foo
|
||||
bar (ref:here)
|
||||
baz
|
||||
#+end_src
|
||||
Link to the reference: [[(here)]]
|
||||
@@ -0,0 +1,4 @@
|
||||
#+NAME: foo
|
||||
#+begin_verse text
|
||||
bar
|
||||
#+end_verse
|
||||
2
org_mode_samples/lesser_element/paragraph/name.org
Normal file
2
org_mode_samples/lesser_element/paragraph/name.org
Normal file
@@ -0,0 +1,2 @@
|
||||
#+NAME: foo
|
||||
bar
|
||||
13
org_mode_samples/object/regular_link/code_ref_link.org
Normal file
13
org_mode_samples/object/regular_link/code_ref_link.org
Normal file
@@ -0,0 +1,13 @@
|
||||
[[(foo)]]
|
||||
|
||||
[[((bar))]]
|
||||
|
||||
[[((baz)]]
|
||||
|
||||
[[(lo
|
||||
rem)]]
|
||||
|
||||
# These become fuzzy
|
||||
[[(foo) ]]
|
||||
[[ (foo)]]
|
||||
[[(foo)::3]]
|
||||
6
org_mode_samples/object/regular_link/custom_id_link.org
Normal file
6
org_mode_samples/object/regular_link/custom_id_link.org
Normal file
@@ -0,0 +1,6 @@
|
||||
[[#foo]]
|
||||
|
||||
[[#fo
|
||||
o]]
|
||||
|
||||
[[#foo::3]]
|
||||
21
org_mode_samples/object/regular_link/file_link.org
Normal file
21
org_mode_samples/object/regular_link/file_link.org
Normal file
@@ -0,0 +1,21 @@
|
||||
[[./simple.org]]
|
||||
[[../simple.org]]
|
||||
[[/simple.org]]
|
||||
[[file:simple.org]]
|
||||
[[file:sim ple.org]]
|
||||
|
||||
[[file:simp
|
||||
le.org]]
|
||||
|
||||
[[file:simple.org::3]]
|
||||
[[file:simple.org::foo]]
|
||||
[[file:simple.org::#foo]]
|
||||
[[file:simple.org::foo bar]]
|
||||
[[file:simple.org::foo
|
||||
bar]]
|
||||
[[file:simple.org::foo
|
||||
bar]]
|
||||
[[file:simple.org::foo
|
||||
bar]]
|
||||
[[file:simple.org::foo::bar]]
|
||||
[[file:simple.org::/foo/]]
|
||||
6
org_mode_samples/object/regular_link/fuzzy_link.org
Normal file
6
org_mode_samples/object/regular_link/fuzzy_link.org
Normal file
@@ -0,0 +1,6 @@
|
||||
[[elisp.org]]
|
||||
|
||||
[[eli
|
||||
sp.org]]
|
||||
|
||||
[[elisp.org::3]]
|
||||
6
org_mode_samples/object/regular_link/id_link.org
Normal file
6
org_mode_samples/object/regular_link/id_link.org
Normal file
@@ -0,0 +1,6 @@
|
||||
[[id:83986bdf-987c-465d-8851-44cb4c02a86c]]
|
||||
|
||||
[[id:83986bdf-987c-465d
|
||||
-8851-44cb4c02a86c]]
|
||||
|
||||
[[id:83986bdf-987c-465d-8851-44cb4c02a86c::foo]]
|
||||
6
org_mode_samples/object/regular_link/protocol_link.org
Normal file
6
org_mode_samples/object/regular_link/protocol_link.org
Normal file
@@ -0,0 +1,6 @@
|
||||
[[shell:foo]]
|
||||
|
||||
[[shell:fo
|
||||
o]]
|
||||
|
||||
[[shell:foo::3]]
|
||||
7
org_mode_samples/object/regular_link/template.org
Normal file
7
org_mode_samples/object/regular_link/template.org
Normal file
@@ -0,0 +1,7 @@
|
||||
#+LINK: foo https://foo.bar/baz#%s
|
||||
[[foo::lorem]]
|
||||
[[foo::ipsum][dolar]]
|
||||
|
||||
[[cat::bat]]
|
||||
#+LINK: cat dog%s
|
||||
[[cat:bat]]
|
||||
@@ -0,0 +1,7 @@
|
||||
# All the marks for repeater and warning delay
|
||||
[1970-01-01 Thu 8:15-13:15foo +1h -2h]
|
||||
[1970-01-01 Thu 8:15-13:15foo ++1d -2d]
|
||||
[1970-01-01 Thu 8:15-13:15foo .+1w -2w]
|
||||
[1970-01-01 Thu 8:15-13:15foo +1m --2m]
|
||||
[1970-01-01 Thu 8:15-13:15foo ++1y --2y]
|
||||
[1970-01-01 Thu 8:15-13:15foo .+1d --2h]
|
||||
@@ -2,13 +2,17 @@
|
||||
<%%(foo bar baz)>
|
||||
# active
|
||||
<1970-01-01 Thu 8:15rest +1w -1d>
|
||||
# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range.
|
||||
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
|
||||
# inactive
|
||||
[1970-01-01 Thu 8:15rest +1w -1d]
|
||||
# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range.
|
||||
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
|
||||
# active date range
|
||||
<1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d>
|
||||
# active time range
|
||||
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
|
||||
<1970-01-01 Thu 8:15-13:15otherrest +1w -1d>
|
||||
# inactive date range
|
||||
[1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d]
|
||||
# inactive time range
|
||||
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
|
||||
[1970-01-01 Thu 8:15-13:15otherrest +1w -1d]
|
||||
|
||||
2
org_mode_samples/object/timestamp/timeless_rest.org
Normal file
2
org_mode_samples/object/timestamp/timeless_rest.org
Normal file
@@ -0,0 +1,2 @@
|
||||
# This should be a malformed timestamp according to the current org-mode documentation but it is accepted anyway (with no repeater).
|
||||
<1970-01-01 Thu ++y>
|
||||
@@ -0,0 +1,2 @@
|
||||
<<<Footnotes>>> and stuff
|
||||
* Footnotes
|
||||
@@ -0,0 +1,3 @@
|
||||
* FOOTNOTES
|
||||
* Footnotes
|
||||
* footnotes
|
||||
@@ -0,0 +1,2 @@
|
||||
* Footnotes
|
||||
* Footnotes
|
||||
@@ -0,0 +1,3 @@
|
||||
* Foo
|
||||
* Footnotes :foo:bar:
|
||||
* Footnotes and stuff
|
||||
@@ -4,10 +4,23 @@ set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
: ${PROFILE:="perf"}
|
||||
|
||||
function main {
|
||||
local additional_flags=()
|
||||
if [ "$PROFILE" = "dev" ] || [ "$PROFILE" = "debug" ]; then
|
||||
PROFILE="debug"
|
||||
else
|
||||
additional_flags+=(--profile "$PROFILE")
|
||||
# We have to disable avx512 because valgrind does not yet support it.
|
||||
export RUSTFLAGS="-C target-feature=-avx512"
|
||||
fi
|
||||
|
||||
(cd "$DIR/../" && RUSTFLAGS="-C opt-level=0" cargo build --no-default-features)
|
||||
valgrind --tool=callgrind --callgrind-out-file="$DIR/../callgrind.out" "$DIR/../target/debug/parse" "${@}"
|
||||
(cd "$DIR/../" && RUSTFLAGS="-C target-cpu=x86-64-v3" cargo build --no-default-features "${additional_flags[@]}")
|
||||
valgrind --tool=callgrind --callgrind-out-file="$DIR/../callgrind.out" "$DIR/../target/${PROFILE}/parse" "${@}"
|
||||
|
||||
echo "You probably want to run:"
|
||||
echo "callgrind_annotate --auto=yes '$DIR/../callgrind.out'"
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::compare::diff::compare_document;
|
||||
use crate::compare::diff::DiffResult;
|
||||
use crate::compare::parse::emacs_parse_anonymous_org_document;
|
||||
use crate::compare::parse::emacs_parse_file_org_document;
|
||||
use crate::compare::parse::get_emacs_version;
|
||||
use crate::compare::parse::get_org_mode_version;
|
||||
use crate::compare::sexp::sexp;
|
||||
use crate::parser::parse;
|
||||
use crate::context::GlobalSettings;
|
||||
use crate::context::LocalFileAccessInterface;
|
||||
use crate::parser::parse_file_with_settings;
|
||||
use crate::parser::parse_with_settings;
|
||||
use crate::GlobalSettings;
|
||||
use crate::LocalFileAccessInterface;
|
||||
|
||||
pub fn run_anonymous_compare<P: AsRef<str>>(
|
||||
org_contents: P,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default())
|
||||
}
|
||||
|
||||
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 fn run_anonymous_compare_with_settings<P: AsRef<str>>(
|
||||
org_contents: P,
|
||||
global_settings: &GlobalSettings,
|
||||
) -> Result<(), 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();
|
||||
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
||||
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
||||
let rust_parsed = parse(org_contents)?;
|
||||
let org_sexp = emacs_parse_anonymous_org_document(org_contents)?;
|
||||
print_versions()?;
|
||||
let rust_parsed = parse_with_settings(org_contents, global_settings)?;
|
||||
let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings)?;
|
||||
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
||||
|
||||
println!("{}\n\n\n", org_contents);
|
||||
@@ -33,15 +44,23 @@ pub fn run_anonymous_compare<P: AsRef<str>>(
|
||||
|
||||
if diff_result.is_bad() {
|
||||
Err("Diff results do not match.")?;
|
||||
} else {
|
||||
println!(
|
||||
"{color}Entire document passes.{reset}",
|
||||
color = DiffResult::foreground_color(0, 255, 0),
|
||||
reset = DiffResult::reset_color(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
|
||||
org_path: P,
|
||||
global_settings: &GlobalSettings,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let org_path = org_path.as_ref();
|
||||
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
|
||||
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
|
||||
print_versions()?;
|
||||
let parent_directory = org_path
|
||||
.parent()
|
||||
.ok_or("Should be contained inside a directory.")?;
|
||||
@@ -53,12 +72,12 @@ pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn st
|
||||
working_directory: Some(parent_directory.to_path_buf()),
|
||||
};
|
||||
let global_settings = {
|
||||
let mut global_settings = GlobalSettings::default();
|
||||
let mut global_settings = global_settings.clone();
|
||||
global_settings.file_access = &file_access_interface;
|
||||
global_settings
|
||||
};
|
||||
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
|
||||
let org_sexp = emacs_parse_file_org_document(org_path)?;
|
||||
let 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 (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
|
||||
|
||||
println!("{}\n\n\n", org_contents);
|
||||
@@ -71,7 +90,19 @@ pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn st
|
||||
|
||||
if diff_result.is_bad() {
|
||||
Err("Diff results do not match.")?;
|
||||
} else {
|
||||
println!(
|
||||
"{color}Entire document passes.{reset}",
|
||||
color = DiffResult::foreground_color(0, 255, 0),
|
||||
reset = DiffResult::reset_color(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
104
src/compare/compare_field.rs
Normal file
104
src/compare/compare_field.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::diff::DiffStatus;
|
||||
use super::sexp::Token;
|
||||
use super::util::get_property;
|
||||
use super::util::get_property_quoted_string;
|
||||
use super::util::get_property_unquoted_atom;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum EmacsField<'s> {
|
||||
Required(&'s str),
|
||||
#[allow(dead_code)]
|
||||
Optional(&'s str),
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn compare_noop<'b, 's, 'x, R, RG>(
|
||||
_emacs: &'b Token<'s>,
|
||||
_rust_node: R,
|
||||
_emacs_field: &'x str,
|
||||
_rust_value_getter: RG,
|
||||
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn compare_identity() -> () {
|
||||
()
|
||||
}
|
||||
|
||||
/// Assert that the emacs value is always nil or absent.
|
||||
///
|
||||
/// This is usually used for fields which, in my testing, are always nil. Using this compare function instead of simply doing a compare_noop will enable us to be alerted when we finally come across an org-mode document that has a value other than nil for the property.
|
||||
pub(crate) fn compare_property_always_nil<'b, 's, 'x, R, RG>(
|
||||
emacs: &'b Token<'s>,
|
||||
_rust_node: R,
|
||||
emacs_field: &'x str,
|
||||
_rust_value_getter: RG,
|
||||
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
|
||||
let value = get_property(emacs, emacs_field)?;
|
||||
if value.is_some() {
|
||||
let this_status = DiffStatus::Bad;
|
||||
let message = Some(format!(
|
||||
"{} was expected to always be nil: {:?}",
|
||||
emacs_field, value
|
||||
));
|
||||
Ok(Some((this_status, message)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compare_property_quoted_string<
|
||||
'b,
|
||||
's,
|
||||
'x,
|
||||
R,
|
||||
RV: AsRef<str> + std::fmt::Debug,
|
||||
RG: Fn(R) -> Option<RV>,
|
||||
>(
|
||||
emacs: &'b Token<'s>,
|
||||
rust_node: R,
|
||||
emacs_field: &'x str,
|
||||
rust_value_getter: RG,
|
||||
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
|
||||
let value = get_property_quoted_string(emacs, emacs_field)?;
|
||||
let rust_value = rust_value_getter(rust_node);
|
||||
if rust_value.as_ref().map(|s| s.as_ref()) != value.as_ref().map(String::as_str) {
|
||||
let this_status = DiffStatus::Bad;
|
||||
let message = Some(format!(
|
||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||
emacs_field, value, rust_value
|
||||
));
|
||||
Ok(Some((this_status, message)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compare_property_unquoted_atom<'b, 's, 'x, R, RG: Fn(R) -> Option<&'s str>>(
|
||||
emacs: &'b Token<'s>,
|
||||
rust_node: R,
|
||||
emacs_field: &'x str,
|
||||
rust_value_getter: RG,
|
||||
) -> Result<Option<(DiffStatus, Option<String>)>, Box<dyn std::error::Error>> {
|
||||
let value = get_property_unquoted_atom(emacs, emacs_field)?;
|
||||
let rust_value = rust_value_getter(rust_node);
|
||||
if rust_value != value {
|
||||
let this_status = DiffStatus::Bad;
|
||||
let message = Some(format!(
|
||||
"{} mismatch (emacs != rust) {:?} != {:?}",
|
||||
emacs_field, value, rust_value
|
||||
));
|
||||
Ok(Some((this_status, message)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
3287
src/compare/diff.rs
3287
src/compare/diff.rs
File diff suppressed because it is too large
Load Diff
552
src/compare/elisp_fact.rs
Normal file
552
src/compare/elisp_fact.rs
Normal file
@@ -0,0 +1,552 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::types::AngleLink;
|
||||
use crate::types::AstNode;
|
||||
use crate::types::BabelCall;
|
||||
use crate::types::Bold;
|
||||
use crate::types::CenterBlock;
|
||||
use crate::types::Citation;
|
||||
use crate::types::CitationReference;
|
||||
use crate::types::Clock;
|
||||
use crate::types::Code;
|
||||
use crate::types::Comment;
|
||||
use crate::types::CommentBlock;
|
||||
use crate::types::DiarySexp;
|
||||
use crate::types::Document;
|
||||
use crate::types::Drawer;
|
||||
use crate::types::DynamicBlock;
|
||||
use crate::types::Element;
|
||||
use crate::types::Entity;
|
||||
use crate::types::ExampleBlock;
|
||||
use crate::types::ExportBlock;
|
||||
use crate::types::ExportSnippet;
|
||||
use crate::types::FixedWidthArea;
|
||||
use crate::types::FootnoteDefinition;
|
||||
use crate::types::FootnoteReference;
|
||||
use crate::types::Heading;
|
||||
use crate::types::HorizontalRule;
|
||||
use crate::types::InlineBabelCall;
|
||||
use crate::types::InlineSourceBlock;
|
||||
use crate::types::Italic;
|
||||
use crate::types::Keyword;
|
||||
use crate::types::LatexEnvironment;
|
||||
use crate::types::LatexFragment;
|
||||
use crate::types::LineBreak;
|
||||
use crate::types::NodeProperty;
|
||||
use crate::types::Object;
|
||||
use crate::types::OrgMacro;
|
||||
use crate::types::Paragraph;
|
||||
use crate::types::PlainLink;
|
||||
use crate::types::PlainList;
|
||||
use crate::types::PlainListItem;
|
||||
use crate::types::PlainText;
|
||||
use crate::types::Planning;
|
||||
use crate::types::PropertyDrawer;
|
||||
use crate::types::QuoteBlock;
|
||||
use crate::types::RadioLink;
|
||||
use crate::types::RadioTarget;
|
||||
use crate::types::RegularLink;
|
||||
use crate::types::Section;
|
||||
use crate::types::SpecialBlock;
|
||||
use crate::types::SrcBlock;
|
||||
use crate::types::StatisticsCookie;
|
||||
use crate::types::StrikeThrough;
|
||||
use crate::types::Subscript;
|
||||
use crate::types::Superscript;
|
||||
use crate::types::Table;
|
||||
use crate::types::TableCell;
|
||||
use crate::types::TableRow;
|
||||
use crate::types::Target;
|
||||
use crate::types::Timestamp;
|
||||
use crate::types::Underline;
|
||||
use crate::types::Verbatim;
|
||||
use crate::types::VerseBlock;
|
||||
|
||||
pub(crate) trait ElispFact<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str>;
|
||||
}
|
||||
|
||||
pub(crate) trait GetElispFact<'s> {
|
||||
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s>;
|
||||
}
|
||||
|
||||
impl<'s, I: ElispFact<'s>> GetElispFact<'s> for I {
|
||||
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> GetElispFact<'s> for AstNode<'r, 's> {
|
||||
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||
match self {
|
||||
AstNode::Document(inner) => *inner,
|
||||
AstNode::Heading(inner) => *inner,
|
||||
AstNode::Section(inner) => *inner,
|
||||
AstNode::Paragraph(inner) => *inner,
|
||||
AstNode::PlainList(inner) => *inner,
|
||||
AstNode::PlainListItem(inner) => *inner,
|
||||
AstNode::CenterBlock(inner) => *inner,
|
||||
AstNode::QuoteBlock(inner) => *inner,
|
||||
AstNode::SpecialBlock(inner) => *inner,
|
||||
AstNode::DynamicBlock(inner) => *inner,
|
||||
AstNode::FootnoteDefinition(inner) => *inner,
|
||||
AstNode::Comment(inner) => *inner,
|
||||
AstNode::Drawer(inner) => *inner,
|
||||
AstNode::PropertyDrawer(inner) => *inner,
|
||||
AstNode::NodeProperty(inner) => *inner,
|
||||
AstNode::Table(inner) => *inner,
|
||||
AstNode::TableRow(inner) => *inner,
|
||||
AstNode::VerseBlock(inner) => *inner,
|
||||
AstNode::CommentBlock(inner) => *inner,
|
||||
AstNode::ExampleBlock(inner) => *inner,
|
||||
AstNode::ExportBlock(inner) => *inner,
|
||||
AstNode::SrcBlock(inner) => *inner,
|
||||
AstNode::Clock(inner) => *inner,
|
||||
AstNode::DiarySexp(inner) => *inner,
|
||||
AstNode::Planning(inner) => *inner,
|
||||
AstNode::FixedWidthArea(inner) => *inner,
|
||||
AstNode::HorizontalRule(inner) => *inner,
|
||||
AstNode::Keyword(inner) => *inner,
|
||||
AstNode::BabelCall(inner) => *inner,
|
||||
AstNode::LatexEnvironment(inner) => *inner,
|
||||
AstNode::Bold(inner) => *inner,
|
||||
AstNode::Italic(inner) => *inner,
|
||||
AstNode::Underline(inner) => *inner,
|
||||
AstNode::StrikeThrough(inner) => *inner,
|
||||
AstNode::Code(inner) => *inner,
|
||||
AstNode::Verbatim(inner) => *inner,
|
||||
AstNode::PlainText(inner) => *inner,
|
||||
AstNode::RegularLink(inner) => *inner,
|
||||
AstNode::RadioLink(inner) => *inner,
|
||||
AstNode::RadioTarget(inner) => *inner,
|
||||
AstNode::PlainLink(inner) => *inner,
|
||||
AstNode::AngleLink(inner) => *inner,
|
||||
AstNode::OrgMacro(inner) => *inner,
|
||||
AstNode::Entity(inner) => *inner,
|
||||
AstNode::LatexFragment(inner) => *inner,
|
||||
AstNode::ExportSnippet(inner) => *inner,
|
||||
AstNode::FootnoteReference(inner) => *inner,
|
||||
AstNode::Citation(inner) => *inner,
|
||||
AstNode::CitationReference(inner) => *inner,
|
||||
AstNode::InlineBabelCall(inner) => *inner,
|
||||
AstNode::InlineSourceBlock(inner) => *inner,
|
||||
AstNode::LineBreak(inner) => *inner,
|
||||
AstNode::Target(inner) => *inner,
|
||||
AstNode::StatisticsCookie(inner) => *inner,
|
||||
AstNode::Subscript(inner) => *inner,
|
||||
AstNode::Superscript(inner) => *inner,
|
||||
AstNode::TableCell(inner) => *inner,
|
||||
AstNode::Timestamp(inner) => *inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> GetElispFact<'s> for Element<'s> {
|
||||
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||
match self {
|
||||
Element::Paragraph(inner) => inner,
|
||||
Element::PlainList(inner) => inner,
|
||||
Element::CenterBlock(inner) => inner,
|
||||
Element::QuoteBlock(inner) => inner,
|
||||
Element::SpecialBlock(inner) => inner,
|
||||
Element::DynamicBlock(inner) => inner,
|
||||
Element::FootnoteDefinition(inner) => inner,
|
||||
Element::Comment(inner) => inner,
|
||||
Element::Drawer(inner) => inner,
|
||||
Element::PropertyDrawer(inner) => inner,
|
||||
Element::Table(inner) => inner,
|
||||
Element::VerseBlock(inner) => inner,
|
||||
Element::CommentBlock(inner) => inner,
|
||||
Element::ExampleBlock(inner) => inner,
|
||||
Element::ExportBlock(inner) => inner,
|
||||
Element::SrcBlock(inner) => inner,
|
||||
Element::Clock(inner) => inner,
|
||||
Element::DiarySexp(inner) => inner,
|
||||
Element::Planning(inner) => inner,
|
||||
Element::FixedWidthArea(inner) => inner,
|
||||
Element::HorizontalRule(inner) => inner,
|
||||
Element::Keyword(inner) => inner,
|
||||
Element::BabelCall(inner) => inner,
|
||||
Element::LatexEnvironment(inner) => inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> GetElispFact<'s> for Object<'s> {
|
||||
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||
match self {
|
||||
Object::Bold(inner) => inner,
|
||||
Object::Italic(inner) => inner,
|
||||
Object::Underline(inner) => inner,
|
||||
Object::StrikeThrough(inner) => inner,
|
||||
Object::Code(inner) => inner,
|
||||
Object::Verbatim(inner) => inner,
|
||||
Object::PlainText(inner) => inner,
|
||||
Object::RegularLink(inner) => inner,
|
||||
Object::RadioLink(inner) => inner,
|
||||
Object::RadioTarget(inner) => inner,
|
||||
Object::PlainLink(inner) => inner,
|
||||
Object::AngleLink(inner) => inner,
|
||||
Object::OrgMacro(inner) => inner,
|
||||
Object::Entity(inner) => inner,
|
||||
Object::LatexFragment(inner) => inner,
|
||||
Object::ExportSnippet(inner) => inner,
|
||||
Object::FootnoteReference(inner) => inner,
|
||||
Object::Citation(inner) => inner,
|
||||
Object::CitationReference(inner) => inner,
|
||||
Object::InlineBabelCall(inner) => inner,
|
||||
Object::InlineSourceBlock(inner) => inner,
|
||||
Object::LineBreak(inner) => inner,
|
||||
Object::Target(inner) => inner,
|
||||
Object::StatisticsCookie(inner) => inner,
|
||||
Object::Subscript(inner) => inner,
|
||||
Object::Superscript(inner) => inner,
|
||||
Object::Timestamp(inner) => inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Document<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"org-data".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Section<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"section".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Heading<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"headline".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainList<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"plain-list".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainListItem<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"item".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for CenterBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"center-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for QuoteBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"quote-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for SpecialBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"special-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for DynamicBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"dynamic-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for FootnoteDefinition<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"footnote-definition".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Drawer<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"drawer".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PropertyDrawer<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"property-drawer".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for NodeProperty<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"node-property".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Table<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"table".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for TableRow<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"table-row".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Paragraph<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"paragraph".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for TableCell<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"table-cell".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Comment<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"comment".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for VerseBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"verse-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for CommentBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"comment-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for ExampleBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"example-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for ExportBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"export-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for SrcBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"src-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Clock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"clock".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for DiarySexp<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"diary-sexp".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Planning<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"planning".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for FixedWidthArea<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"fixed-width".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for HorizontalRule<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"horizontal-rule".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Keyword<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"keyword".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for BabelCall<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"babel-call".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for LatexEnvironment<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"latex-environment".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Bold<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"bold".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Italic<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"italic".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Underline<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"underline".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for StrikeThrough<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"strike-through".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Code<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"code".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Verbatim<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"verbatim".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for RegularLink<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for RadioLink<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for RadioTarget<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"radio-target".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainLink<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for AngleLink<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for OrgMacro<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"macro".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Entity<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"entity".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for LatexFragment<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"latex-fragment".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for ExportSnippet<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"export-snippet".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for FootnoteReference<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"footnote-reference".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Citation<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"citation".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for CitationReference<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"citation-reference".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for InlineBabelCall<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"inline-babel-call".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for InlineSourceBlock<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"inline-src-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for LineBreak<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"line-break".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Target<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"target".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for StatisticsCookie<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"statistics-cookie".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Subscript<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"subscript".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Superscript<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"superscript".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Timestamp<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"timestamp".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainText<'s> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
// plain text from upstream emacs does not actually have a name but this is included here to make rendering the status diff easier.
|
||||
"plain-text".into()
|
||||
}
|
||||
}
|
||||
155
src/compare/macros.rs
Normal file
155
src/compare/macros.rs
Normal file
@@ -0,0 +1,155 @@
|
||||
/// Assert only the listed properties exist on the Emacs AST node and compare their values to the values on the rust AST node.
|
||||
///
|
||||
/// Example invocation:
|
||||
/// ```
|
||||
/// if let Some((new_status, new_message)) = compare_properties!(
|
||||
/// emacs,
|
||||
/// rust,
|
||||
/// (
|
||||
/// EmacsField::Required(":key"),
|
||||
/// |r| Some(r.key),
|
||||
/// compare_property_quoted_string
|
||||
/// ),
|
||||
/// (
|
||||
/// EmacsField::Required(":value"),
|
||||
/// |r| Some(r.contents),
|
||||
/// compare_property_quoted_string
|
||||
/// ),
|
||||
/// (EmacsField::Required(":pre-blank"), compare_identity, compare_noop)
|
||||
/// )? {
|
||||
/// this_status = new_status;
|
||||
/// message = new_message;
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Or if the node has no properties aside from :standard-properties, we can still assert that the node has no unexpected properties:
|
||||
/// ```
|
||||
/// if let Some((new_status, new_message)) = compare_properties!(emacs)? {
|
||||
/// this_status = new_status;
|
||||
/// message = new_message;
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! compare_properties {
|
||||
($emacs:expr, $rust:expr, $(($emacs_field:expr, $rust_value_getter:expr, $compare_fn: expr)),+) => {
|
||||
{
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let mut message: Option<String> = None;
|
||||
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 {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"Emacs token lacks :standard-properties field.",
|
||||
));
|
||||
}
|
||||
$(
|
||||
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) => {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = 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(", ");
|
||||
this_status = DiffStatus::Bad;
|
||||
message = 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($emacs, $rust, emacs_name, $rust_value_getter)?;
|
||||
match result {
|
||||
Some((DiffStatus::Good, _)) => unreachable!("No comparison functions should return Some() when DiffStatus is good."),
|
||||
Some((status, msg)) => {
|
||||
this_status = status;
|
||||
message = msg;
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
)+
|
||||
|
||||
match this_status {
|
||||
DiffStatus::Good => {
|
||||
let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
|
||||
result
|
||||
},
|
||||
_ => {
|
||||
Ok(Some((this_status, message)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
// Default case for when there are no expected properties except for :standard-properties
|
||||
($emacs:expr) => {
|
||||
{
|
||||
let mut this_status = DiffStatus::Good;
|
||||
let mut message: Option<String> = None;
|
||||
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 {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"Emacs token lacks :standard-properties field.",
|
||||
));
|
||||
}
|
||||
|
||||
if !emacs_keys.is_empty() {
|
||||
let unexpected_keys: Vec<&str> = emacs_keys.into_iter().collect();
|
||||
let unexpected_keys = unexpected_keys.join(", ");
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"Emacs token had extra field(s): {}",
|
||||
unexpected_keys
|
||||
));
|
||||
}
|
||||
match this_status {
|
||||
DiffStatus::Good => {
|
||||
let result: Result<_, Box<dyn std::error::Error>> = Ok(None);
|
||||
result
|
||||
},
|
||||
_ => {
|
||||
Ok(Some((this_status, message)))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use compare_properties;
|
||||
@@ -1,7 +1,12 @@
|
||||
mod compare;
|
||||
mod compare_field;
|
||||
mod diff;
|
||||
mod elisp_fact;
|
||||
mod macros;
|
||||
mod parse;
|
||||
mod sexp;
|
||||
mod util;
|
||||
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;
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
pub fn emacs_parse_anonymous_org_document<C>(
|
||||
use crate::context::HeadlineLevelFilter;
|
||||
use crate::settings::GlobalSettings;
|
||||
|
||||
/// Generate elisp to configure org-mode parsing settings
|
||||
///
|
||||
/// Currently only org-list-allow-alphabetical is supported.
|
||||
fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
|
||||
// This string concatenation is wildly inefficient but its only called in tests 🤷.
|
||||
let mut ret = "".to_owned();
|
||||
if global_settings.list_allow_alphabetical {
|
||||
ret += "(setq org-list-allow-alphabetical t)\n"
|
||||
}
|
||||
if global_settings.tab_width != crate::settings::DEFAULT_TAB_WIDTH {
|
||||
ret += format!("(setq-default tab-width {})", global_settings.tab_width).as_str();
|
||||
}
|
||||
if global_settings.odd_levels_only != HeadlineLevelFilter::default() {
|
||||
ret += match global_settings.odd_levels_only {
|
||||
HeadlineLevelFilter::Odd => "(setq org-odd-levels-only t)\n",
|
||||
HeadlineLevelFilter::OddEven => "(setq org-odd-levels-only nil)\n",
|
||||
};
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
pub(crate) fn emacs_parse_anonymous_org_document<C>(
|
||||
file_contents: C,
|
||||
global_settings: &GlobalSettings,
|
||||
) -> Result<String, Box<dyn std::error::Error>>
|
||||
where
|
||||
C: AsRef<str>,
|
||||
@@ -14,10 +39,12 @@ where
|
||||
(require 'org)
|
||||
(defun org-table-align () t)
|
||||
(insert "{escaped_file_contents}")
|
||||
{global_settings}
|
||||
(org-mode)
|
||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||
)"#,
|
||||
escaped_file_contents = escaped_file_contents
|
||||
escaped_file_contents = escaped_file_contents,
|
||||
global_settings = global_settings_elisp(global_settings)
|
||||
);
|
||||
let mut cmd = Command::new("emacs");
|
||||
let cmd = cmd
|
||||
@@ -33,7 +60,10 @@ where
|
||||
Ok(String::from_utf8(org_sexp)?)
|
||||
}
|
||||
|
||||
pub fn emacs_parse_file_org_document<P>(file_path: P) -> Result<String, Box<dyn std::error::Error>>
|
||||
pub(crate) fn emacs_parse_file_org_document<P>(
|
||||
file_path: P,
|
||||
global_settings: &GlobalSettings,
|
||||
) -> Result<String, Box<dyn std::error::Error>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
@@ -46,9 +76,17 @@ where
|
||||
r#"(progn
|
||||
(require 'org)
|
||||
(defun org-table-align () t)
|
||||
(setq vc-handled-backends nil)
|
||||
{global_settings}
|
||||
(find-file-read-only "{file_path}")
|
||||
(org-mode)
|
||||
(message "%s" (pp-to-string (org-element-parse-buffer)))
|
||||
)"#
|
||||
)"#,
|
||||
global_settings = global_settings_elisp(global_settings),
|
||||
file_path = file_path
|
||||
.as_os_str()
|
||||
.to_str()
|
||||
.expect("File name should be valid utf-8.")
|
||||
);
|
||||
let mut cmd = Command::new("emacs");
|
||||
let cmd = cmd
|
||||
@@ -57,8 +95,6 @@ where
|
||||
.arg("--no-site-file")
|
||||
.arg("--no-splash")
|
||||
.arg("--batch")
|
||||
.arg("--insert")
|
||||
.arg(file_path.as_os_str())
|
||||
.arg("--eval")
|
||||
.arg(elisp_script);
|
||||
let out = cmd.output()?;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::escaped;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::take_till1;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::digit1;
|
||||
use nom::character::complete::multispace0;
|
||||
use nom::character::complete::multispace1;
|
||||
use nom::character::complete::one_of;
|
||||
@@ -11,6 +12,7 @@ use nom::combinator::map;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::separated_list1;
|
||||
use nom::sequence::delimited;
|
||||
use nom::sequence::preceded;
|
||||
@@ -18,6 +20,8 @@ use nom::sequence::tuple;
|
||||
|
||||
use crate::error::Res;
|
||||
|
||||
const MAX_OCTAL_LENGTH: usize = 3;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Token<'s> {
|
||||
Atom(&'s str),
|
||||
@@ -35,6 +39,7 @@ pub struct TextWithProperties<'s> {
|
||||
enum ParseState {
|
||||
Normal,
|
||||
Escape,
|
||||
Octal(Vec<u8>),
|
||||
}
|
||||
|
||||
impl<'s> Token<'s> {
|
||||
@@ -106,8 +111,8 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
|
||||
pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||
assert!(is_slice_of(input, remaining));
|
||||
fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||
debug_assert!(is_slice_of(input, remaining));
|
||||
let source = {
|
||||
let offset = remaining.as_ptr() as usize - input.as_ptr() as usize;
|
||||
&input[..offset]
|
||||
@@ -116,7 +121,7 @@ pub fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
|
||||
}
|
||||
|
||||
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut out = String::with_capacity(text.len());
|
||||
let mut out: Vec<u8> = Vec::with_capacity(text.len());
|
||||
if !text.starts_with(r#"""#) {
|
||||
return Err("Quoted text does not start with quote.".into());
|
||||
}
|
||||
@@ -125,30 +130,53 @@ pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>>
|
||||
}
|
||||
let interior_text = &text[1..(text.len() - 1)];
|
||||
let mut state = ParseState::Normal;
|
||||
for current_char in interior_text.chars().into_iter() {
|
||||
for current_char in interior_text.bytes().into_iter() {
|
||||
// Check to see if octal finished
|
||||
state = match (state, current_char) {
|
||||
(ParseState::Normal, '\\') => ParseState::Escape,
|
||||
(ParseState::Octal(octal), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => {
|
||||
ParseState::Octal(octal)
|
||||
}
|
||||
(ParseState::Octal(octal), _) => {
|
||||
let octal_number_string = String::from_utf8(octal)?;
|
||||
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
|
||||
out.push(decoded_byte);
|
||||
ParseState::Normal
|
||||
}
|
||||
(state, _) => state,
|
||||
};
|
||||
|
||||
state = match (state, current_char) {
|
||||
(ParseState::Normal, b'\\') => ParseState::Escape,
|
||||
(ParseState::Normal, _) => {
|
||||
out.push(current_char);
|
||||
ParseState::Normal
|
||||
}
|
||||
(ParseState::Escape, 'n') => {
|
||||
out.push('\n');
|
||||
(ParseState::Escape, b'n') => {
|
||||
out.push(b'\n');
|
||||
ParseState::Normal
|
||||
}
|
||||
(ParseState::Escape, '\\') => {
|
||||
out.push('\\');
|
||||
(ParseState::Escape, b'\\') => {
|
||||
out.push(b'\\');
|
||||
ParseState::Normal
|
||||
}
|
||||
(ParseState::Escape, '"') => {
|
||||
out.push('"');
|
||||
(ParseState::Escape, b'"') => {
|
||||
out.push(b'"');
|
||||
ParseState::Normal
|
||||
}
|
||||
_ => todo!(),
|
||||
(ParseState::Escape, b'0'..=b'7') => {
|
||||
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
|
||||
octal.push(current_char);
|
||||
ParseState::Octal(octal)
|
||||
}
|
||||
(ParseState::Octal(mut octal), b'0'..=b'7') => {
|
||||
octal.push(current_char);
|
||||
ParseState::Octal(octal)
|
||||
}
|
||||
_ => panic!("Invalid state unquoting string."),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
Ok(String::from_utf8(out)?)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -210,15 +238,30 @@ fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
|
||||
let (remaining, _) = tag(r#"""#)(input)?;
|
||||
let (remaining, _) = escaped(
|
||||
take_till1(|c| match c {
|
||||
'\\' | '"' => true,
|
||||
_ => false,
|
||||
}),
|
||||
'\\',
|
||||
one_of(r#""n\\"#),
|
||||
)(remaining)?;
|
||||
let (mut remaining, _) = tag(r#"""#)(input)?;
|
||||
let mut in_escape = false;
|
||||
loop {
|
||||
if in_escape {
|
||||
let (remain, _) = alt((recognize(one_of(r#""n\\"#)), digit1))(remaining)?;
|
||||
remaining = remain;
|
||||
in_escape = false;
|
||||
} else {
|
||||
let end_quote = tag::<_, _, nom::error::Error<_>>(r#"""#)(remaining);
|
||||
if end_quote.is_ok() {
|
||||
break;
|
||||
}
|
||||
|
||||
let escape_backslash = tag::<_, _, nom::error::Error<_>>("\\")(remaining);
|
||||
if let Ok((remain, _)) = escape_backslash {
|
||||
remaining = remain;
|
||||
in_escape = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
let (remain, _) = anychar(remaining)?;
|
||||
remaining = remain;
|
||||
}
|
||||
}
|
||||
let (remaining, _) = tag(r#"""#)(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, Token::Atom(source.into())))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user