48 Commits

Author SHA1 Message Date
Tom Alexander
8450785186 Add test showing we are not handling the odd startup option for headline depth.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-15 22:08:42 -04:00
Tom Alexander
d443dbd468 Introduce the tab_width setting and give tabs a greater value when counting indentation level. 2023-09-15 21:59:48 -04:00
Tom Alexander
c9ce32c881 Remve redundant org_spaces functions.
Turns out the nom space0/space1 parsers accept tab characters already.
2023-09-15 21:28:40 -04:00
Tom Alexander
85454a0a27 Fix footnote reference function label matcher.
Previously when a label started with a number but contained other characters, this parser would fail because it would not match the entire label.
2023-09-15 21:14:44 -04:00
Tom Alexander
fdebf6dec5 Delete already solved TODO. 2023-09-15 21:08:52 -04:00
Tom Alexander
444d6758aa Handle leading blank lines in greater blocks.
Some checks failed
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-15 21:03:35 -04:00
Tom Alexander
6c7203410e Add a test showing we're not handling leading blank lines in greater blocks. 2023-09-15 17:02:41 -04:00
Tom Alexander
bfe67b1f75 Parse plain list item checkboxes. 2023-09-15 16:09:57 -04:00
Tom Alexander
fd41ad9c29 Pretend dos line endings do not exist. 2023-09-15 14:13:17 -04:00
Tom Alexander
7f751d4f28 Allow no digit in repeater in timestamp. 2023-09-15 13:12:54 -04:00
Tom Alexander
52a4dab67c Use the timestamp parser in planning.
Previously we did not support inactive timestamps in planning. This fixes that.
2023-09-15 12:45:19 -04:00
Tom Alexander
3d86e75059 Always match the entire entity name.
Some checks failed
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-14 04:29:50 -04:00
Tom Alexander
ca6fdf1924 Support different cases in radio links. 2023-09-14 04:04:21 -04:00
Tom Alexander
66d16d89ed Support interchangeable whitespace in re-matching plain text. 2023-09-14 04:00:34 -04:00
Tom Alexander
ee5e0698b1 Add an optimization idea. 2023-09-14 03:25:12 -04:00
Tom Alexander
22681b6a58 Support trailing whitespace in fixed-width areas. 2023-09-14 03:20:44 -04:00
Tom Alexander
876d33239e Allow any character to be escaped in the path for links. 2023-09-14 03:05:11 -04:00
Tom Alexander
87941271a4 Handle headlines with trailing spaces without tags. 2023-09-14 02:43:40 -04:00
Tom Alexander
32b19d68d0 Support todo keywords with fast access. 2023-09-14 02:24:06 -04:00
Tom Alexander
830097b0a9 Add a test showing we are not handling fast access states in todo keywords. 2023-09-14 02:18:49 -04:00
Tom Alexander
44e9f708c9 Handle the possibility of a title-less headline. 2023-09-14 02:01:24 -04:00
Tom Alexander
fc4ff97c14 Add a test showing we are not handling empty headlines properly. 2023-09-14 00:50:31 -04:00
Tom Alexander
33372429dd Add a config option for org-list-allow-alphabetical.
This fixes an issue where lines in a paragraph were incorrectly getting identified as lists because I had defaulted to assuming alphabetical bullets were allowed.
2023-09-14 00:27:54 -04:00
Tom Alexander
ac0db64081 Add cargo directive to rebuild the auto-generated tests when files under org_mode_samples get updated.
Some checks failed
rust-test Build rust-test has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-13 21:28:44 -04:00
Tom Alexander
b8a4876779 Disable auto-aligning tables when Emacs loads Org-mode.
Emacs will auto-align tables when org-mode is loaded if the document contains "#+STARTUP: align". Since Organic is just a parser, it has no business editing the input it receives so we are disabling this auto-align in Emacs to make the tests work properly.
2023-09-13 21:02:38 -04:00
Tom Alexander
925c42c8fb Add test showing we currently are letting emacs align tables at startup. 2023-09-13 21:02:38 -04:00
Tom Alexander
7d4100d956 Add worg to the foreign document test.
A lot of the documents are failing so there are going to be a lot of bug fixes in this branch.
2023-09-13 20:10:50 -04:00
Tom Alexander
53d90a2949 Update the README to have instructions on running the tests and development programs.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-13 20:10:14 -04:00
Tom Alexander
26f41b83aa Publish version 0.1.7.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-12 16:15:53 -04:00
Tom Alexander
e4c0e32536 Change public interface to return boxed dynamic error instead of String.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-12 15:52:01 -04:00
Tom Alexander
37e85158ea Reduce the exposed functions when compare feature is enabled. 2023-09-12 15:48:37 -04:00
Tom Alexander
6589a755a6 Merge branch 'reduce_pub'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 15:37:33 -04:00
Tom Alexander
a651b79e77 Move sexp into compare. 2023-09-11 15:37:20 -04:00
Tom Alexander
98de5e4ec5 Remove unused sexp parser entry point. 2023-09-11 15:07:52 -04:00
Tom Alexander
cf383fa394 Only include sexp module if compare feature is enabled.
All checks were successful
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 14:59:24 -04:00
Tom Alexander
84953c1669 Put back in needed pubs. 2023-09-11 14:59:23 -04:00
Tom Alexander
7650a9edff Remove all pub. 2023-09-11 13:11:08 -04:00
Tom Alexander
a74319d381 Add TODO. 2023-09-11 13:09:46 -04:00
Tom Alexander
7e57285ea7 Merge branch 'additional_detects'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 12:59:18 -04:00
Tom Alexander
f103d168d5 Update diary sexp parser to match org-mode's behavior. 2023-09-11 12:34:57 -04:00
Tom Alexander
f54081437a Add detect element functions for all elements that can be reasonably detected more efficiently than just parsing normally. 2023-09-11 12:28:15 -04:00
Tom Alexander
aa5988bc2f Re-enable a test.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 11:18:25 -04:00
Tom Alexander
76ca2b9762 Merge branch 'description_list_colon_in_tag'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-09-11 10:42:25 -04:00
Tom Alexander
1561e1e580 Update plain list item tag parser to allow double colon as long as its not the last one on that line. 2023-09-11 10:29:15 -04:00
Tom Alexander
1f11bfa2ec Join the plain list item tag end matchers again. 2023-09-11 10:13:22 -04:00
Tom Alexander
8440a3b256 Update test to show that we are not parsing description lists with double colon inside the tag correctly. 2023-09-11 10:01:50 -04:00
Tom Alexander
de7ad182b3 Make parse and compare their own binaries.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
This ensures that both code paths are capable of being parsed by rust-analyzer simultaneously and I think it will be less confusing to newcomers.
2023-09-09 04:21:34 -04:00
Tom Alexander
b75d9f5c91 Remove an outdated comment. 2023-09-09 00:00:12 -04:00
89 changed files with 955 additions and 551 deletions

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "organic" name = "organic"
version = "0.1.6" version = "0.1.7"
authors = ["Tom Alexander <tom@fizz.buzz>"] authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser." description = "An org-mode parser."
edition = "2021" edition = "2021"
@@ -21,9 +21,15 @@ name = "organic"
path = "src/lib.rs" path = "src/lib.rs"
[[bin]] [[bin]]
# This bin exists for development purposes only. The real target of this crate is the library. # This bin exists for development purposes only. The real target of this crate is the library.
name = "parse" name = "parse"
path = "src/main.rs" path = "src/main.rs"
[[bin]]
# This bin exists for development purposes only. The real target of this crate is the library.
name = "compare"
path = "src/bin_compare.rs"
required-features = ["compare"]
[dependencies] [dependencies]
nom = "7.1.1" nom = "7.1.1"

View File

