Compare commits
118 Commits
0b2a5f4fbf
...
v0.1.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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"
|
||||
|
||||
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").
|
||||
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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +1,5 @@
|
||||
# Comment
|
||||
#
|
||||
# indented line
|
||||
# At the top of the file
|
||||
|
||||
|
||||
@@ -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'"
|
||||
echo "You probably want to run:"
|
||||
echo "callgrind_annotate --auto=yes '$DIR/../callgrind.out'"
|
||||
}
|
||||
|
||||
main "${@}"
|
||||
|
||||
@@ -6,21 +6,31 @@ 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);
|
||||
@@ -38,10 +48,12 @@ pub fn run_anonymous_compare<P: AsRef<str>>(
|
||||
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 +65,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);
|
||||
@@ -75,3 +87,9 @@ pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn st
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
1711
src/compare/diff.rs
1711
src/compare/diff.rs
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
||||
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;
|
||||
@@ -21,7 +23,6 @@ use crate::types::ExportSnippet;
|
||||
use crate::types::FixedWidthArea;
|
||||
use crate::types::FootnoteDefinition;
|
||||
use crate::types::FootnoteReference;
|
||||
use crate::types::GreaterBlock;
|
||||
use crate::types::Heading;
|
||||
use crate::types::HorizontalRule;
|
||||
use crate::types::InlineBabelCall;
|
||||
@@ -41,10 +42,12 @@ 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;
|
||||
@@ -60,25 +63,92 @@ use crate::types::Verbatim;
|
||||
use crate::types::VerseBlock;
|
||||
|
||||
pub(crate) trait ElispFact<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str>;
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str>;
|
||||
}
|
||||
|
||||
pub(crate) trait GetElispFact<'s> {
|
||||
fn get_elisp_fact(&'s self) -> &'s dyn ElispFact<'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(&'s self) -> &'s dyn ElispFact<'s> {
|
||||
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(&'s self) -> &'s dyn ElispFact<'s> {
|
||||
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||
match self {
|
||||
Element::Paragraph(inner) => inner,
|
||||
Element::PlainList(inner) => inner,
|
||||
Element::GreaterBlock(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,
|
||||
@@ -103,7 +173,7 @@ impl<'s> GetElispFact<'s> for Element<'s> {
|
||||
}
|
||||
|
||||
impl<'s> GetElispFact<'s> for Object<'s> {
|
||||
fn get_elisp_fact(&'s self) -> &'s dyn ElispFact<'s> {
|
||||
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
|
||||
match self {
|
||||
Object::Bold(inner) => inner,
|
||||
Object::Italic(inner) => inner,
|
||||
@@ -137,337 +207,345 @@ impl<'s> GetElispFact<'s> for Object<'s> {
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Document<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"org-data".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Section<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"section".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Heading<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"headline".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainList<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"plain-list".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainListItem<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"item".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for GreaterBlock<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
match self.name.to_lowercase().as_str() {
|
||||
"center" => "center-block".into(),
|
||||
"quote" => "quote-block".into(),
|
||||
_ => "special-block".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(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"dynamic-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for FootnoteDefinition<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"footnote-definition".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Drawer<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"drawer".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PropertyDrawer<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"property-drawer".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for NodeProperty<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"node-property".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Table<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"table".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for TableRow<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"table-row".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Paragraph<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"paragraph".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for TableCell<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"table-cell".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Comment<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"comment".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for VerseBlock<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"verse-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for CommentBlock<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"comment-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for ExampleBlock<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"example-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for ExportBlock<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"export-block".into()
|
||||
}
|
||||
}
|
||||
impl<'s> ElispFact<'s> for SrcBlock<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"src-block".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Clock<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"clock".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for DiarySexp<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"diary-sexp".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Planning<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"planning".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for FixedWidthArea<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"fixed-width".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for HorizontalRule<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"horizontal-rule".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Keyword<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"keyword".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for BabelCall<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"babel-call".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for LatexEnvironment<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"latex-environment".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Bold<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"bold".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Italic<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"italic".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Underline<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"underline".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for StrikeThrough<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"strike-through".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Code<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"code".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Verbatim<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"verbatim".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for RegularLink<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for RadioLink<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for RadioTarget<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"radio-target".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainLink<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for AngleLink<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"link".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for OrgMacro<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"macro".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Entity<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"entity".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for LatexFragment<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"latex-fragment".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for ExportSnippet<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"export-snippet".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for FootnoteReference<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"footnote-reference".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Citation<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"citation".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for CitationReference<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"citation-reference".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for InlineBabelCall<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
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(&'s self) -> Cow<'s, str> {
|
||||
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(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"line-break".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Target<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"target".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for StatisticsCookie<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"statistics-cookie".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Subscript<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"subscript".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Superscript<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"superscript".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for Timestamp<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
|
||||
"timestamp".into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> ElispFact<'s> for PlainText<'s> {
|
||||
fn get_elisp_name(&'s self) -> Cow<'s, str> {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -5,4 +5,6 @@ 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> {
|
||||
@@ -116,7 +121,7 @@ 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())))
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::elisp_fact::GetElispFact;
|
||||
use super::sexp::Token;
|
||||
use crate::compare::sexp::unquote;
|
||||
use crate::types::GetStandardProperties;
|
||||
use crate::types::StandardProperties;
|
||||
|
||||
@@ -15,9 +18,9 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
|
||||
/// Get the byte offset into source that the rust object exists at.
|
||||
///
|
||||
/// These offsets are zero-based unlike the elisp ones.
|
||||
fn get_rust_byte_offsets<'s, S: StandardProperties<'s> + ?Sized>(
|
||||
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||
original_document: &'s str,
|
||||
rust_ast_node: &'s S,
|
||||
rust_ast_node: &'b S,
|
||||
) -> (usize, usize) {
|
||||
let rust_object_source = rust_ast_node.get_source();
|
||||
debug_assert!(is_slice_of(original_document, rust_object_source));
|
||||
@@ -27,20 +30,21 @@ fn get_rust_byte_offsets<'s, S: StandardProperties<'s> + ?Sized>(
|
||||
}
|
||||
|
||||
pub(crate) fn compare_standard_properties<
|
||||
'b,
|
||||
's,
|
||||
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized,
|
||||
>(
|
||||
original_document: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s S,
|
||||
emacs: &'b Token<'s>,
|
||||
rust: &'b S,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
|
||||
assert_bounds(original_document, emacs, rust.get_standard_properties())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn assert_name<'s, S: AsRef<str>>(
|
||||
emacs: &'s Token<'s>,
|
||||
pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
|
||||
emacs: &'b Token<'s>,
|
||||
name: S,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let name = name.as_ref();
|
||||
@@ -62,10 +66,10 @@ pub(crate) fn assert_name<'s, S: AsRef<str>>(
|
||||
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
|
||||
///
|
||||
/// This does **not** handle plain text because plain text is a special case.
|
||||
pub(crate) fn assert_bounds<'s, S: StandardProperties<'s> + ?Sized>(
|
||||
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
|
||||
original_document: &'s str,
|
||||
emacs: &'s Token<'s>,
|
||||
rust: &'s S,
|
||||
emacs: &'b Token<'s>,
|
||||
rust: &'b S,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
|
||||
let (begin, end) = (
|
||||
@@ -98,8 +102,8 @@ struct EmacsStandardProperties {
|
||||
post_blank: Option<usize>,
|
||||
}
|
||||
|
||||
fn get_emacs_standard_properties<'s>(
|
||||
emacs: &'s Token<'s>,
|
||||
fn get_emacs_standard_properties<'b, 's>(
|
||||
emacs: &'b Token<'s>,
|
||||
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
|
||||
let children = emacs.as_list()?;
|
||||
let attributes_child = children
|
||||
@@ -168,25 +172,83 @@ fn maybe_token_to_usize(
|
||||
|
||||
/// Get a named property from the emacs token.
|
||||
///
|
||||
/// Returns Ok(None) if value is nil.
|
||||
///
|
||||
/// Returns error if the attribute is not specified on the token at all.
|
||||
pub(crate) fn get_property<'s, 'x>(
|
||||
emacs: &'s Token<'s>,
|
||||
/// Returns Ok(None) if value is nil or absent.
|
||||
pub(crate) fn get_property<'b, 's, 'x>(
|
||||
emacs: &'b Token<'s>,
|
||||
key: &'x str,
|
||||
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {
|
||||
) -> Result<Option<&'b Token<'s>>, Box<dyn std::error::Error>> {
|
||||
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 prop = attributes_map
|
||||
.get(key)
|
||||
.ok_or(format!("Missing {} attribute.", key))?;
|
||||
match prop.as_atom() {
|
||||
Ok("nil") => return Ok(None),
|
||||
let prop = attributes_map.get(key).map(|token| *token);
|
||||
match prop.map(|token| token.as_atom()) {
|
||||
Some(Ok("nil")) => return Ok(None),
|
||||
_ => {}
|
||||
};
|
||||
Ok(Some(*prop))
|
||||
Ok(prop)
|
||||
}
|
||||
|
||||
/// Get a named property containing an unquoted atom from the emacs token.
|
||||
///
|
||||
/// Returns None if key is not found.
|
||||
pub(crate) fn get_property_unquoted_atom<'b, 's, 'x>(
|
||||
emacs: &'b Token<'s>,
|
||||
key: &'x str,
|
||||
) -> Result<Option<&'s str>, Box<dyn std::error::Error>> {
|
||||
Ok(get_property(emacs, key)?
|
||||
.map(Token::as_atom)
|
||||
.map_or(Ok(None), |r| r.map(Some))?)
|
||||
}
|
||||
|
||||
/// Get a named property containing an quoted string from the emacs token.
|
||||
///
|
||||
/// Returns None if key is not found.
|
||||
pub(crate) fn get_property_quoted_string<'b, 's, 'x>(
|
||||
emacs: &'b Token<'s>,
|
||||
key: &'x str,
|
||||
) -> Result<Option<String>, Box<dyn std::error::Error>> {
|
||||
Ok(get_property(emacs, key)?
|
||||
.map(Token::as_atom)
|
||||
.map_or(Ok(None), |r| r.map(Some))?
|
||||
.map(unquote)
|
||||
.map_or(Ok(None), |r| r.map(Some))?)
|
||||
}
|
||||
|
||||
/// Get a named property containing a boolean value.
|
||||
///
|
||||
/// This uses the elisp convention of nil == false, non-nil == true.
|
||||
///
|
||||
/// Returns false if key is not found.
|
||||
pub(crate) fn get_property_boolean<'b, 's, 'x>(
|
||||
emacs: &'b Token<'s>,
|
||||
key: &'x str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
Ok(get_property(emacs, key)?
|
||||
.map(Token::as_atom)
|
||||
.map_or(Ok(None), |r| r.map(Some))?
|
||||
.unwrap_or("nil")
|
||||
!= "nil")
|
||||
}
|
||||
|
||||
/// Get a named property containing an unquoted numeric value.
|
||||
///
|
||||
/// Returns None if key is not found.
|
||||
pub(crate) fn get_property_numeric<'b, 's, 'x, N: FromStr>(
|
||||
emacs: &'b Token<'s>,
|
||||
key: &'x str,
|
||||
) -> Result<Option<N>, Box<dyn std::error::Error + 's>>
|
||||
where
|
||||
<N as FromStr>::Err: std::error::Error,
|
||||
<N as FromStr>::Err: 's,
|
||||
{
|
||||
let unparsed_string = get_property(emacs, key)?
|
||||
.map(Token::as_atom)
|
||||
.map_or(Ok(None), |r| r.map(Some))?;
|
||||
let parsed_number = unparsed_string
|
||||
.map(|val| val.parse::<N>())
|
||||
.map_or(Ok(None), |r| r.map(Some))?;
|
||||
Ok(parsed_number)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ pub struct GlobalSettings<'g, 's> {
|
||||
/// Set to true to allow for plain lists using single letters as the bullet in the same way that numbers are used.
|
||||
///
|
||||
/// Corresponds to the org-list-allow-alphabetical elisp variable.
|
||||
pub org_list_allow_alphabetical: bool,
|
||||
pub list_allow_alphabetical: bool,
|
||||
|
||||
/// How many spaces a tab should be equal to.
|
||||
///
|
||||
@@ -27,8 +27,15 @@ pub struct GlobalSettings<'g, 's> {
|
||||
///
|
||||
/// Corresponds to org-odd-levels-only elisp variable.
|
||||
pub odd_levels_only: HeadlineLevelFilter,
|
||||
|
||||
/// If a headline title matches this string exactly, then that section will become a "footnote section".
|
||||
///
|
||||
/// Corresponds to org-footnote-section elisp variable.
|
||||
pub footnote_section: &'g str,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
|
||||
|
||||
impl<'g, 's> GlobalSettings<'g, 's> {
|
||||
fn new() -> GlobalSettings<'g, 's> {
|
||||
GlobalSettings {
|
||||
@@ -38,9 +45,10 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
||||
},
|
||||
in_progress_todo_keywords: BTreeSet::new(),
|
||||
complete_todo_keywords: BTreeSet::new(),
|
||||
org_list_allow_alphabetical: false,
|
||||
tab_width: 8,
|
||||
odd_levels_only: HeadlineLevelFilter::OddEven,
|
||||
list_allow_alphabetical: false,
|
||||
tab_width: DEFAULT_TAB_WIDTH,
|
||||
odd_levels_only: HeadlineLevelFilter::default(),
|
||||
footnote_section: "Footnotes",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,8 +59,14 @@ impl<'g, 's> Default for GlobalSettings<'g, 's> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum HeadlineLevelFilter {
|
||||
Odd,
|
||||
OddEven,
|
||||
}
|
||||
|
||||
impl Default for HeadlineLevelFilter {
|
||||
fn default() -> Self {
|
||||
HeadlineLevelFilter::OddEven
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,6 @@ pub use file_access_interface::FileAccessInterface;
|
||||
pub use file_access_interface::LocalFileAccessInterface;
|
||||
pub use global_settings::GlobalSettings;
|
||||
pub use global_settings::HeadlineLevelFilter;
|
||||
pub use global_settings::DEFAULT_TAB_WIDTH;
|
||||
pub(crate) use list::List;
|
||||
pub(crate) use parser_with_context::parser_with_context;
|
||||
|
||||
@@ -4,12 +4,12 @@ use nom::IResult;
|
||||
|
||||
pub(crate) type Res<T, U> = IResult<T, U, CustomError<T>>;
|
||||
|
||||
// TODO: MyError probably shouldn't be based on the same type as the input type since it's used exclusively with static strings right now.
|
||||
#[derive(Debug)]
|
||||
pub enum CustomError<I> {
|
||||
MyError(MyError<I>),
|
||||
MyError(MyError<&'static str>),
|
||||
Nom(I, ErrorKind),
|
||||
IO(std::io::Error),
|
||||
BoxedError(Box<dyn std::error::Error>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -31,3 +31,15 @@ impl<I> From<std::io::Error> for CustomError<I> {
|
||||
CustomError::IO(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> From<&'static str> for CustomError<I> {
|
||||
fn from(value: &'static str) -> Self {
|
||||
CustomError::MyError(MyError(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
|
||||
fn from(value: Box<dyn std::error::Error>) -> Self {
|
||||
CustomError::BoxedError(value)
|
||||
}
|
||||
}
|
||||
|
||||
104
src/iter/all_ast_node_iter.rs
Normal file
104
src/iter/all_ast_node_iter.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::ast_node_iter::AstNodeIter;
|
||||
use crate::types::AstNode;
|
||||
|
||||
pub struct AllAstNodeIter<'r, 's> {
|
||||
root: Option<AstNode<'r, 's>>,
|
||||
queue: VecDeque<AstNodeIter<'r, 's>>,
|
||||
}
|
||||
|
||||
impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(root) = self.root.take() {
|
||||
self.queue.push_back(AstNodeIter::from_ast_node(&root));
|
||||
return Some(root);
|
||||
}
|
||||
while let Some(child) = self.queue.front_mut() {
|
||||
let next_elem_this_iter = match child {
|
||||
AstNodeIter::Document(ref mut i) => i.next(),
|
||||
AstNodeIter::Heading(ref mut i) => i.next(),
|
||||
AstNodeIter::Section(ref mut i) => i.next(),
|
||||
AstNodeIter::Paragraph(ref mut i) => i.next(),
|
||||
AstNodeIter::PlainList(ref mut i) => i.next(),
|
||||
AstNodeIter::PlainListItem(ref mut i) => i.next(),
|
||||
AstNodeIter::CenterBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::QuoteBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::SpecialBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::DynamicBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::FootnoteDefinition(ref mut i) => i.next(),
|
||||
AstNodeIter::Comment(ref mut i) => i.next(),
|
||||
AstNodeIter::Drawer(ref mut i) => i.next(),
|
||||
AstNodeIter::PropertyDrawer(ref mut i) => i.next(),
|
||||
AstNodeIter::NodeProperty(ref mut i) => i.next(),
|
||||
AstNodeIter::Table(ref mut i) => i.next(),
|
||||
AstNodeIter::TableRow(ref mut i) => i.next(),
|
||||
AstNodeIter::VerseBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::CommentBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::ExampleBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::ExportBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::SrcBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::Clock(ref mut i) => i.next(),
|
||||
AstNodeIter::DiarySexp(ref mut i) => i.next(),
|
||||
AstNodeIter::Planning(ref mut i) => i.next(),
|
||||
AstNodeIter::FixedWidthArea(ref mut i) => i.next(),
|
||||
AstNodeIter::HorizontalRule(ref mut i) => i.next(),
|
||||
AstNodeIter::Keyword(ref mut i) => i.next(),
|
||||
AstNodeIter::BabelCall(ref mut i) => i.next(),
|
||||
AstNodeIter::LatexEnvironment(ref mut i) => i.next(),
|
||||
AstNodeIter::Bold(ref mut i) => i.next(),
|
||||
AstNodeIter::Italic(ref mut i) => i.next(),
|
||||
AstNodeIter::Underline(ref mut i) => i.next(),
|
||||
AstNodeIter::StrikeThrough(ref mut i) => i.next(),
|
||||
AstNodeIter::Code(ref mut i) => i.next(),
|
||||
AstNodeIter::Verbatim(ref mut i) => i.next(),
|
||||
AstNodeIter::PlainText(ref mut i) => i.next(),
|
||||
AstNodeIter::RegularLink(ref mut i) => i.next(),
|
||||
AstNodeIter::RadioLink(ref mut i) => i.next(),
|
||||
AstNodeIter::RadioTarget(ref mut i) => i.next(),
|
||||
AstNodeIter::PlainLink(ref mut i) => i.next(),
|
||||
AstNodeIter::AngleLink(ref mut i) => i.next(),
|
||||
AstNodeIter::OrgMacro(ref mut i) => i.next(),
|
||||
AstNodeIter::Entity(ref mut i) => i.next(),
|
||||
AstNodeIter::LatexFragment(ref mut i) => i.next(),
|
||||
AstNodeIter::ExportSnippet(ref mut i) => i.next(),
|
||||
AstNodeIter::FootnoteReference(ref mut i) => i.next(),
|
||||
AstNodeIter::Citation(ref mut i) => i.next(),
|
||||
AstNodeIter::CitationReference(ref mut i) => i.next(),
|
||||
AstNodeIter::InlineBabelCall(ref mut i) => i.next(),
|
||||
AstNodeIter::InlineSourceBlock(ref mut i) => i.next(),
|
||||
AstNodeIter::LineBreak(ref mut i) => i.next(),
|
||||
AstNodeIter::Target(ref mut i) => i.next(),
|
||||
AstNodeIter::StatisticsCookie(ref mut i) => i.next(),
|
||||
AstNodeIter::Subscript(ref mut i) => i.next(),
|
||||
AstNodeIter::Superscript(ref mut i) => i.next(),
|
||||
AstNodeIter::TableCell(ref mut i) => i.next(),
|
||||
AstNodeIter::Timestamp(ref mut i) => i.next(),
|
||||
};
|
||||
if let Some(next_elem_this_iter) = next_elem_this_iter {
|
||||
self.queue
|
||||
.push_back(AstNodeIter::from_ast_node(&next_elem_this_iter));
|
||||
return Some(next_elem_this_iter);
|
||||
} else {
|
||||
self.queue.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> IntoIterator for AstNode<'r, 's> {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
type IntoIter = AllAstNodeIter<'r, 's>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
AllAstNodeIter {
|
||||
root: Some(self),
|
||||
queue: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
348
src/iter/ast_node_iter.rs
Normal file
348
src/iter/ast_node_iter.rs
Normal file
@@ -0,0 +1,348 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::macros::children_iter;
|
||||
use super::macros::empty_iter;
|
||||
use super::macros::multi_field_iter;
|
||||
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::DocumentElement;
|
||||
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;
|
||||
|
||||
/// Iterator over the AST nodes contained within the starting node.
|
||||
///
|
||||
/// This only iterates over the children, not the starting node itself. So an AstNodeIter::PlainList would only return PlainListItems, not the PlainList.
|
||||
///
|
||||
/// This only iterates over AST nodes, so an AstNodeIter::Heading would iterate over both the title and section contents, but it would not iterate over simple strings like the TODO keyword or priority.
|
||||
pub(crate) enum AstNodeIter<'r, 's> {
|
||||
// Document Nodes
|
||||
Document(DocumentIter<'r, 's>),
|
||||
Heading(HeadingIter<'r, 's>),
|
||||
Section(SectionIter<'r, 's>),
|
||||
// Elements
|
||||
Paragraph(ParagraphIter<'r, 's>),
|
||||
PlainList(PlainListIter<'r, 's>),
|
||||
PlainListItem(PlainListItemIter<'r, 's>),
|
||||
CenterBlock(CenterBlockIter<'r, 's>),
|
||||
QuoteBlock(QuoteBlockIter<'r, 's>),
|
||||
SpecialBlock(SpecialBlockIter<'r, 's>),
|
||||
DynamicBlock(DynamicBlockIter<'r, 's>),
|
||||
FootnoteDefinition(FootnoteDefinitionIter<'r, 's>),
|
||||
Comment(CommentIter<'r, 's>),
|
||||
Drawer(DrawerIter<'r, 's>),
|
||||
PropertyDrawer(PropertyDrawerIter<'r, 's>),
|
||||
NodeProperty(NodePropertyIter<'r, 's>),
|
||||
Table(TableIter<'r, 's>),
|
||||
TableRow(TableRowIter<'r, 's>),
|
||||
VerseBlock(VerseBlockIter<'r, 's>),
|
||||
CommentBlock(CommentBlockIter<'r, 's>),
|
||||
ExampleBlock(ExampleBlockIter<'r, 's>),
|
||||
ExportBlock(ExportBlockIter<'r, 's>),
|
||||
SrcBlock(SrcBlockIter<'r, 's>),
|
||||
Clock(ClockIter<'r, 's>),
|
||||
DiarySexp(DiarySexpIter<'r, 's>),
|
||||
Planning(PlanningIter<'r, 's>),
|
||||
FixedWidthArea(FixedWidthAreaIter<'r, 's>),
|
||||
HorizontalRule(HorizontalRuleIter<'r, 's>),
|
||||
Keyword(KeywordIter<'r, 's>),
|
||||
BabelCall(BabelCallIter<'r, 's>),
|
||||
LatexEnvironment(LatexEnvironmentIter<'r, 's>),
|
||||
// Objects
|
||||
Bold(BoldIter<'r, 's>),
|
||||
Italic(ItalicIter<'r, 's>),
|
||||
Underline(UnderlineIter<'r, 's>),
|
||||
StrikeThrough(StrikeThroughIter<'r, 's>),
|
||||
Code(CodeIter<'r, 's>),
|
||||
Verbatim(VerbatimIter<'r, 's>),
|
||||
PlainText(PlainTextIter<'r, 's>),
|
||||
RegularLink(RegularLinkIter<'r, 's>),
|
||||
RadioLink(RadioLinkIter<'r, 's>),
|
||||
RadioTarget(RadioTargetIter<'r, 's>),
|
||||
PlainLink(PlainLinkIter<'r, 's>),
|
||||
AngleLink(AngleLinkIter<'r, 's>),
|
||||
OrgMacro(OrgMacroIter<'r, 's>),
|
||||
Entity(EntityIter<'r, 's>),
|
||||
LatexFragment(LatexFragmentIter<'r, 's>),
|
||||
ExportSnippet(ExportSnippetIter<'r, 's>),
|
||||
FootnoteReference(FootnoteReferenceIter<'r, 's>),
|
||||
Citation(CitationIter<'r, 's>),
|
||||
CitationReference(CitationReferenceIter<'r, 's>),
|
||||
InlineBabelCall(InlineBabelCallIter<'r, 's>),
|
||||
InlineSourceBlock(InlineSourceBlockIter<'r, 's>),
|
||||
LineBreak(LineBreakIter<'r, 's>),
|
||||
Target(TargetIter<'r, 's>),
|
||||
StatisticsCookie(StatisticsCookieIter<'r, 's>),
|
||||
Subscript(SubscriptIter<'r, 's>),
|
||||
Superscript(SuperscriptIter<'r, 's>),
|
||||
TableCell(TableCellIter<'r, 's>),
|
||||
Timestamp(TimestampIter<'r, 's>),
|
||||
}
|
||||
|
||||
impl<'r, 's> AstNodeIter<'r, 's> {
|
||||
pub(crate) fn from_ast_node(node: &AstNode<'r, 's>) -> AstNodeIter<'r, 's> {
|
||||
match node {
|
||||
AstNode::Document(inner) => AstNodeIter::Document(inner.into_iter()),
|
||||
AstNode::Heading(inner) => AstNodeIter::Heading(inner.into_iter()),
|
||||
AstNode::Section(inner) => AstNodeIter::Section(inner.into_iter()),
|
||||
AstNode::Paragraph(inner) => AstNodeIter::Paragraph(inner.into_iter()),
|
||||
AstNode::PlainList(inner) => AstNodeIter::PlainList(inner.into_iter()),
|
||||
AstNode::PlainListItem(inner) => AstNodeIter::PlainListItem(inner.into_iter()),
|
||||
AstNode::CenterBlock(inner) => AstNodeIter::CenterBlock(inner.into_iter()),
|
||||
AstNode::QuoteBlock(inner) => AstNodeIter::QuoteBlock(inner.into_iter()),
|
||||
AstNode::SpecialBlock(inner) => AstNodeIter::SpecialBlock(inner.into_iter()),
|
||||
AstNode::DynamicBlock(inner) => AstNodeIter::DynamicBlock(inner.into_iter()),
|
||||
AstNode::FootnoteDefinition(inner) => {
|
||||
AstNodeIter::FootnoteDefinition(inner.into_iter())
|
||||
}
|
||||
AstNode::Comment(inner) => AstNodeIter::Comment(inner.into_iter()),
|
||||
AstNode::Drawer(inner) => AstNodeIter::Drawer(inner.into_iter()),
|
||||
AstNode::PropertyDrawer(inner) => AstNodeIter::PropertyDrawer(inner.into_iter()),
|
||||
AstNode::NodeProperty(inner) => AstNodeIter::NodeProperty(inner.into_iter()),
|
||||
AstNode::Table(inner) => AstNodeIter::Table(inner.into_iter()),
|
||||
AstNode::TableRow(inner) => AstNodeIter::TableRow(inner.into_iter()),
|
||||
AstNode::VerseBlock(inner) => AstNodeIter::VerseBlock(inner.into_iter()),
|
||||
AstNode::CommentBlock(inner) => AstNodeIter::CommentBlock(inner.into_iter()),
|
||||
AstNode::ExampleBlock(inner) => AstNodeIter::ExampleBlock(inner.into_iter()),
|
||||
AstNode::ExportBlock(inner) => AstNodeIter::ExportBlock(inner.into_iter()),
|
||||
AstNode::SrcBlock(inner) => AstNodeIter::SrcBlock(inner.into_iter()),
|
||||
AstNode::Clock(inner) => AstNodeIter::Clock(inner.into_iter()),
|
||||
AstNode::DiarySexp(inner) => AstNodeIter::DiarySexp(inner.into_iter()),
|
||||
AstNode::Planning(inner) => AstNodeIter::Planning(inner.into_iter()),
|
||||
AstNode::FixedWidthArea(inner) => AstNodeIter::FixedWidthArea(inner.into_iter()),
|
||||
AstNode::HorizontalRule(inner) => AstNodeIter::HorizontalRule(inner.into_iter()),
|
||||
AstNode::Keyword(inner) => AstNodeIter::Keyword(inner.into_iter()),
|
||||
AstNode::BabelCall(inner) => AstNodeIter::BabelCall(inner.into_iter()),
|
||||
AstNode::LatexEnvironment(inner) => AstNodeIter::LatexEnvironment(inner.into_iter()),
|
||||
AstNode::Bold(inner) => AstNodeIter::Bold(inner.into_iter()),
|
||||
AstNode::Italic(inner) => AstNodeIter::Italic(inner.into_iter()),
|
||||
AstNode::Underline(inner) => AstNodeIter::Underline(inner.into_iter()),
|
||||
AstNode::StrikeThrough(inner) => AstNodeIter::StrikeThrough(inner.into_iter()),
|
||||
AstNode::Code(inner) => AstNodeIter::Code(inner.into_iter()),
|
||||
AstNode::Verbatim(inner) => AstNodeIter::Verbatim(inner.into_iter()),
|
||||
AstNode::PlainText(inner) => AstNodeIter::PlainText(inner.into_iter()),
|
||||
AstNode::RegularLink(inner) => AstNodeIter::RegularLink(inner.into_iter()),
|
||||
AstNode::RadioLink(inner) => AstNodeIter::RadioLink(inner.into_iter()),
|
||||
AstNode::RadioTarget(inner) => AstNodeIter::RadioTarget(inner.into_iter()),
|
||||
AstNode::PlainLink(inner) => AstNodeIter::PlainLink(inner.into_iter()),
|
||||
AstNode::AngleLink(inner) => AstNodeIter::AngleLink(inner.into_iter()),
|
||||
AstNode::OrgMacro(inner) => AstNodeIter::OrgMacro(inner.into_iter()),
|
||||
AstNode::Entity(inner) => AstNodeIter::Entity(inner.into_iter()),
|
||||
AstNode::LatexFragment(inner) => AstNodeIter::LatexFragment(inner.into_iter()),
|
||||
AstNode::ExportSnippet(inner) => AstNodeIter::ExportSnippet(inner.into_iter()),
|
||||
AstNode::FootnoteReference(inner) => AstNodeIter::FootnoteReference(inner.into_iter()),
|
||||
AstNode::Citation(inner) => AstNodeIter::Citation(inner.into_iter()),
|
||||
AstNode::CitationReference(inner) => AstNodeIter::CitationReference(inner.into_iter()),
|
||||
AstNode::InlineBabelCall(inner) => AstNodeIter::InlineBabelCall(inner.into_iter()),
|
||||
AstNode::InlineSourceBlock(inner) => AstNodeIter::InlineSourceBlock(inner.into_iter()),
|
||||
AstNode::LineBreak(inner) => AstNodeIter::LineBreak(inner.into_iter()),
|
||||
AstNode::Target(inner) => AstNodeIter::Target(inner.into_iter()),
|
||||
AstNode::StatisticsCookie(inner) => AstNodeIter::StatisticsCookie(inner.into_iter()),
|
||||
AstNode::Subscript(inner) => AstNodeIter::Subscript(inner.into_iter()),
|
||||
AstNode::Superscript(inner) => AstNodeIter::Superscript(inner.into_iter()),
|
||||
AstNode::TableCell(inner) => AstNodeIter::TableCell(inner.into_iter()),
|
||||
AstNode::Timestamp(inner) => AstNodeIter::Timestamp(inner.into_iter()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
multi_field_iter!(
|
||||
Document<'s>,
|
||||
DocumentIter,
|
||||
zeroth_section,
|
||||
std::option::Iter<'r, Section<'s>>,
|
||||
children,
|
||||
std::slice::Iter<'r, Heading<'s>>
|
||||
);
|
||||
multi_field_iter!(
|
||||
Heading<'s>,
|
||||
HeadingIter,
|
||||
title,
|
||||
std::slice::Iter<'r, Object<'s>>,
|
||||
children,
|
||||
std::slice::Iter<'r, DocumentElement<'s>>
|
||||
);
|
||||
children_iter!(Section<'s>, SectionIter, std::slice::Iter<'r, Element<'s>>);
|
||||
children_iter!(
|
||||
Paragraph<'s>,
|
||||
ParagraphIter,
|
||||
std::slice::Iter<'r, Object<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
PlainList<'s>,
|
||||
PlainListIter,
|
||||
std::slice::Iter<'r, PlainListItem<'s>>
|
||||
);
|
||||
multi_field_iter!(
|
||||
PlainListItem<'s>,
|
||||
PlainListItemIter,
|
||||
tag,
|
||||
std::slice::Iter<'r, Object<'s>>,
|
||||
children,
|
||||
std::slice::Iter<'r, Element<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
CenterBlock<'s>,
|
||||
CenterBlockIter,
|
||||
std::slice::Iter<'r, Element<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
QuoteBlock<'s>,
|
||||
QuoteBlockIter,
|
||||
std::slice::Iter<'r, Element<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
SpecialBlock<'s>,
|
||||
SpecialBlockIter,
|
||||
std::slice::Iter<'r, Element<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
DynamicBlock<'s>,
|
||||
DynamicBlockIter,
|
||||
std::slice::Iter<'r, Element<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
FootnoteDefinition<'s>,
|
||||
FootnoteDefinitionIter,
|
||||
std::slice::Iter<'r, Element<'s>>
|
||||
);
|
||||
empty_iter!(Comment<'s>, CommentIter);
|
||||
children_iter!(Drawer<'s>, DrawerIter, std::slice::Iter<'r, Element<'s>>);
|
||||
children_iter!(
|
||||
PropertyDrawer<'s>,
|
||||
PropertyDrawerIter,
|
||||
std::slice::Iter<'r, NodeProperty<'s>>
|
||||
);
|
||||
empty_iter!(NodeProperty<'s>, NodePropertyIter);
|
||||
children_iter!(Table<'s>, TableIter, std::slice::Iter<'r, TableRow<'s>>);
|
||||
children_iter!(
|
||||
TableRow<'s>,
|
||||
TableRowIter,
|
||||
std::slice::Iter<'r, TableCell<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
VerseBlock<'s>,
|
||||
VerseBlockIter,
|
||||
std::slice::Iter<'r, Object<'s>>
|
||||
);
|
||||
empty_iter!(CommentBlock<'s>, CommentBlockIter);
|
||||
empty_iter!(ExampleBlock<'s>, ExampleBlockIter);
|
||||
empty_iter!(ExportBlock<'s>, ExportBlockIter);
|
||||
empty_iter!(SrcBlock<'s>, SrcBlockIter);
|
||||
empty_iter!(Clock<'s>, ClockIter);
|
||||
empty_iter!(DiarySexp<'s>, DiarySexpIter);
|
||||
empty_iter!(Planning<'s>, PlanningIter);
|
||||
empty_iter!(FixedWidthArea<'s>, FixedWidthAreaIter);
|
||||
empty_iter!(HorizontalRule<'s>, HorizontalRuleIter);
|
||||
empty_iter!(Keyword<'s>, KeywordIter);
|
||||
empty_iter!(BabelCall<'s>, BabelCallIter);
|
||||
empty_iter!(LatexEnvironment<'s>, LatexEnvironmentIter);
|
||||
children_iter!(Bold<'s>, BoldIter, std::slice::Iter<'r, Object<'s>>);
|
||||
children_iter!(Italic<'s>, ItalicIter, std::slice::Iter<'r, Object<'s>>);
|
||||
children_iter!(
|
||||
Underline<'s>,
|
||||
UnderlineIter,
|
||||
std::slice::Iter<'r, Object<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
StrikeThrough<'s>,
|
||||
StrikeThroughIter,
|
||||
std::slice::Iter<'r, Object<'s>>
|
||||
);
|
||||
empty_iter!(Code<'s>, CodeIter);
|
||||
empty_iter!(Verbatim<'s>, VerbatimIter);
|
||||
empty_iter!(PlainText<'s>, PlainTextIter);
|
||||
empty_iter!(RegularLink<'s>, RegularLinkIter);
|
||||
children_iter!(
|
||||
RadioLink<'s>,
|
||||
RadioLinkIter,
|
||||
std::slice::Iter<'r, Object<'s>>
|
||||
);
|
||||
children_iter!(
|
||||
RadioTarget<'s>,
|
||||
RadioTargetIter,
|
||||
std::slice::Iter<'r, Object<'s>>
|
||||
);
|
||||
empty_iter!(PlainLink<'s>, PlainLinkIter);
|
||||
empty_iter!(AngleLink<'s>, AngleLinkIter);
|
||||
empty_iter!(OrgMacro<'s>, OrgMacroIter);
|
||||
empty_iter!(Entity<'s>, EntityIter);
|
||||
empty_iter!(LatexFragment<'s>, LatexFragmentIter);
|
||||
empty_iter!(ExportSnippet<'s>, ExportSnippetIter);
|
||||
multi_field_iter!(
|
||||
FootnoteReference<'s>,
|
||||
FootnoteReferenceIter,
|
||||
definition,
|
||||
std::slice::Iter<'r, Object<'s>>,
|
||||
);
|
||||
empty_iter!(Citation<'s>, CitationIter);
|
||||
empty_iter!(CitationReference<'s>, CitationReferenceIter);
|
||||
empty_iter!(InlineBabelCall<'s>, InlineBabelCallIter);
|
||||
empty_iter!(InlineSourceBlock<'s>, InlineSourceBlockIter);
|
||||
empty_iter!(LineBreak<'s>, LineBreakIter);
|
||||
empty_iter!(Target<'s>, TargetIter);
|
||||
empty_iter!(StatisticsCookie<'s>, StatisticsCookieIter);
|
||||
empty_iter!(Subscript<'s>, SubscriptIter);
|
||||
empty_iter!(Superscript<'s>, SuperscriptIter);
|
||||
children_iter!(
|
||||
TableCell<'s>,
|
||||
TableCellIter,
|
||||
std::slice::Iter<'r, Object<'s>>
|
||||
);
|
||||
empty_iter!(Timestamp<'s>, TimestampIter);
|
||||
101
src/iter/macros.rs
Normal file
101
src/iter/macros.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
/// Create iterators for ast nodes where it only has to iterate over children
|
||||
macro_rules! children_iter {
|
||||
($astnodetype:ty, $itertype:ident, $innertype:ty) => {
|
||||
pub struct $itertype<'r, 's> {
|
||||
next: $innertype,
|
||||
}
|
||||
|
||||
impl<'r, 's> Iterator for $itertype<'r, 's> {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.next.next().map(Into::<AstNode>::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> IntoIterator for &'r $astnodetype {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
type IntoIter = $itertype<'r, 's>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
$itertype {
|
||||
next: self.children.iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use children_iter;
|
||||
|
||||
/// Create iterators for ast nodes that do not contain any ast node children.
|
||||
macro_rules! empty_iter {
|
||||
($astnodetype:ty, $itertype:ident) => {
|
||||
pub struct $itertype<'r, 's> {
|
||||
phantom: PhantomData<&'r $astnodetype>,
|
||||
}
|
||||
|
||||
impl<'r, 's> Iterator for $itertype<'r, 's> {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> IntoIterator for &'r $astnodetype {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
type IntoIter = $itertype<'r, 's>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
$itertype {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use empty_iter;
|
||||
|
||||
/// Create iterators for ast nodes where it has to iterate over multiple child lists.
|
||||
macro_rules! multi_field_iter {
|
||||
($astnodetype:ty, $itertype:ident, $firstfieldname: ident, $firstinnertype:ty, $($fieldname: ident, $innertype:ty),*) => {
|
||||
pub struct $itertype<'r, 's> {
|
||||
$firstfieldname: $firstinnertype,
|
||||
$(
|
||||
$fieldname: $innertype,
|
||||
),*
|
||||
}
|
||||
|
||||
impl<'r, 's> Iterator for $itertype<'r, 's> {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.$firstfieldname.next().map(Into::<AstNode>::into)
|
||||
$(
|
||||
.or_else(|| self.$fieldname.next().map(Into::<AstNode>::into))
|
||||
),*
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> IntoIterator for &'r $astnodetype {
|
||||
type Item = AstNode<'r, 's>;
|
||||
|
||||
type IntoIter = $itertype<'r, 's>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
$itertype {
|
||||
$firstfieldname: self.$firstfieldname.iter(),
|
||||
$(
|
||||
$fieldname: self.$fieldname.iter(),
|
||||
),*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use multi_field_iter;
|
||||
3
src/iter/mod.rs
Normal file
3
src/iter/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod all_ast_node_iter;
|
||||
mod ast_node_iter;
|
||||
mod macros;
|
||||
13
src/lib.rs
13
src/lib.rs
@@ -1,6 +1,6 @@
|
||||
#![feature(round_char_boundary)]
|
||||
#![feature(exit_status_error)]
|
||||
#![feature(trait_alias)]
|
||||
#![feature(path_file_prefix)]
|
||||
// TODO: #![warn(missing_docs)]
|
||||
|
||||
#[cfg(feature = "compare")]
|
||||
@@ -8,9 +8,14 @@ pub mod compare;
|
||||
|
||||
mod context;
|
||||
mod error;
|
||||
mod iter;
|
||||
pub mod parser;
|
||||
pub mod types;
|
||||
|
||||
pub use context::FileAccessInterface;
|
||||
pub use context::GlobalSettings;
|
||||
pub use context::LocalFileAccessInterface;
|
||||
pub mod settings {
|
||||
pub use crate::context::FileAccessInterface;
|
||||
pub use crate::context::GlobalSettings;
|
||||
pub use crate::context::HeadlineLevelFilter;
|
||||
pub use crate::context::LocalFileAccessInterface;
|
||||
pub use crate::context::DEFAULT_TAB_WIDTH;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ use std::path::Path;
|
||||
|
||||
use ::organic::parser::parse;
|
||||
use organic::parser::parse_with_settings;
|
||||
use organic::GlobalSettings;
|
||||
use organic::LocalFileAccessInterface;
|
||||
use organic::settings::GlobalSettings;
|
||||
use organic::settings::LocalFileAccessInterface;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
use crate::init_tracing::init_telemetry;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::sequence::preceded;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::get_consumed;
|
||||
use super::util::org_line_ending;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::RefContext;
|
||||
@@ -38,15 +39,29 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
|
||||
let parser_context = context.with_additional_node(&parser_context);
|
||||
let comment_line_matcher = parser_with_context!(comment_line)(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
let (remaining, _first_line) = comment_line_matcher(input)?;
|
||||
let (remaining, _remaining_lines) =
|
||||
let (remaining, first_line) = comment_line_matcher(input)?;
|
||||
let (remaining, mut remaining_lines) =
|
||||
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
|
||||
let last_line = remaining_lines.pop();
|
||||
if let Some(last_line) = last_line {
|
||||
value.push(Into::<&str>::into(first_line));
|
||||
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
|
||||
let last_line = Into::<&str>::into(last_line);
|
||||
// Trim the line ending from the final line.
|
||||
value.push(&last_line[..(last_line.len() - 1)])
|
||||
} else {
|
||||
// Trim the line ending from the only line.
|
||||
let only_line = Into::<&str>::into(first_line);
|
||||
value.push(&only_line[..(only_line.len() - 1)])
|
||||
}
|
||||
Ok((
|
||||
remaining,
|
||||
Comment {
|
||||
source: source.into(),
|
||||
value,
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -57,14 +72,13 @@ fn comment_line<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, _indent) = space0(input)?;
|
||||
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
|
||||
tag("#"),
|
||||
opt(tuple((space1, is_not("\r\n")))),
|
||||
alt((line_ending, eof)),
|
||||
))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
let (remaining, _) = tuple((space0, tag("#")))(input)?;
|
||||
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
|
||||
return Ok((remaining, line_break));
|
||||
}
|
||||
let (remaining, _) = tag(" ")(remaining)?;
|
||||
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
|
||||
Ok((remaining, value))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use std::path::Path;
|
||||
|
||||
use nom::combinator::all_consuming;
|
||||
use nom::combinator::opt;
|
||||
use nom::multi::many0;
|
||||
|
||||
use super::headline::heading;
|
||||
use super::in_buffer_settings::apply_in_buffer_settings;
|
||||
use super::in_buffer_settings::apply_post_parse_in_buffer_settings;
|
||||
use super::in_buffer_settings::scan_for_in_buffer_settings;
|
||||
use super::org_source::OrgSource;
|
||||
use super::section::zeroth_section;
|
||||
use super::token::AllTokensIterator;
|
||||
use super::token::Token;
|
||||
use super::util::get_consumed;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::Context;
|
||||
@@ -21,35 +22,77 @@ use crate::error::MyError;
|
||||
use crate::error::Res;
|
||||
use crate::parser::org_source::convert_error;
|
||||
use crate::parser::util::blank_line;
|
||||
use crate::types::AstNode;
|
||||
use crate::types::Document;
|
||||
use crate::types::Object;
|
||||
|
||||
/// Parse a full org-mode document.
|
||||
///
|
||||
/// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document.
|
||||
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document without an underlying file attached.
|
||||
#[allow(dead_code)]
|
||||
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||
parse_with_settings(input, &GlobalSettings::default())
|
||||
parse_file_with_settings::<&Path>(input, &GlobalSettings::default(), None)
|
||||
}
|
||||
|
||||
/// Parse a full org-mode document.
|
||||
///
|
||||
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path.
|
||||
///
|
||||
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_file<'s, P: AsRef<Path>>(
|
||||
input: &'s str,
|
||||
file_path: Option<P>,
|
||||
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||
parse_file_with_settings(input, &GlobalSettings::default(), file_path)
|
||||
}
|
||||
|
||||
/// Parse a full org-mode document with starting settings.
|
||||
///
|
||||
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied.
|
||||
/// This is a secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied without an underlying file attached.
|
||||
///
|
||||
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_with_settings<'g, 's>(
|
||||
input: &'s str,
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||
parse_file_with_settings::<&Path>(input, global_settings, None)
|
||||
}
|
||||
|
||||
/// Parse a full org-mode document with starting settings.
|
||||
///
|
||||
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path starting with the settings you supplied.
|
||||
///
|
||||
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
|
||||
///
|
||||
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
|
||||
#[allow(dead_code)]
|
||||
pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
|
||||
input: &'s str,
|
||||
global_settings: &'g GlobalSettings<'g, 's>,
|
||||
file_path: Option<P>,
|
||||
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(global_settings, List::new(&initial_context));
|
||||
let wrapped_input = OrgSource::new(input);
|
||||
let ret =
|
||||
let mut doc =
|
||||
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
|
||||
.map_err(|err| err.to_string())
|
||||
.map(|(_remaining, parsed_document)| parsed_document);
|
||||
Ok(ret?)
|
||||
.map(|(_remaining, parsed_document)| parsed_document)?;
|
||||
if let Some(file_path) = file_path {
|
||||
let full_path = file_path.as_ref().canonicalize()?;
|
||||
if doc.category.is_none() {
|
||||
let category = full_path
|
||||
.file_stem()
|
||||
.expect("File should have a name.")
|
||||
.to_str()
|
||||
.expect("File name should be valid utf-8.");
|
||||
doc.category = Some(category.to_owned());
|
||||
}
|
||||
doc.path = Some(full_path);
|
||||
}
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
/// Parse a full org-mode document.
|
||||
@@ -107,19 +150,18 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
||||
let new_context = context.with_global_settings(&new_settings);
|
||||
let context = &new_context;
|
||||
|
||||
let (remaining, document) =
|
||||
let (remaining, mut document) =
|
||||
_document(context, input).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
||||
{
|
||||
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
|
||||
let all_radio_targets: Vec<&Vec<Object<'_>>> = document
|
||||
.iter_tokens()
|
||||
.filter_map(|tkn| match tkn {
|
||||
Token::Object(obj) => Some(obj),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(|obj| match obj {
|
||||
Object::RadioTarget(rt) => Some(rt),
|
||||
_ => None,
|
||||
let all_radio_targets: Vec<&Vec<Object<'_>>> = Into::<AstNode>::into(&document)
|
||||
.into_iter()
|
||||
.filter_map(|ast_node| {
|
||||
if let AstNode::RadioTarget(ast_node) = ast_node {
|
||||
Some(ast_node)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|rt| &rt.children)
|
||||
.collect();
|
||||
@@ -127,11 +169,18 @@ fn document_org_source<'b, 'g, 'r, 's>(
|
||||
let mut new_global_settings = context.get_global_settings().clone();
|
||||
new_global_settings.radio_targets = all_radio_targets;
|
||||
let parser_context = context.with_global_settings(&new_global_settings);
|
||||
let (remaining, document) = _document(&parser_context, input)
|
||||
let (remaining, mut document) = _document(&parser_context, input)
|
||||
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
|
||||
apply_post_parse_in_buffer_settings(&mut document)
|
||||
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
||||
return Ok((remaining.into(), document));
|
||||
}
|
||||
}
|
||||
|
||||
// Find final in-buffer settings that do not impact parsing
|
||||
apply_post_parse_in_buffer_settings(&mut document)
|
||||
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
|
||||
|
||||
Ok((remaining.into(), document))
|
||||
}
|
||||
|
||||
@@ -150,14 +199,10 @@ fn _document<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Document {
|
||||
source: source.into(),
|
||||
category: None,
|
||||
path: None,
|
||||
zeroth_section,
|
||||
children,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
impl<'s> Document<'s> {
|
||||
fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
||||
AllTokensIterator::new(Token::Document(self))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
@@ -9,6 +10,7 @@ use nom::combinator::consumed;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
@@ -47,10 +49,11 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
|
||||
}
|
||||
start_of_line(input)?;
|
||||
let (remaining, _leading_whitespace) = space0(input)?;
|
||||
let (remaining, (_begin, name, parameters, _ws)) = tuple((
|
||||
let (remaining, (_, name, parameters, _, _)) = tuple((
|
||||
recognize(tuple((tag_no_case("#+begin:"), space1))),
|
||||
name,
|
||||
opt(tuple((space1, parameters))),
|
||||
space0,
|
||||
line_ending,
|
||||
))(remaining)?;
|
||||
let contexts = [
|
||||
@@ -108,7 +111,7 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
is_not("\r\n")(input)
|
||||
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
|
||||
@@ -81,7 +81,7 @@ fn _element<'b, 'g, 'r, 's>(
|
||||
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
|
||||
let (remaining, mut element) = match alt((
|
||||
map(plain_list_matcher, Element::PlainList),
|
||||
map(greater_block_matcher, Element::GreaterBlock),
|
||||
greater_block_matcher,
|
||||
map(dynamic_block_matcher, Element::DynamicBlock),
|
||||
map(footnote_definition_matcher, Element::FootnoteDefinition),
|
||||
map(comment_matcher, Element::Comment),
|
||||
@@ -107,6 +107,7 @@ fn _element<'b, 'g, 'r, 's>(
|
||||
match map(paragraph_matcher, Element::Paragraph)(remaining) {
|
||||
the_ok @ Ok(_) => the_ok,
|
||||
Err(_) => {
|
||||
// TODO: Because this function expects a single element, if there are multiple affiliated keywords before an element that cannot have affiliated keywords, we end up re-parsing the affiliated keywords many times.
|
||||
affiliated_keywords.clear();
|
||||
map(affiliated_keyword_matcher, Element::Keyword)(input)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
@@ -8,6 +9,8 @@ use nom::combinator::consumed;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
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;
|
||||
@@ -30,17 +33,18 @@ use crate::parser::util::blank_line;
|
||||
use crate::parser::util::exit_matcher_parser;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::CenterBlock;
|
||||
use crate::types::Element;
|
||||
use crate::types::GreaterBlock;
|
||||
use crate::types::Paragraph;
|
||||
use crate::types::QuoteBlock;
|
||||
use crate::types::SetSource;
|
||||
use crate::types::SpecialBlock;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
|
||||
// TODO: Do I need to differentiate between different greater block types.
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, _leading_whitespace) = space0(input)?;
|
||||
let (remaining, (_begin, name)) = tuple((
|
||||
@@ -52,22 +56,97 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||
}
|
||||
}),
|
||||
))(remaining)?;
|
||||
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
|
||||
"center" => "center block".to_owned(),
|
||||
"quote" => "quote block".to_owned(),
|
||||
name @ _ => format!("special block {}", name),
|
||||
let name = Into::<&str>::into(name);
|
||||
let (remaining, element) = match name.to_lowercase().as_str() {
|
||||
"center" => center_block(context, remaining, input)?,
|
||||
"quote" => quote_block(context, remaining, input)?,
|
||||
_ => special_block(name)(context, remaining, input)?,
|
||||
};
|
||||
if in_section(context, context_name.as_str()) {
|
||||
Ok((remaining, element))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn center_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
original_input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
let (remaining, (source, children)) =
|
||||
greater_block_body(context, input, original_input, "center", "center block")?;
|
||||
Ok((
|
||||
remaining,
|
||||
Element::CenterBlock(CenterBlock { source, children }),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn quote_block<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
original_input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
let (remaining, (source, children)) =
|
||||
greater_block_body(context, input, original_input, "quote", "quote block")?;
|
||||
Ok((
|
||||
remaining,
|
||||
Element::QuoteBlock(QuoteBlock { source, children }),
|
||||
))
|
||||
}
|
||||
|
||||
fn special_block<'s>(
|
||||
name: &'s str,
|
||||
) -> impl for<'b, 'g, 'r> Fn(
|
||||
RefContext<'b, 'g, 'r, 's>,
|
||||
OrgSource<'s>,
|
||||
OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Element<'s>>
|
||||
+ 's {
|
||||
let context_name = format!("special block {}", name);
|
||||
move |context, input, original_input| {
|
||||
_special_block(context, input, original_input, name, context_name.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn _special_block<'c, 'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
original_input: OrgSource<'s>,
|
||||
name: &'s str,
|
||||
context_name: &'c str,
|
||||
) -> Res<OrgSource<'s>, Element<'s>> {
|
||||
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
|
||||
let (remaining, (source, children)) =
|
||||
greater_block_body(context, remaining, original_input, name, context_name)?;
|
||||
Ok((
|
||||
remaining,
|
||||
Element::SpecialBlock(SpecialBlock {
|
||||
source,
|
||||
children,
|
||||
name,
|
||||
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn greater_block_body<'c, 'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
original_input: OrgSource<'s>,
|
||||
name: &'c str,
|
||||
context_name: &'c str,
|
||||
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
|
||||
if in_section(context, context_name) {
|
||||
return Err(nom::Err::Error(CustomError::MyError(MyError(
|
||||
"Cannot nest objects of the same element".into(),
|
||||
))));
|
||||
}
|
||||
let exit_with_name = greater_block_end(name.into());
|
||||
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
|
||||
let (remaining, _nl) = line_ending(remaining)?;
|
||||
let exit_with_name = greater_block_end(name);
|
||||
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
|
||||
let contexts = [
|
||||
ContextElement::ConsumeTrailingWhitespace(true),
|
||||
ContextElement::Context(context_name.as_str()),
|
||||
ContextElement::Context(context_name),
|
||||
ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Alpha,
|
||||
exit_matcher: &exit_with_name,
|
||||
@@ -76,11 +155,6 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||
let parser_context = context.with_additional_node(&contexts[0]);
|
||||
let parser_context = parser_context.with_additional_node(&contexts[1]);
|
||||
let parser_context = parser_context.with_additional_node(&contexts[2]);
|
||||
let parameters = match parameters {
|
||||
Some((_ws, parameters)) => Some(parameters),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let element_matcher = parser_with_context!(element(true))(&parser_context);
|
||||
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
|
||||
not(exit_matcher)(remaining)?;
|
||||
@@ -104,16 +178,8 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
|
||||
|
||||
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
GreaterBlock {
|
||||
source: source.into(),
|
||||
name: name.into(),
|
||||
parameters: parameters.map(|val| Into::<&str>::into(val)),
|
||||
children,
|
||||
},
|
||||
))
|
||||
let source = get_consumed(original_input, remaining);
|
||||
Ok((remaining, (Into::<&str>::into(source), children)))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -123,7 +189,7 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
is_not("\r\n")(input)
|
||||
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
|
||||
}
|
||||
|
||||
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
|
||||
|
||||
@@ -4,6 +4,7 @@ use nom::bytes::complete::tag;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::consumed;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
@@ -33,6 +34,7 @@ use crate::error::Res;
|
||||
use crate::parser::object_parser::standard_set_object;
|
||||
use crate::parser::util::blank_line;
|
||||
use crate::types::DocumentElement;
|
||||
use crate::types::Element;
|
||||
use crate::types::Heading;
|
||||
use crate::types::HeadlineLevel;
|
||||
use crate::types::Object;
|
||||
@@ -54,27 +56,27 @@ fn _heading<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
let mut scheduled = None;
|
||||
let mut deadline = None;
|
||||
let mut closed = None;
|
||||
not(|i| context.check_exit_matcher(i))(input)?;
|
||||
let (
|
||||
remaining,
|
||||
(
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword,
|
||||
maybe_priority,
|
||||
maybe_comment,
|
||||
title,
|
||||
heading_tags,
|
||||
),
|
||||
) = headline(context, input, parent_star_count)?;
|
||||
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
|
||||
let section_matcher = parser_with_context!(section)(context);
|
||||
let heading_matcher = parser_with_context!(heading(star_count))(context);
|
||||
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
|
||||
let (remaining, maybe_section) =
|
||||
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
|
||||
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
|
||||
let (remaining, mut children) =
|
||||
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
|
||||
if let Some(section) = maybe_section {
|
||||
// If the section has a planning then the timestamp values are copied to the heading.
|
||||
if let DocumentElement::Section(inner_section) = §ion {
|
||||
if let Some(Element::Planning(planning)) = inner_section.children.first() {
|
||||
scheduled = planning.scheduled.clone();
|
||||
deadline = planning.deadline.clone();
|
||||
closed = planning.closed.clone();
|
||||
}
|
||||
}
|
||||
children.insert(0, section);
|
||||
}
|
||||
let remaining = if children.is_empty() {
|
||||
@@ -84,23 +86,29 @@ fn _heading<'b, 'g, 'r, 's>(
|
||||
} else {
|
||||
remaining
|
||||
};
|
||||
let is_archived = heading_tags.contains(&"ARCHIVE");
|
||||
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
Heading {
|
||||
source: source.into(),
|
||||
level: headline_level,
|
||||
todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
|
||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||
}),
|
||||
priority_cookie: maybe_priority.map(|(_, priority)| priority),
|
||||
title,
|
||||
tags: heading_tags,
|
||||
level: pre_headline.headline_level,
|
||||
todo_keyword: pre_headline
|
||||
.todo_keyword
|
||||
.map(|(todo_keyword_type, todo_keyword)| {
|
||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||
}),
|
||||
priority_cookie: pre_headline.priority_cookie.map(|(_, priority)| priority),
|
||||
title: pre_headline.title,
|
||||
tags: pre_headline.tags,
|
||||
children,
|
||||
is_comment: maybe_comment.is_some(),
|
||||
is_comment: pre_headline.comment.is_some(),
|
||||
is_archived,
|
||||
is_footnote_section: pre_headline.is_footnote_section,
|
||||
scheduled,
|
||||
deadline,
|
||||
closed,
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -111,23 +119,27 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
|
||||
Ok((input, ()))
|
||||
}
|
||||
|
||||
/// Fields from a not-yet-fully-parsed Headline.
|
||||
///
|
||||
/// This struct exists to give names to the fields of a partially-parsed Headline to avoid returning a large tuple of nameless fields.
|
||||
#[derive(Debug)]
|
||||
struct PreHeadline<'s> {
|
||||
headline_level: HeadlineLevel,
|
||||
star_count: HeadlineLevel,
|
||||
todo_keyword: Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||
priority_cookie: Option<(OrgSource<'s>, PriorityCookie)>,
|
||||
comment: Option<OrgSource<'s>>,
|
||||
title: Vec<Object<'s>>,
|
||||
tags: Vec<&'s str>,
|
||||
is_footnote_section: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn headline<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<
|
||||
OrgSource<'s>,
|
||||
(
|
||||
HeadlineLevel,
|
||||
HeadlineLevel,
|
||||
Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||
Option<(OrgSource<'s>, PriorityCookie)>,
|
||||
Option<OrgSource<'s>>,
|
||||
Vec<Object<'s>>,
|
||||
Vec<&'s str>,
|
||||
),
|
||||
> {
|
||||
) -> Res<OrgSource<'s>, PreHeadline<'s>> {
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Document,
|
||||
exit_matcher: &headline_title_end,
|
||||
@@ -159,30 +171,43 @@ fn headline<'b, 'g, 'r, 's>(
|
||||
|
||||
let (remaining, maybe_title) = opt(tuple((
|
||||
space1,
|
||||
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
||||
consumed(many1(parser_with_context!(standard_set_object)(
|
||||
&parser_context,
|
||||
))),
|
||||
)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
|
||||
|
||||
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
||||
|
||||
let is_footnote_section = maybe_title
|
||||
.as_ref()
|
||||
.map(|(_, (raw_title, _))| raw_title)
|
||||
.map(|raw_title| {
|
||||
Into::<&str>::into(raw_title) == context.get_global_settings().footnote_section
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok((
|
||||
remaining,
|
||||
(
|
||||
PreHeadline {
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||
maybe_priority,
|
||||
maybe_comment.map(|(_, comment, _)| comment),
|
||||
maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
|
||||
maybe_tags
|
||||
todo_keyword: maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||
priority_cookie: maybe_priority,
|
||||
comment: maybe_comment.map(|(_, comment, _)| comment),
|
||||
title: maybe_title
|
||||
.map(|(_, (_, title))| title)
|
||||
.unwrap_or(Vec::new()),
|
||||
tags: maybe_tags
|
||||
.map(|(_ws, tags)| {
|
||||
tags.into_iter()
|
||||
.map(|single_tag| Into::<&str>::into(single_tag))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or(Vec::new()),
|
||||
),
|
||||
is_footnote_section,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,32 +1,59 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::bytes::complete::take_until;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::map;
|
||||
use nom::multi::many0;
|
||||
use nom::multi::many_till;
|
||||
use nom::multi::separated_list0;
|
||||
|
||||
use super::keyword::filtered_keyword;
|
||||
use super::keyword_todo::todo_keywords;
|
||||
use super::OrgSource;
|
||||
use crate::context::HeadlineLevelFilter;
|
||||
use crate::error::CustomError;
|
||||
use crate::error::Res;
|
||||
use crate::settings::GlobalSettings;
|
||||
use crate::types::AstNode;
|
||||
use crate::types::Document;
|
||||
use crate::types::Keyword;
|
||||
use crate::GlobalSettings;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn scan_for_in_buffer_settings<'s>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Vec<Keyword<'s>>> {
|
||||
// TODO: Optimization idea: since this is slicing the OrgSource at each character, it might be more efficient to do a parser that uses a search function like take_until, and wrap it in a function similar to consumed but returning the input along with the normal output, then pass all of that into a verify that confirms we were at the start of a line using the input we just returned.
|
||||
// TODO: Write some tests to make sure this is functioning properly.
|
||||
|
||||
let keywords = many0(map(
|
||||
many_till(anychar, filtered_keyword(in_buffer_settings_key)),
|
||||
|(_, kw)| kw,
|
||||
))(input);
|
||||
keywords
|
||||
let mut keywords = Vec::new();
|
||||
let mut remaining = input;
|
||||
loop {
|
||||
// Skip text until possible in_buffer_setting
|
||||
let start_of_pound = take_until::<_, _, CustomError<_>>("#+")(remaining);
|
||||
let start_of_pound = if let Ok((start_of_pound, _)) = start_of_pound {
|
||||
start_of_pound
|
||||
} else {
|
||||
break;
|
||||
};
|
||||
// Go backwards to the start of the line and run the filtered_keyword parser
|
||||
let start_of_line = start_of_pound.get_start_of_line();
|
||||
|
||||
let (remain, maybe_kw) = match filtered_keyword(in_buffer_settings_key)(start_of_line) {
|
||||
Ok((remain, kw)) => (remain, Some(kw)),
|
||||
Err(_) => {
|
||||
let end_of_line = take_until::<_, _, CustomError<_>>("\n")(start_of_pound);
|
||||
if let Ok((end_of_line, _)) = end_of_line {
|
||||
(end_of_line, None)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(kw) = maybe_kw {
|
||||
keywords.push(kw);
|
||||
}
|
||||
remaining = remain;
|
||||
}
|
||||
|
||||
Ok((remaining, keywords))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -88,3 +115,52 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
|
||||
|
||||
Ok(new_settings)
|
||||
}
|
||||
|
||||
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
|
||||
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
|
||||
document: &mut Document<'s>,
|
||||
) -> Result<(), &'static str> {
|
||||
document.category = Into::<AstNode>::into(&*document)
|
||||
.into_iter()
|
||||
.filter_map(|ast_node| {
|
||||
if let AstNode::Keyword(ast_node) = ast_node {
|
||||
if ast_node.key.eq_ignore_ascii_case("category") {
|
||||
return Some(ast_node);
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.last()
|
||||
.map(|kw| kw.value.to_owned());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn scan_test() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let input = OrgSource::new(
|
||||
r#"
|
||||
foo
|
||||
#+archive: bar
|
||||
|
||||
baz #+category: lorem
|
||||
|
||||
#+label: ipsum
|
||||
|
||||
#+todo: dolar
|
||||
cat
|
||||
"#,
|
||||
);
|
||||
let (remaining, settings) = scan_for_in_buffer_settings(input)?;
|
||||
assert_eq!(Into::<&str>::into(remaining), "cat\n");
|
||||
let keys: Vec<_> = settings.iter().map(|kw| kw.key).collect();
|
||||
// category is skipped because it is not the first non-whitespace on the line.
|
||||
//
|
||||
// label is skipped because it is not an in-buffer setting.
|
||||
assert_eq!(keys, vec!["archive", "todo"]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ mod table;
|
||||
mod target;
|
||||
mod text_markup;
|
||||
mod timestamp;
|
||||
mod token;
|
||||
mod util;
|
||||
pub use document::parse;
|
||||
pub use document::parse_file;
|
||||
pub use document::parse_file_with_settings;
|
||||
pub use document::parse_with_settings;
|
||||
pub(crate) use org_source::OrgSource;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::ops::RangeBounds;
|
||||
|
||||
use nom::Compare;
|
||||
use nom::FindSubstring;
|
||||
use nom::InputIter;
|
||||
use nom::InputLength;
|
||||
use nom::InputTake;
|
||||
@@ -77,6 +78,61 @@ impl<'s> OrgSource<'s> {
|
||||
self.slice(..(other.start - self.start))
|
||||
}
|
||||
|
||||
pub(crate) fn get_until_end_of(&self, other: OrgSource<'s>) -> OrgSource<'s> {
|
||||
debug_assert!(other.start >= self.start);
|
||||
debug_assert!(other.end <= self.end);
|
||||
self.slice(..(other.end - self.start))
|
||||
}
|
||||
|
||||
pub(crate) fn get_start_of_line(&self) -> OrgSource<'s> {
|
||||
let skipped_text = self.text_since_line_break();
|
||||
let mut bracket_depth = self.bracket_depth;
|
||||
let mut brace_depth = self.brace_depth;
|
||||
let mut parenthesis_depth = self.parenthesis_depth;
|
||||
// Since we're going backwards, this does the opposite.
|
||||
for byte in skipped_text.bytes() {
|
||||
match byte {
|
||||
b'\n' => {
|
||||
panic!("Should not hit a line break when only going back to the start of the line.");
|
||||
}
|
||||
b'[' => {
|
||||
bracket_depth -= 1;
|
||||
}
|
||||
b']' => {
|
||||
bracket_depth += 1;
|
||||
}
|
||||
b'{' => {
|
||||
brace_depth -= 1;
|
||||
}
|
||||
b'}' => {
|
||||
brace_depth += 1;
|
||||
}
|
||||
b'(' => {
|
||||
parenthesis_depth -= 1;
|
||||
}
|
||||
b')' => {
|
||||
parenthesis_depth += 1;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
OrgSource {
|
||||
full_source: self.full_source,
|
||||
start: self.start_of_line,
|
||||
end: self.end,
|
||||
start_of_line: self.start_of_line,
|
||||
preceding_character: if self.start_of_line > 0 {
|
||||
Some('\n')
|
||||
} else {
|
||||
None
|
||||
},
|
||||
bracket_depth,
|
||||
brace_depth,
|
||||
parenthesis_depth,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_bracket_depth(&self) -> BracketDepth {
|
||||
self.bracket_depth
|
||||
}
|
||||
@@ -310,6 +366,12 @@ impl<'s> InputTakeAtPosition for OrgSource<'s> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'n, 's> FindSubstring<&'n str> for OrgSource<'s> {
|
||||
fn find_substring(&self, substr: &'n str) -> Option<usize> {
|
||||
Into::<&str>::into(self).find(substr)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn convert_error<'a, I: Into<CustomError<&'a str>>>(
|
||||
err: nom::Err<I>,
|
||||
) -> nom::Err<CustomError<&'a str>> {
|
||||
@@ -326,6 +388,7 @@ impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
|
||||
CustomError::MyError(err) => CustomError::MyError(err.into()),
|
||||
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
|
||||
CustomError::IO(err) => CustomError::IO(err),
|
||||
CustomError::BoxedError(err) => CustomError::BoxedError(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ use crate::types::IndentationLevel;
|
||||
use crate::types::Object;
|
||||
use crate::types::PlainList;
|
||||
use crate::types::PlainListItem;
|
||||
use crate::types::PlainListItemCounter;
|
||||
use crate::types::PlainListItemPreBlank;
|
||||
use crate::types::PlainListType;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
||||
@@ -53,12 +56,12 @@ pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
|
||||
if verify(
|
||||
tuple((
|
||||
start_of_line,
|
||||
space0,
|
||||
parser_with_context!(indentation_level)(context),
|
||||
parser_with_context!(bullet)(context),
|
||||
alt((space1, line_ending, eof)),
|
||||
)),
|
||||
|(_start, indent, (_bullet_type, bull), _after_whitespace)| {
|
||||
Into::<&str>::into(bull) != "*" || indent.len() > 0
|
||||
|(_start, (indent_level, _), (_bullet_type, bull), _after_whitespace)| {
|
||||
!Into::<&str>::into(bull).starts_with("*") || *indent_level > 0
|
||||
},
|
||||
)(input)
|
||||
.is_ok()
|
||||
@@ -90,6 +93,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
// children stores tuple of (input string, parsed object) so we can re-parse the final item
|
||||
let mut children = Vec::new();
|
||||
let mut first_item_indentation: Option<IndentationLevel> = None;
|
||||
let mut first_item_list_type: Option<PlainListType> = None;
|
||||
let mut remaining = input;
|
||||
|
||||
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
|
||||
@@ -102,8 +106,15 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
|
||||
loop {
|
||||
let list_item = parser_with_context!(plain_list_item)(&parser_context)(remaining);
|
||||
match (&first_item_list_type, &list_item) {
|
||||
(None, Ok((_remain, (list_type, _item)))) => {
|
||||
let _ = first_item_list_type.insert(*list_type);
|
||||
}
|
||||
(None, Err(_)) => {}
|
||||
(Some(_), _) => {}
|
||||
};
|
||||
match list_item {
|
||||
Ok((remain, item))
|
||||
Ok((remain, (_list_type, item)))
|
||||
if item.indentation == *first_item_indentation.get_or_insert(item.indentation) =>
|
||||
{
|
||||
children.push((remaining, item));
|
||||
@@ -130,7 +141,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
};
|
||||
let final_item_context = ContextElement::ConsumeTrailingWhitespace(false);
|
||||
let final_item_context = parser_context.with_additional_node(&final_item_context);
|
||||
let (remaining, reparsed_final_item) =
|
||||
let (remaining, (_, reparsed_final_item)) =
|
||||
parser_with_context!(plain_list_item)(&final_item_context)(final_child_start)?;
|
||||
children.push((final_child_start, reparsed_final_item));
|
||||
|
||||
@@ -139,6 +150,7 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
PlainList {
|
||||
source: source.into(),
|
||||
list_type: first_item_list_type.expect("Plain lists require at least one element."),
|
||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||
},
|
||||
))
|
||||
@@ -148,20 +160,17 @@ pub(crate) fn plain_list<'b, 'g, 'r, 's>(
|
||||
fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, PlainListItem<'s>> {
|
||||
) -> Res<OrgSource<'s>, (PlainListType, PlainListItem<'s>)> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
|
||||
let (remaining, (bullet_type, bull)) = verify(
|
||||
parser_with_context!(bullet)(context),
|
||||
|(_bullet_type, bull)| Into::<&str>::into(bull) != "*" || indent_level > 0,
|
||||
|(_bullet_type, bull)| !Into::<&str>::into(bull).starts_with("*") || indent_level > 0,
|
||||
)(remaining)?;
|
||||
|
||||
let (remaining, _maybe_counter_set) = opt(tuple((
|
||||
space1,
|
||||
tag("[@"),
|
||||
parser_with_context!(counter)(context),
|
||||
tag("]"),
|
||||
)))(remaining)?;
|
||||
let (remaining, maybe_counter_set) =
|
||||
opt(tuple((space1, tag("[@"), counter_set_value, tag("]"))))(remaining)?;
|
||||
let maybe_counter_set = maybe_counter_set.map(|(_, _, val, _)| val);
|
||||
|
||||
let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
|
||||
|
||||
@@ -170,6 +179,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
} else {
|
||||
(remaining, None)
|
||||
};
|
||||
let list_type = match (&maybe_tag, bullet_type) {
|
||||
(None, BulletType::Ordered) => PlainListType::Ordered,
|
||||
(None, BulletType::Unordered) => PlainListType::Unordered,
|
||||
(Some(_), BulletType::Ordered) => unreachable!(),
|
||||
(Some(_), BulletType::Unordered) => PlainListType::Descriptive,
|
||||
};
|
||||
|
||||
let exit_matcher = plain_list_item_end(indent_level);
|
||||
let contexts = [
|
||||
@@ -195,21 +210,30 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
let source = get_consumed(input, remaining);
|
||||
return Ok((
|
||||
remaining,
|
||||
PlainListItem {
|
||||
source: source.into(),
|
||||
indentation: indent_level,
|
||||
bullet: bull.into(),
|
||||
checkbox: None,
|
||||
tag: maybe_tag
|
||||
.map(|(_ws, item_tag)| item_tag)
|
||||
.unwrap_or(Vec::new()),
|
||||
children: Vec::new(),
|
||||
},
|
||||
(
|
||||
list_type,
|
||||
PlainListItem {
|
||||
source: source.into(),
|
||||
indentation: indent_level,
|
||||
bullet: bull.into(),
|
||||
counter: maybe_counter_set,
|
||||
checkbox: None,
|
||||
tag: maybe_tag
|
||||
.map(|(_ws, item_tag)| item_tag)
|
||||
.unwrap_or(Vec::new()),
|
||||
pre_blank: 0,
|
||||
children: Vec::new(),
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
Err(_) => {}
|
||||
};
|
||||
let (remaining, _ws) = item_tag_post_gap(&parser_context, remaining)?;
|
||||
let (remaining, pre_blank) = item_tag_post_gap(&parser_context, remaining)?;
|
||||
let pre_blank = Into::<&str>::into(pre_blank)
|
||||
.bytes()
|
||||
.filter(|b| *b == b'\n')
|
||||
.count();
|
||||
|
||||
let (mut remaining, (mut children, _exit_contents)) = many_till(
|
||||
include_input(parser_with_context!(element(true))(&parser_context)),
|
||||
@@ -235,17 +259,24 @@ fn plain_list_item<'b, 'g, 'r, 's>(
|
||||
let source = get_consumed(input, remaining);
|
||||
return Ok((
|
||||
remaining,
|
||||
PlainListItem {
|
||||
source: source.into(),
|
||||
indentation: indent_level,
|
||||
bullet: bull.into(),
|
||||
checkbox: maybe_checkbox
|
||||
.map(|(_, (checkbox_type, source))| (checkbox_type, Into::<&str>::into(source))),
|
||||
tag: maybe_tag
|
||||
.map(|(_ws, item_tag)| item_tag)
|
||||
.unwrap_or(Vec::new()),
|
||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||
},
|
||||
(
|
||||
list_type,
|
||||
PlainListItem {
|
||||
source: source.into(),
|
||||
indentation: indent_level,
|
||||
bullet: bull.into(),
|
||||
counter: maybe_counter_set,
|
||||
checkbox: maybe_checkbox.map(|(_, (checkbox_type, source))| {
|
||||
(checkbox_type, Into::<&str>::into(source))
|
||||
}),
|
||||
tag: maybe_tag
|
||||
.map(|(_ws, item_tag)| item_tag)
|
||||
.unwrap_or(Vec::new()),
|
||||
pre_blank: PlainListItemPreBlank::try_from(pre_blank)
|
||||
.expect("pre-blank cannot be larger than 2."),
|
||||
children: children.into_iter().map(|(_start, item)| item).collect(),
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -260,18 +291,23 @@ fn bullet<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, (BulletType, OrgSource<'s>)> {
|
||||
alt((
|
||||
map(tag("*"), |bull| (BulletType::Unordered, bull)),
|
||||
map(tag("-"), |bull| (BulletType::Unordered, bull)),
|
||||
map(tag("+"), |bull| (BulletType::Unordered, bull)),
|
||||
map(
|
||||
recognize(tuple((
|
||||
parser_with_context!(counter)(context),
|
||||
alt((tag("."), tag(")"))),
|
||||
))),
|
||||
|bull| (BulletType::Ordered, bull),
|
||||
),
|
||||
))(input)
|
||||
let (remaining, ((bullet_type, _without_space), peek_trailing_space)) = tuple((
|
||||
alt((
|
||||
map(tag("*"), |bull| (BulletType::Unordered, bull)),
|
||||
map(tag("-"), |bull| (BulletType::Unordered, bull)),
|
||||
map(tag("+"), |bull| (BulletType::Unordered, bull)),
|
||||
map(
|
||||
recognize(tuple((
|
||||
parser_with_context!(counter)(context),
|
||||
alt((tag("."), tag(")"))),
|
||||
))),
|
||||
|bull| (BulletType::Ordered, bull),
|
||||
),
|
||||
)),
|
||||
peek(space0),
|
||||
))(input)?;
|
||||
let with_space = input.get_until_end_of(peek_trailing_space);
|
||||
Ok((remaining, (bullet_type, with_space)))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -279,7 +315,7 @@ fn counter<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
if context.get_global_settings().org_list_allow_alphabetical {
|
||||
if context.get_global_settings().list_allow_alphabetical {
|
||||
alt((
|
||||
recognize(one_of(
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
@@ -291,6 +327,29 @@ fn counter<'b, 'g, 'r, 's>(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn counter_set_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, PlainListItemCounter> {
|
||||
alt((
|
||||
map(
|
||||
one_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"),
|
||||
|letter| {
|
||||
let num = match letter {
|
||||
'a'..='z' => (letter as u32) - ('a' as u32) + 1,
|
||||
'A'..='Z' => (letter as u32) - ('A' as u32) + 1,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
PlainListItemCounter::try_from(num)
|
||||
.expect("Counter set value should be between 1 and 26 inclusive.")
|
||||
},
|
||||
),
|
||||
map(digit1, |num: OrgSource<'_>| {
|
||||
Into::<&str>::into(num)
|
||||
.parse()
|
||||
.expect("digit1 must parse to a number.")
|
||||
}),
|
||||
))(input)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn plain_list_end<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
@@ -454,7 +513,7 @@ mod tests {
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
||||
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
||||
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(result.get_standard_properties().get_source(), "1.");
|
||||
}
|
||||
@@ -466,7 +525,7 @@ mod tests {
|
||||
let initial_context = ContextElement::document_context();
|
||||
let initial_context = Context::new(&global_settings, List::new(&initial_context));
|
||||
let plain_list_item_matcher = parser_with_context!(plain_list_item)(&initial_context);
|
||||
let (remaining, result) = plain_list_item_matcher(input).unwrap();
|
||||
let (remaining, (_, result)) = plain_list_item_matcher(input).unwrap();
|
||||
assert_eq!(Into::<&str>::into(remaining), "");
|
||||
assert_eq!(result.get_standard_properties().get_source(), "1. foo");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
|
||||
use nom::bytes::complete::tag_no_case;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::map;
|
||||
use nom::multi::many1;
|
||||
use nom::sequence::tuple;
|
||||
|
||||
@@ -16,6 +17,7 @@ use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::parser::util::start_of_line;
|
||||
use crate::types::Planning;
|
||||
use crate::types::Timestamp;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn planning<'b, 'g, 'r, 's>(
|
||||
@@ -24,7 +26,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, Planning<'s>> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, _leading_whitespace) = space0(input)?;
|
||||
let (remaining, _planning_parameters) =
|
||||
let (remaining, planning_parameters) =
|
||||
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
|
||||
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
|
||||
|
||||
@@ -32,26 +34,55 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
|
||||
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
|
||||
let mut scheduled = None;
|
||||
let mut deadline = None;
|
||||
let mut closed = None;
|
||||
|
||||
for (timestamp_type, timestamp) in planning_parameters.into_iter() {
|
||||
match timestamp_type {
|
||||
PlanningTimestampType::Scheduled => {
|
||||
scheduled = Some(timestamp);
|
||||
}
|
||||
PlanningTimestampType::Deadline => {
|
||||
deadline = Some(timestamp);
|
||||
}
|
||||
PlanningTimestampType::Closed => {
|
||||
closed = Some(timestamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
remaining,
|
||||
Planning {
|
||||
source: source.into(),
|
||||
scheduled,
|
||||
deadline,
|
||||
closed,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PlanningTimestampType {
|
||||
Scheduled,
|
||||
Deadline,
|
||||
Closed,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn planning_parameter<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _planning_type) = alt((
|
||||
tag_no_case("DEADLINE"),
|
||||
tag_no_case("SCHEDULED"),
|
||||
tag_no_case("CLOSED"),
|
||||
) -> Res<OrgSource<'s>, (PlanningTimestampType, Timestamp<'s>)> {
|
||||
let (remaining, planning_type) = alt((
|
||||
map(tag_no_case("DEADLINE"), |_| PlanningTimestampType::Deadline),
|
||||
map(tag_no_case("SCHEDULED"), |_| {
|
||||
PlanningTimestampType::Scheduled
|
||||
}),
|
||||
map(tag_no_case("CLOSED"), |_| PlanningTimestampType::Closed),
|
||||
))(input)?;
|
||||
let (remaining, _gap) = tuple((tag(":"), space1))(remaining)?;
|
||||
let (remaining, _timestamp) = timestamp(context, remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
let (remaining, timestamp) = timestamp(context, remaining)?;
|
||||
Ok((remaining, (planning_type, timestamp)))
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ fn node_property<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, NodeProperty<'s>> {
|
||||
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, _name, _close_colon)) =
|
||||
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, name, _close_colon)) =
|
||||
tuple((
|
||||
start_of_line,
|
||||
space0,
|
||||
@@ -120,6 +120,7 @@ fn node_property<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
NodeProperty {
|
||||
source: source.into(),
|
||||
name: Into::<&str>::into(name),
|
||||
value: None,
|
||||
},
|
||||
))
|
||||
@@ -132,6 +133,7 @@ fn node_property<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
NodeProperty {
|
||||
source: source.into(),
|
||||
name: Into::<&str>::into(name),
|
||||
value: Some(value.into()),
|
||||
},
|
||||
))
|
||||
|
||||
@@ -3,8 +3,8 @@ use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::line_ending;
|
||||
use nom::character::complete::space0;
|
||||
use nom::combinator::eof;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::peek;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
@@ -17,6 +17,7 @@ use super::keyword::table_formula_keyword;
|
||||
use super::object_parser::table_cell_set_object;
|
||||
use super::org_source::OrgSource;
|
||||
use super::util::exit_matcher_parser;
|
||||
use super::util::org_line_ending;
|
||||
use crate::context::parser_with_context;
|
||||
use crate::context::ContextElement;
|
||||
use crate::context::ExitClass;
|
||||
@@ -105,7 +106,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, TableRow<'s>> {
|
||||
start_of_line(input)?;
|
||||
let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?;
|
||||
let (remaining, _) = tuple((space0, tag("|-"), opt(is_not("\r\n")), org_line_ending))(input)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
@@ -125,7 +126,7 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
|
||||
let (remaining, _) = tuple((space0, tag("|")))(input)?;
|
||||
let (remaining, children) =
|
||||
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
|
||||
let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
|
||||
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::digit0;
|
||||
use nom::character::complete::digit1;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::opt;
|
||||
use nom::combinator::recognize;
|
||||
use nom::combinator::verify;
|
||||
@@ -21,7 +21,21 @@ use crate::context::ExitMatcherNode;
|
||||
use crate::context::RefContext;
|
||||
use crate::error::Res;
|
||||
use crate::parser::util::get_consumed;
|
||||
use crate::types::Date;
|
||||
use crate::types::DayOfMonth;
|
||||
use crate::types::Hour;
|
||||
use crate::types::Minute;
|
||||
use crate::types::Month;
|
||||
use crate::types::Repeater;
|
||||
use crate::types::RepeaterType;
|
||||
use crate::types::Time;
|
||||
use crate::types::TimeUnit;
|
||||
use crate::types::Timestamp;
|
||||
use crate::types::TimestampRangeType;
|
||||
use crate::types::TimestampType;
|
||||
use crate::types::WarningDelay;
|
||||
use crate::types::WarningDelayType;
|
||||
use crate::types::Year;
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
pub(crate) fn timestamp<'b, 'g, 'r, 's>(
|
||||
@@ -57,6 +71,14 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Timestamp {
|
||||
source: source.into(),
|
||||
timestamp_type: TimestampType::Diary,
|
||||
range_type: TimestampRangeType::None,
|
||||
start: None,
|
||||
end: None,
|
||||
start_time: None,
|
||||
end_time: None,
|
||||
repeater: None,
|
||||
warning_delay: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -97,17 +119,26 @@ fn active_timestamp<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||
let (remaining, _) = tag("<")(input)?;
|
||||
let (remaining, _date) = date(context, remaining)?;
|
||||
let (remaining, start) = date(context, remaining)?;
|
||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &active_time_rest_end,
|
||||
});
|
||||
let time_context = context.with_additional_node(&time_context);
|
||||
let (remaining, _time) =
|
||||
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
|
||||
let (remaining, _repeater) =
|
||||
let (remaining, time) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(time(true))(&time_context),
|
||||
)))(remaining)?;
|
||||
let remaining = if time.is_none() {
|
||||
// Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'.
|
||||
let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?;
|
||||
remain
|
||||
} else {
|
||||
remaining
|
||||
};
|
||||
let (remaining, repeater) =
|
||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||
let (remaining, _warning_delay) = opt(tuple((
|
||||
let (remaining, warning_delay) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(warning_delay)(context),
|
||||
)))(remaining)?;
|
||||
@@ -121,6 +152,14 @@ fn active_timestamp<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Timestamp {
|
||||
source: source.into(),
|
||||
timestamp_type: TimestampType::Active,
|
||||
range_type: TimestampRangeType::None,
|
||||
start: Some(start.clone()),
|
||||
end: Some(start),
|
||||
start_time: time.as_ref().map(|(_, time)| time.clone()),
|
||||
end_time: time.map(|(_, time)| time),
|
||||
repeater: repeater.map(|(_, repeater)| repeater),
|
||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -131,17 +170,26 @@ fn inactive_timestamp<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||
let (remaining, _) = tag("[")(input)?;
|
||||
let (remaining, _date) = date(context, remaining)?;
|
||||
let (remaining, start) = date(context, remaining)?;
|
||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &inactive_time_rest_end,
|
||||
});
|
||||
let time_context = context.with_additional_node(&time_context);
|
||||
let (remaining, _time) =
|
||||
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
|
||||
let (remaining, _repeater) =
|
||||
let (remaining, time) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(time(true))(&time_context),
|
||||
)))(remaining)?;
|
||||
let remaining = if time.is_none() {
|
||||
// Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'.
|
||||
let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?;
|
||||
remain
|
||||
} else {
|
||||
remaining
|
||||
};
|
||||
let (remaining, repeater) =
|
||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||
let (remaining, _warning_delay) = opt(tuple((
|
||||
let (remaining, warning_delay) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(warning_delay)(context),
|
||||
)))(remaining)?;
|
||||
@@ -155,6 +203,14 @@ fn inactive_timestamp<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Timestamp {
|
||||
source: source.into(),
|
||||
timestamp_type: TimestampType::Inactive,
|
||||
range_type: TimestampRangeType::None,
|
||||
start: Some(start.clone()),
|
||||
end: Some(start),
|
||||
start_time: time.as_ref().map(|(_, time)| time.clone()),
|
||||
end_time: time.map(|(_, time)| time),
|
||||
repeater: repeater.map(|(_, repeater)| repeater),
|
||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -164,10 +220,10 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||
let (remaining, _first_timestamp) = active_timestamp(context, input)?;
|
||||
let (remaining, first_timestamp) = active_timestamp(context, input)?;
|
||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||
let (remaining, _separator) = tag("--")(remaining)?;
|
||||
let (remaining, _second_timestamp) = active_timestamp(context, remaining)?;
|
||||
let (remaining, second_timestamp) = active_timestamp(context, remaining)?;
|
||||
|
||||
let (remaining, _trailing_whitespace) =
|
||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||
@@ -177,6 +233,16 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Timestamp {
|
||||
source: source.into(),
|
||||
timestamp_type: TimestampType::ActiveRange,
|
||||
range_type: TimestampRangeType::DateRange,
|
||||
start: first_timestamp.start,
|
||||
end: second_timestamp.end,
|
||||
start_time: first_timestamp.start_time,
|
||||
end_time: second_timestamp.end_time,
|
||||
repeater: first_timestamp.repeater.or(second_timestamp.repeater),
|
||||
warning_delay: first_timestamp
|
||||
.warning_delay
|
||||
.or(second_timestamp.warning_delay),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -187,7 +253,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||
let (remaining, _) = tag("<")(input)?;
|
||||
let (remaining, _date) = date(context, remaining)?;
|
||||
let (remaining, start_date) = date(context, remaining)?;
|
||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &active_time_rest_end,
|
||||
@@ -198,13 +264,15 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
||||
exit_matcher: &time_range_rest_end,
|
||||
});
|
||||
let first_time_context = time_context.with_additional_node(&first_time_context);
|
||||
let (remaining, _first_time) =
|
||||
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
|
||||
let (remaining, (_, first_time)) = tuple((
|
||||
space1,
|
||||
parser_with_context!(time(false))(&first_time_context),
|
||||
))(remaining)?;
|
||||
let (remaining, _) = tag("-")(remaining)?;
|
||||
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
|
||||
let (remaining, _repeater) =
|
||||
let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?;
|
||||
let (remaining, repeater) =
|
||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||
let (remaining, _warning_delay) = opt(tuple((
|
||||
let (remaining, warning_delay) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(warning_delay)(context),
|
||||
)))(remaining)?;
|
||||
@@ -218,6 +286,14 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Timestamp {
|
||||
source: source.into(),
|
||||
timestamp_type: TimestampType::ActiveRange,
|
||||
range_type: TimestampRangeType::TimeRange,
|
||||
start: Some(start_date.clone()),
|
||||
end: Some(start_date),
|
||||
start_time: Some(first_time),
|
||||
end_time: Some(second_time),
|
||||
repeater: repeater.map(|(_, repeater)| repeater),
|
||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -227,10 +303,10 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||
let (remaining, _first_timestamp) = inactive_timestamp(context, input)?;
|
||||
let (remaining, first_timestamp) = inactive_timestamp(context, input)?;
|
||||
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
|
||||
let (remaining, _separator) = tag("--")(remaining)?;
|
||||
let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?;
|
||||
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?;
|
||||
|
||||
let (remaining, _trailing_whitespace) =
|
||||
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
|
||||
@@ -240,6 +316,17 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Timestamp {
|
||||
source: source.into(),
|
||||
|
||||
timestamp_type: TimestampType::InactiveRange,
|
||||
range_type: TimestampRangeType::DateRange,
|
||||
start: first_timestamp.start,
|
||||
end: second_timestamp.end,
|
||||
start_time: first_timestamp.start_time,
|
||||
end_time: second_timestamp.end_time,
|
||||
repeater: first_timestamp.repeater.or(second_timestamp.repeater),
|
||||
warning_delay: first_timestamp
|
||||
.warning_delay
|
||||
.or(second_timestamp.warning_delay),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -250,7 +337,7 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, Timestamp<'s>> {
|
||||
let (remaining, _) = tag("[")(input)?;
|
||||
let (remaining, _date) = date(context, remaining)?;
|
||||
let (remaining, start_date) = date(context, remaining)?;
|
||||
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Gamma,
|
||||
exit_matcher: &inactive_time_rest_end,
|
||||
@@ -261,13 +348,15 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
||||
exit_matcher: &time_range_rest_end,
|
||||
});
|
||||
let first_time_context = time_context.with_additional_node(&first_time_context);
|
||||
let (remaining, _first_time) =
|
||||
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
|
||||
let (remaining, (_, first_time)) = tuple((
|
||||
space1,
|
||||
parser_with_context!(time(false))(&first_time_context),
|
||||
))(remaining)?;
|
||||
let (remaining, _) = tag("-")(remaining)?;
|
||||
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
|
||||
let (remaining, _repeater) =
|
||||
let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?;
|
||||
let (remaining, repeater) =
|
||||
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
|
||||
let (remaining, _warning_delay) = opt(tuple((
|
||||
let (remaining, warning_delay) = opt(tuple((
|
||||
space1,
|
||||
parser_with_context!(warning_delay)(context),
|
||||
)))(remaining)?;
|
||||
@@ -281,6 +370,14 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
||||
remaining,
|
||||
Timestamp {
|
||||
source: source.into(),
|
||||
timestamp_type: TimestampType::InactiveRange,
|
||||
range_type: TimestampRangeType::TimeRange,
|
||||
start: Some(start_date.clone()),
|
||||
end: Some(start_date),
|
||||
start_time: Some(first_time),
|
||||
end_time: Some(second_time),
|
||||
repeater: repeater.map(|(_, repeater)| repeater),
|
||||
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -289,18 +386,33 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
|
||||
fn date<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
|
||||
) -> Res<OrgSource<'s>, Date<'s>> {
|
||||
let (remaining, year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
|
||||
let (remaining, _) = tag("-")(remaining)?;
|
||||
let (remaining, _month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
|
||||
let (remaining, month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
|
||||
let (remaining, _) = tag("-")(remaining)?;
|
||||
let (remaining, _day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
|
||||
let (remaining, day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
|
||||
day_of_month.len() == 2
|
||||
})(remaining)?;
|
||||
let (remaining, _dayname) =
|
||||
let (remaining, day_name) =
|
||||
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
|
||||
let year = Year::new(Into::<&str>::into(year))
|
||||
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||
let month = Month::new(Into::<&str>::into(month))
|
||||
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||
let day_of_month = DayOfMonth::new(Into::<&str>::into(day_of_month))
|
||||
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||
|
||||
let date = Date::new(
|
||||
year,
|
||||
month,
|
||||
day_of_month,
|
||||
day_name.map(|(_, day_name)| Into::<&str>::into(day_name)),
|
||||
)
|
||||
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||
|
||||
Ok((remaining, date))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -335,20 +447,39 @@ fn dayname_end<'b, 'g, 'r, 's>(
|
||||
}))(input)
|
||||
}
|
||||
|
||||
const fn time<'c>(
|
||||
allow_rest: bool,
|
||||
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Time<'s>>
|
||||
{
|
||||
move |context, input| _time(context, input, allow_rest)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn time<'b, 'g, 'r, 's>(
|
||||
fn _time<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
let (remaining, _hour) = verify(digit1, |hour: &OrgSource<'_>| {
|
||||
allow_rest: bool,
|
||||
) -> Res<OrgSource<'s>, Time<'s>> {
|
||||
let (remaining, hour) = verify(digit1, |hour: &OrgSource<'_>| {
|
||||
hour.len() >= 1 && hour.len() <= 2
|
||||
})(input)?;
|
||||
let (remaining, _) = tag(":")(remaining)?;
|
||||
let (remaining, _minute) =
|
||||
let (remaining, minute) =
|
||||
verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?;
|
||||
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
let (remaining, time_rest) = if allow_rest {
|
||||
opt(parser_with_context!(time_rest)(context))(remaining)?
|
||||
} else {
|
||||
(remaining, None)
|
||||
};
|
||||
|
||||
let hour = Hour::new(Into::<&str>::into(hour))
|
||||
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||
let minute = Minute::new(Into::<&str>::into(minute))
|
||||
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||
let time = Time::new(hour, minute, time_rest.map(Into::<&str>::into))
|
||||
.expect("TODO: I should be able to return CustomError from nom parsers.");
|
||||
|
||||
Ok((remaining, time))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
@@ -401,8 +532,10 @@ fn time_range_rest_end<'b, 'g, 'r, 's>(
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
|
||||
let parent_node = context.get_parent().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
|
||||
let exit_contents =
|
||||
recognize(tuple((tag("-"), parser_with_context!(time)(&parent_node))))(input);
|
||||
let exit_contents = recognize(tuple((
|
||||
tag("-"),
|
||||
parser_with_context!(time(true))(&parent_node),
|
||||
)))(input);
|
||||
exit_contents
|
||||
}
|
||||
|
||||
@@ -410,29 +543,66 @@ fn time_range_rest_end<'b, 'g, 'r, 's>(
|
||||
fn repeater<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
) -> Res<OrgSource<'s>, Repeater> {
|
||||
// + for cumulative type
|
||||
// ++ for catch-up type
|
||||
// .+ for restart type
|
||||
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
|
||||
let (remaining, _value) = digit0(remaining)?;
|
||||
let (remaining, repeater_type) = alt((
|
||||
map(tag("++"), |_| RepeaterType::CatchUp),
|
||||
map(tag("+"), |_| RepeaterType::Cumulative),
|
||||
map(tag(".+"), |_| RepeaterType::Restart),
|
||||
))(input)?;
|
||||
let (remaining, value) = digit1(remaining)?;
|
||||
let value = Into::<&str>::into(value)
|
||||
.parse()
|
||||
.expect("digit1 ensures this will parse as a number.");
|
||||
// h = hour, d = day, w = week, m = month, y = year
|
||||
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
let (remaining, unit) = alt((
|
||||
map(tag("h"), |_| TimeUnit::Hour),
|
||||
map(tag("d"), |_| TimeUnit::Day),
|
||||
map(tag("w"), |_| TimeUnit::Week),
|
||||
map(tag("m"), |_| TimeUnit::Month),
|
||||
map(tag("y"), |_| TimeUnit::Year),
|
||||
))(remaining)?;
|
||||
Ok((
|
||||
remaining,
|
||||
Repeater {
|
||||
repeater_type,
|
||||
value,
|
||||
unit,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn warning_delay<'b, 'g, 'r, 's>(
|
||||
_context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||
) -> Res<OrgSource<'s>, WarningDelay> {
|
||||
// - for all type
|
||||
// -- for first type
|
||||
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
|
||||
let (remaining, _value) = digit0(remaining)?;
|
||||
let (remaining, warning_delay_type) = alt((
|
||||
map(tag("--"), |_| WarningDelayType::First),
|
||||
map(tag("-"), |_| WarningDelayType::All),
|
||||
))(input)?;
|
||||
let (remaining, value) = digit1(remaining)?;
|
||||
let value = Into::<&str>::into(value)
|
||||
.parse()
|
||||
.expect("digit1 ensures this will parse as a number.");
|
||||
// h = hour, d = day, w = week, m = month, y = year
|
||||
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((remaining, source))
|
||||
let (remaining, unit) = alt((
|
||||
map(tag("h"), |_| TimeUnit::Hour),
|
||||
map(tag("d"), |_| TimeUnit::Day),
|
||||
map(tag("w"), |_| TimeUnit::Week),
|
||||
map(tag("m"), |_| TimeUnit::Month),
|
||||
map(tag("y"), |_| TimeUnit::Year),
|
||||
))(remaining)?;
|
||||
Ok((
|
||||
remaining,
|
||||
WarningDelay {
|
||||
warning_delay_type,
|
||||
value,
|
||||
unit,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::types::Document;
|
||||
use crate::types::DocumentElement;
|
||||
use crate::types::Element;
|
||||
use crate::types::Heading;
|
||||
use crate::types::NodeProperty;
|
||||
use crate::types::Object;
|
||||
use crate::types::PlainListItem;
|
||||
use crate::types::Section;
|
||||
use crate::types::TableCell;
|
||||
use crate::types::TableRow;
|
||||
|
||||
pub(crate) enum Token<'r, 's> {
|
||||
Document(&'r Document<'s>),
|
||||
Heading(&'r Heading<'s>),
|
||||
Section(&'r Section<'s>),
|
||||
Object(&'r Object<'s>),
|
||||
Element(&'r Element<'s>),
|
||||
PlainListItem(&'r PlainListItem<'s>),
|
||||
TableRow(&'r TableRow<'s>),
|
||||
TableCell(&'r TableCell<'s>),
|
||||
NodeProperty(&'r NodeProperty<'s>),
|
||||
}
|
||||
|
||||
impl<'r, 's> Token<'r, 's> {
|
||||
fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> {
|
||||
match self {
|
||||
Token::Document(document) => Box::new(
|
||||
document
|
||||
.zeroth_section
|
||||
.iter()
|
||||
.map(Token::Section)
|
||||
.chain(document.children.iter().map(Token::Heading)),
|
||||
),
|
||||
Token::Heading(heading) => Box::new(heading.title.iter().map(Token::Object).chain(
|
||||
heading.children.iter().map(|de| match de {
|
||||
DocumentElement::Heading(ref obj) => Token::Heading(obj),
|
||||
DocumentElement::Section(ref obj) => Token::Section(obj),
|
||||
}),
|
||||
)),
|
||||
Token::Section(section) => Box::new(section.children.iter().map(Token::Element)),
|
||||
Token::Object(obj) => match obj {
|
||||
Object::Bold(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Object::Italic(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Object::Underline(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Object::StrikeThrough(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Object::Code(_) => Box::new(std::iter::empty()),
|
||||
Object::Verbatim(_) => Box::new(std::iter::empty()),
|
||||
Object::PlainText(_) => Box::new(std::iter::empty()),
|
||||
Object::RegularLink(_) => Box::new(std::iter::empty()),
|
||||
Object::RadioLink(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Object::RadioTarget(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Object::PlainLink(_) => Box::new(std::iter::empty()),
|
||||
Object::AngleLink(_) => Box::new(std::iter::empty()),
|
||||
Object::OrgMacro(_) => Box::new(std::iter::empty()),
|
||||
Object::Entity(_) => Box::new(std::iter::empty()),
|
||||
Object::LatexFragment(_) => Box::new(std::iter::empty()),
|
||||
Object::ExportSnippet(_) => Box::new(std::iter::empty()),
|
||||
Object::FootnoteReference(inner) => {
|
||||
Box::new(inner.definition.iter().map(Token::Object))
|
||||
}
|
||||
Object::Citation(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||
Object::CitationReference(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||
Object::InlineBabelCall(_) => Box::new(std::iter::empty()),
|
||||
Object::InlineSourceBlock(_) => Box::new(std::iter::empty()),
|
||||
Object::LineBreak(_) => Box::new(std::iter::empty()),
|
||||
Object::Target(_) => Box::new(std::iter::empty()),
|
||||
Object::StatisticsCookie(_) => Box::new(std::iter::empty()),
|
||||
Object::Subscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||
Object::Superscript(_) => Box::new(std::iter::empty()), // TODO: Iterate over children
|
||||
Object::Timestamp(_) => Box::new(std::iter::empty()),
|
||||
},
|
||||
Token::Element(elem) => match elem {
|
||||
Element::Paragraph(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Element::PlainList(inner) => {
|
||||
Box::new(inner.children.iter().map(Token::PlainListItem))
|
||||
}
|
||||
Element::GreaterBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
||||
Element::DynamicBlock(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
||||
Element::FootnoteDefinition(inner) => {
|
||||
Box::new(inner.children.iter().map(Token::Element))
|
||||
}
|
||||
Element::Comment(_) => Box::new(std::iter::empty()),
|
||||
Element::Drawer(inner) => Box::new(inner.children.iter().map(Token::Element)),
|
||||
Element::PropertyDrawer(inner) => {
|
||||
Box::new(inner.children.iter().map(Token::NodeProperty))
|
||||
}
|
||||
Element::Table(inner) => Box::new(inner.children.iter().map(Token::TableRow)),
|
||||
Element::VerseBlock(inner) => Box::new(inner.children.iter().map(Token::Object)),
|
||||
Element::CommentBlock(_) => Box::new(std::iter::empty()),
|
||||
Element::ExampleBlock(_) => Box::new(std::iter::empty()),
|
||||
Element::ExportBlock(_) => Box::new(std::iter::empty()),
|
||||
Element::SrcBlock(_) => Box::new(std::iter::empty()),
|
||||
Element::Clock(_) => Box::new(std::iter::empty()),
|
||||
Element::DiarySexp(_) => Box::new(std::iter::empty()),
|
||||
Element::Planning(_) => Box::new(std::iter::empty()),
|
||||
Element::FixedWidthArea(_) => Box::new(std::iter::empty()),
|
||||
Element::HorizontalRule(_) => Box::new(std::iter::empty()),
|
||||
Element::Keyword(_) => Box::new(std::iter::empty()),
|
||||
Element::BabelCall(_) => Box::new(std::iter::empty()),
|
||||
Element::LatexEnvironment(_) => Box::new(std::iter::empty()),
|
||||
},
|
||||
Token::PlainListItem(elem) => Box::new(elem.children.iter().map(Token::Element)),
|
||||
Token::TableRow(elem) => Box::new(elem.children.iter().map(Token::TableCell)),
|
||||
Token::TableCell(elem) => Box::new(elem.children.iter().map(Token::Object)),
|
||||
Token::NodeProperty(_) => Box::new(std::iter::empty()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AllTokensIterator<'r, 's> {
|
||||
queued_tokens: VecDeque<Token<'r, 's>>,
|
||||
}
|
||||
|
||||
impl<'r, 's> AllTokensIterator<'r, 's> {
|
||||
pub(crate) fn new(tkn: Token<'r, 's>) -> Self {
|
||||
let mut queued_tokens = VecDeque::new();
|
||||
queued_tokens.push_back(tkn);
|
||||
AllTokensIterator { queued_tokens }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> Iterator for AllTokensIterator<'r, 's> {
|
||||
type Item = Token<'r, 's>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next_token = match self.queued_tokens.pop_front() {
|
||||
Some(tkn) => tkn,
|
||||
None => return None,
|
||||
};
|
||||
self.queued_tokens.extend(next_token.iter_tokens());
|
||||
Some(next_token)
|
||||
}
|
||||
}
|
||||
325
src/types/ast_node.rs
Normal file
325
src/types/ast_node.rs
Normal file
@@ -0,0 +1,325 @@
|
||||
use super::macros::to_ast_node;
|
||||
use super::CenterBlock;
|
||||
use super::QuoteBlock;
|
||||
use super::SpecialBlock;
|
||||
use crate::types::AngleLink;
|
||||
use crate::types::BabelCall;
|
||||
use crate::types::Bold;
|
||||
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::DocumentElement;
|
||||
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::GetStandardProperties;
|
||||
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::RadioLink;
|
||||
use crate::types::RadioTarget;
|
||||
use crate::types::RegularLink;
|
||||
use crate::types::Section;
|
||||
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 enum AstNode<'r, 's> {
|
||||
// Document Nodes
|
||||
Document(&'r Document<'s>),
|
||||
Heading(&'r Heading<'s>),
|
||||
Section(&'r Section<'s>),
|
||||
// Elements
|
||||
Paragraph(&'r Paragraph<'s>),
|
||||
PlainList(&'r PlainList<'s>),
|
||||
PlainListItem(&'r PlainListItem<'s>),
|
||||
CenterBlock(&'r CenterBlock<'s>),
|
||||
QuoteBlock(&'r QuoteBlock<'s>),
|
||||
SpecialBlock(&'r SpecialBlock<'s>),
|
||||
DynamicBlock(&'r DynamicBlock<'s>),
|
||||
FootnoteDefinition(&'r FootnoteDefinition<'s>),
|
||||
Comment(&'r Comment<'s>),
|
||||
Drawer(&'r Drawer<'s>),
|
||||
PropertyDrawer(&'r PropertyDrawer<'s>),
|
||||
NodeProperty(&'r NodeProperty<'s>),
|
||||
Table(&'r Table<'s>),
|
||||
TableRow(&'r TableRow<'s>),
|
||||
VerseBlock(&'r VerseBlock<'s>),
|
||||
CommentBlock(&'r CommentBlock<'s>),
|
||||
ExampleBlock(&'r ExampleBlock<'s>),
|
||||
ExportBlock(&'r ExportBlock<'s>),
|
||||
SrcBlock(&'r SrcBlock<'s>),
|
||||
Clock(&'r Clock<'s>),
|
||||
DiarySexp(&'r DiarySexp<'s>),
|
||||
Planning(&'r Planning<'s>),
|
||||
FixedWidthArea(&'r FixedWidthArea<'s>),
|
||||
HorizontalRule(&'r HorizontalRule<'s>),
|
||||
Keyword(&'r Keyword<'s>),
|
||||
BabelCall(&'r BabelCall<'s>),
|
||||
LatexEnvironment(&'r LatexEnvironment<'s>),
|
||||
// Objects
|
||||
Bold(&'r Bold<'s>),
|
||||
Italic(&'r Italic<'s>),
|
||||
Underline(&'r Underline<'s>),
|
||||
StrikeThrough(&'r StrikeThrough<'s>),
|
||||
Code(&'r Code<'s>),
|
||||
Verbatim(&'r Verbatim<'s>),
|
||||
PlainText(&'r PlainText<'s>),
|
||||
RegularLink(&'r RegularLink<'s>),
|
||||
RadioLink(&'r RadioLink<'s>),
|
||||
RadioTarget(&'r RadioTarget<'s>),
|
||||
PlainLink(&'r PlainLink<'s>),
|
||||
AngleLink(&'r AngleLink<'s>),
|
||||
OrgMacro(&'r OrgMacro<'s>),
|
||||
Entity(&'r Entity<'s>),
|
||||
LatexFragment(&'r LatexFragment<'s>),
|
||||
ExportSnippet(&'r ExportSnippet<'s>),
|
||||
FootnoteReference(&'r FootnoteReference<'s>),
|
||||
Citation(&'r Citation<'s>),
|
||||
CitationReference(&'r CitationReference<'s>),
|
||||
InlineBabelCall(&'r InlineBabelCall<'s>),
|
||||
InlineSourceBlock(&'r InlineSourceBlock<'s>),
|
||||
LineBreak(&'r LineBreak<'s>),
|
||||
Target(&'r Target<'s>),
|
||||
StatisticsCookie(&'r StatisticsCookie<'s>),
|
||||
Subscript(&'r Subscript<'s>),
|
||||
Superscript(&'r Superscript<'s>),
|
||||
TableCell(&'r TableCell<'s>),
|
||||
Timestamp(&'r Timestamp<'s>),
|
||||
}
|
||||
|
||||
impl<'r, 's> From<&'r DocumentElement<'s>> for AstNode<'r, 's> {
|
||||
fn from(value: &'r DocumentElement<'s>) -> Self {
|
||||
match value {
|
||||
DocumentElement::Heading(inner) => inner.into(),
|
||||
DocumentElement::Section(inner) => inner.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> From<&'r Element<'s>> for AstNode<'r, 's> {
|
||||
fn from(value: &'r Element<'s>) -> Self {
|
||||
match value {
|
||||
Element::Paragraph(inner) => inner.into(),
|
||||
Element::PlainList(inner) => inner.into(),
|
||||
Element::CenterBlock(inner) => inner.into(),
|
||||
Element::QuoteBlock(inner) => inner.into(),
|
||||
Element::SpecialBlock(inner) => inner.into(),
|
||||
Element::DynamicBlock(inner) => inner.into(),
|
||||
Element::FootnoteDefinition(inner) => inner.into(),
|
||||
Element::Comment(inner) => inner.into(),
|
||||
Element::Drawer(inner) => inner.into(),
|
||||
Element::PropertyDrawer(inner) => inner.into(),
|
||||
Element::Table(inner) => inner.into(),
|
||||
Element::VerseBlock(inner) => inner.into(),
|
||||
Element::CommentBlock(inner) => inner.into(),
|
||||
Element::ExampleBlock(inner) => inner.into(),
|
||||
Element::ExportBlock(inner) => inner.into(),
|
||||
Element::SrcBlock(inner) => inner.into(),
|
||||
Element::Clock(inner) => inner.into(),
|
||||
Element::DiarySexp(inner) => inner.into(),
|
||||
Element::Planning(inner) => inner.into(),
|
||||
Element::FixedWidthArea(inner) => inner.into(),
|
||||
Element::HorizontalRule(inner) => inner.into(),
|
||||
Element::Keyword(inner) => inner.into(),
|
||||
Element::BabelCall(inner) => inner.into(),
|
||||
Element::LatexEnvironment(inner) => inner.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r, 's> From<&'r Object<'s>> for AstNode<'r, 's> {
|
||||
fn from(value: &'r Object<'s>) -> Self {
|
||||
match value {
|
||||
Object::Bold(inner) => inner.into(),
|
||||
Object::Italic(inner) => inner.into(),
|
||||
Object::Underline(inner) => inner.into(),
|
||||
Object::StrikeThrough(inner) => inner.into(),
|
||||
Object::Code(inner) => inner.into(),
|
||||
Object::Verbatim(inner) => inner.into(),
|
||||
Object::PlainText(inner) => inner.into(),
|
||||
Object::RegularLink(inner) => inner.into(),
|
||||
Object::RadioLink(inner) => inner.into(),
|
||||
Object::RadioTarget(inner) => inner.into(),
|
||||
Object::PlainLink(inner) => inner.into(),
|
||||
Object::AngleLink(inner) => inner.into(),
|
||||
Object::OrgMacro(inner) => inner.into(),
|
||||
Object::Entity(inner) => inner.into(),
|
||||
Object::LatexFragment(inner) => inner.into(),
|
||||
Object::ExportSnippet(inner) => inner.into(),
|
||||
Object::FootnoteReference(inner) => inner.into(),
|
||||
Object::Citation(inner) => inner.into(),
|
||||
Object::CitationReference(inner) => inner.into(),
|
||||
Object::InlineBabelCall(inner) => inner.into(),
|
||||
Object::InlineSourceBlock(inner) => inner.into(),
|
||||
Object::LineBreak(inner) => inner.into(),
|
||||
Object::Target(inner) => inner.into(),
|
||||
Object::StatisticsCookie(inner) => inner.into(),
|
||||
Object::Subscript(inner) => inner.into(),
|
||||
Object::Superscript(inner) => inner.into(),
|
||||
Object::Timestamp(inner) => inner.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
to_ast_node!(&'r Document<'s>, AstNode::Document);
|
||||
to_ast_node!(&'r Heading<'s>, AstNode::Heading);
|
||||
to_ast_node!(&'r Section<'s>, AstNode::Section);
|
||||
to_ast_node!(&'r Paragraph<'s>, AstNode::Paragraph);
|
||||
to_ast_node!(&'r PlainList<'s>, AstNode::PlainList);
|
||||
to_ast_node!(&'r PlainListItem<'s>, AstNode::PlainListItem);
|
||||
to_ast_node!(&'r CenterBlock<'s>, AstNode::CenterBlock);
|
||||
to_ast_node!(&'r QuoteBlock<'s>, AstNode::QuoteBlock);
|
||||
to_ast_node!(&'r SpecialBlock<'s>, AstNode::SpecialBlock);
|
||||
to_ast_node!(&'r DynamicBlock<'s>, AstNode::DynamicBlock);
|
||||
to_ast_node!(&'r FootnoteDefinition<'s>, AstNode::FootnoteDefinition);
|
||||
to_ast_node!(&'r Comment<'s>, AstNode::Comment);
|
||||
to_ast_node!(&'r Drawer<'s>, AstNode::Drawer);
|
||||
to_ast_node!(&'r PropertyDrawer<'s>, AstNode::PropertyDrawer);
|
||||
to_ast_node!(&'r NodeProperty<'s>, AstNode::NodeProperty);
|
||||
to_ast_node!(&'r Table<'s>, AstNode::Table);
|
||||
to_ast_node!(&'r TableRow<'s>, AstNode::TableRow);
|
||||
to_ast_node!(&'r VerseBlock<'s>, AstNode::VerseBlock);
|
||||
to_ast_node!(&'r CommentBlock<'s>, AstNode::CommentBlock);
|
||||
to_ast_node!(&'r ExampleBlock<'s>, AstNode::ExampleBlock);
|
||||
to_ast_node!(&'r ExportBlock<'s>, AstNode::ExportBlock);
|
||||
to_ast_node!(&'r SrcBlock<'s>, AstNode::SrcBlock);
|
||||
to_ast_node!(&'r Clock<'s>, AstNode::Clock);
|
||||
to_ast_node!(&'r DiarySexp<'s>, AstNode::DiarySexp);
|
||||
to_ast_node!(&'r Planning<'s>, AstNode::Planning);
|
||||
to_ast_node!(&'r FixedWidthArea<'s>, AstNode::FixedWidthArea);
|
||||
to_ast_node!(&'r HorizontalRule<'s>, AstNode::HorizontalRule);
|
||||
to_ast_node!(&'r Keyword<'s>, AstNode::Keyword);
|
||||
to_ast_node!(&'r BabelCall<'s>, AstNode::BabelCall);
|
||||
to_ast_node!(&'r LatexEnvironment<'s>, AstNode::LatexEnvironment);
|
||||
to_ast_node!(&'r Bold<'s>, AstNode::Bold);
|
||||
to_ast_node!(&'r Italic<'s>, AstNode::Italic);
|
||||
to_ast_node!(&'r Underline<'s>, AstNode::Underline);
|
||||
to_ast_node!(&'r StrikeThrough<'s>, AstNode::StrikeThrough);
|
||||
to_ast_node!(&'r Code<'s>, AstNode::Code);
|
||||
to_ast_node!(&'r Verbatim<'s>, AstNode::Verbatim);
|
||||
to_ast_node!(&'r PlainText<'s>, AstNode::PlainText);
|
||||
to_ast_node!(&'r RegularLink<'s>, AstNode::RegularLink);
|
||||
to_ast_node!(&'r RadioLink<'s>, AstNode::RadioLink);
|
||||
to_ast_node!(&'r RadioTarget<'s>, AstNode::RadioTarget);
|
||||
to_ast_node!(&'r PlainLink<'s>, AstNode::PlainLink);
|
||||
to_ast_node!(&'r AngleLink<'s>, AstNode::AngleLink);
|
||||
to_ast_node!(&'r OrgMacro<'s>, AstNode::OrgMacro);
|
||||
to_ast_node!(&'r Entity<'s>, AstNode::Entity);
|
||||
to_ast_node!(&'r LatexFragment<'s>, AstNode::LatexFragment);
|
||||
to_ast_node!(&'r ExportSnippet<'s>, AstNode::ExportSnippet);
|
||||
to_ast_node!(&'r FootnoteReference<'s>, AstNode::FootnoteReference);
|
||||
to_ast_node!(&'r Citation<'s>, AstNode::Citation);
|
||||
to_ast_node!(&'r CitationReference<'s>, AstNode::CitationReference);
|
||||
to_ast_node!(&'r InlineBabelCall<'s>, AstNode::InlineBabelCall);
|
||||
to_ast_node!(&'r InlineSourceBlock<'s>, AstNode::InlineSourceBlock);
|
||||
to_ast_node!(&'r LineBreak<'s>, AstNode::LineBreak);
|
||||
to_ast_node!(&'r Target<'s>, AstNode::Target);
|
||||
to_ast_node!(&'r StatisticsCookie<'s>, AstNode::StatisticsCookie);
|
||||
to_ast_node!(&'r Subscript<'s>, AstNode::Subscript);
|
||||
to_ast_node!(&'r Superscript<'s>, AstNode::Superscript);
|
||||
to_ast_node!(&'r TableCell<'s>, AstNode::TableCell);
|
||||
to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp);
|
||||
|
||||
impl<'r, 's> GetStandardProperties<'s> for AstNode<'r, 's> {
|
||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn crate::types::StandardProperties<'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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::Element;
|
||||
use super::GetStandardProperties;
|
||||
use super::Object;
|
||||
use super::StandardProperties;
|
||||
use super::Timestamp;
|
||||
|
||||
pub type PriorityCookie = u8;
|
||||
pub type HeadlineLevel = u16;
|
||||
@@ -9,6 +12,8 @@ pub type HeadlineLevel = u16;
|
||||
#[derive(Debug)]
|
||||
pub struct Document<'s> {
|
||||
pub source: &'s str,
|
||||
pub category: Option<String>,
|
||||
pub path: Option<PathBuf>,
|
||||
pub zeroth_section: Option<Section<'s>>,
|
||||
pub children: Vec<Heading<'s>>,
|
||||
}
|
||||
@@ -24,6 +29,10 @@ pub struct Heading<'s> {
|
||||
pub children: Vec<DocumentElement<'s>>,
|
||||
pub is_comment: bool,
|
||||
pub is_archived: bool,
|
||||
pub is_footnote_section: bool,
|
||||
pub scheduled: Option<Timestamp<'s>>,
|
||||
pub deadline: Option<Timestamp<'s>>,
|
||||
pub closed: Option<Timestamp<'s>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -45,7 +54,7 @@ pub enum TodoKeywordType {
|
||||
}
|
||||
|
||||
impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
|
||||
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
|
||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
||||
match self {
|
||||
DocumentElement::Heading(inner) => inner,
|
||||
DocumentElement::Section(inner) => inner,
|
||||
@@ -54,19 +63,31 @@ impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Document<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Section<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Heading<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Heading<'s> {
|
||||
pub fn get_raw_value(&self) -> String {
|
||||
// TODO: I think this could just return a string slice instead of an owned string.
|
||||
let title_source: String = self
|
||||
.title
|
||||
.iter()
|
||||
.map(|obj| obj.get_standard_properties().get_source())
|
||||
.collect();
|
||||
title_source
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::greater_element::DynamicBlock;
|
||||
use super::greater_element::FootnoteDefinition;
|
||||
use super::greater_element::GreaterBlock;
|
||||
use super::greater_element::PlainList;
|
||||
use super::greater_element::PropertyDrawer;
|
||||
use super::greater_element::Table;
|
||||
@@ -19,16 +18,21 @@ use super::lesser_element::Paragraph;
|
||||
use super::lesser_element::Planning;
|
||||
use super::lesser_element::SrcBlock;
|
||||
use super::lesser_element::VerseBlock;
|
||||
use super::CenterBlock;
|
||||
use super::Drawer;
|
||||
use super::GetStandardProperties;
|
||||
use super::QuoteBlock;
|
||||
use super::SetSource;
|
||||
use super::SpecialBlock;
|
||||
use super::StandardProperties;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Element<'s> {
|
||||
Paragraph(Paragraph<'s>),
|
||||
PlainList(PlainList<'s>),
|
||||
GreaterBlock(GreaterBlock<'s>),
|
||||
CenterBlock(CenterBlock<'s>),
|
||||
QuoteBlock(QuoteBlock<'s>),
|
||||
SpecialBlock(SpecialBlock<'s>),
|
||||
DynamicBlock(DynamicBlock<'s>),
|
||||
FootnoteDefinition(FootnoteDefinition<'s>),
|
||||
Comment(Comment<'s>),
|
||||
@@ -56,7 +60,9 @@ impl<'s> SetSource<'s> for Element<'s> {
|
||||
match self {
|
||||
Element::Paragraph(obj) => obj.source = source,
|
||||
Element::PlainList(obj) => obj.source = source,
|
||||
Element::GreaterBlock(obj) => obj.source = source,
|
||||
Element::CenterBlock(obj) => obj.source = source,
|
||||
Element::QuoteBlock(obj) => obj.source = source,
|
||||
Element::SpecialBlock(obj) => obj.source = source,
|
||||
Element::DynamicBlock(obj) => obj.source = source,
|
||||
Element::FootnoteDefinition(obj) => obj.source = source,
|
||||
Element::Comment(obj) => obj.source = source,
|
||||
@@ -81,11 +87,13 @@ impl<'s> SetSource<'s> for Element<'s> {
|
||||
}
|
||||
|
||||
impl<'s> GetStandardProperties<'s> for Element<'s> {
|
||||
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
|
||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
||||
match self {
|
||||
Element::Paragraph(inner) => inner,
|
||||
Element::PlainList(inner) => inner,
|
||||
Element::GreaterBlock(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,
|
||||
|
||||
@@ -2,11 +2,11 @@ use super::StandardProperties;
|
||||
|
||||
pub trait GetStandardProperties<'s> {
|
||||
// TODO: Can I eliminate this dynamic dispatch, perhaps using nominal generic structs? Low prioritiy since this is not used during parsing.
|
||||
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties;
|
||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s>;
|
||||
}
|
||||
|
||||
impl<'s, I: StandardProperties<'s>> GetStandardProperties<'s> for I {
|
||||
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
|
||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,17 @@ use super::StandardProperties;
|
||||
#[derive(Debug)]
|
||||
pub struct PlainList<'s> {
|
||||
pub source: &'s str,
|
||||
pub list_type: PlainListType,
|
||||
pub children: Vec<PlainListItem<'s>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum PlainListType {
|
||||
Unordered,
|
||||
Ordered,
|
||||
Descriptive,
|
||||
}
|
||||
|
||||
/// The width that something is indented. For example, a single tab character could be a value of 4 or 8.
|
||||
pub type IndentationLevel = u16;
|
||||
|
||||
@@ -18,11 +26,16 @@ pub struct PlainListItem<'s> {
|
||||
pub source: &'s str,
|
||||
pub indentation: IndentationLevel,
|
||||
pub bullet: &'s str,
|
||||
pub counter: Option<PlainListItemCounter>,
|
||||
pub checkbox: Option<(CheckboxType, &'s str)>,
|
||||
pub tag: Vec<Object<'s>>,
|
||||
pub pre_blank: PlainListItemPreBlank,
|
||||
pub children: Vec<Element<'s>>,
|
||||
}
|
||||
|
||||
pub type PlainListItemCounter = u16;
|
||||
pub type PlainListItemPreBlank = u8;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CheckboxType {
|
||||
On,
|
||||
@@ -31,7 +44,19 @@ pub enum CheckboxType {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GreaterBlock<'s> {
|
||||
pub struct CenterBlock<'s> {
|
||||
pub source: &'s str,
|
||||
pub children: Vec<Element<'s>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuoteBlock<'s> {
|
||||
pub source: &'s str,
|
||||
pub children: Vec<Element<'s>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SpecialBlock<'s> {
|
||||
pub source: &'s str,
|
||||
pub name: &'s str,
|
||||
pub parameters: Option<&'s str>,
|
||||
@@ -69,6 +94,7 @@ pub struct PropertyDrawer<'s> {
|
||||
#[derive(Debug)]
|
||||
pub struct NodeProperty<'s> {
|
||||
pub source: &'s str,
|
||||
pub name: &'s str,
|
||||
pub value: Option<&'s str>,
|
||||
}
|
||||
|
||||
@@ -85,62 +111,102 @@ pub struct TableRow<'s> {
|
||||
pub children: Vec<TableCell<'s>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TableRowType {
|
||||
Standard,
|
||||
Rule,
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for PlainList<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for PlainListItem<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for GreaterBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
impl<'s> StandardProperties<'s> for CenterBlock<'s> {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Drawer<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for NodeProperty<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Table<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for TableRow<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> PlainListItem<'s> {
|
||||
pub fn get_checkbox(&self) -> Option<&'s str> {
|
||||
self.checkbox.as_ref().map(|(_, checkbox)| *checkbox)
|
||||
}
|
||||
|
||||
pub fn get_checkbox_type(&self) -> Option<&CheckboxType> {
|
||||
self.checkbox
|
||||
.as_ref()
|
||||
.map(|(checkbox_type, _)| checkbox_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> TableRow<'s> {
|
||||
pub fn get_type(&self) -> TableRowType {
|
||||
if self.children.is_empty() {
|
||||
TableRowType::Rule
|
||||
} else {
|
||||
TableRowType::Standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::object::Object;
|
||||
use super::PlainText;
|
||||
use super::StandardProperties;
|
||||
use super::Timestamp;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Paragraph<'s> {
|
||||
@@ -11,6 +12,7 @@ pub struct Paragraph<'s> {
|
||||
#[derive(Debug)]
|
||||
pub struct Comment<'s> {
|
||||
pub source: &'s str,
|
||||
pub value: Vec<&'s str>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -72,6 +74,9 @@ pub struct DiarySexp<'s> {
|
||||
#[derive(Debug)]
|
||||
pub struct Planning<'s> {
|
||||
pub source: &'s str,
|
||||
pub scheduled: Option<Timestamp<'s>>,
|
||||
pub deadline: Option<Timestamp<'s>>,
|
||||
pub closed: Option<Timestamp<'s>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -115,93 +120,106 @@ impl<'s> Paragraph<'s> {
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Paragraph<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for TableCell<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Comment<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for VerseBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
impl<'s> StandardProperties<'s> for ExportBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
impl<'s> StandardProperties<'s> for SrcBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Clock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for DiarySexp<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Planning<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Keyword<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for BabelCall<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Comment<'s> {
|
||||
pub fn get_value(&self) -> String {
|
||||
// TODO: maybe we should handle parsing here instead of storing the parsing result in the AST since I imagine getting the value of comments won't be a common operation.
|
||||
let final_size = self.value.iter().map(|line| line.len()).sum();
|
||||
let mut ret = String::with_capacity(final_size);
|
||||
for line in &self.value {
|
||||
ret.push_str(line);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
12
src/types/macros.rs
Normal file
12
src/types/macros.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
/// Write the implementation of From<> to convert a borrow of the type to an AstNode
|
||||
macro_rules! to_ast_node {
|
||||
($inp:ty, $enum:expr) => {
|
||||
impl<'r, 's> From<$inp> for AstNode<'r, 's> {
|
||||
fn from(value: $inp) -> Self {
|
||||
$enum(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) use to_ast_node;
|
||||
@@ -1,11 +1,14 @@
|
||||
mod ast_node;
|
||||
mod document;
|
||||
mod element;
|
||||
mod get_standard_properties;
|
||||
mod greater_element;
|
||||
mod lesser_element;
|
||||
mod macros;
|
||||
mod object;
|
||||
mod source;
|
||||
mod standard_properties;
|
||||
pub(crate) use ast_node::AstNode;
|
||||
pub use document::Document;
|
||||
pub use document::DocumentElement;
|
||||
pub use document::Heading;
|
||||
@@ -15,18 +18,24 @@ pub use document::Section;
|
||||
pub use document::TodoKeywordType;
|
||||
pub use element::Element;
|
||||
pub use get_standard_properties::GetStandardProperties;
|
||||
pub use greater_element::CenterBlock;
|
||||
pub use greater_element::CheckboxType;
|
||||
pub use greater_element::Drawer;
|
||||
pub use greater_element::DynamicBlock;
|
||||
pub use greater_element::FootnoteDefinition;
|
||||
pub use greater_element::GreaterBlock;
|
||||
pub use greater_element::IndentationLevel;
|
||||
pub use greater_element::NodeProperty;
|
||||
pub use greater_element::PlainList;
|
||||
pub use greater_element::PlainListItem;
|
||||
pub use greater_element::PlainListItemCounter;
|
||||
pub use greater_element::PlainListItemPreBlank;
|
||||
pub use greater_element::PlainListType;
|
||||
pub use greater_element::PropertyDrawer;
|
||||
pub use greater_element::QuoteBlock;
|
||||
pub use greater_element::SpecialBlock;
|
||||
pub use greater_element::Table;
|
||||
pub use greater_element::TableRow;
|
||||
pub use greater_element::TableRowType;
|
||||
pub use lesser_element::BabelCall;
|
||||
pub use lesser_element::Clock;
|
||||
pub use lesser_element::Comment;
|
||||
@@ -48,14 +57,23 @@ pub use object::Bold;
|
||||
pub use object::Citation;
|
||||
pub use object::CitationReference;
|
||||
pub use object::Code;
|
||||
pub use object::Date;
|
||||
pub use object::DayOfMonth;
|
||||
pub use object::DayOfMonthInner;
|
||||
pub use object::Entity;
|
||||
pub use object::ExportSnippet;
|
||||
pub use object::FootnoteReference;
|
||||
pub use object::Hour;
|
||||
pub use object::HourInner;
|
||||
pub use object::InlineBabelCall;
|
||||
pub use object::InlineSourceBlock;
|
||||
pub use object::Italic;
|
||||
pub use object::LatexFragment;
|
||||
pub use object::LineBreak;
|
||||
pub use object::Minute;
|
||||
pub use object::MinuteInner;
|
||||
pub use object::Month;
|
||||
pub use object::MonthInner;
|
||||
pub use object::Object;
|
||||
pub use object::OrgMacro;
|
||||
pub use object::PlainLink;
|
||||
@@ -63,13 +81,24 @@ pub use object::PlainText;
|
||||
pub use object::RadioLink;
|
||||
pub use object::RadioTarget;
|
||||
pub use object::RegularLink;
|
||||
pub use object::Repeater;
|
||||
pub use object::RepeaterType;
|
||||
pub use object::RepeaterWarningDelayValueType;
|
||||
pub use object::StatisticsCookie;
|
||||
pub use object::StrikeThrough;
|
||||
pub use object::Subscript;
|
||||
pub use object::Superscript;
|
||||
pub use object::Target;
|
||||
pub use object::Time;
|
||||
pub use object::TimeUnit;
|
||||
pub use object::Timestamp;
|
||||
pub use object::TimestampRangeType;
|
||||
pub use object::TimestampType;
|
||||
pub use object::Underline;
|
||||
pub use object::Verbatim;
|
||||
pub use object::WarningDelay;
|
||||
pub use object::WarningDelayType;
|
||||
pub use object::Year;
|
||||
pub use object::YearInner;
|
||||
pub(crate) use source::SetSource;
|
||||
pub use standard_properties::StandardProperties;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::GetStandardProperties;
|
||||
use super::StandardProperties;
|
||||
|
||||
// TODO: Why did we make Object implement PartialEq again? Was it just for tests?
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Object<'s> {
|
||||
Bold(Bold<'s>),
|
||||
@@ -181,13 +182,260 @@ pub struct Superscript<'s> {
|
||||
pub source: &'s str,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Timestamp<'s> {
|
||||
pub source: &'s str,
|
||||
pub timestamp_type: TimestampType,
|
||||
pub range_type: TimestampRangeType,
|
||||
pub start: Option<Date<'s>>,
|
||||
pub end: Option<Date<'s>>,
|
||||
pub start_time: Option<Time<'s>>,
|
||||
pub end_time: Option<Time<'s>>,
|
||||
pub repeater: Option<Repeater>,
|
||||
pub warning_delay: Option<WarningDelay>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TimestampType {
|
||||
Diary,
|
||||
Active,
|
||||
Inactive,
|
||||
ActiveRange,
|
||||
InactiveRange,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TimestampRangeType {
|
||||
None,
|
||||
DateRange,
|
||||
TimeRange,
|
||||
}
|
||||
|
||||
pub type YearInner = u16;
|
||||
pub type MonthInner = u8;
|
||||
pub type DayOfMonthInner = u8;
|
||||
pub type HourInner = u8;
|
||||
pub type MinuteInner = u8;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Year(YearInner);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Month(MonthInner);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct DayOfMonth(DayOfMonthInner);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Hour(HourInner);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Minute(MinuteInner);
|
||||
|
||||
impl Year {
|
||||
// TODO: Make a real error type instead of a boxed any error.
|
||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let year = source.parse::<YearInner>()?;
|
||||
Ok(Year(year))
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> YearInner {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Month {
|
||||
// TODO: Make a real error type instead of a boxed any error.
|
||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let month = source.parse::<MonthInner>()?;
|
||||
if month < 1 || month > 12 {
|
||||
Err("Month exceeds possible range.")?;
|
||||
}
|
||||
Ok(Month(month))
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> MonthInner {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DayOfMonth {
|
||||
// TODO: Make a real error type instead of a boxed any error.
|
||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let day_of_month = source.parse::<DayOfMonthInner>()?;
|
||||
if day_of_month < 1 || day_of_month > 31 {
|
||||
Err("Day of month exceeds possible range.")?;
|
||||
}
|
||||
Ok(DayOfMonth(day_of_month))
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> DayOfMonthInner {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Hour {
|
||||
// TODO: Make a real error type instead of a boxed any error.
|
||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let hour = source.parse::<HourInner>()?;
|
||||
if hour > 23 {
|
||||
Err("Hour exceeds possible range.")?;
|
||||
}
|
||||
Ok(Hour(hour))
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> HourInner {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Minute {
|
||||
// TODO: Make a real error type instead of a boxed any error.
|
||||
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let minute = source.parse::<MinuteInner>()?;
|
||||
if minute > 59 {
|
||||
Err("Minute exceeds possible range.")?;
|
||||
}
|
||||
Ok(Minute(minute))
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> MinuteInner {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Date<'s> {
|
||||
year: Year,
|
||||
month: Month,
|
||||
day_of_month: DayOfMonth,
|
||||
day_name: Option<&'s str>,
|
||||
}
|
||||
|
||||
impl<'s> Date<'s> {
|
||||
// TODO: Make a real error type instead of a boxed any error.
|
||||
pub fn new(
|
||||
year: Year,
|
||||
month: Month,
|
||||
day_of_month: DayOfMonth,
|
||||
day_name: Option<&'s str>,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
// TODO: Does org-mode support non-gregorian calendars?
|
||||
// TODO: Do I want to validate leap year?
|
||||
match (month.get_value(), day_of_month.get_value()) {
|
||||
(1, 1..=31) => {}
|
||||
(2, 1..=29) => {}
|
||||
(3, 1..=31) => {}
|
||||
(4, 1..=30) => {}
|
||||
(5, 1..=31) => {}
|
||||
(6, 1..=30) => {}
|
||||
(7, 1..=31) => {}
|
||||
(8, 1..=31) => {}
|
||||
(9, 1..=30) => {}
|
||||
(10, 1..=31) => {}
|
||||
(11, 1..=30) => {}
|
||||
(12, 1..=31) => {}
|
||||
_ => Err("Invalid day of month for the month.")?,
|
||||
};
|
||||
Ok(Date {
|
||||
year,
|
||||
month,
|
||||
day_of_month,
|
||||
day_name,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_year(&self) -> &Year {
|
||||
&self.year
|
||||
}
|
||||
|
||||
pub fn get_month(&self) -> &Month {
|
||||
&self.month
|
||||
}
|
||||
|
||||
pub fn get_day_of_month(&self) -> &DayOfMonth {
|
||||
&self.day_of_month
|
||||
}
|
||||
|
||||
pub fn get_day_name(&self) -> Option<&'s str> {
|
||||
self.day_name
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Time<'s> {
|
||||
hour: Hour,
|
||||
minute: Minute,
|
||||
postfix: Option<&'s str>,
|
||||
}
|
||||
|
||||
impl<'s> Time<'s> {
|
||||
// TODO: Make a real error type instead of a boxed any error.
|
||||
pub fn new(
|
||||
hour: Hour,
|
||||
minute: Minute,
|
||||
postfix: Option<&'s str>,
|
||||
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
Ok(Time {
|
||||
hour,
|
||||
minute,
|
||||
postfix,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_hour(&self) -> &Hour {
|
||||
&self.hour
|
||||
}
|
||||
|
||||
pub fn get_minute(&self) -> &Minute {
|
||||
&self.minute
|
||||
}
|
||||
|
||||
pub fn get_postfix(&self) -> Option<&'s str> {
|
||||
self.postfix
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum RepeaterType {
|
||||
Cumulative,
|
||||
CatchUp,
|
||||
Restart,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum WarningDelayType {
|
||||
All,
|
||||
First,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum TimeUnit {
|
||||
Hour,
|
||||
Day,
|
||||
Week,
|
||||
Month,
|
||||
Year,
|
||||
}
|
||||
|
||||
pub type RepeaterWarningDelayValueType = u16;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Repeater {
|
||||
pub repeater_type: RepeaterType,
|
||||
pub value: RepeaterWarningDelayValueType,
|
||||
pub unit: TimeUnit,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct WarningDelay {
|
||||
pub warning_delay_type: WarningDelayType,
|
||||
pub value: RepeaterWarningDelayValueType,
|
||||
pub unit: TimeUnit,
|
||||
}
|
||||
|
||||
impl<'s> GetStandardProperties<'s> for Object<'s> {
|
||||
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
|
||||
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
|
||||
match self {
|
||||
Object::Bold(inner) => inner,
|
||||
Object::Italic(inner) => inner,
|
||||
@@ -221,163 +469,169 @@ impl<'s> GetStandardProperties<'s> for Object<'s> {
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Bold<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Italic<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Underline<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for StrikeThrough<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Code<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Verbatim<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for RegularLink<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for RadioLink<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for RadioTarget<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for PlainLink<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for AngleLink<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for OrgMacro<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Entity<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for LatexFragment<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for ExportSnippet<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for FootnoteReference<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Citation<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for CitationReference<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for InlineBabelCall<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for InlineSourceBlock<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for LineBreak<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Target<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for StatisticsCookie<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Subscript<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Superscript<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for Timestamp<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> StandardProperties<'s> for PlainText<'s> {
|
||||
fn get_source(&'s self) -> &'s str {
|
||||
fn get_source<'b>(&'b self) -> &'s str {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Timestamp<'s> {
|
||||
pub fn get_raw_value(&self) -> &'s str {
|
||||
self.source.trim_end()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ pub trait StandardProperties<'s> {
|
||||
/// Get the slice of the entire AST node.
|
||||
///
|
||||
/// This corresponds to :begin to :end in upstream org-mode's standard properties.
|
||||
fn get_source(&'s self) -> &'s str;
|
||||
fn get_source<'b>(&'b self) -> &'s str;
|
||||
|
||||
// Get the slice of the AST node's contents.
|
||||
//
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
#[cfg(feature = "compare")]
|
||||
#![cfg(feature = "compare")]
|
||||
|
||||
#[feature(exit_status_error)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/tests.rs"));
|
||||
|
||||
@@ -1,7 +1,66 @@
|
||||
// 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 {name}() -> Result<(), Box<dyn std::error::Error>> {{
|
||||
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())?;
|
||||
Ok(())
|
||||
}}
|
||||
|
||||
{expect_fail}
|
||||
#[test]
|
||||
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 = {{
|
||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
||||
global_settings.list_allow_alphabetical = true;
|
||||
global_settings
|
||||
}};
|
||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
|
||||
Ok(())
|
||||
}}
|
||||
|
||||
{expect_fail}
|
||||
#[test]
|
||||
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 = {{
|
||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
||||
global_settings.tab_width = 1;
|
||||
global_settings
|
||||
}};
|
||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
|
||||
Ok(())
|
||||
}}
|
||||
|
||||
{expect_fail}
|
||||
#[test]
|
||||
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 = {{
|
||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
||||
global_settings.tab_width = 16;
|
||||
global_settings
|
||||
}};
|
||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
|
||||
Ok(())
|
||||
}}
|
||||
|
||||
{expect_fail}
|
||||
#[test]
|
||||
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 = {{
|
||||
let mut global_settings = organic::settings::GlobalSettings::default();
|
||||
global_settings.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd;
|
||||
global_settings
|
||||
}};
|
||||
organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?;
|
||||
Ok(())
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user