@@ -2,12 +2,63 @@
Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) parser. Organic is an emacs-less implementation of an [org-mode](https://orgmode.org/) parser.
## Project Status ## Project Status
This project is a personal learning project to grow my experience in [rust](https://www.rust-lang.org/). It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README. This project is a personal learning project to grow my experience in [rust](https://www.rust-lang.org/). It is under development and at this time I would not recommend anyone use this code. The goal is to turn this into a project others can use, at which point more information will appear in this README.
## Using this library
TODO: Add section on using Organic as a library (which is the intended use for this project).
### The parse binary
This program takes org-mode input either streamed in on stdin or as paths to files passed in as arguments. It then parses them using Organic and dumps the result to stdout. This program is intended solely as a development tool. Examples:
```bash
cat /foo/bar.org | cargo run --bin parse
```
```bash
cargo build --profile release-lto
./target/release-lto/parse /foo/bar.org /lorem/ipsum.org
```
### The compare binary
This program takes org-mode input either streamed in on stdin or as paths to files passed in as arguments. It then parses them using Organic and the official Emacs Org-mode parser and compares the parse result. This program is intended solely as a development tool. Since org-mode is a moving target, it is recommended that you run this through docker since we pin the version of org-mode to a specific revision. Examples:
```bash
cat /foo/bar.org | ./scripts/run_docker_compare.bash
```
```bash
./scripts/run_docker_compare.bash /foo/bar.org /lorem/ipsum.org
```
Not recommended since it is not through docker:
```bash
cat /foo/bar.org | cargo run --features compare --bin compare
```
```bash
cargo build --profile release-lto --features compare
./target/release-lto/compare /foo/bar.org /lorem/ipsum.org
```
## Running the tests
There are three levels of tests for this repository: the standard tests, the autogenerated tests, and the foreign document tests.
### The standard tests
These are regular hand-written rust tests. These can be run with:
```bash
make unittest
```
### The auto-generated tests
These tests are automatically generated from the files in the `org_mode_samples` directory and they are still integrated with the rust/cargo testing framework. For each org-mode document in that folder, a test is generated that will parse the document with both Organic and the official Emacs Org-mode parser and then it will compare the parse results. Any deviation is considered a failure. Since org-mode is a moving target, it is recommended that you run these tests inside docker since the `organic-test` docker image is pinned to a specific revision of org-mode. These can be run with:
```bash
make dockertest
```
### The foreign document tests
These tests function the same as the auto-generated tests except they are **not** integrated with the rust/cargo testing framework and they involve comparing the parse of org-mode documents that live outside this repository. This allows us to test against a far greater variety of org-mode input documents without pulling massive sets of org-mode documents into this repository. The recommended way to run these tests is still through docker because it pins org-mode and the test documents to specific git revisions. These can be run with:
```bash
make foreign_document_test
```
## License ## License
This project is released under the public-domain-equivalent [0BSD license](https://www.tldrlegal.com/license/bsd-0-clause-license). This license puts no restrictions on the use of this code (you do not even have to include the copyright notice or license text when using it). HOWEVER, this project has a couple permissively licensed dependencies which do require their copyright notices and/or license texts to be included. I am not a lawyer and this is not legal advice but it is my layperson's understanding that if you distribute a binary with this library linked in, you will need to abide by their terms since their code will also be linked in your binary. I try to keep the dependencies to a minimum and the most restrictive dependency I will ever include is a permissively licensed one. This project is released under the public-domain-equivalent [0BSD license](https://www.tldrlegal.com/license/bsd-0-clause-license), however, this project has a couple permissively licensed non-public-domain-equivalent dependencies which require their copyright notices and/or license texts to be included. I am not a lawyer and this is not legal advice but it is my layperson's understanding that if you distribute a binary statically linking this library, you will need to abide by their terms since their code will also be linked in your binary.

View File

@@ -16,6 +16,9 @@ fn main() {
let destination = Path::new(&out_dir).join("tests.rs"); let destination = Path::new(&out_dir).join("tests.rs");
let mut test_file = File::create(&destination).unwrap(); let mut test_file = File::create(&destination).unwrap();
// Re-generate the tests if any org-mode files change
println!("cargo:rerun-if-changed=org_mode_samples");
write_header(&mut test_file); write_header(&mut test_file);
let test_files = WalkDir::new("org_mode_samples") let test_files = WalkDir::new("org_mode_samples")
@@ -71,10 +74,6 @@ fn write_header(test_file: &mut File) {
test_file, test_file,
r#" r#"
#[feature(exit_status_error)] #[feature(exit_status_error)]
use organic::compare_document;
use organic::parser::parse;
use organic::emacs_parse_anonymous_org_document;
use organic::parser::sexp::sexp_with_padding;
"# "#
) )
@@ -86,7 +85,6 @@ fn is_expect_fail(name: &str) -> Option<&str> {
match name { match name {
"autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."), "autogen_greater_element_drawer_drawer_with_headline_inside" => Some("Apparently lines with :end: become their own paragraph. This odd behavior needs to be investigated more."),
"autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."), "autogen_element_container_priority_footnote_definition_dynamic_block" => Some("Apparently broken begin lines become their own paragraph."),
"autogen_lesser_element_paragraphs_paragraph_with_backslash_line_breaks" => Some("The text we're getting out of the parse tree is already processed to remove line breaks, so our comparison needs to take that into account."),
_ => None, _ => None,
} }
} }

View File

@@ -88,14 +88,20 @@ ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git 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 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=74e80b0f7600801b1d1594542602394c085cc2f9
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
FROM tester as foreign-document-test FROM tester as foreign-document-test
RUN apk add --no-cache bash coreutils RUN apk add --no-cache bash coreutils
RUN mkdir /foreign_documents RUN mkdir /foreign_documents
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams COPY --from=foreign-document-gather /foreign_documents/howardabrams /foreign_documents/howardabrams
COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_documents/doomemacs
COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs
COPY foreign_document_test_entrypoint.sh /entrypoint.sh COPY foreign_document_test_entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]

View File

@@ -23,7 +23,7 @@ function main {
if [ "${CARGO_TARGET_DIR:-}" = "" ]; then if [ "${CARGO_TARGET_DIR:-}" = "" ]; then
CARGO_TARGET_DIR=$(realpath target/) CARGO_TARGET_DIR=$(realpath target/)
fi fi
PARSE="${CARGO_TARGET_DIR}/release-lto/parse" PARSE="${CARGO_TARGET_DIR}/release-lto/compare"
local all_status=0 local all_status=0
set +e set +e
@@ -32,6 +32,8 @@ function main {
if [ "$?" -ne 0 ]; then all_status=1; fi if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs") (run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs")
if [ "$?" -ne 0 ]; then all_status=1; fi if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "worg" compare_all_org_document "/foreign_documents/worg")
if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "howard_abrams" compare_howard_abrams) (run_compare_function "howard_abrams" compare_howard_abrams)
if [ "$?" -ne 0 ]; then all_status=1; fi if [ "$?" -ne 0 ]; then all_status=1; fi
(run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs") (run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs")

View File

@@ -25,3 +25,4 @@ This could significantly reduce our calls to exit matchers.
I think targets would break this. I think targets would break this.
The exit matchers are already implicitly building this behavior since they should all exit very early when the starting character is wrong. Putting this logic in a centralized place, far away from where those characters are actually going to be used, is unfortunate for readability. The exit matchers are already implicitly building this behavior since they should all exit very early when the starting character is wrong. Putting this logic in a centralized place, far away from where those characters are actually going to be used, is unfortunate for readability.
** Use exit matcher to cut off trailing whitespace instead of re-matching in plain lists.

View File

@@ -0,0 +1,5 @@
#+begin_quote
foo
#+end_quote

View File

@@ -0,0 +1,3 @@
# These are only allowed by configuring org-list-allow-alphabetical which the automated tests are not currently set up to do, so this will parse as a paragraph:
a. foo
b. bar

View File

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

View File

@@ -0,0 +1,6 @@
# The STARTUP directive here instructs org-mode to align tables which emacs normally does when opening the file. Since Organic is solely a parser, we have no business editing the org-mode document so Organic does not handle aligning tables, so in order for this test to pass, we have to avoid that behavior in Emacs.
#+STARTUP: align
|foo|bar|
|-
|lorem|ipsum|

View File

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

View File

@@ -0,0 +1,3 @@
<<<Foo Bar Baz>>>
foo bar baz

View File

@@ -0,0 +1,6 @@
<<<foo bar baz>>>
foo
bar
baz

View File

@@ -0,0 +1 @@
[[elisp:(local-set-key "\M-\x" 'foo-bar-baz)]]

View File

@@ -0,0 +1,2 @@
* DONE
*

View File

@@ -0,0 +1,6 @@
#+TODO: TODO(t) INPROGRESS(i/!) | DONE(d!) CANCELED(c@/!)
# ! : Log changes leading to this state.
# @ : Log changes leading to this state and prompt for a comment to include.
# /! : Log changes leaving this state if and only if to a state that does not log. This can be combined with the above like WAIT(w!/!) or DELAYED(d@/!)
* INPROGRESS
- State "TODO" from "INPROGRESS" [2023-09-14 Thu 02:13]

View File

@@ -0,0 +1,6 @@
#+STARTUP: odd
* Foo
***** Bar
* Baz
*** Lorem
* Ipsum

View File

@@ -38,7 +38,7 @@ function launch_container {
if [ "$SHELL" != "YES" ]; then if [ "$SHELL" != "YES" ]; then
local features_joined=$(IFS=","; echo "${features[*]}") local features_joined=$(IFS=","; echo "${features[*]}")
additional_args+=(cargo run --no-default-features --features "$features_joined") additional_args+=(cargo run --bin compare --no-default-features --features "$features_joined")
additional_flags+=(--read-only) additional_flags+=(--read-only)
else else
additional_args+=(/bin/sh) additional_args+=(/bin/sh)

52
src/bin_compare.rs Normal file
View File

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

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

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

View File

@@ -1,13 +1,14 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::collections::HashSet; use std::collections::HashSet;
use super::sexp::unquote;
use super::sexp::Token;
use super::util::assert_bounds; use super::util::assert_bounds;
use super::util::assert_name; use super::util::assert_name;
use super::util::get_property; use super::util::get_property;
use crate::parser::sexp::unquote;
use crate::parser::sexp::Token;
use crate::types::AngleLink; use crate::types::AngleLink;
use crate::types::Bold; use crate::types::Bold;
use crate::types::CheckboxType;
use crate::types::Citation; use crate::types::Citation;
use crate::types::CitationReference; use crate::types::CitationReference;
use crate::types::Clock; use crate::types::Clock;
@@ -86,7 +87,7 @@ pub struct DiffResult<'s> {
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum DiffStatus { enum DiffStatus {
Good, Good,
Bad, Bad,
} }
@@ -546,7 +547,17 @@ fn compare_heading<'s>(
}; };
// Compare title // Compare title
let title = get_property(emacs, ":title")?.ok_or("Missing :title attribute.")?; let title = get_property(emacs, ":title")?;
match (title, rust.title.len()) {
(None, 0) => {}
(None, _) => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Titles do not match (emacs != rust): {:?} != {:?}",
title, rust.title
))
}
(Some(title), _) => {
let title_status = title let title_status = title
.as_list()? .as_list()?
.iter() .iter()
@@ -554,6 +565,8 @@ fn compare_heading<'s>(
.map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child)) .map(|(emacs_child, rust_child)| compare_object(source, emacs_child, rust_child))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
child_status.push(artificial_diff_scope("title".to_owned(), title_status)?); child_status.push(artificial_diff_scope("title".to_owned(), title_status)?);
}
};
// Compare priority // Compare priority
let priority = get_property(emacs, ":priority")?; let priority = get_property(emacs, ":priority")?;
@@ -787,7 +800,26 @@ fn compare_plain_list_item<'s>(
contents_status, contents_status,
)?); )?);
// TODO: compare :bullet :checkbox :counter :pre-blank // TODO: compare :bullet :counter :pre-blank
// Compare checkbox
let checkbox = get_property(emacs, ":checkbox")?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
match (checkbox, &rust.checkbox) {
("nil", None) => {}
("off", Some((CheckboxType::Off, _))) => {}
("trans", Some((CheckboxType::Trans, _))) => {}
("on", Some((CheckboxType::On, _))) => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Checkbox mismatch (emacs != rust) {:?} != {:?}",
checkbox, rust.checkbox
));
}
};
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
@@ -1914,6 +1946,8 @@ fn compare_regular_link<'s>(
Ok(_) => {} Ok(_) => {}
}; };
// TODO: Compare :type :path :format :raw-link :application :search-option
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,
name: emacs_name.to_owned(), name: emacs_name.to_owned(),

View File

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

View File

@@ -11,6 +11,8 @@ where
let elisp_script = format!( let elisp_script = format!(
r#"(progn r#"(progn
(erase-buffer) (erase-buffer)
(require 'org)
(defun org-table-align () t)
(insert "{escaped_file_contents}") (insert "{escaped_file_contents}")
(org-mode) (org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer))) (message "%s" (pp-to-string (org-element-parse-buffer)))
@@ -42,6 +44,8 @@ where
))?; ))?;
let elisp_script = format!( let elisp_script = format!(
r#"(progn r#"(progn
(require 'org)
(defun org-table-align () t)
(org-mode) (org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer))) (message "%s" (pp-to-string (org-element-parse-buffer)))
)"# )"#

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,9 @@
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum ExitClass { pub(crate) enum ExitClass {
Document = 1, Document = 1,
Alpha = 2, Alpha = 2,
Beta = 3, Beta = 3,
Gamma = 4, Gamma = 4,
Delta = 5,
} }
impl std::fmt::Display for ExitClass { impl std::fmt::Display for ExitClass {

View File

@@ -2,6 +2,7 @@ use std::collections::BTreeSet;
use super::FileAccessInterface; use super::FileAccessInterface;
use super::LocalFileAccessInterface; use super::LocalFileAccessInterface;
use crate::types::IndentationLevel;
use crate::types::Object; use crate::types::Object;
// TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html // TODO: Ultimately, I think we'll need most of this: https://orgmode.org/manual/In_002dbuffer-Settings.html
@@ -12,10 +13,19 @@ pub struct GlobalSettings<'g, 's> {
pub file_access: &'g dyn FileAccessInterface, pub file_access: &'g dyn FileAccessInterface,
pub in_progress_todo_keywords: BTreeSet<String>, pub in_progress_todo_keywords: BTreeSet<String>,
pub complete_todo_keywords: BTreeSet<String>, pub complete_todo_keywords: BTreeSet<String>,
/// 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,
/// How many spaces a tab should be equal to.
///
/// Corresponds to the tab-width elisp variable.
pub tab_width: IndentationLevel,
} }
impl<'g, 's> GlobalSettings<'g, 's> { impl<'g, 's> GlobalSettings<'g, 's> {
pub fn new() -> GlobalSettings<'g, 's> { fn new() -> GlobalSettings<'g, 's> {
GlobalSettings { GlobalSettings {
radio_targets: Vec::new(), radio_targets: Vec::new(),
file_access: &LocalFileAccessInterface { file_access: &LocalFileAccessInterface {
@@ -23,6 +33,8 @@ impl<'g, 's> GlobalSettings<'g, 's> {
}, },
in_progress_todo_keywords: BTreeSet::new(), in_progress_todo_keywords: BTreeSet::new(),
complete_todo_keywords: BTreeSet::new(), complete_todo_keywords: BTreeSet::new(),
org_list_allow_alphabetical: false,
tab_width: 8,
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,7 @@ use crate::types::Object;
/// ///
/// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document. /// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document.
#[allow(dead_code)] #[allow(dead_code)]
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, String> { pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
parse_with_settings(input, &GlobalSettings::default()) parse_with_settings(input, &GlobalSettings::default())
} }
@@ -41,7 +41,7 @@ pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, String> {
pub fn parse_with_settings<'g, 's>( pub fn parse_with_settings<'g, 's>(
input: &'s str, input: &'s str,
global_settings: &'g GlobalSettings<'g, 's>, global_settings: &'g GlobalSettings<'g, 's>,
) -> Result<Document<'s>, String> { ) -> Result<Document<'s>, Box<dyn std::error::Error>> {
let initial_context = ContextElement::document_context(); let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context)); let initial_context = Context::new(global_settings, List::new(&initial_context));
let wrapped_input = OrgSource::new(input); let wrapped_input = OrgSource::new(input);
@@ -49,7 +49,7 @@ pub fn parse_with_settings<'g, 's>(
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input) all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
.map_err(|err| err.to_string()) .map_err(|err| err.to_string())
.map(|(_remaining, parsed_document)| parsed_document); .map(|(_remaining, parsed_document)| parsed_document);
ret Ok(ret?)
} }
/// Parse a full org-mode document. /// Parse a full org-mode document.
@@ -58,7 +58,7 @@ pub fn parse_with_settings<'g, 's>(
/// ///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO". /// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)] #[allow(dead_code)]
pub fn document<'b, 'g, 'r, 's>( fn document<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: &'s str, input: &'s str,
) -> Res<&'s str, Document<'s>> { ) -> Res<&'s str, Document<'s>> {
@@ -157,7 +157,7 @@ fn _document<'b, 'g, 'r, 's>(
} }
impl<'s> Document<'s> { impl<'s> Document<'s> {
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> { fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
AllTokensIterator::new(Token::Document(self)) AllTokensIterator::new(Token::Document(self))
} }
} }

View File

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

View File

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

View File

@@ -4,10 +4,14 @@ use nom::multi::many0;
use super::clock::clock; use super::clock::clock;
use super::comment::comment; use super::comment::comment;
use super::comment::detect_comment;
use super::diary_sexp::detect_diary_sexp;
use super::diary_sexp::diary_sexp; use super::diary_sexp::diary_sexp;
use super::drawer::drawer; use super::drawer::drawer;
use super::dynamic_block::dynamic_block; use super::dynamic_block::dynamic_block;
use super::fixed_width_area::detect_fixed_width_area;
use super::fixed_width_area::fixed_width_area; use super::fixed_width_area::fixed_width_area;
use super::footnote_definition::detect_footnote_definition;
use super::footnote_definition::footnote_definition; use super::footnote_definition::footnote_definition;
use super::greater_block::greater_block; use super::greater_block::greater_block;
use super::horizontal_rule::horizontal_rule; use super::horizontal_rule::horizontal_rule;
@@ -24,6 +28,7 @@ use super::org_source::OrgSource;
use super::paragraph::paragraph; use super::paragraph::paragraph;
use super::plain_list::detect_plain_list; use super::plain_list::detect_plain_list;
use super::plain_list::plain_list; use super::plain_list::plain_list;
use super::table::detect_table;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::parser_with_context; use crate::context::parser_with_context;
@@ -35,7 +40,7 @@ use crate::parser::table::org_mode_table;
use crate::types::Element; use crate::types::Element;
use crate::types::SetSource; use crate::types::SetSource;
pub const fn element( pub(crate) const fn element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
@@ -122,7 +127,7 @@ fn _element<'b, 'g, 'r, 's>(
Ok((remaining, element)) Ok((remaining, element))
} }
pub const fn detect_element( pub(crate) const fn detect_element(
can_be_paragraph: bool, can_be_paragraph: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()> ) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, ()>
{ {
@@ -135,7 +140,16 @@ fn _detect_element<'b, 'g, 'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
can_be_paragraph: bool, can_be_paragraph: bool,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
if detect_plain_list(input).is_ok() { if alt((
parser_with_context!(detect_plain_list)(context),
detect_footnote_definition,
detect_diary_sexp,
detect_comment,
detect_fixed_width_area,
detect_table,
))(input)
.is_ok()
{
return Ok((input, ())); return Ok((input, ()));
} }
if _element(context, input, can_be_paragraph).is_ok() { if _element(context, input, can_be_paragraph).is_ok() {

View File

@@ -1,10 +1,10 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::satisfy; use nom::character::complete::satisfy;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_object_trailing_whitespace_if_not_exiting;
@@ -433,13 +433,13 @@ const ORG_ENTITIES: [&'static str; 413] = [
]; ];
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn entity<'b, 'g, 'r, 's>( pub(crate) fn entity<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Entity<'s>> { ) -> Res<OrgSource<'s>, Entity<'s>> {
let (remaining, _) = tag("\\")(input)?; let (remaining, _) = tag("\\")(input)?;
let (remaining, entity_name) = name(context, remaining)?; let (remaining, entity_name) = name(context, remaining)?;
let (remaining, _) = alt((tag("{}"), peek(recognize(entity_end))))(remaining)?;
let (remaining, _trailing_whitespace) = let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -460,9 +460,12 @@ fn name<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
// TODO: This should be defined by org-entities and optionally org-entities-user // TODO: This should be defined by org-entities and optionally org-entities-user
for entity in ORG_ENTITIES { for entity in ORG_ENTITIES {
let result = tag_no_case::<_, _, CustomError<_>>(entity)(input); let result = tuple((
tag::<_, _, CustomError<_>>(entity),
alt((tag("{}"), peek(recognize(entity_end)))),
))(input);
match result { match result {
Ok((remaining, ent)) => { Ok((remaining, (ent, _))) => {
return Ok((remaining, ent)); return Ok((remaining, ent));
} }
Err(_) => {} Err(_) => {}

View File

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

View File

@@ -6,12 +6,13 @@ use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::recognize;
use nom::multi::many0; use nom::multi::many0;
use nom::sequence::preceded; use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::org_line_ending;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
@@ -21,7 +22,7 @@ use crate::parser::util::start_of_line;
use crate::types::FixedWidthArea; use crate::types::FixedWidthArea;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fixed_width_area<'b, 'g, 'r, 's>( pub(crate) fn fixed_width_area<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FixedWidthArea<'s>> { ) -> Res<OrgSource<'s>, FixedWidthArea<'s>> {
@@ -47,11 +48,22 @@ fn fixed_width_area_line<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _indent) = space0(input)?; let (remaining, _indent) = space0(input)?;
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple(( let (remaining, _) = tuple((
tag(":"), tag(":"),
opt(tuple((space1, is_not("\r\n")))), alt((recognize(tuple((space1, is_not("\r\n")))), space0)),
alt((line_ending, eof)), org_line_ending,
))(remaining)?; ))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_fixed_width_area<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((
start_of_line,
space0,
tag(":"),
alt((tag(" "), line_ending, eof)),
))(input)?;
Ok((input, ()))
}

View File

@@ -2,7 +2,6 @@ use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while; use nom::bytes::complete::take_while;
use nom::character::complete::digit1;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::recognize; use nom::combinator::recognize;
@@ -33,7 +32,7 @@ use crate::parser::util::start_of_line;
use crate::types::FootnoteDefinition; use crate::types::FootnoteDefinition;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn footnote_definition<'b, 'g, 'r, 's>( pub(crate) fn footnote_definition<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> { ) -> Res<OrgSource<'s>, FootnoteDefinition<'s>> {
@@ -93,11 +92,8 @@ pub fn footnote_definition<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { pub(crate) fn label<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)
digit1,
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c)),
))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -122,7 +118,7 @@ fn footnote_definition_end<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn detect_footnote_definition<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?; tuple((start_of_line, tag_no_case("[fn:"), label, tag("]")))(input)?;
Ok((input, ())) Ok((input, ()))
} }

View File

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

View File

@@ -4,11 +4,14 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@@ -33,7 +36,7 @@ use crate::types::Paragraph;
use crate::types::SetSource; use crate::types::SetSource;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn greater_block<'b, 'g, 'r, 's>( pub(crate) fn greater_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, GreaterBlock<'s>> { ) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
@@ -80,25 +83,23 @@ pub fn greater_block<'b, 'g, 'r, 's>(
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
// Check for a completely empty block not(exit_matcher)(remaining)?;
let (remaining, children) = match tuple(( let (remaining, leading_blank_lines) = opt(consumed(tuple((
not(exit_matcher),
blank_line, blank_line,
many_till(blank_line, exit_matcher), many0(preceded(not(exit_matcher), blank_line)),
))(remaining) ))))(remaining)?;
{ let leading_blank_lines =
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into())); let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source.into()); element.set_source(source.into());
(remain, vec![element]) element
} });
Err(_) => { let (remaining, (mut children, _exit_contents)) =
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?; many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children) if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
} }
};
let (remaining, _end) = exit_with_name(&parser_context, remaining)?; let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block // Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
@@ -126,7 +127,6 @@ fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
} }
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c { fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {
// TODO: Can this be done without making an owned copy?
move |context, input: OrgSource<'_>| _greater_block_end(context, input, name) move |context, input: OrgSource<'_>| _greater_block_end(context, input, name)
} }

View File

@@ -1,13 +1,12 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::map; use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0; use nom::multi::many0;
@@ -19,6 +18,9 @@ use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::section::section; use super::section::section;
use super::util::get_consumed; use super::util::get_consumed;
use super::util::org_line_ending;
use super::util::org_space;
use super::util::org_space_or_line_ending;
use super::util::start_of_line; use super::util::start_of_line;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -36,7 +38,7 @@ use crate::types::Object;
use crate::types::PriorityCookie; use crate::types::PriorityCookie;
use crate::types::TodoKeywordType; use crate::types::TodoKeywordType;
pub const fn heading( pub(crate) const fn heading(
parent_stars: usize, parent_stars: usize,
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
@@ -81,10 +83,10 @@ fn _heading<'b, 'g, 'r, 's>(
Heading { Heading {
source: source.into(), source: source.into(),
stars: star_count, stars: star_count,
todo_keyword: maybe_todo_keyword.map(|((todo_keyword_type, todo_keyword), _ws)| { todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword)) (todo_keyword_type, Into::<&str>::into(todo_keyword))
}), }),
priority_cookie: maybe_priority.map(|(priority, _)| priority), priority_cookie: maybe_priority.map(|(_, priority)| priority),
title, title,
tags: heading_tags, tags: heading_tags,
children, children,
@@ -95,7 +97,7 @@ fn _heading<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, many1(tag("*")), space1))(input)?; tuple((start_of_line, many1(tag("*")), space1))(input)?;
Ok((input, ())) Ok((input, ()))
} }
@@ -109,9 +111,9 @@ fn headline<'b, 'g, 'r, 's>(
OrgSource<'s>, OrgSource<'s>,
( (
usize, usize,
Option<((TodoKeywordType, OrgSource<'s>), OrgSource<'s>)>, Option<(TodoKeywordType, OrgSource<'s>)>,
Option<(PriorityCookie, OrgSource<'s>)>, Option<(OrgSource<'s>, PriorityCookie)>,
Option<(OrgSource<'s>, OrgSource<'s>)>, Option<OrgSource<'s>>,
Vec<Object<'s>>, Vec<Object<'s>>,
Vec<&'s str>, Vec<&'s str>,
), ),
@@ -122,45 +124,45 @@ fn headline<'b, 'g, 'r, 's>(
}); });
let parser_context = context.with_additional_node(&parser_context); let parser_context = context.with_additional_node(&parser_context);
let ( let (remaining, (_, star_count, _)) = tuple((
remaining,
(
_,
star_count,
_,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
maybe_tags,
_,
_,
),
) = tuple((
start_of_line, start_of_line,
verify(many1_count(tag("*")), |star_count| { verify(many1_count(tag("*")), |star_count| {
*star_count > parent_stars *star_count > parent_stars
}), }),
space1, peek(org_space),
opt(tuple((
parser_with_context!(heading_keyword)(&parser_context),
space1,
))),
opt(tuple((priority_cookie, space1))),
opt(tuple((tag("COMMENT"), space1))),
many1(parser_with_context!(standard_set_object)(&parser_context)),
opt(tuple((space0, tags))),
space0,
alt((line_ending, eof)),
))(input)?; ))(input)?;
let (remaining, maybe_todo_keyword) = opt(tuple((
space1,
parser_with_context!(heading_keyword)(&parser_context),
peek(org_space_or_line_ending),
)))(remaining)?;
let (remaining, maybe_priority) = opt(tuple((space1, priority_cookie)))(remaining)?;
let (remaining, maybe_comment) = opt(tuple((
space1,
tag("COMMENT"),
peek(org_space_or_line_ending),
)))(remaining)?;
let (remaining, maybe_title) = opt(tuple((
space1,
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)?;
Ok(( Ok((
remaining, remaining,
( (
star_count, star_count,
maybe_todo_keyword, maybe_todo_keyword.map(|(_, todo, _)| todo),
maybe_priority, maybe_priority,
maybe_comment, maybe_comment.map(|(_, comment, _)| comment),
title, maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
maybe_tags maybe_tags
.map(|(_ws, tags)| { .map(|(_ws, tags)| {
tags.into_iter() tags.into_iter()
@@ -177,10 +179,7 @@ fn headline_title_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(tuple(( recognize(tuple((space0, opt(tuple((tags, space0))), org_line_ending)))(input)
opt(tuple((space0, tags, space0))),
alt((line_ending, eof)),
)))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ const ORG_ELEMENT_AFFILIATED_KEYWORDS: [&'static str; 13] = [
]; ];
const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"]; const ORG_ELEMENT_DUAL_KEYWORDS: [&'static str; 2] = ["caption", "results"];
pub fn filtered_keyword<F: Matcher>( pub(crate) fn filtered_keyword<F: Matcher>(
key_parser: F, key_parser: F,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, Keyword<'s>> {
move |input| _filtered_keyword(&key_parser, input) move |input| _filtered_keyword(&key_parser, input)
@@ -83,7 +83,7 @@ fn _filtered_keyword<'s, F: Matcher>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn keyword<'b, 'g, 'r, 's>( pub(crate) fn keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
@@ -91,7 +91,7 @@ pub fn keyword<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn affiliated_keyword<'b, 'g, 'r, 's>( pub(crate) fn affiliated_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
@@ -99,7 +99,7 @@ pub fn affiliated_keyword<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn babel_call_keyword<'b, 'g, 'r, 's>( pub(crate) fn babel_call_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {
@@ -112,7 +112,7 @@ fn babel_call_key<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_formula_keyword<'b, 'g, 'r, 's>( pub(crate) fn table_formula_keyword<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Keyword<'s>> { ) -> Res<OrgSource<'s>, Keyword<'s>> {

View File

@@ -19,7 +19,7 @@ use crate::error::Res;
/// Parses the text in the value of a #+TODO keyword. /// Parses the text in the value of a #+TODO keyword.
/// ///
/// Example input: "foo bar baz | lorem ipsum" /// Example input: "foo bar baz | lorem ipsum"
pub fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> { pub(crate) fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s str>)> {
let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?; let (remaining, mut before_pipe_words) = separated_list0(space1, todo_keyword_word)(input)?;
let (remaining, after_pipe_words) = opt(tuple(( let (remaining, after_pipe_words) = opt(tuple((
tuple((space0, tag("|"), space0)), tuple((space0, tag("|"), space0)),
@@ -44,9 +44,17 @@ pub fn todo_keywords<'s>(input: &'s str) -> Res<&'s str, (Vec<&'s str>, Vec<&'s
} }
fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> { fn todo_keyword_word<'s>(input: &'s str) -> Res<&'s str, &'s str> {
verify(take_till(|c| " \t\r\n|".contains(c)), |result: &str| { let (remaining, keyword) = verify(take_till(|c| "( \t\r\n|".contains(c)), |result: &str| {
!result.is_empty() !result.is_empty()
})(input) })(input)?;
let (remaining, _) = opt(tuple((
tag("("),
take_till(|c| "() \t\r\n|".contains(c)),
tag(")"),
)))(remaining)?;
Ok((remaining, keyword))
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

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

View File

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

View File

@@ -35,7 +35,7 @@ use crate::types::SrcBlock;
use crate::types::VerseBlock; use crate::types::VerseBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verse_block<'b, 'g, 'r, 's>( pub(crate) fn verse_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, VerseBlock<'s>> { ) -> Res<OrgSource<'s>, VerseBlock<'s>> {
@@ -90,7 +90,7 @@ pub fn verse_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn comment_block<'b, 'g, 'r, 's>( pub(crate) fn comment_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, CommentBlock<'s>> { ) -> Res<OrgSource<'s>, CommentBlock<'s>> {
@@ -130,7 +130,7 @@ pub fn comment_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn example_block<'b, 'g, 'r, 's>( pub(crate) fn example_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExampleBlock<'s>> { ) -> Res<OrgSource<'s>, ExampleBlock<'s>> {
@@ -170,7 +170,7 @@ pub fn example_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn export_block<'b, 'g, 'r, 's>( pub(crate) fn export_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ExportBlock<'s>> { ) -> Res<OrgSource<'s>, ExportBlock<'s>> {
@@ -211,7 +211,7 @@ pub fn export_block<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn src_block<'b, 'g, 'r, 's>( pub(crate) fn src_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, SrcBlock<'s>> { ) -> Res<OrgSource<'s>, SrcBlock<'s>> {

View File

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

View File

@@ -37,7 +37,6 @@ mod property_drawer;
mod radio_link; mod radio_link;
mod regular_link; mod regular_link;
mod section; mod section;
pub mod sexp;
mod statistics_cookie; mod statistics_cookie;
mod subscript_and_superscript; mod subscript_and_superscript;
mod table; mod table;
@@ -46,7 +45,6 @@ mod text_markup;
mod timestamp; mod timestamp;
mod token; mod token;
mod util; mod util;
pub use document::document;
pub use document::parse; pub use document::parse;
pub use document::parse_with_settings; pub use document::parse_with_settings;
pub use org_source::OrgSource; pub(crate) use org_source::OrgSource;

View File

@@ -32,7 +32,7 @@ use crate::parser::timestamp::timestamp;
use crate::types::Object; use crate::types::Object;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn standard_set_object<'b, 'g, 'r, 's>( pub(crate) fn standard_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -47,7 +47,7 @@ pub fn standard_set_object<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn minimal_set_object<'b, 'g, 'r, 's>( pub(crate) fn minimal_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -140,7 +140,7 @@ fn minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>( pub(crate) fn detect_standard_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
@@ -174,7 +174,7 @@ fn detect_minimal_set_object_sans_plain_text<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_description_set_object<'b, 'g, 'r, 's>( pub(crate) fn regular_link_description_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -221,7 +221,7 @@ fn regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>( fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
@@ -238,7 +238,7 @@ pub fn detect_regular_link_description_set_object_sans_plain_text<'b, 'g, 'r, 's
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object<'b, 'g, 'r, 's>( pub(crate) fn table_cell_set_object<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -253,7 +253,7 @@ pub fn table_cell_set_object<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>( fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -287,7 +287,7 @@ pub fn table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>( fn detect_table_cell_set_object_sans_plain_text<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,13 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
@@ -20,6 +22,7 @@ use super::element_parser::element;
use super::object_parser::standard_set_object; use super::object_parser::standard_set_object;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::util::include_input; use super::util::include_input;
use super::util::indentation_level;
use super::util::non_whitespace_character; use super::util::non_whitespace_character;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::ContextElement; use crate::context::ContextElement;
@@ -34,18 +37,24 @@ use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::parser::util::org_space;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::CheckboxType;
use crate::types::IndentationLevel;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainList; use crate::types::PlainList;
use crate::types::PlainListItem; use crate::types::PlainListItem;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn detect_plain_list<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> {
if verify( if verify(
tuple(( tuple((
start_of_line, start_of_line,
space0, space0,
bullet, parser_with_context!(bullet)(context),
alt((space1, line_ending, eof)), alt((space1, line_ending, eof)),
)), )),
|(_start, indent, bull, _after_whitespace)| { |(_start, indent, bull, _after_whitespace)| {
@@ -62,7 +71,7 @@ pub fn detect_plain_list<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_list<'b, 'g, 'r, 's>( pub(crate) fn plain_list<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainList<'s>> { ) -> Res<OrgSource<'s>, PlainList<'s>> {
@@ -80,7 +89,7 @@ pub fn plain_list<'b, 'g, 'r, 's>(
let parser_context = parser_context.with_additional_node(&contexts[2]); let parser_context = parser_context.with_additional_node(&contexts[2]);
// children stores tuple of (input string, parsed object) so we can re-parse the final item // children stores tuple of (input string, parsed object) so we can re-parse the final item
let mut children = Vec::new(); let mut children = Vec::new();
let mut first_item_indentation: Option<usize> = None; let mut first_item_indentation: Option<IndentationLevel> = None;
let mut remaining = input; 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: // The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
@@ -136,22 +145,25 @@ pub fn plain_list<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn plain_list_item<'b, 'g, 'r, 's>( fn plain_list_item<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, PlainListItem<'s>> { ) -> Res<OrgSource<'s>, PlainListItem<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, leading_whitespace) = space0(input)?; let (remaining, (indent_level, _leading_whitespace)) = indentation_level(context, input)?;
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09) let (remaining, bull) = verify(
let indent_level = leading_whitespace.len(); parser_with_context!(bullet)(context),
let (remaining, bull) = verify(bullet, |bull: &OrgSource<'_>| { |bull: &OrgSource<'_>| Into::<&str>::into(bull) != "*" || indent_level > 0,
Into::<&str>::into(bull) != "*" || indent_level > 0 )(remaining)?;
})(remaining)?;
let (remaining, _maybe_counter_set) = let (remaining, _maybe_counter_set) = opt(tuple((
opt(tuple((space1, tag("[@"), counter, tag("]"))))(remaining)?; space1,
tag("[@"),
parser_with_context!(counter)(context),
tag("]"),
)))(remaining)?;
// TODO: parse checkbox let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
let (remaining, maybe_tag) = let (remaining, maybe_tag) =
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?; opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?;
@@ -169,6 +181,7 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
checkbox: None,
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
@@ -218,6 +231,8 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
checkbox: maybe_checkbox
.map(|(_, (checkbox_type, source))| (checkbox_type, Into::<&str>::into(source))),
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
@@ -227,18 +242,36 @@ pub fn plain_list_item<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn bullet<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn bullet<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt(( alt((
tag("*"), tag("*"),
tag("-"), tag("-"),
tag("+"), tag("+"),
recognize(tuple((counter, alt((tag("."), tag(")")))))), recognize(tuple((
))(i) parser_with_context!(counter)(context),
alt((tag("."), tag(")"))),
))),
))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn counter<'s>(i: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn counter<'b, 'g, 'r, 's>(
alt((recognize(one_of("abcdefghijklmnopqrstuvwxyz")), digit1))(i) context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
if context.get_global_settings().org_list_allow_alphabetical {
alt((
recognize(one_of(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
)),
digit1,
))(input)
} else {
digit1(input)
}
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -254,7 +287,7 @@ fn plain_list_end<'b, 'g, 'r, 's>(
)))(input) )))(input)
} }
const fn plain_list_item_end(indent_level: usize) -> impl ContextMatcher { const fn plain_list_item_end(indent_level: IndentationLevel) -> impl ContextMatcher {
let line_indented_lte_matcher = line_indented_lte(indent_level); let line_indented_lte_matcher = line_indented_lte(indent_level);
move |context, input: OrgSource<'_>| { move |context, input: OrgSource<'_>| {
_plain_list_item_end(context, input, &line_indented_lte_matcher) _plain_list_item_end(context, input, &line_indented_lte_matcher)
@@ -277,20 +310,23 @@ fn _plain_list_item_end<'b, 'g, 'r, 's>(
)))(input) )))(input)
} }
const fn line_indented_lte(indent_level: usize) -> impl ContextMatcher { const fn line_indented_lte(indent_level: IndentationLevel) -> impl ContextMatcher {
move |context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level) move |context, input: OrgSource<'_>| _line_indented_lte(context, input, indent_level)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _line_indented_lte<'b, 'g, 'r, 's>( fn _line_indented_lte<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
indent_level: usize, indent_level: IndentationLevel,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
let matched = recognize(verify( let matched = recognize(verify(
tuple((space0::<OrgSource<'_>, _>, non_whitespace_character)), tuple((
parser_with_context!(indentation_level)(context),
non_whitespace_character,
)),
// It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09) // It is fine that we get the indent level using the number of bytes rather than the number of characters because nom's space0 only matches space and tab (0x20 and 0x09)
|(_space0, _anychar)| _space0.len() <= indent_level, |((indentation_level, _leading_whitespace), _anychar)| *indentation_level <= indent_level,
))(input)?; ))(input)?;
Ok(matched) Ok(matched)
@@ -301,18 +337,11 @@ fn item_tag<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {
let contexts = [ let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma, class: ExitClass::Gamma,
exit_matcher: &item_tag_line_ending_end,
}),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Delta,
exit_matcher: &item_tag_end, exit_matcher: &item_tag_end,
}), });
]; let parser_context = context.with_additional_node(&parser_context);
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let (remaining, (children, _exit_contents)) = verify( let (remaining, (children, _exit_contents)) = verify(
many_till( many_till(
// TODO: Should this be using a different set like the minimal set? // TODO: Should this be using a different set like the minimal set?
@@ -321,7 +350,7 @@ fn item_tag<'b, 'g, 'r, 's>(
), ),
|(children, _exit_contents)| !children.is_empty(), |(children, _exit_contents)| !children.is_empty(),
)(input)?; )(input)?;
let (remaining, _) = tuple((one_of(" \t"), tag("::")))(remaining)?; let (remaining, _) = item_tag_divider(remaining)?;
Ok((remaining, children)) Ok((remaining, children))
} }
@@ -330,19 +359,22 @@ fn item_tag_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((
recognize(tuple(( recognize(tuple((
one_of(" \t"), item_tag_divider,
tag("::"), opt(tuple((
alt((recognize(one_of(" \t")), line_ending, eof)), peek(one_of(" \t")),
)))(input) many_till(anychar, peek(alt((item_tag_divider, line_ending, eof)))),
))),
alt((line_ending, eof)),
))),
line_ending,
))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_tag_line_ending_end<'b, 'g, 'r, 's>( fn item_tag_divider<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
_context: RefContext<'b, 'g, 'r, 's>, recognize(tuple((one_of(" \t"), tag("::"))))(input)
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
line_ending(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -366,6 +398,18 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
)(input) )(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_checkbox<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, (CheckboxType, OrgSource<'s>)> {
alt((
map(
recognize(tuple((tag("["), org_space, tag("]")))),
|capture| (CheckboxType::Off, capture),
),
map(tag("[-]"), |capture| (CheckboxType::Trans, capture)),
map(tag("[X]"), |capture| (CheckboxType::On, capture)),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_contentless_item_contents<'b, 'g, 'r, 's>( fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
@@ -561,21 +605,30 @@ dolar"#,
r#"+ r#"+
"#, "#,
); );
let result = detect_plain_list(input); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test] #[test]
fn detect_eof() { fn detect_eof() {
let input = OrgSource::new(r#"+"#); let input = OrgSource::new(r#"+"#);
let result = detect_plain_list(input); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
#[test] #[test]
fn detect_no_gap() { fn detect_no_gap() {
let input = OrgSource::new(r#"+foo"#); let input = OrgSource::new(r#"+foo"#);
let result = detect_plain_list(input); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
// Since there is no whitespace after the '+' this is a paragraph, not a plain list. // Since there is no whitespace after the '+' this is a paragraph, not a plain list.
assert!(result.is_err()); assert!(result.is_err());
} }
@@ -583,7 +636,10 @@ dolar"#,
#[test] #[test]
fn detect_with_gap() { fn detect_with_gap() {
let input = OrgSource::new(r#"+ foo"#); let input = OrgSource::new(r#"+ foo"#);
let result = detect_plain_list(input); let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let result = detect_plain_list(&initial_context, input);
assert!(result.is_ok()); assert!(result.is_ok());
} }
} }

View File

@@ -1,22 +1,29 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::is_not;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::combinator::map; use nom::character::complete::line_ending;
use nom::character::complete::one_of;
use nom::combinator::peek; use nom::combinator::peek;
use nom::combinator::recognize; use nom::combinator::recognize;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many1;
use nom::multi::many_till; use nom::multi::many_till;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::radio_link::RematchObject; use super::radio_link::RematchObject;
use super::util::exit_matcher_parser; use super::util::exit_matcher_parser;
use super::util::get_consumed;
use super::util::org_space_or_line_ending;
use crate::context::parser_with_context; use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainText; use crate::types::PlainText;
pub fn plain_text<F>( pub(crate) fn plain_text<F>(
end_condition: F, end_condition: F,
) -> impl for<'b, 'g, 'r, 's> Fn( ) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>, RefContext<'b, 'g, 'r, 's>,
@@ -72,11 +79,52 @@ impl<'x> RematchObject<'x> for PlainText<'x> {
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
map(tag(self.source), |s| { let mut remaining = input;
let mut goal = self.source;
loop {
if goal.is_empty() {
break;
}
// let is_whitespace = recognize(many1(org_space_or_line_ending))(input);
let is_not_whitespace = is_not::<&str, &str, CustomError<_>>(" \t\r\n")(goal);
match is_not_whitespace {
Ok((new_goal, payload)) => {
let (new_remaining, _) = tag_no_case(payload)(remaining)?;
remaining = new_remaining;
goal = new_goal;
continue;
}
Err(_) => {}
};
let is_whitespace = recognize(many1(alt((
recognize(one_of::<&str, &str, CustomError<_>>(" \t")),
line_ending,
))))(goal);
match is_whitespace {
Ok((new_goal, _)) => {
let (new_remaining, _) = many1(org_space_or_line_ending)(remaining)?;
remaining = new_remaining;
goal = new_goal;
continue;
}
Err(_) => {}
};
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Target does not match.".into(),
))));
}
let source = get_consumed(input, remaining);
Ok((
remaining,
Object::PlainText(PlainText { Object::PlainText(PlainText {
source: Into::<&str>::into(s), source: Into::<&str>::into(source),
}) }),
})(input) ))
} }
} }

View File

@@ -1,16 +1,16 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case; use nom::bytes::complete::tag_no_case;
use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::multi::many1;
use nom::multi::separated_list1;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
use super::timestamp::timestamp;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting; use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::RefContext; use crate::context::RefContext;
use crate::error::Res; use crate::error::Res;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
@@ -18,14 +18,15 @@ use crate::parser::util::start_of_line;
use crate::types::Planning; use crate::types::Planning;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn planning<'b, 'g, 'r, 's>( pub(crate) fn planning<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Planning<'s>> { ) -> Res<OrgSource<'s>, Planning<'s>> {
start_of_line(input)?; start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?; let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _planning_parameters) = separated_list1(space1, planning_parameter)(remaining)?; let (remaining, _planning_parameters) =
let (remaining, _trailing_ws) = tuple((space0, alt((line_ending, eof))))(remaining)?; many1(parser_with_context!(planning_parameter)(context))(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) = let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -40,15 +41,17 @@ pub fn planning<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn planning_parameter<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { 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(( let (remaining, _planning_type) = alt((
tag_no_case("DEADLINE"), tag_no_case("DEADLINE"),
tag_no_case("SCHEDULED"), tag_no_case("SCHEDULED"),
tag_no_case("CLOSED"), tag_no_case("CLOSED"),
))(input)?; ))(input)?;
let (remaining, _gap) = tuple((tag(":"), space1))(remaining)?; let (remaining, _gap) = tuple((tag(":"), space1))(remaining)?;
// TODO: Make this invoke the real timestamp parser. let (remaining, _timestamp) = timestamp(context, remaining)?;
let (remaining, _timestamp) = tuple((tag("<"), is_not("\r\n>"), tag(">")))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok((remaining, source)) Ok((remaining, source))
} }

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ use nom::branch::alt;
use nom::bytes::complete::escaped; use nom::bytes::complete::escaped;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::bytes::complete::take_till1; use nom::bytes::complete::take_till1;
use nom::character::complete::one_of; use nom::character::complete::anychar;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many_till; use nom::multi::many_till;
@@ -21,7 +21,7 @@ use crate::types::Object;
use crate::types::RegularLink; use crate::types::RegularLink;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link<'b, 'g, 'r, 's>( pub(crate) fn regular_link<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
@@ -32,7 +32,7 @@ pub fn regular_link<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_without_description<'b, 'g, 'r, 's>( fn regular_link_without_description<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
@@ -51,7 +51,7 @@ pub fn regular_link_without_description<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn regular_link_with_description<'b, 'g, 'r, 's>( fn regular_link_with_description<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, RegularLink<'s>> { ) -> Res<OrgSource<'s>, RegularLink<'s>> {
@@ -72,7 +72,7 @@ pub fn regular_link_with_description<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pathreg<'b, 'g, 'r, 's>( fn pathreg<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -82,13 +82,13 @@ pub fn pathreg<'b, 'g, 'r, 's>(
_ => false, _ => false,
}), }),
'\\', '\\',
one_of(r#"]"#), anychar,
)(input)?; )(input)?;
Ok((remaining, path)) Ok((remaining, path))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn description<'b, 'g, 'r, 's>( fn description<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Object<'s>>> { ) -> Res<OrgSource<'s>, Vec<Object<'s>>> {

View File

@@ -25,7 +25,7 @@ use crate::types::Element;
use crate::types::Section; use crate::types::Section;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn zeroth_section<'b, 'g, 'r, 's>( pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> { ) -> Res<OrgSource<'s>, Section<'s>> {
@@ -83,7 +83,7 @@ pub fn zeroth_section<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn section<'b, 'g, 'r, 's>( pub(crate) fn section<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
mut input: OrgSource<'s>, mut input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Section<'s>> { ) -> Res<OrgSource<'s>, Section<'s>> {

View File

@@ -13,7 +13,7 @@ use crate::error::Res;
use crate::types::StatisticsCookie; use crate::types::StatisticsCookie;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn statistics_cookie<'b, 'g, 'r, 's>( pub(crate) fn statistics_cookie<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
@@ -24,7 +24,7 @@ pub fn statistics_cookie<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn percent_statistics_cookie<'b, 'g, 'r, 's>( fn percent_statistics_cookie<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {
@@ -45,7 +45,7 @@ pub fn percent_statistics_cookie<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn fraction_statistics_cookie<'b, 'g, 'r, 's>( fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StatisticsCookie<'s>> { ) -> Res<OrgSource<'s>, StatisticsCookie<'s>> {

View File

@@ -33,7 +33,7 @@ use crate::types::Subscript;
use crate::types::Superscript; use crate::types::Superscript;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
// This does not have to detect all valid subscript/superscript but all that it detects must be valid. // This does not have to detect all valid subscript/superscript but all that it detects must be valid.
let (remaining, _) = one_of("_^")(input)?; let (remaining, _) = one_of("_^")(input)?;
pre(input)?; pre(input)?;
@@ -46,7 +46,7 @@ pub fn detect_subscript_or_superscript<'s>(input: OrgSource<'s>) -> Res<OrgSourc
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn subscript<'b, 'g, 'r, 's>( pub(crate) fn subscript<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Subscript<'s>> { ) -> Res<OrgSource<'s>, Subscript<'s>> {
@@ -66,7 +66,7 @@ pub fn subscript<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn superscript<'b, 'g, 'r, 's>( pub(crate) fn superscript<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Superscript<'s>> { ) -> Res<OrgSource<'s>, Superscript<'s>> {

View File

@@ -33,7 +33,7 @@ use crate::types::TableRow;
/// ///
/// This is not the table.el style. /// This is not the table.el style.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table<'b, 'g, 'r, 's>( pub(crate) fn org_mode_table<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Table<'s>> { ) -> Res<OrgSource<'s>, Table<'s>> {
@@ -61,7 +61,6 @@ pub fn org_mode_table<'b, 'g, 'r, 's>(
let (remaining, formulas) = let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?; many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
// TODO: Consume trailing formulas
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
Ok(( Ok((
@@ -74,6 +73,12 @@ pub fn org_mode_table<'b, 'g, 'r, 's>(
)) ))
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn detect_table<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
tuple((start_of_line, space0, tag("|")))(input)?;
Ok((input, ()))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn table_end<'b, 'g, 'r, 's>( fn table_end<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
@@ -84,7 +89,7 @@ fn table_end<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row<'b, 'g, 'r, 's>( fn org_mode_table_row<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
@@ -95,7 +100,7 @@ pub fn org_mode_table_row<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row_rule<'b, 'g, 'r, 's>( fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
@@ -112,7 +117,7 @@ pub fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_row_regular<'b, 'g, 'r, 's>( fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, TableRow<'s>> { ) -> Res<OrgSource<'s>, TableRow<'s>> {
@@ -132,7 +137,7 @@ pub fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn org_mode_table_cell<'b, 'g, 'r, 's>( fn org_mode_table_cell<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, TableCell<'s>> { ) -> Res<OrgSource<'s>, TableCell<'s>> {

View File

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

View File

@@ -42,7 +42,7 @@ use crate::types::Underline;
use crate::types::Verbatim; use crate::types::Verbatim;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn text_markup<'b, 'g, 'r, 's>( pub(crate) fn text_markup<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Object<'s>> { ) -> Res<OrgSource<'s>, Object<'s>> {
@@ -60,7 +60,7 @@ pub fn text_markup<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn bold<'b, 'g, 'r, 's>( fn bold<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Bold<'s>> { ) -> Res<OrgSource<'s>, Bold<'s>> {
@@ -77,7 +77,7 @@ pub fn bold<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn italic<'b, 'g, 'r, 's>( fn italic<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Italic<'s>> { ) -> Res<OrgSource<'s>, Italic<'s>> {
@@ -94,7 +94,7 @@ pub fn italic<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn underline<'b, 'g, 'r, 's>( fn underline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Underline<'s>> { ) -> Res<OrgSource<'s>, Underline<'s>> {
@@ -111,7 +111,7 @@ pub fn underline<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn strike_through<'b, 'g, 'r, 's>( fn strike_through<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, StrikeThrough<'s>> { ) -> Res<OrgSource<'s>, StrikeThrough<'s>> {
@@ -128,7 +128,7 @@ pub fn strike_through<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn verbatim<'b, 'g, 'r, 's>( fn verbatim<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Verbatim<'s>> { ) -> Res<OrgSource<'s>, Verbatim<'s>> {
@@ -145,7 +145,7 @@ pub fn verbatim<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn code<'b, 'g, 'r, 's>( fn code<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Code<'s>> { ) -> Res<OrgSource<'s>, Code<'s>> {
@@ -288,7 +288,7 @@ fn _text_markup_string<'b, 'g, 'r, 's, 'c>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn pre<'b, 'g, 'r, 's>( fn pre<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {
@@ -307,7 +307,7 @@ pub fn pre<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn post<'b, 'g, 'r, 's>( fn post<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>, _context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, ()> { ) -> Res<OrgSource<'s>, ()> {

View File

@@ -1,6 +1,7 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::digit0;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space1; use nom::character::complete::space1;
@@ -23,7 +24,7 @@ use crate::parser::util::get_consumed;
use crate::types::Timestamp; use crate::types::Timestamp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn timestamp<'b, 'g, 'r, 's>( pub(crate) fn timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> { ) -> Res<OrgSource<'s>, Timestamp<'s>> {
@@ -414,7 +415,7 @@ fn repeater<'b, 'g, 'r, 's>(
// ++ for catch-up type // ++ for catch-up type
// .+ for restart type // .+ for restart type
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?; let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
let (remaining, _value) = digit1(remaining)?; let (remaining, _value) = digit0(remaining)?;
// h = hour, d = day, w = week, m = month, y = year // h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?; let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@@ -429,7 +430,7 @@ fn warning_delay<'b, 'g, 'r, 's>(
// - for all type // - for all type
// -- for first type // -- for first type
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?; let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
let (remaining, _value) = digit1(remaining)?; let (remaining, _value) = digit0(remaining)?;
// h = hour, d = day, w = week, m = month, y = year // h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?; let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);

View File

@@ -11,7 +11,7 @@ use crate::types::Section;
use crate::types::TableCell; use crate::types::TableCell;
use crate::types::TableRow; use crate::types::TableRow;
pub enum Token<'r, 's> { pub(crate) enum Token<'r, 's> {
Document(&'r Document<'s>), Document(&'r Document<'s>),
Heading(&'r Heading<'s>), Heading(&'r Heading<'s>),
Section(&'r Section<'s>), Section(&'r Section<'s>),
@@ -24,7 +24,7 @@ pub enum Token<'r, 's> {
} }
impl<'r, 's> Token<'r, 's> { impl<'r, 's> Token<'r, 's> {
pub fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> { fn iter_tokens(&self) -> Box<dyn Iterator<Item = Token<'r, 's>> + '_> {
match self { match self {
Token::Document(document) => Box::new( Token::Document(document) => Box::new(
document document
@@ -109,12 +109,12 @@ impl<'r, 's> Token<'r, 's> {
} }
} }
pub struct AllTokensIterator<'r, 's> { pub(crate) struct AllTokensIterator<'r, 's> {
queued_tokens: VecDeque<Token<'r, 's>>, queued_tokens: VecDeque<Token<'r, 's>>,
} }
impl<'r, 's> AllTokensIterator<'r, 's> { impl<'r, 's> AllTokensIterator<'r, 's> {
pub fn new(tkn: Token<'r, 's>) -> Self { pub(crate) fn new(tkn: Token<'r, 's>) -> Self {
let mut queued_tokens = VecDeque::new(); let mut queued_tokens = VecDeque::new();
queued_tokens.push_back(tkn); queued_tokens.push_back(tkn);
AllTokensIterator { queued_tokens } AllTokensIterator { queued_tokens }

View File

@@ -20,12 +20,13 @@ use crate::context::RefContext;
use crate::error::CustomError; use crate::error::CustomError;
use crate::error::MyError; use crate::error::MyError;
use crate::error::Res; use crate::error::Res;
use crate::types::IndentationLevel;
pub const WORD_CONSTITUENT_CHARACTERS: &str = pub(crate) const WORD_CONSTITUENT_CHARACTERS: &str =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
/// Check if we are below a section of the given section type regardless of depth /// Check if we are below a section of the given section type regardless of depth
pub fn in_section<'b, 'g, 'r, 's, 'x>( pub(crate) fn in_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str, section_name: &'x str,
) -> bool { ) -> bool {
@@ -39,7 +40,7 @@ pub fn in_section<'b, 'g, 'r, 's, 'x>(
} }
/// Checks if we are currently an immediate child of the given section type /// Checks if we are currently an immediate child of the given section type
pub fn immediate_in_section<'b, 'g, 'r, 's, 'x>( pub(crate) fn immediate_in_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str, section_name: &'x str,
) -> bool { ) -> bool {
@@ -54,7 +55,7 @@ pub fn immediate_in_section<'b, 'g, 'r, 's, 'x>(
} }
/// Check if we are below a section of the given section type regardless of depth /// Check if we are below a section of the given section type regardless of depth
pub fn in_object_section<'b, 'g, 'r, 's, 'x>( pub(crate) fn in_object_section<'b, 'g, 'r, 's, 'x>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
section_name: &'x str, section_name: &'x str,
) -> bool { ) -> bool {
@@ -68,7 +69,7 @@ pub fn in_object_section<'b, 'g, 'r, 's, 'x>(
} }
/// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser. /// Get a slice of the string that was consumed in a parser using the original input to the parser and the remaining input after the parser.
pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> { pub(crate) fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSource<'s> {
input.get_until(remaining) input.get_until(remaining)
} }
@@ -76,19 +77,19 @@ pub fn get_consumed<'s>(input: OrgSource<'s>, remaining: OrgSource<'s>) -> OrgSo
/// ///
/// It is up to the caller to ensure this is called at the start of a line. /// It is up to the caller to ensure this is called at the start of a line.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn blank_line(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> { pub(crate) fn blank_line(input: OrgSource<'_>) -> Res<OrgSource<'_>, OrgSource<'_>> {
not(eof)(input)?; not(eof)(input)?;
recognize(tuple((space0, alt((line_ending, eof)))))(input) recognize(tuple((space0, alt((line_ending, eof)))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> { fn element_trailing_whitespace<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?; start_of_line(input)?;
alt((eof, recognize(many0(blank_line))))(input) alt((eof, recognize(many0(blank_line))))(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>( pub(crate) fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
@@ -104,7 +105,7 @@ pub fn maybe_consume_object_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>( pub(crate) fn maybe_consume_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
@@ -117,7 +118,7 @@ pub fn maybe_consume_trailing_whitespace_if_not_exiting<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn maybe_consume_trailing_whitespace<'b, 'g, 'r, 's>( pub(crate) fn maybe_consume_trailing_whitespace<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> { ) -> Res<OrgSource<'s>, Option<OrgSource<'s>>> {
@@ -130,7 +131,7 @@ pub fn maybe_consume_trailing_whitespace<'b, 'g, 'r, 's>(
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> { pub(crate) fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
if input.is_at_start_of_line() { if input.is_at_start_of_line() {
Ok((input, ())) Ok((input, ()))
} else { } else {
@@ -140,7 +141,7 @@ pub fn start_of_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
} }
} }
pub fn preceded_by_whitespace( pub(crate) fn preceded_by_whitespace(
allow_start_of_file: bool, allow_start_of_file: bool,
) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, ()> { ) -> impl for<'s> Fn(OrgSource<'s>) -> Res<OrgSource<'s>, ()> {
move |input| _preceded_by_whitespace(allow_start_of_file, input) move |input| _preceded_by_whitespace(allow_start_of_file, input)
@@ -168,13 +169,13 @@ fn _preceded_by_whitespace<'s>(
/// ///
/// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace. /// This function only operates on spaces, tabs, carriage returns, and line feeds. It does not handle fancy unicode whitespace.
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn non_whitespace_character(input: OrgSource<'_>) -> Res<OrgSource<'_>, char> { pub(crate) fn non_whitespace_character(input: OrgSource<'_>) -> Res<OrgSource<'_>, char> {
none_of(" \t\r\n")(input) none_of(" \t\r\n")(input)
} }
/// Check that we are at the start of a line /// Check that we are at the start of a line
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn exit_matcher_parser<'b, 'g, 'r, 's>( pub(crate) fn exit_matcher_parser<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -182,7 +183,7 @@ pub fn exit_matcher_parser<'b, 'g, 'r, 's>(
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub fn text_until_exit<'b, 'g, 'r, 's>( pub(crate) fn text_until_exit<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> { ) -> Res<OrgSource<'s>, OrgSource<'s>> {
@@ -193,7 +194,7 @@ pub fn text_until_exit<'b, 'g, 'r, 's>(
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> { fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
return Err(nom::Err::Error(CustomError::MyError(MyError( return Err(nom::Err::Error(CustomError::MyError(MyError(
"Not implemented yet.".into(), "Not implemented yet.".into(),
)))); ))));
@@ -204,7 +205,7 @@ pub fn not_yet_implemented() -> Res<OrgSource<'static>, ()> {
/// Text from the current point until the next line break or end of file /// Text from the current point until the next line break or end of file
/// ///
/// Useful for debugging. /// Useful for debugging.
pub fn text_until_eol<'r, 's>( fn text_until_eol<'r, 's>(
input: OrgSource<'s>, input: OrgSource<'s>,
) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> { ) -> Result<&'s str, nom::Err<CustomError<OrgSource<'s>>>> {
let line = recognize(many_till(anychar, alt((line_ending, eof))))(input) let line = recognize(many_till(anychar, alt((line_ending, eof))))(input)
@@ -212,7 +213,10 @@ pub fn text_until_eol<'r, 's>(
Ok(line.trim()) Ok(line.trim())
} }
pub fn include_input<'s, F, O>( /// Return a tuple of (input, output) from a nom parser.
///
/// This is similar to recognize except it returns the input instead of the portion of the input that was consumed.
pub(crate) fn include_input<'s, F, O>(
mut inner: F, mut inner: F,
) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)> ) -> impl FnMut(OrgSource<'s>) -> Res<OrgSource<'s>, (OrgSource<'s>, O)>
where where
@@ -223,3 +227,43 @@ where
Ok((remaining, (input, output))) Ok((remaining, (input, output)))
} }
} }
/// Match single space or tab.
///
/// In org-mode syntax, spaces and tabs are interchangeable.
pub(crate) fn org_space<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, char> {
one_of(" \t")(input)
}
/// Matches a single space, tab, line ending, or end of file.
///
/// In org-mode syntax there are often delimiters that could be any whitespace at all or the end of file.
pub(crate) fn org_space_or_line_ending<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((recognize(org_space), org_line_ending))(input)
}
/// Match a line break or the end of the file.
///
/// In org-mode syntax, the end of the file can serve the same purpose as a line break syntactically.
pub(crate) fn org_line_ending<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
alt((line_ending, eof))(input)
}
/// Match the whitespace at the beginning of a line and give it an indentation level.
pub(crate) fn indentation_level<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, (IndentationLevel, OrgSource<'s>)> {
let (remaining, leading_whitespace) = space0(input)?;
let indentation_level = Into::<&str>::into(leading_whitespace)
.chars()
.map(|c| match c {
' ' => 1,
'\t' => context.get_global_settings().tab_width,
_ => unreachable!(),
})
.sum();
Ok((remaining, (indentation_level, leading_whitespace)))
}

View File

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

View File

@@ -10,15 +10,26 @@ pub struct PlainList<'s> {
pub children: Vec<PlainListItem<'s>>, pub children: Vec<PlainListItem<'s>>,
} }
/// The width that something is indented. For example, a single tab character could be a value of 4 or 8.
pub type IndentationLevel = u16;
#[derive(Debug)] #[derive(Debug)]
pub struct PlainListItem<'s> { pub struct PlainListItem<'s> {
pub source: &'s str, pub source: &'s str,
pub indentation: usize, pub indentation: IndentationLevel,
pub bullet: &'s str, pub bullet: &'s str,
pub checkbox: Option<(CheckboxType, &'s str)>,
pub tag: Vec<Object<'s>>, pub tag: Vec<Object<'s>>,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
} }
#[derive(Debug)]
pub enum CheckboxType {
On,
Trans,
Off,
}
#[derive(Debug)] #[derive(Debug)]
pub struct GreaterBlock<'s> { pub struct GreaterBlock<'s> {
pub source: &'s str, pub source: &'s str,

View File

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

View File

@@ -11,10 +11,12 @@ pub use document::PriorityCookie;
pub use document::Section; pub use document::Section;
pub use document::TodoKeywordType; pub use document::TodoKeywordType;
pub use element::Element; pub use element::Element;
pub use greater_element::CheckboxType;
pub use greater_element::Drawer; pub use greater_element::Drawer;
pub use greater_element::DynamicBlock; pub use greater_element::DynamicBlock;
pub use greater_element::FootnoteDefinition; pub use greater_element::FootnoteDefinition;
pub use greater_element::GreaterBlock; pub use greater_element::GreaterBlock;
pub use greater_element::IndentationLevel;
pub use greater_element::NodeProperty; pub use greater_element::NodeProperty;
pub use greater_element::PlainList; pub use greater_element::PlainList;
pub use greater_element::PlainListItem; pub use greater_element::PlainListItem;
@@ -64,5 +66,5 @@ pub use object::Target;
pub use object::Timestamp; pub use object::Timestamp;
pub use object::Underline; pub use object::Underline;
pub use object::Verbatim; pub use object::Verbatim;
pub use source::SetSource; pub(crate) use source::SetSource;
pub use source::Source; pub use source::Source;

View File

@@ -1,6 +1,6 @@
pub trait Source<'s> { pub trait Source<'s> {
fn get_source(&'s self) -> &'s str; fn get_source(&'s self) -> &'s str;
} }
pub trait SetSource<'s> { pub(crate) trait SetSource<'s> {
fn set_source(&mut self, source: &'s str); fn set_source(&mut self, source: &'s str);
} }

View File

@@ -1,17 +1,7 @@
#[test] #[test]
fn {name}() {{ fn {name}() -> Result<(), Box<dyn std::error::Error>> {{
let todo_org_path = "{path}"; let org_path = "{path}";
let org_contents = std::fs::read_to_string(todo_org_path).expect("Read org file."); let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
println!("{{}}", org_contents); organic::compare::run_anonymous_compare(org_contents.as_str())?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents.as_str()).expect("Use emacs to parse org file."); Ok(())
println!("{{}}", org_sexp);
let (_remaining, parsed_sexp) = sexp_with_padding(org_sexp.as_str()).expect("Sexp Parse failure");
let rust_parsed = parse(org_contents.as_str()).expect("Org Parse failure");
println!("{{:#?}}", rust_parsed);
let diff_result =
compare_document(&parsed_sexp, &rust_parsed).expect("Compare parsed documents.");
diff_result
.print(org_contents.as_str())
.expect("Print document parse tree diff.");
assert!(!diff_result.is_bad());
}} }}