74 Commits

Author SHA1 Message Date
Tom Alexander
e673aa862e Publish version 0.1.9.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-03 00:18:00 -04:00
Tom Alexander
3b6659c5fd Merge branch 'table_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-03 00:14:21 -04:00
Tom Alexander
68a3f8b87e Fix table rule row detection. 2023-10-03 00:13:15 -04:00
Tom Alexander
b1244de1dc Compare row type. 2023-10-03 00:03:58 -04:00
Tom Alexander
e5a402ee1b Compare type and value.
All checks were successful
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
Since we only support org-mode tables, type is always org. Value seems to always be nil, not sure why.
2023-10-02 23:57:17 -04:00
Tom Alexander
d4a2ad4a7f Merge branch 'node_property_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 23:47:11 -04:00
Tom Alexander
3d1b2713ed Compare key and value. 2023-10-02 23:45:31 -04:00
Tom Alexander
60bec4695b Merge branch 'drawer_properties' 2023-10-02 23:38:34 -04:00
Tom Alexander
d992947ff1 Compare name. 2023-10-02 23:34:06 -04:00
Tom Alexander
76fb24d1d1 Merge branch 'comment_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 23:30:29 -04:00
Tom Alexander
b56318fbe4 Add TODO comment. 2023-10-02 23:29:58 -04:00
Tom Alexander
8169499de3 Compare value. 2023-10-02 23:28:32 -04:00
Tom Alexander
29d9e76545 Merge branch 'footnote_definition_properties' 2023-10-02 22:50:26 -04:00
Tom Alexander
4d356b855e Compare label. 2023-10-02 22:48:54 -04:00
Tom Alexander
ae66d1bd89 Fix tracing build.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 22:45:25 -04:00
Tom Alexander
c551938904 Merge branch 'dynamic_block_properties' 2023-10-02 22:43:26 -04:00
Tom Alexander
0fb80e3fee Compare name and parameters. 2023-10-02 22:41:56 -04:00
Tom Alexander
590e7fba0e Merge branch 'greater_block_properties'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 22:37:11 -04:00
Tom Alexander
4a72747dc9 Compare name and parameters. 2023-10-02 22:33:00 -04:00
Tom Alexander
2352636672 Split GreaterBlock into CenterBlock, QuoteBlock, and SpecialBlock.
Center and quote blocks do not have parameters nor do they store their name so I am separating them out.
2023-10-02 22:33:00 -04:00
Tom Alexander
36217f5704 Do not capture trailing whitespace in parameters. 2023-10-02 21:14:07 -04:00
Tom Alexander
0654b676f7 Merge branch 'planning_properties'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 21:00:08 -04:00
Tom Alexander
a80d171e4d Bubble up planning variables to the headline. 2023-10-02 20:37:46 -04:00
Tom Alexander
2e1a946ac9 Compare scheduled, deadline, and closed. 2023-10-02 20:25:08 -04:00
Tom Alexander
01c2f1bf66 Add a test for a timestamp with a malformed repeater. 2023-10-02 20:04:39 -04:00
Tom Alexander
be483110ef Merge branch 'timestamp_properties'
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 20:00:29 -04:00
Tom Alexander
94401dcf00 Allow REST despite no TIME. 2023-10-02 19:51:29 -04:00
Tom Alexander
2b5df83956 Format the code. 2023-10-02 19:24:47 -04:00
Tom Alexander
d53b9e1e1f Fix get_property.
This was returning the error when a token was not an atom whereas we only wanted to check to see if it was the atom nil.
2023-10-02 19:22:35 -04:00
Tom Alexander
5c929ffc13 Fix repeater type.
I had Cumulative and CatchUp backwards.
2023-10-02 19:18:25 -04:00
Tom Alexander
bc3224be7a Revert the rest_end functions. 2023-10-02 19:09:20 -04:00
Tom Alexander
54c66fb4d6 Change get_property to allow absent values.
We're returning an Option<> anyway so might as well handle absent values.
2023-10-02 19:07:12 -04:00
Tom Alexander
6a8ae9d838 Compare warning delay and repeater. 2023-10-02 18:58:30 -04:00
Tom Alexander
512432c5f0 Do not allow time range timestamps with REST on the first TIME. 2023-10-02 17:51:33 -04:00
Tom Alexander
890cd3e4fd Compare start/end time. 2023-10-02 17:17:05 -04:00
Tom Alexander
9846cde2f0 Trim whitespace from raw value. 2023-10-02 16:32:33 -04:00
Tom Alexander
dec3242e72 Implement the Time struct. 2023-10-02 16:24:51 -04:00
Tom Alexander
a8a34e2d9c Compare date start/end. 2023-10-02 16:16:19 -04:00
Tom Alexander
c55fae86f8 Improve lifetimes for get_property_numeric. 2023-10-02 15:51:29 -04:00
Tom Alexander
e7ec23af3d Move the Date struct into types and implement a get_property_numeric. 2023-10-02 15:49:51 -04:00
Tom Alexander
10ae36a419 Implement date types with basic validation. 2023-10-02 15:10:39 -04:00
Tom Alexander
ecdfd7087f Compare raw-value. 2023-10-02 14:45:20 -04:00
Tom Alexander
3ed9b552e2 Compare range type. 2023-10-02 14:35:45 -04:00
Tom Alexander
d04c8c832c Compare timestamp type. 2023-10-02 13:40:37 -04:00
Tom Alexander
9575ef30ac Fix compilation.
All checks were successful
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 13:19:43 -04:00
Tom Alexander
06ecf41663 Add notes about the fields for timestamps. 2023-10-02 13:19:43 -04:00
Tom Alexander
10d03fd432 Merge branch 'standard_ast_node'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded
2023-10-02 13:14:22 -04:00
Tom Alexander
a62c3fc522 Move AstNode into the types crate.
Now that it is used for more than just iteration, it makes sense to promote it to the types crate.
2023-10-02 13:10:45 -04:00
Tom Alexander
25f664e69e Fix warnings. 2023-10-02 13:05:51 -04:00
Tom Alexander
52e0d305aa Remove compare_element and compare_object. 2023-10-02 13:05:29 -04:00
Tom Alexander
418c5c1ce8 Implement the traits for all ast node types. 2023-10-02 12:53:23 -04:00
Tom Alexander
ecd523fa8f Fix lifetimes in the compare functions. 2023-10-02 12:36:09 -04:00
Tom Alexander
c0555dec0b Fix lifetimes for DiffEntry/DiffResult. 2023-10-02 12:28:48 -04:00
Tom Alexander
1b788f3f21 Fix lifetimes on StandardProperties. 2023-10-02 12:11:05 -04:00
Tom Alexander
b3382c66cd Fix lifetimes on ElispFact.
This was listed as a yellow flag on https://quinedot.github.io/rust-learning/pf-shared-nested.html.
2023-10-02 12:11:05 -04:00
Tom Alexander
2a003b85fd Merge branch 'headline_properties'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 11:41:04 -04:00
Tom Alexander
270ba53150 Set is_footnote_section during parsing. 2023-10-02 11:20:43 -04:00
Tom Alexander
de5788d8f3 Introduce a struct for the partially-parsed headline.
We are returning so many fields from that parser that managing a tuple is becoming unreadable. The struct should add some structure 😉 to the code.
2023-10-02 11:16:05 -04:00
Tom Alexander
5a254392cb Add more tests. 2023-10-02 10:53:52 -04:00
Tom Alexander
178894680b Compare footnote section. 2023-10-02 10:48:34 -04:00
Tom Alexander
599b3b8f0a Apply category even if there are radio targets.
Some checks failed
rustfmt Build rustfmt has failed
rust-build Build rust-build has failed
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
2023-10-02 10:26:57 -04:00
Tom Alexander
d78ce10a0b Compare raw-value. 2023-10-02 10:26:57 -04:00
Tom Alexander
12ab9beada Merge branch 'document_properties'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-30 16:06:05 -04:00
Tom Alexander
186201a4b5 Remove category from global settings.
Some checks failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
This setting does not impact parsing so we can iterate over the final document to find the keywords.
2023-09-30 14:35:22 -04:00
Tom Alexander
d38b0a84f6 Fix handling file names with periods before the file extension.
Some checks failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-30 01:26:24 -04:00
Tom Alexander
6ed35f4674 Minor cleanup. 2023-09-30 00:16:19 -04:00
Tom Alexander
846a8b3729 Support reading category from in-buffer-settings. 2023-09-30 00:14:26 -04:00
Tom Alexander
896250836b Add support for parsing quoted strings containing escaped octals. 2023-09-29 23:59:33 -04:00
Tom Alexander
6c77586960 Improve error message. 2023-09-29 23:59:32 -04:00
Tom Alexander
fc7d4bd949 Set Document path and category based on file path. 2023-09-29 23:59:32 -04:00
Tom Alexander
f1e35e317b Compare document path. 2023-09-29 21:20:23 -04:00
Tom Alexander
3fb2b5d31c Undo the getters change.
The getters were a good idea, but if we are going to support editing later, we will need to expose the fields or write A LOT of boiler-plate. The getters also would prevent people from moving values out of the AST without even more boiler-plate. It is simply not worth it at this stage, so we will need to tolerate frequently changing semver versions as the public interface changes since *every* field in the AST is public.
2023-09-29 21:14:55 -04:00
Tom Alexander
d1dac0b8de Compare document category. 2023-09-29 20:57:09 -04:00
Tom Alexander
93f1bcd744 Add getters for Document. 2023-09-29 20:57:09 -04:00
49 changed files with 2539 additions and 944 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "organic"
version = "0.1.8"
version = "0.1.9"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021"

View File

@@ -88,7 +88,7 @@ ARG DOOMEMACS_PATH=/foreign_documents/doomemacs
ARG DOOMEMACS_REPO=https://github.com/doomemacs/doomemacs.git
RUN mkdir -p $DOOMEMACS_PATH && git -C $DOOMEMACS_PATH init --initial-branch=main && git -C $DOOMEMACS_PATH remote add origin $DOOMEMACS_REPO && git -C $DOOMEMACS_PATH fetch origin $DOOMEMACS_VERSION && git -C $DOOMEMACS_PATH checkout FETCH_HEAD
ARG WORG_VERSION=0c8d5679b536af450b61812246a3e02b8103f4b8
ARG WORG_VERSION=ba6cda890f200d428a5d68e819eef15b5306055f
ARG WORG_PATH=/foreign_documents/worg
ARG WORG_REPO=https://git.sr.ht/~bzg/worg
RUN mkdir -p $WORG_PATH && git -C $WORG_PATH init --initial-branch=main && git -C $WORG_PATH remote add origin $WORG_REPO && git -C $WORG_PATH fetch origin $WORG_VERSION && git -C $WORG_PATH checkout FETCH_HEAD

View File

@@ -0,0 +1 @@
#+CATEGORY: theory

View File

@@ -0,0 +1,5 @@
#+CATEGORY: foo
#+CATEGORY: bar
#+begin_src text
#+CATEGORY: baz
#+end_src

View File

@@ -0,0 +1,3 @@
#+begin_defun foo bar baz
lorem
#+end_defun

View File

@@ -1,4 +1,5 @@
# Comment
#
# indented line
# At the top of the file

View File

@@ -0,0 +1,7 @@
# All the marks for repeater and warning delay
[1970-01-01 Thu 8:15-13:15foo +1h -2h]
[1970-01-01 Thu 8:15-13:15foo ++1d -2d]
[1970-01-01 Thu 8:15-13:15foo .+1w -2w]
[1970-01-01 Thu 8:15-13:15foo +1m --2m]
[1970-01-01 Thu 8:15-13:15foo ++1y --2y]
[1970-01-01 Thu 8:15-13:15foo .+1d --2h]

View File

@@ -2,13 +2,17 @@
<%%(foo bar baz)>
# active
<1970-01-01 Thu 8:15rest +1w -1d>
# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range.
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
# inactive
[1970-01-01 Thu 8:15rest +1w -1d]
# Any value for "REST" in the first timestamp makes this a regular timestamp rather than a time range.
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
# active date range
<1970-01-01 Thu 8:15rest +1w -1d>--<1970-01-01 Thu 8:15rest +1w -1d>
# active time range
<1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d>
<1970-01-01 Thu 8:15-13:15otherrest +1w -1d>
# inactive date range
[1970-01-01 Thu 8:15rest +1w -1d]--[1970-01-01 Thu 8:15rest +1w -1d]
# inactive time range
[1970-01-01 Thu 8:15rest-13:15otherrest +1w -1d]
[1970-01-01 Thu 8:15-13:15otherrest +1w -1d]

View File

@@ -0,0 +1,2 @@
# This should be a malformed timestamp according to the current org-mode documentation but it is accepted anyway (with no repeater).
<1970-01-01 Thu ++y>

View File

@@ -0,0 +1,2 @@
<<<Footnotes>>> and stuff
* Footnotes

View File

@@ -0,0 +1,3 @@
* FOOTNOTES
* Footnotes
* footnotes

View File

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

View File

@@ -0,0 +1,3 @@
* Foo
* Footnotes :foo:bar:
* Footnotes and stuff

View File

@@ -8,6 +8,7 @@ use crate::compare::parse::get_org_mode_version;
use crate::compare::sexp::sexp;
use crate::context::GlobalSettings;
use crate::context::LocalFileAccessInterface;
use crate::parser::parse_file_with_settings;
use crate::parser::parse_with_settings;
pub fn run_anonymous_compare<P: AsRef<str>>(
@@ -27,8 +28,7 @@ pub fn run_anonymous_compare_with_settings<P: AsRef<str>>(
// 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());
print_versions()?;
let rust_parsed = parse_with_settings(org_contents, global_settings)?;
let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
@@ -53,8 +53,7 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
global_settings: &GlobalSettings,
) -> Result<(), Box<dyn std::error::Error>> {
let org_path = org_path.as_ref();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
print_versions()?;
let parent_directory = org_path
.parent()
.ok_or("Should be contained inside a directory.")?;
@@ -70,7 +69,7 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
global_settings.file_access = &file_access_interface;
global_settings
};
let rust_parsed = parse_with_settings(org_contents, &global_settings)?;
let rust_parsed = parse_file_with_settings(org_contents, &global_settings, Some(org_path))?;
let org_sexp = emacs_parse_file_org_document(org_path, &global_settings)?;
let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
@@ -88,3 +87,9 @@ pub fn run_compare_on_file_with_settings<P: AsRef<Path>>(
Ok(())
}
fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
Ok(())
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,10 @@
use std::borrow::Cow;
use crate::types::AngleLink;
use crate::types::AstNode;
use crate::types::BabelCall;
use crate::types::Bold;
use crate::types::CenterBlock;
use crate::types::Citation;
use crate::types::CitationReference;
use crate::types::Clock;
@@ -21,7 +23,6 @@ use crate::types::ExportSnippet;
use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::GreaterBlock;
use crate::types::Heading;
use crate::types::HorizontalRule;
use crate::types::InlineBabelCall;
@@ -41,10 +42,12 @@ use crate::types::PlainListItem;
use crate::types::PlainText;
use crate::types::Planning;
use crate::types::PropertyDrawer;
use crate::types::QuoteBlock;
use crate::types::RadioLink;
use crate::types::RadioTarget;
use crate::types::RegularLink;
use crate::types::Section;
use crate::types::SpecialBlock;
use crate::types::SrcBlock;
use crate::types::StatisticsCookie;
use crate::types::StrikeThrough;
@@ -60,25 +63,92 @@ use crate::types::Verbatim;
use crate::types::VerseBlock;
pub(crate) trait ElispFact<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str>;
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str>;
}
pub(crate) trait GetElispFact<'s> {
fn get_elisp_fact(&'s self) -> &'s dyn ElispFact<'s>;
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s>;
}
impl<'s, I: ElispFact<'s>> GetElispFact<'s> for I {
fn get_elisp_fact(&'s self) -> &'s dyn ElispFact<'s> {
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
self
}
}
impl<'r, 's> GetElispFact<'s> for AstNode<'r, 's> {
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
match self {
AstNode::Document(inner) => *inner,
AstNode::Heading(inner) => *inner,
AstNode::Section(inner) => *inner,
AstNode::Paragraph(inner) => *inner,
AstNode::PlainList(inner) => *inner,
AstNode::PlainListItem(inner) => *inner,
AstNode::CenterBlock(inner) => *inner,
AstNode::QuoteBlock(inner) => *inner,
AstNode::SpecialBlock(inner) => *inner,
AstNode::DynamicBlock(inner) => *inner,
AstNode::FootnoteDefinition(inner) => *inner,
AstNode::Comment(inner) => *inner,
AstNode::Drawer(inner) => *inner,
AstNode::PropertyDrawer(inner) => *inner,
AstNode::NodeProperty(inner) => *inner,
AstNode::Table(inner) => *inner,
AstNode::TableRow(inner) => *inner,
AstNode::VerseBlock(inner) => *inner,
AstNode::CommentBlock(inner) => *inner,
AstNode::ExampleBlock(inner) => *inner,
AstNode::ExportBlock(inner) => *inner,
AstNode::SrcBlock(inner) => *inner,
AstNode::Clock(inner) => *inner,
AstNode::DiarySexp(inner) => *inner,
AstNode::Planning(inner) => *inner,
AstNode::FixedWidthArea(inner) => *inner,
AstNode::HorizontalRule(inner) => *inner,
AstNode::Keyword(inner) => *inner,
AstNode::BabelCall(inner) => *inner,
AstNode::LatexEnvironment(inner) => *inner,
AstNode::Bold(inner) => *inner,
AstNode::Italic(inner) => *inner,
AstNode::Underline(inner) => *inner,
AstNode::StrikeThrough(inner) => *inner,
AstNode::Code(inner) => *inner,
AstNode::Verbatim(inner) => *inner,
AstNode::PlainText(inner) => *inner,
AstNode::RegularLink(inner) => *inner,
AstNode::RadioLink(inner) => *inner,
AstNode::RadioTarget(inner) => *inner,
AstNode::PlainLink(inner) => *inner,
AstNode::AngleLink(inner) => *inner,
AstNode::OrgMacro(inner) => *inner,
AstNode::Entity(inner) => *inner,
AstNode::LatexFragment(inner) => *inner,
AstNode::ExportSnippet(inner) => *inner,
AstNode::FootnoteReference(inner) => *inner,
AstNode::Citation(inner) => *inner,
AstNode::CitationReference(inner) => *inner,
AstNode::InlineBabelCall(inner) => *inner,
AstNode::InlineSourceBlock(inner) => *inner,
AstNode::LineBreak(inner) => *inner,
AstNode::Target(inner) => *inner,
AstNode::StatisticsCookie(inner) => *inner,
AstNode::Subscript(inner) => *inner,
AstNode::Superscript(inner) => *inner,
AstNode::TableCell(inner) => *inner,
AstNode::Timestamp(inner) => *inner,
}
}
}
impl<'s> GetElispFact<'s> for Element<'s> {
fn get_elisp_fact(&'s self) -> &'s dyn ElispFact<'s> {
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
match self {
Element::Paragraph(inner) => inner,
Element::PlainList(inner) => inner,
Element::GreaterBlock(inner) => inner,
Element::CenterBlock(inner) => inner,
Element::QuoteBlock(inner) => inner,
Element::SpecialBlock(inner) => inner,
Element::DynamicBlock(inner) => inner,
Element::FootnoteDefinition(inner) => inner,
Element::Comment(inner) => inner,
@@ -103,7 +173,7 @@ impl<'s> GetElispFact<'s> for Element<'s> {
}
impl<'s> GetElispFact<'s> for Object<'s> {
fn get_elisp_fact(&'s self) -> &'s dyn ElispFact<'s> {
fn get_elisp_fact<'b>(&'b self) -> &'b dyn ElispFact<'s> {
match self {
Object::Bold(inner) => inner,
Object::Italic(inner) => inner,
@@ -137,337 +207,345 @@ impl<'s> GetElispFact<'s> for Object<'s> {
}
impl<'s> ElispFact<'s> for Document<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"org-data".into()
}
}
impl<'s> ElispFact<'s> for Section<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"section".into()
}
}
impl<'s> ElispFact<'s> for Heading<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"headline".into()
}
}
impl<'s> ElispFact<'s> for PlainList<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"plain-list".into()
}
}
impl<'s> ElispFact<'s> for PlainListItem<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"item".into()
}
}
impl<'s> ElispFact<'s> for GreaterBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
match self.name.to_lowercase().as_str() {
"center" => "center-block".into(),
"quote" => "quote-block".into(),
_ => "special-block".into(),
}
impl<'s> ElispFact<'s> for CenterBlock<'s> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"center-block".into()
}
}
impl<'s> ElispFact<'s> for QuoteBlock<'s> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"quote-block".into()
}
}
impl<'s> ElispFact<'s> for SpecialBlock<'s> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"special-block".into()
}
}
impl<'s> ElispFact<'s> for DynamicBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"dynamic-block".into()
}
}
impl<'s> ElispFact<'s> for FootnoteDefinition<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"footnote-definition".into()
}
}
impl<'s> ElispFact<'s> for Drawer<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"drawer".into()
}
}
impl<'s> ElispFact<'s> for PropertyDrawer<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"property-drawer".into()
}
}
impl<'s> ElispFact<'s> for NodeProperty<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"node-property".into()
}
}
impl<'s> ElispFact<'s> for Table<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"table".into()
}
}
impl<'s> ElispFact<'s> for TableRow<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"table-row".into()
}
}
impl<'s> ElispFact<'s> for Paragraph<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"paragraph".into()
}
}
impl<'s> ElispFact<'s> for TableCell<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"table-cell".into()
}
}
impl<'s> ElispFact<'s> for Comment<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"comment".into()
}
}
impl<'s> ElispFact<'s> for VerseBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"verse-block".into()
}
}
impl<'s> ElispFact<'s> for CommentBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"comment-block".into()
}
}
impl<'s> ElispFact<'s> for ExampleBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"example-block".into()
}
}
impl<'s> ElispFact<'s> for ExportBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"export-block".into()
}
}
impl<'s> ElispFact<'s> for SrcBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"src-block".into()
}
}
impl<'s> ElispFact<'s> for Clock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"clock".into()
}
}
impl<'s> ElispFact<'s> for DiarySexp<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"diary-sexp".into()
}
}
impl<'s> ElispFact<'s> for Planning<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"planning".into()
}
}
impl<'s> ElispFact<'s> for FixedWidthArea<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"fixed-width".into()
}
}
impl<'s> ElispFact<'s> for HorizontalRule<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"horizontal-rule".into()
}
}
impl<'s> ElispFact<'s> for Keyword<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"keyword".into()
}
}
impl<'s> ElispFact<'s> for BabelCall<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"babel-call".into()
}
}
impl<'s> ElispFact<'s> for LatexEnvironment<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"latex-environment".into()
}
}
impl<'s> ElispFact<'s> for Bold<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"bold".into()
}
}
impl<'s> ElispFact<'s> for Italic<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"italic".into()
}
}
impl<'s> ElispFact<'s> for Underline<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"underline".into()
}
}
impl<'s> ElispFact<'s> for StrikeThrough<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"strike-through".into()
}
}
impl<'s> ElispFact<'s> for Code<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"code".into()
}
}
impl<'s> ElispFact<'s> for Verbatim<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"verbatim".into()
}
}
impl<'s> ElispFact<'s> for RegularLink<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"link".into()
}
}
impl<'s> ElispFact<'s> for RadioLink<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"link".into()
}
}
impl<'s> ElispFact<'s> for RadioTarget<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"radio-target".into()
}
}
impl<'s> ElispFact<'s> for PlainLink<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"link".into()
}
}
impl<'s> ElispFact<'s> for AngleLink<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"link".into()
}
}
impl<'s> ElispFact<'s> for OrgMacro<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"macro".into()
}
}
impl<'s> ElispFact<'s> for Entity<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"entity".into()
}
}
impl<'s> ElispFact<'s> for LatexFragment<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"latex-fragment".into()
}
}
impl<'s> ElispFact<'s> for ExportSnippet<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"export-snippet".into()
}
}
impl<'s> ElispFact<'s> for FootnoteReference<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"footnote-reference".into()
}
}
impl<'s> ElispFact<'s> for Citation<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"citation".into()
}
}
impl<'s> ElispFact<'s> for CitationReference<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"citation-reference".into()
}
}
impl<'s> ElispFact<'s> for InlineBabelCall<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"inline-babel-call".into()
}
}
impl<'s> ElispFact<'s> for InlineSourceBlock<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"inline-src-block".into()
}
}
impl<'s> ElispFact<'s> for LineBreak<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"line-break".into()
}
}
impl<'s> ElispFact<'s> for Target<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"target".into()
}
}
impl<'s> ElispFact<'s> for StatisticsCookie<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"statistics-cookie".into()
}
}
impl<'s> ElispFact<'s> for Subscript<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"subscript".into()
}
}
impl<'s> ElispFact<'s> for Superscript<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"superscript".into()
}
}
impl<'s> ElispFact<'s> for Timestamp<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
"timestamp".into()
}
}
impl<'s> ElispFact<'s> for PlainText<'s> {
fn get_elisp_name(&'s self) -> Cow<'s, str> {
fn get_elisp_name<'b>(&'b self) -> Cow<'s, str> {
// plain text from upstream emacs does not actually have a name but this is included here to make rendering the status diff easier.
"plain-text".into()
}

View File

@@ -76,11 +76,17 @@ where
r#"(progn
(require 'org)
(defun org-table-align () t)
(setq vc-handled-backends nil)
{global_settings}
(find-file-read-only "{file_path}")
(org-mode)
(message "%s" (pp-to-string (org-element-parse-buffer)))
)"#,
global_settings = global_settings_elisp(global_settings)
global_settings = global_settings_elisp(global_settings),
file_path = file_path
.as_os_str()
.to_str()
.expect("File name should be valid utf-8.")
);
let mut cmd = Command::new("emacs");
let cmd = cmd
@@ -89,8 +95,6 @@ where
.arg("--no-site-file")
.arg("--no-splash")
.arg("--batch")
.arg("--insert")
.arg(file_path.as_os_str())
.arg("--eval")
.arg(elisp_script);
let out = cmd.output()?;

View File

@@ -1,9 +1,10 @@
use std::collections::HashMap;
use nom::branch::alt;
use nom::bytes::complete::escaped;
use nom::bytes::complete::tag;
use nom::bytes::complete::take_till1;
use nom::character::complete::anychar;
use nom::character::complete::digit1;
use nom::character::complete::multispace0;
use nom::character::complete::multispace1;
use nom::character::complete::one_of;
@@ -11,6 +12,7 @@ use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::separated_list1;
use nom::sequence::delimited;
use nom::sequence::preceded;
@@ -18,6 +20,8 @@ use nom::sequence::tuple;
use crate::error::Res;
const MAX_OCTAL_LENGTH: usize = 3;
#[derive(Debug)]
pub enum Token<'s> {
Atom(&'s str),
@@ -35,6 +39,7 @@ pub struct TextWithProperties<'s> {
enum ParseState {
Normal,
Escape,
Octal(Vec<u8>),
}
impl<'s> Token<'s> {
@@ -116,7 +121,7 @@ fn get_consumed<'s>(input: &'s str, remaining: &'s str) -> &'s str {
}
pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>> {
let mut out = String::with_capacity(text.len());
let mut out: Vec<u8> = Vec::with_capacity(text.len());
if !text.starts_with(r#"""#) {
return Err("Quoted text does not start with quote.".into());
}
@@ -125,30 +130,53 @@ pub(crate) fn unquote(text: &str) -> Result<String, Box<dyn std::error::Error>>
}
let interior_text = &text[1..(text.len() - 1)];
let mut state = ParseState::Normal;
for current_char in interior_text.chars().into_iter() {
for current_char in interior_text.bytes().into_iter() {
// Check to see if octal finished
state = match (state, current_char) {
(ParseState::Normal, '\\') => ParseState::Escape,
(ParseState::Octal(octal), b'0'..=b'7') if octal.len() < MAX_OCTAL_LENGTH => {
ParseState::Octal(octal)
}
(ParseState::Octal(octal), _) => {
let octal_number_string = String::from_utf8(octal)?;
let decoded_byte = u8::from_str_radix(&octal_number_string, 8)?;
out.push(decoded_byte);
ParseState::Normal
}
(state, _) => state,
};
state = match (state, current_char) {
(ParseState::Normal, b'\\') => ParseState::Escape,
(ParseState::Normal, _) => {
out.push(current_char);
ParseState::Normal
}
(ParseState::Escape, 'n') => {
out.push('\n');
(ParseState::Escape, b'n') => {
out.push(b'\n');
ParseState::Normal
}
(ParseState::Escape, '\\') => {
out.push('\\');
(ParseState::Escape, b'\\') => {
out.push(b'\\');
ParseState::Normal
}
(ParseState::Escape, '"') => {
out.push('"');
(ParseState::Escape, b'"') => {
out.push(b'"');
ParseState::Normal
}
_ => todo!(),
(ParseState::Escape, b'0'..=b'7') => {
let mut octal = Vec::with_capacity(MAX_OCTAL_LENGTH);
octal.push(current_char);
ParseState::Octal(octal)
}
(ParseState::Octal(mut octal), b'0'..=b'7') => {
octal.push(current_char);
ParseState::Octal(octal)
}
_ => panic!("Invalid state unquoting string."),
};
}
Ok(out)
Ok(String::from_utf8(out)?)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -210,15 +238,30 @@ fn unquoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn quoted_atom<'s>(input: &'s str) -> Res<&'s str, Token<'s>> {
let (remaining, _) = tag(r#"""#)(input)?;
let (remaining, _) = escaped(
take_till1(|c| match c {
'\\' | '"' => true,
_ => false,
}),
'\\',
one_of(r#""n\\"#),
)(remaining)?;
let (mut remaining, _) = tag(r#"""#)(input)?;
let mut in_escape = false;
loop {
if in_escape {
let (remain, _) = alt((recognize(one_of(r#""n\\"#)), digit1))(remaining)?;
remaining = remain;
in_escape = false;
} else {
let end_quote = tag::<_, _, nom::error::Error<_>>(r#"""#)(remaining);
if end_quote.is_ok() {
break;
}
let escape_backslash = tag::<_, _, nom::error::Error<_>>("\\")(remaining);
if let Ok((remain, _)) = escape_backslash {
remaining = remain;
in_escape = true;
continue;
}
let (remain, _) = anychar(remaining)?;
remaining = remain;
}
}
let (remaining, _) = tag(r#"""#)(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, Token::Atom(source.into())))

View File

@@ -1,3 +1,5 @@
use std::str::FromStr;
use super::elisp_fact::GetElispFact;
use super::sexp::Token;
use crate::compare::sexp::unquote;
@@ -16,9 +18,9 @@ fn is_slice_of(parent: &str, child: &str) -> bool {
/// Get the byte offset into source that the rust object exists at.
///
/// These offsets are zero-based unlike the elisp ones.
fn get_rust_byte_offsets<'s, S: StandardProperties<'s> + ?Sized>(
fn get_rust_byte_offsets<'b, 's, S: StandardProperties<'s> + ?Sized>(
original_document: &'s str,
rust_ast_node: &'s S,
rust_ast_node: &'b S,
) -> (usize, usize) {
let rust_object_source = rust_ast_node.get_source();
debug_assert!(is_slice_of(original_document, rust_object_source));
@@ -28,20 +30,21 @@ fn get_rust_byte_offsets<'s, S: StandardProperties<'s> + ?Sized>(
}
pub(crate) fn compare_standard_properties<
'b,
's,
S: GetStandardProperties<'s> + GetElispFact<'s> + ?Sized,
>(
original_document: &'s str,
emacs: &'s Token<'s>,
rust: &'s S,
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
assert_name(emacs, rust.get_elisp_fact().get_elisp_name())?;
assert_bounds(original_document, emacs, rust.get_standard_properties())?;
Ok(())
}
pub(crate) fn assert_name<'s, S: AsRef<str>>(
emacs: &'s Token<'s>,
pub(crate) fn assert_name<'b, 's, S: AsRef<str>>(
emacs: &'b Token<'s>,
name: S,
) -> Result<(), Box<dyn std::error::Error>> {
let name = name.as_ref();
@@ -63,10 +66,10 @@ pub(crate) fn assert_name<'s, S: AsRef<str>>(
/// Assert that the character ranges defined by upstream org-mode's :standard-properties match the slices in Organic's StandardProperties.
///
/// This does **not** handle plain text because plain text is a special case.
pub(crate) fn assert_bounds<'s, S: StandardProperties<'s> + ?Sized>(
pub(crate) fn assert_bounds<'b, 's, S: StandardProperties<'s> + ?Sized>(
original_document: &'s str,
emacs: &'s Token<'s>,
rust: &'s S,
emacs: &'b Token<'s>,
rust: &'b S,
) -> Result<(), Box<dyn std::error::Error>> {
let standard_properties = get_emacs_standard_properties(emacs)?; // 1-based
let (begin, end) = (
@@ -99,8 +102,8 @@ struct EmacsStandardProperties {
post_blank: Option<usize>,
}
fn get_emacs_standard_properties<'s>(
emacs: &'s Token<'s>,
fn get_emacs_standard_properties<'b, 's>(
emacs: &'b Token<'s>,
) -> Result<EmacsStandardProperties, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
@@ -169,34 +172,30 @@ fn maybe_token_to_usize(
/// Get a named property from the emacs token.
///
/// Returns Ok(None) if value is nil.
///
/// Returns error if the attribute is not specified on the token at all.
pub(crate) fn get_property<'s, 'x>(
emacs: &'s Token<'s>,
/// Returns Ok(None) if value is nil or absent.
pub(crate) fn get_property<'b, 's, 'x>(
emacs: &'b Token<'s>,
key: &'x str,
) -> Result<Option<&'s Token<'s>>, Box<dyn std::error::Error>> {
) -> Result<Option<&'b Token<'s>>, Box<dyn std::error::Error>> {
let children = emacs.as_list()?;
let attributes_child = children
.iter()
.nth(1)
.ok_or("Should have an attributes child.")?;
let attributes_map = attributes_child.as_map()?;
let prop = attributes_map
.get(key)
.ok_or(format!("Missing {} attribute.", key))?;
match prop.as_atom() {
Ok("nil") => return Ok(None),
let prop = attributes_map.get(key).map(|token| *token);
match prop.map(|token| token.as_atom()) {
Some(Ok("nil")) => return Ok(None),
_ => {}
};
Ok(Some(*prop))
Ok(prop)
}
/// Get a named property containing an unquoted atom from the emacs token.
///
/// Returns None if key is not found.
pub(crate) fn get_property_unquoted_atom<'s, 'x>(
emacs: &'s Token<'s>,
pub(crate) fn get_property_unquoted_atom<'b, 's, 'x>(
emacs: &'b Token<'s>,
key: &'x str,
) -> Result<Option<&'s str>, Box<dyn std::error::Error>> {
Ok(get_property(emacs, key)?
@@ -207,8 +206,8 @@ pub(crate) fn get_property_unquoted_atom<'s, 'x>(
/// Get a named property containing an quoted string from the emacs token.
///
/// Returns None if key is not found.
pub(crate) fn get_property_quoted_string<'s, 'x>(
emacs: &'s Token<'s>,
pub(crate) fn get_property_quoted_string<'b, 's, 'x>(
emacs: &'b Token<'s>,
key: &'x str,
) -> Result<Option<String>, Box<dyn std::error::Error>> {
Ok(get_property(emacs, key)?
@@ -217,3 +216,39 @@ pub(crate) fn get_property_quoted_string<'s, 'x>(
.map(unquote)
.map_or(Ok(None), |r| r.map(Some))?)
}
/// Get a named property containing a boolean value.
///
/// This uses the elisp convention of nil == false, non-nil == true.
///
/// Returns false if key is not found.
pub(crate) fn get_property_boolean<'b, 's, 'x>(
emacs: &'b Token<'s>,
key: &'x str,
) -> Result<bool, Box<dyn std::error::Error>> {
Ok(get_property(emacs, key)?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil")
!= "nil")
}
/// Get a named property containing an unquoted numeric value.
///
/// Returns None if key is not found.
pub(crate) fn get_property_numeric<'b, 's, 'x, N: FromStr>(
emacs: &'b Token<'s>,
key: &'x str,
) -> Result<Option<N>, Box<dyn std::error::Error + 's>>
where
<N as FromStr>::Err: std::error::Error,
<N as FromStr>::Err: 's,
{
let unparsed_string = get_property(emacs, key)?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?;
let parsed_number = unparsed_string
.map(|val| val.parse::<N>())
.map_or(Ok(None), |r| r.map(Some))?;
Ok(parsed_number)
}

View File

@@ -27,6 +27,11 @@ pub struct GlobalSettings<'g, 's> {
///
/// Corresponds to org-odd-levels-only elisp variable.
pub odd_levels_only: HeadlineLevelFilter,
/// If a headline title matches this string exactly, then that section will become a "footnote section".
///
/// Corresponds to org-footnote-section elisp variable.
pub footnote_section: &'g str,
}
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
@@ -43,6 +48,7 @@ impl<'g, 's> GlobalSettings<'g, 's> {
list_allow_alphabetical: false,
tab_width: DEFAULT_TAB_WIDTH,
odd_levels_only: HeadlineLevelFilter::default(),
footnote_section: "Footnotes",
}
}
}

View File

@@ -9,6 +9,7 @@ pub enum CustomError<I> {
MyError(MyError<&'static str>),
Nom(I, ErrorKind),
IO(std::io::Error),
BoxedError(Box<dyn std::error::Error>),
}
#[derive(Debug)]
@@ -36,3 +37,9 @@ impl<I> From<&'static str> for CustomError<I> {
CustomError::MyError(MyError(value))
}
}
impl<I> From<Box<dyn std::error::Error>> for CustomError<I> {
fn from(value: Box<dyn std::error::Error>) -> Self {
CustomError::BoxedError(value)
}
}

View File

@@ -1,7 +1,7 @@
use std::collections::VecDeque;
use super::ast_node::AstNode;
use super::ast_node_iter::AstNodeIter;
use crate::types::AstNode;
pub struct AllAstNodeIter<'r, 's> {
root: Option<AstNode<'r, 's>>,
@@ -24,7 +24,9 @@ impl<'r, 's> Iterator for AllAstNodeIter<'r, 's> {
AstNodeIter::Paragraph(ref mut i) => i.next(),
AstNodeIter::PlainList(ref mut i) => i.next(),
AstNodeIter::PlainListItem(ref mut i) => i.next(),
AstNodeIter::GreaterBlock(ref mut i) => i.next(),
AstNodeIter::CenterBlock(ref mut i) => i.next(),
AstNodeIter::QuoteBlock(ref mut i) => i.next(),
AstNodeIter::SpecialBlock(ref mut i) => i.next(),
AstNodeIter::DynamicBlock(ref mut i) => i.next(),
AstNodeIter::FootnoteDefinition(ref mut i) => i.next(),
AstNodeIter::Comment(ref mut i) => i.next(),

View File

@@ -1,12 +1,13 @@
use std::marker::PhantomData;
use super::ast_node::AstNode;
use super::macros::children_iter;
use super::macros::empty_iter;
use super::macros::multi_field_iter;
use crate::types::AngleLink;
use crate::types::AstNode;
use crate::types::BabelCall;
use crate::types::Bold;
use crate::types::CenterBlock;
use crate::types::Citation;
use crate::types::CitationReference;
use crate::types::Clock;
@@ -26,7 +27,6 @@ use crate::types::ExportSnippet;
use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::GreaterBlock;
use crate::types::Heading;
use crate::types::HorizontalRule;
use crate::types::InlineBabelCall;
@@ -46,10 +46,12 @@ use crate::types::PlainListItem;
use crate::types::PlainText;
use crate::types::Planning;
use crate::types::PropertyDrawer;
use crate::types::QuoteBlock;
use crate::types::RadioLink;
use crate::types::RadioTarget;
use crate::types::RegularLink;
use crate::types::Section;
use crate::types::SpecialBlock;
use crate::types::SrcBlock;
use crate::types::StatisticsCookie;
use crate::types::StrikeThrough;
@@ -78,7 +80,9 @@ pub(crate) enum AstNodeIter<'r, 's> {
Paragraph(ParagraphIter<'r, 's>),
PlainList(PlainListIter<'r, 's>),
PlainListItem(PlainListItemIter<'r, 's>),
GreaterBlock(GreaterBlockIter<'r, 's>),
CenterBlock(CenterBlockIter<'r, 's>),
QuoteBlock(QuoteBlockIter<'r, 's>),
SpecialBlock(SpecialBlockIter<'r, 's>),
DynamicBlock(DynamicBlockIter<'r, 's>),
FootnoteDefinition(FootnoteDefinitionIter<'r, 's>),
Comment(CommentIter<'r, 's>),
@@ -140,7 +144,9 @@ impl<'r, 's> AstNodeIter<'r, 's> {
AstNode::Paragraph(inner) => AstNodeIter::Paragraph(inner.into_iter()),
AstNode::PlainList(inner) => AstNodeIter::PlainList(inner.into_iter()),
AstNode::PlainListItem(inner) => AstNodeIter::PlainListItem(inner.into_iter()),
AstNode::GreaterBlock(inner) => AstNodeIter::GreaterBlock(inner.into_iter()),
AstNode::CenterBlock(inner) => AstNodeIter::CenterBlock(inner.into_iter()),
AstNode::QuoteBlock(inner) => AstNodeIter::QuoteBlock(inner.into_iter()),
AstNode::SpecialBlock(inner) => AstNodeIter::SpecialBlock(inner.into_iter()),
AstNode::DynamicBlock(inner) => AstNodeIter::DynamicBlock(inner.into_iter()),
AstNode::FootnoteDefinition(inner) => {
AstNodeIter::FootnoteDefinition(inner.into_iter())
@@ -232,8 +238,18 @@ multi_field_iter!(
std::slice::Iter<'r, Element<'s>>
);
children_iter!(
GreaterBlock<'s>,
GreaterBlockIter,
CenterBlock<'s>,
CenterBlockIter,
std::slice::Iter<'r, Element<'s>>
);
children_iter!(
QuoteBlock<'s>,
QuoteBlockIter,
std::slice::Iter<'r, Element<'s>>
);
children_iter!(
SpecialBlock<'s>,
SpecialBlockIter,
std::slice::Iter<'r, Element<'s>>
);
children_iter!(

View File

@@ -1,16 +1,3 @@
/// Write the implementation of From<> to convert a borrow of the type to an AstNode
macro_rules! to_ast_node {
($inp:ty, $enum:expr) => {
impl<'r, 's> From<$inp> for AstNode<'r, 's> {
fn from(value: $inp) -> Self {
$enum(value)
}
}
};
}
pub(crate) use to_ast_node;
/// Create iterators for ast nodes where it only has to iterate over children
macro_rules! children_iter {
($astnodetype:ty, $itertype:ident, $innertype:ty) => {

View File

@@ -1,5 +1,3 @@
mod all_ast_node_iter;
mod ast_node;
mod ast_node_iter;
mod macros;
pub(crate) use ast_node::AstNode;

View File

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

View File

@@ -1,18 +1,19 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::org_source::OrgSource;
use super::util::get_consumed;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::RefContext;
@@ -38,15 +39,29 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&parser_context);
let comment_line_matcher = parser_with_context!(comment_line)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, _first_line) = comment_line_matcher(input)?;
let (remaining, _remaining_lines) =
let (remaining, first_line) = comment_line_matcher(input)?;
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
let last_line = remaining_lines.pop();
if let Some(last_line) = last_line {
value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
let last_line = Into::<&str>::into(last_line);
// Trim the line ending from the final line.
value.push(&last_line[..(last_line.len() - 1)])
} else {
// Trim the line ending from the only line.
let only_line = Into::<&str>::into(first_line);
value.push(&only_line[..(only_line.len() - 1)])
}
Ok((
remaining,
Comment {
source: source.into(),
value,
},
))
}
@@ -57,14 +72,13 @@ fn comment_line<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _indent) = space0(input)?;
let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = tuple((
tag("#"),
opt(tuple((space1, is_not("\r\n")))),
alt((line_ending, eof)),
))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
let (remaining, _) = tuple((space0, tag("#")))(input)?;
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
return Ok((remaining, line_break));
}
let (remaining, _) = tag(" ")(remaining)?;
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
Ok((remaining, value))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -1,9 +1,12 @@
use std::path::Path;
use nom::combinator::all_consuming;
use nom::combinator::opt;
use nom::multi::many0;
use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings;
use super::in_buffer_settings::apply_post_parse_in_buffer_settings;
use super::in_buffer_settings::scan_for_in_buffer_settings;
use super::org_source::OrgSource;
use super::section::zeroth_section;
@@ -17,38 +20,79 @@ use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::MyError;
use crate::error::Res;
use crate::iter::AstNode;
use crate::parser::org_source::convert_error;
use crate::parser::util::blank_line;
use crate::types::AstNode;
use crate::types::Document;
use crate::types::Object;
/// Parse a full org-mode document.
///
/// This is the main entry point for Organic. It will parse the full contents of the input string as an org-mode document.
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document without an underlying file attached.
#[allow(dead_code)]
pub fn parse<'s>(input: &'s str) -> Result<Document<'s>, Box<dyn std::error::Error>> {
parse_with_settings(input, &GlobalSettings::default())
parse_file_with_settings::<&Path>(input, &GlobalSettings::default(), None)
}
/// Parse a full org-mode document.
///
/// This is a main entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path.
///
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
#[allow(dead_code)]
pub fn parse_file<'s, P: AsRef<Path>>(
input: &'s str,
file_path: Option<P>,
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
parse_file_with_settings(input, &GlobalSettings::default(), file_path)
}
/// Parse a full org-mode document with starting settings.
///
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied.
/// This is a secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document starting with the settings you supplied without an underlying file attached.
///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
#[allow(dead_code)]
pub fn parse_with_settings<'g, 's>(
input: &'s str,
global_settings: &'g GlobalSettings<'g, 's>,
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
parse_file_with_settings::<&Path>(input, global_settings, None)
}
/// Parse a full org-mode document with starting settings.
///
/// This is the secondary entry point for Organic. It will parse the full contents of the input string as an org-mode document at the file_path starting with the settings you supplied.
///
/// This will not prevent additional settings from being learned during parsing, for example when encountering a "#+TODO".
///
/// file_path is not used for reading the file contents. It is only used for determining the document category and filling in the path attribute on the Document.
#[allow(dead_code)]
pub fn parse_file_with_settings<'g, 's, P: AsRef<Path>>(
input: &'s str,
global_settings: &'g GlobalSettings<'g, 's>,
file_path: Option<P>,
) -> Result<Document<'s>, Box<dyn std::error::Error>> {
let initial_context = ContextElement::document_context();
let initial_context = Context::new(global_settings, List::new(&initial_context));
let wrapped_input = OrgSource::new(input);
let ret =
let mut doc =
all_consuming(parser_with_context!(document_org_source)(&initial_context))(wrapped_input)
.map_err(|err| err.to_string())
.map(|(_remaining, parsed_document)| parsed_document);
Ok(ret?)
.map(|(_remaining, parsed_document)| parsed_document)?;
if let Some(file_path) = file_path {
let full_path = file_path.as_ref().canonicalize()?;
if doc.category.is_none() {
let category = full_path
.file_stem()
.expect("File should have a name.")
.to_str()
.expect("File name should be valid utf-8.");
doc.category = Some(category.to_owned());
}
doc.path = Some(full_path);
}
Ok(doc)
}
/// Parse a full org-mode document.
@@ -106,7 +150,7 @@ fn document_org_source<'b, 'g, 'r, 's>(
let new_context = context.with_global_settings(&new_settings);
let context = &new_context;
let (remaining, document) =
let (remaining, mut document) =
_document(context, input).map(|(rem, out)| (Into::<&str>::into(rem), out))?;
{
// If there are radio targets in this document then we need to parse the entire document again with the knowledge of the radio targets.
@@ -125,11 +169,18 @@ fn document_org_source<'b, 'g, 'r, 's>(
let mut new_global_settings = context.get_global_settings().clone();
new_global_settings.radio_targets = all_radio_targets;
let parser_context = context.with_global_settings(&new_global_settings);
let (remaining, document) = _document(&parser_context, input)
let (remaining, mut document) = _document(&parser_context, input)
.map(|(rem, out)| (Into::<&str>::into(rem), out))?;
apply_post_parse_in_buffer_settings(&mut document)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
return Ok((remaining.into(), document));
}
}
// Find final in-buffer settings that do not impact parsing
apply_post_parse_in_buffer_settings(&mut document)
.map_err(|err| nom::Err::<CustomError<OrgSource<'_>>>::Failure(err.into()))?;
Ok((remaining.into(), document))
}
@@ -148,6 +199,8 @@ fn _document<'b, 'g, 'r, 's>(
remaining,
Document {
source: source.into(),
category: None,
path: None,
zeroth_section,
children,
},

View File

@@ -2,6 +2,7 @@ use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
@@ -9,6 +10,7 @@ use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
@@ -47,10 +49,11 @@ pub(crate) fn dynamic_block<'b, 'g, 'r, 's>(
}
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name, parameters, _ws)) = tuple((
let (remaining, (_, name, parameters, _, _)) = tuple((
recognize(tuple((tag_no_case("#+begin:"), space1))),
name,
opt(tuple((space1, parameters))),
space0,
line_ending,
))(remaining)?;
let contexts = [
@@ -108,7 +111,7 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input)
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]

View File

@@ -81,7 +81,7 @@ fn _element<'b, 'g, 'r, 's>(
let (remaining, mut affiliated_keywords) = many0(affiliated_keyword_matcher)(input)?;
let (remaining, mut element) = match alt((
map(plain_list_matcher, Element::PlainList),
map(greater_block_matcher, Element::GreaterBlock),
greater_block_matcher,
map(dynamic_block_matcher, Element::DynamicBlock),
map(footnote_definition_matcher, Element::FootnoteDefinition),
map(comment_matcher, Element::Comment),

View File

@@ -1,6 +1,7 @@
use nom::branch::alt;
use nom::bytes::complete::is_not;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
@@ -8,6 +9,8 @@ use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till;
@@ -30,17 +33,18 @@ use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::CenterBlock;
use crate::types::Element;
use crate::types::GreaterBlock;
use crate::types::Paragraph;
use crate::types::QuoteBlock;
use crate::types::SetSource;
use crate::types::SpecialBlock;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn greater_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, GreaterBlock<'s>> {
// TODO: Do I need to differentiate between different greater block types.
) -> Res<OrgSource<'s>, Element<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, (_begin, name)) = tuple((
@@ -52,22 +56,97 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
}
}),
))(remaining)?;
let context_name = match Into::<&str>::into(name).to_lowercase().as_str() {
"center" => "center block".to_owned(),
"quote" => "quote block".to_owned(),
name @ _ => format!("special block {}", name),
let name = Into::<&str>::into(name);
let (remaining, element) = match name.to_lowercase().as_str() {
"center" => center_block(context, remaining, input)?,
"quote" => quote_block(context, remaining, input)?,
_ => special_block(name)(context, remaining, input)?,
};
if in_section(context, context_name.as_str()) {
Ok((remaining, element))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn center_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, (source, children)) =
greater_block_body(context, input, original_input, "center", "center block")?;
Ok((
remaining,
Element::CenterBlock(CenterBlock { source, children }),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn quote_block<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, (source, children)) =
greater_block_body(context, input, original_input, "quote", "quote block")?;
Ok((
remaining,
Element::QuoteBlock(QuoteBlock { source, children }),
))
}
fn special_block<'s>(
name: &'s str,
) -> impl for<'b, 'g, 'r> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Element<'s>>
+ 's {
let context_name = format!("special block {}", name);
move |context, input, original_input| {
_special_block(context, input, original_input, name, context_name.as_str())
}
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn _special_block<'c, 'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
name: &'s str,
context_name: &'c str,
) -> Res<OrgSource<'s>, Element<'s>> {
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) =
greater_block_body(context, remaining, original_input, name, context_name)?;
Ok((
remaining,
Element::SpecialBlock(SpecialBlock {
source,
children,
name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
}),
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn greater_block_body<'c, 'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
original_input: OrgSource<'s>,
name: &'c str,
context_name: &'c str,
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
if in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Cannot nest objects of the same element".into(),
))));
}
let exit_with_name = greater_block_end(name.into());
let (remaining, parameters) = opt(tuple((space1, parameters)))(remaining)?;
let (remaining, _nl) = line_ending(remaining)?;
let exit_with_name = greater_block_end(name);
let (remaining, _nl) = tuple((space0, line_ending))(input)?;
let contexts = [
ContextElement::ConsumeTrailingWhitespace(true),
ContextElement::Context(context_name.as_str()),
ContextElement::Context(context_name),
ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &exit_with_name,
@@ -76,11 +155,6 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
let parser_context = context.with_additional_node(&contexts[0]);
let parser_context = parser_context.with_additional_node(&contexts[1]);
let parser_context = parser_context.with_additional_node(&contexts[2]);
let parameters = match parameters {
Some((_ws, parameters)) => Some(parameters),
None => None,
};
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
not(exit_matcher)(remaining)?;
@@ -104,16 +178,8 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
let source = get_consumed(input, remaining);
Ok((
remaining,
GreaterBlock {
source: source.into(),
name: name.into(),
parameters: parameters.map(|val| Into::<&str>::into(val)),
children,
},
))
let source = get_consumed(original_input, remaining);
Ok((remaining, (Into::<&str>::into(source), children)))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -123,7 +189,7 @@ fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn parameters<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
is_not("\r\n")(input)
recognize(many_till(anychar, peek(tuple((space0, line_ending)))))(input)
}
fn greater_block_end<'c>(name: &'c str) -> impl ContextMatcher + 'c {

View File

@@ -4,6 +4,7 @@ use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::opt;
@@ -33,6 +34,7 @@ use crate::error::Res;
use crate::parser::object_parser::standard_set_object;
use crate::parser::util::blank_line;
use crate::types::DocumentElement;
use crate::types::Element;
use crate::types::Heading;
use crate::types::HeadlineLevel;
use crate::types::Object;
@@ -54,27 +56,27 @@ fn _heading<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
parent_star_count: HeadlineLevel,
) -> Res<OrgSource<'s>, Heading<'s>> {
let mut scheduled = None;
let mut deadline = None;
let mut closed = None;
not(|i| context.check_exit_matcher(i))(input)?;
let (
remaining,
(
headline_level,
star_count,
maybe_todo_keyword,
maybe_priority,
maybe_comment,
title,
heading_tags,
),
) = headline(context, input, parent_star_count)?;
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = parser_with_context!(section)(context);
let heading_matcher = parser_with_context!(heading(star_count))(context);
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
let (remaining, mut children) =
many0(map(heading_matcher, DocumentElement::Heading))(remaining)?;
if let Some(section) = maybe_section {
// If the section has a planning then the timestamp values are copied to the heading.
if let DocumentElement::Section(inner_section) = &section {
if let Some(Element::Planning(planning)) = inner_section.children.first() {
scheduled = planning.scheduled.clone();
deadline = planning.deadline.clone();
closed = planning.closed.clone();
}
}
children.insert(0, section);
}
let remaining = if children.is_empty() {
@@ -84,23 +86,29 @@ fn _heading<'b, 'g, 'r, 's>(
} else {
remaining
};
let is_archived = heading_tags.contains(&"ARCHIVE");
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
let source = get_consumed(input, remaining);
Ok((
remaining,
Heading {
source: source.into(),
level: headline_level,
todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
priority_cookie: maybe_priority.map(|(_, priority)| priority),
title,
tags: heading_tags,
level: pre_headline.headline_level,
todo_keyword: pre_headline
.todo_keyword
.map(|(todo_keyword_type, todo_keyword)| {
(todo_keyword_type, Into::<&str>::into(todo_keyword))
}),
priority_cookie: pre_headline.priority_cookie.map(|(_, priority)| priority),
title: pre_headline.title,
tags: pre_headline.tags,
children,
is_comment: maybe_comment.is_some(),
is_comment: pre_headline.comment.is_some(),
is_archived,
is_footnote_section: pre_headline.is_footnote_section,
scheduled,
deadline,
closed,
},
))
}
@@ -111,23 +119,27 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
Ok((input, ()))
}
/// Fields from a not-yet-fully-parsed Headline.
///
/// This struct exists to give names to the fields of a partially-parsed Headline to avoid returning a large tuple of nameless fields.
#[derive(Debug)]
struct PreHeadline<'s> {
headline_level: HeadlineLevel,
star_count: HeadlineLevel,
todo_keyword: Option<(TodoKeywordType, OrgSource<'s>)>,
priority_cookie: Option<(OrgSource<'s>, PriorityCookie)>,
comment: Option<OrgSource<'s>>,
title: Vec<Object<'s>>,
tags: Vec<&'s str>,
is_footnote_section: bool,
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn headline<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
parent_star_count: HeadlineLevel,
) -> Res<
OrgSource<'s>,
(
HeadlineLevel,
HeadlineLevel,
Option<(TodoKeywordType, OrgSource<'s>)>,
Option<(OrgSource<'s>, PriorityCookie)>,
Option<OrgSource<'s>>,
Vec<Object<'s>>,
Vec<&'s str>,
),
> {
) -> Res<OrgSource<'s>, PreHeadline<'s>> {
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Document,
exit_matcher: &headline_title_end,
@@ -159,30 +171,43 @@ fn headline<'b, 'g, 'r, 's>(
let (remaining, maybe_title) = opt(tuple((
space1,
many1(parser_with_context!(standard_set_object)(&parser_context)),
consumed(many1(parser_with_context!(standard_set_object)(
&parser_context,
))),
)))(remaining)?;
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
let is_footnote_section = maybe_title
.as_ref()
.map(|(_, (raw_title, _))| raw_title)
.map(|raw_title| {
Into::<&str>::into(raw_title) == context.get_global_settings().footnote_section
})
.unwrap_or(false);
Ok((
remaining,
(
PreHeadline {
headline_level,
star_count,
maybe_todo_keyword.map(|(_, todo, _)| todo),
maybe_priority,
maybe_comment.map(|(_, comment, _)| comment),
maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
maybe_tags
todo_keyword: maybe_todo_keyword.map(|(_, todo, _)| todo),
priority_cookie: maybe_priority,
comment: maybe_comment.map(|(_, comment, _)| comment),
title: maybe_title
.map(|(_, (_, title))| title)
.unwrap_or(Vec::new()),
tags: maybe_tags
.map(|(_ws, tags)| {
tags.into_iter()
.map(|single_tag| Into::<&str>::into(single_tag))
.collect()
})
.unwrap_or(Vec::new()),
),
is_footnote_section,
},
))
}

View File

@@ -12,6 +12,8 @@ use crate::context::HeadlineLevelFilter;
use crate::error::CustomError;
use crate::error::Res;
use crate::settings::GlobalSettings;
use crate::types::AstNode;
use crate::types::Document;
use crate::types::Keyword;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -114,6 +116,25 @@ pub(crate) fn apply_in_buffer_settings<'g, 's, 'sf>(
Ok(new_settings)
}
/// Apply in-buffer settings that do not impact parsing and therefore can be applied after parsing.
pub(crate) fn apply_post_parse_in_buffer_settings<'g, 's, 'sf>(
document: &mut Document<'s>,
) -> Result<(), &'static str> {
document.category = Into::<AstNode>::into(&*document)
.into_iter()
.filter_map(|ast_node| {
if let AstNode::Keyword(ast_node) = ast_node {
if ast_node.key.eq_ignore_ascii_case("category") {
return Some(ast_node);
}
}
None
})
.last()
.map(|kw| kw.value.to_owned());
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -45,5 +45,7 @@ mod text_markup;
mod timestamp;
mod util;
pub use document::parse;
pub use document::parse_file;
pub use document::parse_file_with_settings;
pub use document::parse_with_settings;
pub(crate) use org_source::OrgSource;

View File

@@ -388,6 +388,7 @@ impl<'s> From<CustomError<OrgSource<'s>>> for CustomError<&'s str> {
CustomError::MyError(err) => CustomError::MyError(err.into()),
CustomError::Nom(input, error_kind) => CustomError::Nom(input.into(), error_kind),
CustomError::IO(err) => CustomError::IO(err),
CustomError::BoxedError(err) => CustomError::BoxedError(err),
}
}
}

View File

@@ -3,6 +3,7 @@ use nom::bytes::complete::tag;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::map;
use nom::multi::many1;
use nom::sequence::tuple;
@@ -16,6 +17,7 @@ use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::parser::util::start_of_line;
use crate::types::Planning;
use crate::types::Timestamp;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn planning<'b, 'g, 'r, 's>(
@@ -24,7 +26,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, Planning<'s>> {
start_of_line(input)?;
let (remaining, _leading_whitespace) = space0(input)?;
let (remaining, _planning_parameters) =
let (remaining, planning_parameters) =
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
@@ -32,26 +34,55 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let mut scheduled = None;
let mut deadline = None;
let mut closed = None;
for (timestamp_type, timestamp) in planning_parameters.into_iter() {
match timestamp_type {
PlanningTimestampType::Scheduled => {
scheduled = Some(timestamp);
}
PlanningTimestampType::Deadline => {
deadline = Some(timestamp);
}
PlanningTimestampType::Closed => {
closed = Some(timestamp);
}
}
}
Ok((
remaining,
Planning {
source: source.into(),
scheduled,
deadline,
closed,
},
))
}
#[derive(Debug)]
enum PlanningTimestampType {
Scheduled,
Deadline,
Closed,
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn planning_parameter<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _planning_type) = alt((
tag_no_case("DEADLINE"),
tag_no_case("SCHEDULED"),
tag_no_case("CLOSED"),
) -> Res<OrgSource<'s>, (PlanningTimestampType, Timestamp<'s>)> {
let (remaining, planning_type) = alt((
map(tag_no_case("DEADLINE"), |_| PlanningTimestampType::Deadline),
map(tag_no_case("SCHEDULED"), |_| {
PlanningTimestampType::Scheduled
}),
map(tag_no_case("CLOSED"), |_| PlanningTimestampType::Closed),
))(input)?;
let (remaining, _gap) = tuple((tag(":"), space1))(remaining)?;
let (remaining, _timestamp) = timestamp(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
let (remaining, timestamp) = timestamp(context, remaining)?;
Ok((remaining, (planning_type, timestamp)))
}

View File

@@ -101,7 +101,7 @@ fn node_property<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, NodeProperty<'s>> {
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, _name, _close_colon)) =
let (remaining, (_start_of_line, _leading_whitespace, _open_colon, name, _close_colon)) =
tuple((
start_of_line,
space0,
@@ -120,6 +120,7 @@ fn node_property<'b, 'g, 'r, 's>(
remaining,
NodeProperty {
source: source.into(),
name: Into::<&str>::into(name),
value: None,
},
))
@@ -132,6 +133,7 @@ fn node_property<'b, 'g, 'r, 's>(
remaining,
NodeProperty {
source: source.into(),
name: Into::<&str>::into(name),
value: Some(value.into()),
},
))

View File

@@ -3,8 +3,8 @@ use nom::bytes::complete::is_not;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -17,6 +17,7 @@ use super::keyword::table_formula_keyword;
use super::object_parser::table_cell_set_object;
use super::org_source::OrgSource;
use super::util::exit_matcher_parser;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -105,7 +106,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, TableRow<'s>> {
start_of_line(input)?;
let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?;
let (remaining, _) = tuple((space0, tag("|-"), opt(is_not("\r\n")), org_line_ending))(input)?;
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -125,7 +126,7 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
let (remaining, _) = tuple((space0, tag("|")))(input)?;
let (remaining, children) =
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?;
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
let source = get_consumed(input, remaining);
Ok((
remaining,

View File

@@ -1,10 +1,10 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::digit0;
use nom::character::complete::digit1;
use nom::character::complete::one_of;
use nom::character::complete::space1;
use nom::combinator::map;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -21,7 +21,21 @@ use crate::context::ExitMatcherNode;
use crate::context::RefContext;
use crate::error::Res;
use crate::parser::util::get_consumed;
use crate::types::Date;
use crate::types::DayOfMonth;
use crate::types::Hour;
use crate::types::Minute;
use crate::types::Month;
use crate::types::Repeater;
use crate::types::RepeaterType;
use crate::types::Time;
use crate::types::TimeUnit;
use crate::types::Timestamp;
use crate::types::TimestampRangeType;
use crate::types::TimestampType;
use crate::types::WarningDelay;
use crate::types::WarningDelayType;
use crate::types::Year;
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
pub(crate) fn timestamp<'b, 'g, 'r, 's>(
@@ -57,6 +71,14 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
remaining,
Timestamp {
source: source.into(),
timestamp_type: TimestampType::Diary,
range_type: TimestampRangeType::None,
start: None,
end: None,
start_time: None,
end_time: None,
repeater: None,
warning_delay: None,
},
))
}
@@ -97,17 +119,26 @@ fn active_timestamp<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?;
let (remaining, _date) = date(context, remaining)?;
let (remaining, start) = date(context, remaining)?;
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &active_time_rest_end,
});
let time_context = context.with_additional_node(&time_context);
let (remaining, _time) =
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
let (remaining, _repeater) =
let (remaining, time) = opt(tuple((
space1,
parser_with_context!(time(true))(&time_context),
)))(remaining)?;
let remaining = if time.is_none() {
// Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'.
let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?;
remain
} else {
remaining
};
let (remaining, repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
let (remaining, warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
@@ -121,6 +152,14 @@ fn active_timestamp<'b, 'g, 'r, 's>(
remaining,
Timestamp {
source: source.into(),
timestamp_type: TimestampType::Active,
range_type: TimestampRangeType::None,
start: Some(start.clone()),
end: Some(start),
start_time: time.as_ref().map(|(_, time)| time.clone()),
end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
},
))
}
@@ -131,17 +170,26 @@ fn inactive_timestamp<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?;
let (remaining, _date) = date(context, remaining)?;
let (remaining, start) = date(context, remaining)?;
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &inactive_time_rest_end,
});
let time_context = context.with_additional_node(&time_context);
let (remaining, _time) =
opt(tuple((space1, parser_with_context!(time)(&time_context))))(remaining)?;
let (remaining, _repeater) =
let (remaining, time) = opt(tuple((
space1,
parser_with_context!(time(true))(&time_context),
)))(remaining)?;
let remaining = if time.is_none() {
// Upstream org-mode accepts malformed timestamps. For example '<2016-02-14 Sun ++y>'.
let (remain, _) = opt(parser_with_context!(time_rest)(&time_context))(remaining)?;
remain
} else {
remaining
};
let (remaining, repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
let (remaining, warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
@@ -155,6 +203,14 @@ fn inactive_timestamp<'b, 'g, 'r, 's>(
remaining,
Timestamp {
source: source.into(),
timestamp_type: TimestampType::Inactive,
range_type: TimestampRangeType::None,
start: Some(start.clone()),
end: Some(start),
start_time: time.as_ref().map(|(_, time)| time.clone()),
end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
},
))
}
@@ -164,10 +220,10 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _first_timestamp) = active_timestamp(context, input)?;
let (remaining, first_timestamp) = active_timestamp(context, input)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = active_timestamp(context, remaining)?;
let (remaining, second_timestamp) = active_timestamp(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -177,6 +233,16 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
remaining,
Timestamp {
source: source.into(),
timestamp_type: TimestampType::ActiveRange,
range_type: TimestampRangeType::DateRange,
start: first_timestamp.start,
end: second_timestamp.end,
start_time: first_timestamp.start_time,
end_time: second_timestamp.end_time,
repeater: first_timestamp.repeater.or(second_timestamp.repeater),
warning_delay: first_timestamp
.warning_delay
.or(second_timestamp.warning_delay),
},
))
}
@@ -187,7 +253,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?;
let (remaining, _date) = date(context, remaining)?;
let (remaining, start_date) = date(context, remaining)?;
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &active_time_rest_end,
@@ -198,13 +264,15 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
exit_matcher: &time_range_rest_end,
});
let first_time_context = time_context.with_additional_node(&first_time_context);
let (remaining, _first_time) =
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
let (remaining, (_, first_time)) = tuple((
space1,
parser_with_context!(time(false))(&first_time_context),
))(remaining)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
let (remaining, _repeater) =
let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?;
let (remaining, repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
let (remaining, warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
@@ -218,6 +286,14 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
remaining,
Timestamp {
source: source.into(),
timestamp_type: TimestampType::ActiveRange,
range_type: TimestampRangeType::TimeRange,
start: Some(start_date.clone()),
end: Some(start_date),
start_time: Some(first_time),
end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
},
))
}
@@ -227,10 +303,10 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _first_timestamp) = inactive_timestamp(context, input)?;
let (remaining, first_timestamp) = inactive_timestamp(context, input)?;
// TODO: Does the space0 at the end of the active/inactive timestamp parsers cause this to be incorrect? I could use a look-behind to make sure the preceding character is not whitespace
let (remaining, _separator) = tag("--")(remaining)?;
let (remaining, _second_timestamp) = inactive_timestamp(context, remaining)?;
let (remaining, second_timestamp) = inactive_timestamp(context, remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
@@ -240,6 +316,17 @@ fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
remaining,
Timestamp {
source: source.into(),
timestamp_type: TimestampType::InactiveRange,
range_type: TimestampRangeType::DateRange,
start: first_timestamp.start,
end: second_timestamp.end,
start_time: first_timestamp.start_time,
end_time: second_timestamp.end_time,
repeater: first_timestamp.repeater.or(second_timestamp.repeater),
warning_delay: first_timestamp
.warning_delay
.or(second_timestamp.warning_delay),
},
))
}
@@ -250,7 +337,7 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?;
let (remaining, _date) = date(context, remaining)?;
let (remaining, start_date) = date(context, remaining)?;
let time_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Gamma,
exit_matcher: &inactive_time_rest_end,
@@ -261,13 +348,15 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
exit_matcher: &time_range_rest_end,
});
let first_time_context = time_context.with_additional_node(&first_time_context);
let (remaining, _first_time) =
tuple((space1, parser_with_context!(time)(&first_time_context)))(remaining)?;
let (remaining, (_, first_time)) = tuple((
space1,
parser_with_context!(time(false))(&first_time_context),
))(remaining)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _second_time) = parser_with_context!(time)(&time_context)(remaining)?;
let (remaining, _repeater) =
let (remaining, second_time) = parser_with_context!(time(true))(&time_context)(remaining)?;
let (remaining, repeater) =
opt(tuple((space1, parser_with_context!(repeater)(context))))(remaining)?;
let (remaining, _warning_delay) = opt(tuple((
let (remaining, warning_delay) = opt(tuple((
space1,
parser_with_context!(warning_delay)(context),
)))(remaining)?;
@@ -281,6 +370,14 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
remaining,
Timestamp {
source: source.into(),
timestamp_type: TimestampType::InactiveRange,
range_type: TimestampRangeType::TimeRange,
start: Some(start_date.clone()),
end: Some(start_date),
start_time: Some(first_time),
end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
},
))
}
@@ -289,18 +386,33 @@ fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
fn date<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
) -> Res<OrgSource<'s>, Date<'s>> {
let (remaining, year) = verify(digit1, |year: &OrgSource<'_>| year.len() == 4)(input)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
let (remaining, month) = verify(digit1, |month: &OrgSource<'_>| month.len() == 2)(remaining)?;
let (remaining, _) = tag("-")(remaining)?;
let (remaining, _day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
let (remaining, day_of_month) = verify(digit1, |day_of_month: &OrgSource<'_>| {
day_of_month.len() == 2
})(remaining)?;
let (remaining, _dayname) =
let (remaining, day_name) =
opt(tuple((space1, parser_with_context!(dayname)(context))))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
let year = Year::new(Into::<&str>::into(year))
.expect("TODO: I should be able to return CustomError from nom parsers.");
let month = Month::new(Into::<&str>::into(month))
.expect("TODO: I should be able to return CustomError from nom parsers.");
let day_of_month = DayOfMonth::new(Into::<&str>::into(day_of_month))
.expect("TODO: I should be able to return CustomError from nom parsers.");
let date = Date::new(
year,
month,
day_of_month,
day_name.map(|(_, day_name)| Into::<&str>::into(day_name)),
)
.expect("TODO: I should be able to return CustomError from nom parsers.");
Ok((remaining, date))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -335,20 +447,39 @@ fn dayname_end<'b, 'g, 'r, 's>(
}))(input)
}
const fn time<'c>(
allow_rest: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(RefContext<'b, 'g, 'r, 's>, OrgSource<'s>) -> Res<OrgSource<'s>, Time<'s>>
{
move |context, input| _time(context, input, allow_rest)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn time<'b, 'g, 'r, 's>(
fn _time<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
let (remaining, _hour) = verify(digit1, |hour: &OrgSource<'_>| {
allow_rest: bool,
) -> Res<OrgSource<'s>, Time<'s>> {
let (remaining, hour) = verify(digit1, |hour: &OrgSource<'_>| {
hour.len() >= 1 && hour.len() <= 2
})(input)?;
let (remaining, _) = tag(":")(remaining)?;
let (remaining, _minute) =
let (remaining, minute) =
verify(digit1, |minute: &OrgSource<'_>| minute.len() == 2)(remaining)?;
let (remaining, _time_rest) = opt(parser_with_context!(time_rest)(context))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
let (remaining, time_rest) = if allow_rest {
opt(parser_with_context!(time_rest)(context))(remaining)?
} else {
(remaining, None)
};
let hour = Hour::new(Into::<&str>::into(hour))
.expect("TODO: I should be able to return CustomError from nom parsers.");
let minute = Minute::new(Into::<&str>::into(minute))
.expect("TODO: I should be able to return CustomError from nom parsers.");
let time = Time::new(hour, minute, time_rest.map(Into::<&str>::into))
.expect("TODO: I should be able to return CustomError from nom parsers.");
Ok((remaining, time))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -401,8 +532,10 @@ fn time_range_rest_end<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, OrgSource<'s>> {
// We pop off the most recent context element to get a context tree with just the active/inactive_time_rest_end exit matcher (removing this function from the exit matcher chain) because the 2nd time in the range does not end when a "-TIME" pattern is found.
let parent_node = context.get_parent().expect("Two context elements are added to the tree when adding this exit matcher, so it should be impossible for this to return None.");
let exit_contents =
recognize(tuple((tag("-"), parser_with_context!(time)(&parent_node))))(input);
let exit_contents = recognize(tuple((
tag("-"),
parser_with_context!(time(true))(&parent_node),
)))(input);
exit_contents
}
@@ -410,29 +543,66 @@ fn time_range_rest_end<'b, 'g, 'r, 's>(
fn repeater<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
) -> Res<OrgSource<'s>, Repeater> {
// + for cumulative type
// ++ for catch-up type
// .+ for restart type
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
let (remaining, _value) = digit0(remaining)?;
let (remaining, repeater_type) = alt((
map(tag("++"), |_| RepeaterType::CatchUp),
map(tag("+"), |_| RepeaterType::Cumulative),
map(tag(".+"), |_| RepeaterType::Restart),
))(input)?;
let (remaining, value) = digit1(remaining)?;
let value = Into::<&str>::into(value)
.parse()
.expect("digit1 ensures this will parse as a number.");
// h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
let (remaining, unit) = alt((
map(tag("h"), |_| TimeUnit::Hour),
map(tag("d"), |_| TimeUnit::Day),
map(tag("w"), |_| TimeUnit::Week),
map(tag("m"), |_| TimeUnit::Month),
map(tag("y"), |_| TimeUnit::Year),
))(remaining)?;
Ok((
remaining,
Repeater {
repeater_type,
value,
unit,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn warning_delay<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
) -> Res<OrgSource<'s>, WarningDelay> {
// - for all type
// -- for first type
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
let (remaining, _value) = digit0(remaining)?;
let (remaining, warning_delay_type) = alt((
map(tag("--"), |_| WarningDelayType::First),
map(tag("-"), |_| WarningDelayType::All),
))(input)?;
let (remaining, value) = digit1(remaining)?;
let value = Into::<&str>::into(value)
.parse()
.expect("digit1 ensures this will parse as a number.");
// h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining);
Ok((remaining, source))
let (remaining, unit) = alt((
map(tag("h"), |_| TimeUnit::Hour),
map(tag("d"), |_| TimeUnit::Day),
map(tag("w"), |_| TimeUnit::Week),
map(tag("m"), |_| TimeUnit::Month),
map(tag("y"), |_| TimeUnit::Year),
))(remaining)?;
Ok((
remaining,
WarningDelay {
warning_delay_type,
value,
unit,
},
))
}

View File

@@ -1,4 +1,7 @@
use super::macros::to_ast_node;
use super::CenterBlock;
use super::QuoteBlock;
use super::SpecialBlock;
use crate::types::AngleLink;
use crate::types::BabelCall;
use crate::types::Bold;
@@ -21,7 +24,7 @@ use crate::types::ExportSnippet;
use crate::types::FixedWidthArea;
use crate::types::FootnoteDefinition;
use crate::types::FootnoteReference;
use crate::types::GreaterBlock;
use crate::types::GetStandardProperties;
use crate::types::Heading;
use crate::types::HorizontalRule;
use crate::types::InlineBabelCall;
@@ -68,7 +71,9 @@ pub enum AstNode<'r, 's> {
Paragraph(&'r Paragraph<'s>),
PlainList(&'r PlainList<'s>),
PlainListItem(&'r PlainListItem<'s>),
GreaterBlock(&'r GreaterBlock<'s>),
CenterBlock(&'r CenterBlock<'s>),
QuoteBlock(&'r QuoteBlock<'s>),
SpecialBlock(&'r SpecialBlock<'s>),
DynamicBlock(&'r DynamicBlock<'s>),
FootnoteDefinition(&'r FootnoteDefinition<'s>),
Comment(&'r Comment<'s>),
@@ -135,7 +140,9 @@ impl<'r, 's> From<&'r Element<'s>> for AstNode<'r, 's> {
match value {
Element::Paragraph(inner) => inner.into(),
Element::PlainList(inner) => inner.into(),
Element::GreaterBlock(inner) => inner.into(),
Element::CenterBlock(inner) => inner.into(),
Element::QuoteBlock(inner) => inner.into(),
Element::SpecialBlock(inner) => inner.into(),
Element::DynamicBlock(inner) => inner.into(),
Element::FootnoteDefinition(inner) => inner.into(),
Element::Comment(inner) => inner.into(),
@@ -199,7 +206,9 @@ to_ast_node!(&'r Section<'s>, AstNode::Section);
to_ast_node!(&'r Paragraph<'s>, AstNode::Paragraph);
to_ast_node!(&'r PlainList<'s>, AstNode::PlainList);
to_ast_node!(&'r PlainListItem<'s>, AstNode::PlainListItem);
to_ast_node!(&'r GreaterBlock<'s>, AstNode::GreaterBlock);
to_ast_node!(&'r CenterBlock<'s>, AstNode::CenterBlock);
to_ast_node!(&'r QuoteBlock<'s>, AstNode::QuoteBlock);
to_ast_node!(&'r SpecialBlock<'s>, AstNode::SpecialBlock);
to_ast_node!(&'r DynamicBlock<'s>, AstNode::DynamicBlock);
to_ast_node!(&'r FootnoteDefinition<'s>, AstNode::FootnoteDefinition);
to_ast_node!(&'r Comment<'s>, AstNode::Comment);
@@ -249,3 +258,68 @@ to_ast_node!(&'r Subscript<'s>, AstNode::Subscript);
to_ast_node!(&'r Superscript<'s>, AstNode::Superscript);
to_ast_node!(&'r TableCell<'s>, AstNode::TableCell);
to_ast_node!(&'r Timestamp<'s>, AstNode::Timestamp);
impl<'r, 's> GetStandardProperties<'s> for AstNode<'r, 's> {
fn get_standard_properties<'b>(&'b self) -> &'b dyn crate::types::StandardProperties<'s> {
match self {
AstNode::Document(inner) => *inner,
AstNode::Heading(inner) => *inner,
AstNode::Section(inner) => *inner,
AstNode::Paragraph(inner) => *inner,
AstNode::PlainList(inner) => *inner,
AstNode::PlainListItem(inner) => *inner,
AstNode::CenterBlock(inner) => *inner,
AstNode::QuoteBlock(inner) => *inner,
AstNode::SpecialBlock(inner) => *inner,
AstNode::DynamicBlock(inner) => *inner,
AstNode::FootnoteDefinition(inner) => *inner,
AstNode::Comment(inner) => *inner,
AstNode::Drawer(inner) => *inner,
AstNode::PropertyDrawer(inner) => *inner,
AstNode::NodeProperty(inner) => *inner,
AstNode::Table(inner) => *inner,
AstNode::TableRow(inner) => *inner,
AstNode::VerseBlock(inner) => *inner,
AstNode::CommentBlock(inner) => *inner,
AstNode::ExampleBlock(inner) => *inner,
AstNode::ExportBlock(inner) => *inner,
AstNode::SrcBlock(inner) => *inner,
AstNode::Clock(inner) => *inner,
AstNode::DiarySexp(inner) => *inner,
AstNode::Planning(inner) => *inner,
AstNode::FixedWidthArea(inner) => *inner,
AstNode::HorizontalRule(inner) => *inner,
AstNode::Keyword(inner) => *inner,
AstNode::BabelCall(inner) => *inner,
AstNode::LatexEnvironment(inner) => *inner,
AstNode::Bold(inner) => *inner,
AstNode::Italic(inner) => *inner,
AstNode::Underline(inner) => *inner,
AstNode::StrikeThrough(inner) => *inner,
AstNode::Code(inner) => *inner,
AstNode::Verbatim(inner) => *inner,
AstNode::PlainText(inner) => *inner,
AstNode::RegularLink(inner) => *inner,
AstNode::RadioLink(inner) => *inner,
AstNode::RadioTarget(inner) => *inner,
AstNode::PlainLink(inner) => *inner,
AstNode::AngleLink(inner) => *inner,
AstNode::OrgMacro(inner) => *inner,
AstNode::Entity(inner) => *inner,
AstNode::LatexFragment(inner) => *inner,
AstNode::ExportSnippet(inner) => *inner,
AstNode::FootnoteReference(inner) => *inner,
AstNode::Citation(inner) => *inner,
AstNode::CitationReference(inner) => *inner,
AstNode::InlineBabelCall(inner) => *inner,
AstNode::InlineSourceBlock(inner) => *inner,
AstNode::LineBreak(inner) => *inner,
AstNode::Target(inner) => *inner,
AstNode::StatisticsCookie(inner) => *inner,
AstNode::Subscript(inner) => *inner,
AstNode::Superscript(inner) => *inner,
AstNode::TableCell(inner) => *inner,
AstNode::Timestamp(inner) => *inner,
}
}
}

View File

@@ -1,7 +1,10 @@
use std::path::PathBuf;
use super::Element;
use super::GetStandardProperties;
use super::Object;
use super::StandardProperties;
use super::Timestamp;
pub type PriorityCookie = u8;
pub type HeadlineLevel = u16;
@@ -9,6 +12,8 @@ pub type HeadlineLevel = u16;
#[derive(Debug)]
pub struct Document<'s> {
pub source: &'s str,
pub category: Option<String>,
pub path: Option<PathBuf>,
pub zeroth_section: Option<Section<'s>>,
pub children: Vec<Heading<'s>>,
}
@@ -24,6 +29,10 @@ pub struct Heading<'s> {
pub children: Vec<DocumentElement<'s>>,
pub is_comment: bool,
pub is_archived: bool,
pub is_footnote_section: bool,
pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>,
}
#[derive(Debug)]
@@ -45,7 +54,7 @@ pub enum TodoKeywordType {
}
impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self {
DocumentElement::Heading(inner) => inner,
DocumentElement::Section(inner) => inner,
@@ -54,19 +63,31 @@ impl<'s> GetStandardProperties<'s> for DocumentElement<'s> {
}
impl<'s> StandardProperties<'s> for Document<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Section<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Heading<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> Heading<'s> {
pub fn get_raw_value(&self) -> String {
// TODO: I think this could just return a string slice instead of an owned string.
let title_source: String = self
.title
.iter()
.map(|obj| obj.get_standard_properties().get_source())
.collect();
title_source
}
}

View File

@@ -1,6 +1,5 @@
use super::greater_element::DynamicBlock;
use super::greater_element::FootnoteDefinition;
use super::greater_element::GreaterBlock;
use super::greater_element::PlainList;
use super::greater_element::PropertyDrawer;
use super::greater_element::Table;
@@ -19,16 +18,21 @@ use super::lesser_element::Paragraph;
use super::lesser_element::Planning;
use super::lesser_element::SrcBlock;
use super::lesser_element::VerseBlock;
use super::CenterBlock;
use super::Drawer;
use super::GetStandardProperties;
use super::QuoteBlock;
use super::SetSource;
use super::SpecialBlock;
use super::StandardProperties;
#[derive(Debug)]
pub enum Element<'s> {
Paragraph(Paragraph<'s>),
PlainList(PlainList<'s>),
GreaterBlock(GreaterBlock<'s>),
CenterBlock(CenterBlock<'s>),
QuoteBlock(QuoteBlock<'s>),
SpecialBlock(SpecialBlock<'s>),
DynamicBlock(DynamicBlock<'s>),
FootnoteDefinition(FootnoteDefinition<'s>),
Comment(Comment<'s>),
@@ -56,7 +60,9 @@ impl<'s> SetSource<'s> for Element<'s> {
match self {
Element::Paragraph(obj) => obj.source = source,
Element::PlainList(obj) => obj.source = source,
Element::GreaterBlock(obj) => obj.source = source,
Element::CenterBlock(obj) => obj.source = source,
Element::QuoteBlock(obj) => obj.source = source,
Element::SpecialBlock(obj) => obj.source = source,
Element::DynamicBlock(obj) => obj.source = source,
Element::FootnoteDefinition(obj) => obj.source = source,
Element::Comment(obj) => obj.source = source,
@@ -81,11 +87,13 @@ impl<'s> SetSource<'s> for Element<'s> {
}
impl<'s> GetStandardProperties<'s> for Element<'s> {
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self {
Element::Paragraph(inner) => inner,
Element::PlainList(inner) => inner,
Element::GreaterBlock(inner) => inner,
Element::CenterBlock(inner) => inner,
Element::QuoteBlock(inner) => inner,
Element::SpecialBlock(inner) => inner,
Element::DynamicBlock(inner) => inner,
Element::FootnoteDefinition(inner) => inner,
Element::Comment(inner) => inner,

View File

@@ -2,11 +2,11 @@ use super::StandardProperties;
pub trait GetStandardProperties<'s> {
// TODO: Can I eliminate this dynamic dispatch, perhaps using nominal generic structs? Low prioritiy since this is not used during parsing.
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties;
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s>;
}
impl<'s, I: StandardProperties<'s>> GetStandardProperties<'s> for I {
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
self
}
}

View File

@@ -1,7 +1,5 @@
use super::element::Element;
use super::lesser_element::TableCell;
use super::macros::ref_getter;
use super::macros::simple_getter;
use super::Keyword;
use super::Object;
use super::StandardProperties;
@@ -25,14 +23,14 @@ pub type IndentationLevel = u16;
#[derive(Debug)]
pub struct PlainListItem<'s> {
pub(crate) source: &'s str,
pub(crate) indentation: IndentationLevel,
pub(crate) bullet: &'s str,
pub(crate) counter: Option<PlainListItemCounter>,
pub(crate) checkbox: Option<(CheckboxType, &'s str)>,
pub(crate) tag: Vec<Object<'s>>,
pub(crate) pre_blank: PlainListItemPreBlank,
pub(crate) children: Vec<Element<'s>>,
pub source: &'s str,
pub indentation: IndentationLevel,
pub bullet: &'s str,
pub counter: Option<PlainListItemCounter>,
pub checkbox: Option<(CheckboxType, &'s str)>,
pub tag: Vec<Object<'s>>,
pub pre_blank: PlainListItemPreBlank,
pub children: Vec<Element<'s>>,
}
pub type PlainListItemCounter = u16;
@@ -46,7 +44,19 @@ pub enum CheckboxType {
}
#[derive(Debug)]
pub struct GreaterBlock<'s> {
pub struct CenterBlock<'s> {
pub source: &'s str,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub struct QuoteBlock<'s> {
pub source: &'s str,
pub children: Vec<Element<'s>>,
}
#[derive(Debug)]
pub struct SpecialBlock<'s> {
pub source: &'s str,
pub name: &'s str,
pub parameters: Option<&'s str>,
@@ -84,6 +94,7 @@ pub struct PropertyDrawer<'s> {
#[derive(Debug)]
pub struct NodeProperty<'s> {
pub source: &'s str,
pub name: &'s str,
pub value: Option<&'s str>,
}
@@ -100,81 +111,85 @@ pub struct TableRow<'s> {
pub children: Vec<TableCell<'s>>,
}
#[derive(Debug)]
pub enum TableRowType {
Standard,
Rule,
}
impl<'s> StandardProperties<'s> for PlainList<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for PlainListItem<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for GreaterBlock<'s> {
fn get_source(&'s self) -> &'s str {
impl<'s> StandardProperties<'s> for CenterBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for FootnoteDefinition<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Drawer<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for NodeProperty<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Table<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for TableRow<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> PlainListItem<'s> {
simple_getter!(get_indentation_level, indentation, IndentationLevel);
simple_getter!(
/// Get the bullet
///
/// Example output: "1. "
get_bullet,
bullet,
&'s str
);
simple_getter!(get_counter, counter, Option<PlainListItemCounter>);
simple_getter!(get_pre_blank, pre_blank, PlainListItemPreBlank);
ref_getter!(get_tag, tag, Vec<Object<'s>>);
ref_getter!(get_children, children, Vec<Element<'s>>);
pub fn get_checkbox(&self) -> Option<&'s str> {
self.checkbox.as_ref().map(|(_, checkbox)| *checkbox)
}
@@ -185,3 +200,13 @@ impl<'s> PlainListItem<'s> {
.map(|(checkbox_type, _)| checkbox_type)
}
}
impl<'s> TableRow<'s> {
pub fn get_type(&self) -> TableRowType {
if self.children.is_empty() {
TableRowType::Rule
} else {
TableRowType::Standard
}
}
}

View File

@@ -1,6 +1,7 @@
use super::object::Object;
use super::PlainText;
use super::StandardProperties;
use super::Timestamp;
#[derive(Debug)]
pub struct Paragraph<'s> {
@@ -11,6 +12,7 @@ pub struct Paragraph<'s> {
#[derive(Debug)]
pub struct Comment<'s> {
pub source: &'s str,
pub value: Vec<&'s str>,
}
#[derive(Debug)]
@@ -72,6 +74,9 @@ pub struct DiarySexp<'s> {
#[derive(Debug)]
pub struct Planning<'s> {
pub source: &'s str,
pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>,
}
#[derive(Debug)]
@@ -115,93 +120,106 @@ impl<'s> Paragraph<'s> {
}
impl<'s> StandardProperties<'s> for Paragraph<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for TableCell<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Comment<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for VerseBlock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for ExportBlock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for SrcBlock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Clock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for DiarySexp<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Planning<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Keyword<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for BabelCall<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> Comment<'s> {
pub fn get_value(&self) -> String {
// TODO: maybe we should handle parsing here instead of storing the parsing result in the AST since I imagine getting the value of comments won't be a common operation.
let final_size = self.value.iter().map(|line| line.len()).sum();
let mut ret = String::with_capacity(final_size);
for line in &self.value {
ret.push_str(line);
}
ret
}
}

View File

@@ -1,21 +1,12 @@
// TODO: Would be nice if I didn't have to specify a function name but it looks like concat_idents!() cannot be used to create an ident.
// TODO: Find out if proc macros could do this easier (for example, parsing out the field type)
macro_rules! simple_getter {
($(#[$meta:meta])* $funcname: ident, $field:ident, $fieldtype:ty) => {
$(#[$meta])*
pub fn $funcname(&self) -> $fieldtype {
self.$field
/// Write the implementation of From<> to convert a borrow of the type to an AstNode
macro_rules! to_ast_node {
($inp:ty, $enum:expr) => {
impl<'r, 's> From<$inp> for AstNode<'r, 's> {
fn from(value: $inp) -> Self {
$enum(value)
}
}
};
}
pub(crate) use simple_getter;
macro_rules! ref_getter {
($(#[$meta:meta])* $funcname: ident, $field:ident, $fieldtype:ty) => {
$(#[$meta])*
pub fn $funcname(&self) -> &$fieldtype {
&self.$field
}
};
}
pub(crate) use ref_getter;
pub(crate) use to_ast_node;

View File

@@ -1,3 +1,4 @@
mod ast_node;
mod document;
mod element;
mod get_standard_properties;
@@ -7,6 +8,7 @@ mod macros;
mod object;
mod source;
mod standard_properties;
pub(crate) use ast_node::AstNode;
pub use document::Document;
pub use document::DocumentElement;
pub use document::Heading;
@@ -16,11 +18,11 @@ pub use document::Section;
pub use document::TodoKeywordType;
pub use element::Element;
pub use get_standard_properties::GetStandardProperties;
pub use greater_element::CenterBlock;
pub use greater_element::CheckboxType;
pub use greater_element::Drawer;
pub use greater_element::DynamicBlock;
pub use greater_element::FootnoteDefinition;
pub use greater_element::GreaterBlock;
pub use greater_element::IndentationLevel;
pub use greater_element::NodeProperty;
pub use greater_element::PlainList;
@@ -29,8 +31,11 @@ pub use greater_element::PlainListItemCounter;
pub use greater_element::PlainListItemPreBlank;
pub use greater_element::PlainListType;
pub use greater_element::PropertyDrawer;
pub use greater_element::QuoteBlock;
pub use greater_element::SpecialBlock;
pub use greater_element::Table;
pub use greater_element::TableRow;
pub use greater_element::TableRowType;
pub use lesser_element::BabelCall;
pub use lesser_element::Clock;
pub use lesser_element::Comment;
@@ -52,14 +57,23 @@ pub use object::Bold;
pub use object::Citation;
pub use object::CitationReference;
pub use object::Code;
pub use object::Date;
pub use object::DayOfMonth;
pub use object::DayOfMonthInner;
pub use object::Entity;
pub use object::ExportSnippet;
pub use object::FootnoteReference;
pub use object::Hour;
pub use object::HourInner;
pub use object::InlineBabelCall;
pub use object::InlineSourceBlock;
pub use object::Italic;
pub use object::LatexFragment;
pub use object::LineBreak;
pub use object::Minute;
pub use object::MinuteInner;
pub use object::Month;
pub use object::MonthInner;
pub use object::Object;
pub use object::OrgMacro;
pub use object::PlainLink;
@@ -67,13 +81,24 @@ pub use object::PlainText;
pub use object::RadioLink;
pub use object::RadioTarget;
pub use object::RegularLink;
pub use object::Repeater;
pub use object::RepeaterType;
pub use object::RepeaterWarningDelayValueType;
pub use object::StatisticsCookie;
pub use object::StrikeThrough;
pub use object::Subscript;
pub use object::Superscript;
pub use object::Target;
pub use object::Time;
pub use object::TimeUnit;
pub use object::Timestamp;
pub use object::TimestampRangeType;
pub use object::TimestampType;
pub use object::Underline;
pub use object::Verbatim;
pub use object::WarningDelay;
pub use object::WarningDelayType;
pub use object::Year;
pub use object::YearInner;
pub(crate) use source::SetSource;
pub use standard_properties::StandardProperties;

View File

@@ -1,6 +1,7 @@
use super::GetStandardProperties;
use super::StandardProperties;
// TODO: Why did we make Object implement PartialEq again? Was it just for tests?
#[derive(Debug, PartialEq)]
pub enum Object<'s> {
Bold(Bold<'s>),
@@ -181,13 +182,260 @@ pub struct Superscript<'s> {
pub source: &'s str,
}
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub struct Timestamp<'s> {
pub source: &'s str,
pub timestamp_type: TimestampType,
pub range_type: TimestampRangeType,
pub start: Option<Date<'s>>,
pub end: Option<Date<'s>>,
pub start_time: Option<Time<'s>>,
pub end_time: Option<Time<'s>>,
pub repeater: Option<Repeater>,
pub warning_delay: Option<WarningDelay>,
}
#[derive(Debug, PartialEq, Clone)]
pub enum TimestampType {
Diary,
Active,
Inactive,
ActiveRange,
InactiveRange,
}
#[derive(Debug, PartialEq, Clone)]
pub enum TimestampRangeType {
None,
DateRange,
TimeRange,
}
pub type YearInner = u16;
pub type MonthInner = u8;
pub type DayOfMonthInner = u8;
pub type HourInner = u8;
pub type MinuteInner = u8;
#[derive(Debug, PartialEq, Clone)]
pub struct Year(YearInner);
#[derive(Debug, PartialEq, Clone)]
pub struct Month(MonthInner);
#[derive(Debug, PartialEq, Clone)]
pub struct DayOfMonth(DayOfMonthInner);
#[derive(Debug, PartialEq, Clone)]
pub struct Hour(HourInner);
#[derive(Debug, PartialEq, Clone)]
pub struct Minute(MinuteInner);
impl Year {
// TODO: Make a real error type instead of a boxed any error.
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
let year = source.parse::<YearInner>()?;
Ok(Year(year))
}
pub fn get_value(&self) -> YearInner {
self.0
}
}
impl Month {
// TODO: Make a real error type instead of a boxed any error.
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
let month = source.parse::<MonthInner>()?;
if month < 1 || month > 12 {
Err("Month exceeds possible range.")?;
}
Ok(Month(month))
}
pub fn get_value(&self) -> MonthInner {
self.0
}
}
impl DayOfMonth {
// TODO: Make a real error type instead of a boxed any error.
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
let day_of_month = source.parse::<DayOfMonthInner>()?;
if day_of_month < 1 || day_of_month > 31 {
Err("Day of month exceeds possible range.")?;
}
Ok(DayOfMonth(day_of_month))
}
pub fn get_value(&self) -> DayOfMonthInner {
self.0
}
}
impl Hour {
// TODO: Make a real error type instead of a boxed any error.
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
let hour = source.parse::<HourInner>()?;
if hour > 23 {
Err("Hour exceeds possible range.")?;
}
Ok(Hour(hour))
}
pub fn get_value(&self) -> HourInner {
self.0
}
}
impl Minute {
// TODO: Make a real error type instead of a boxed any error.
pub fn new<'s>(source: &'s str) -> Result<Self, Box<dyn std::error::Error>> {
let minute = source.parse::<MinuteInner>()?;
if minute > 59 {
Err("Minute exceeds possible range.")?;
}
Ok(Minute(minute))
}
pub fn get_value(&self) -> MinuteInner {
self.0
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Date<'s> {
year: Year,
month: Month,
day_of_month: DayOfMonth,
day_name: Option<&'s str>,
}
impl<'s> Date<'s> {
// TODO: Make a real error type instead of a boxed any error.
pub fn new(
year: Year,
month: Month,
day_of_month: DayOfMonth,
day_name: Option<&'s str>,
) -> Result<Self, Box<dyn std::error::Error>> {
// TODO: Does org-mode support non-gregorian calendars?
// TODO: Do I want to validate leap year?
match (month.get_value(), day_of_month.get_value()) {
(1, 1..=31) => {}
(2, 1..=29) => {}
(3, 1..=31) => {}
(4, 1..=30) => {}
(5, 1..=31) => {}
(6, 1..=30) => {}
(7, 1..=31) => {}
(8, 1..=31) => {}
(9, 1..=30) => {}
(10, 1..=31) => {}
(11, 1..=30) => {}
(12, 1..=31) => {}
_ => Err("Invalid day of month for the month.")?,
};
Ok(Date {
year,
month,
day_of_month,
day_name,
})
}
pub fn get_year(&self) -> &Year {
&self.year
}
pub fn get_month(&self) -> &Month {
&self.month
}
pub fn get_day_of_month(&self) -> &DayOfMonth {
&self.day_of_month
}
pub fn get_day_name(&self) -> Option<&'s str> {
self.day_name
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Time<'s> {
hour: Hour,
minute: Minute,
postfix: Option<&'s str>,
}
impl<'s> Time<'s> {
// TODO: Make a real error type instead of a boxed any error.
pub fn new(
hour: Hour,
minute: Minute,
postfix: Option<&'s str>,
) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Time {
hour,
minute,
postfix,
})
}
pub fn get_hour(&self) -> &Hour {
&self.hour
}
pub fn get_minute(&self) -> &Minute {
&self.minute
}
pub fn get_postfix(&self) -> Option<&'s str> {
self.postfix
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum RepeaterType {
Cumulative,
CatchUp,
Restart,
}
#[derive(Debug, PartialEq, Clone)]
pub enum WarningDelayType {
All,
First,
}
#[derive(Debug, PartialEq, Clone)]
pub enum TimeUnit {
Hour,
Day,
Week,
Month,
Year,
}
pub type RepeaterWarningDelayValueType = u16;
#[derive(Debug, PartialEq, Clone)]
pub struct Repeater {
pub repeater_type: RepeaterType,
pub value: RepeaterWarningDelayValueType,
pub unit: TimeUnit,
}
#[derive(Debug, PartialEq, Clone)]
pub struct WarningDelay {
pub warning_delay_type: WarningDelayType,
pub value: RepeaterWarningDelayValueType,
pub unit: TimeUnit,
}
impl<'s> GetStandardProperties<'s> for Object<'s> {
fn get_standard_properties(&'s self) -> &'s dyn StandardProperties {
fn get_standard_properties<'b>(&'b self) -> &'b dyn StandardProperties<'s> {
match self {
Object::Bold(inner) => inner,
Object::Italic(inner) => inner,
@@ -221,163 +469,169 @@ impl<'s> GetStandardProperties<'s> for Object<'s> {
}
impl<'s> StandardProperties<'s> for Bold<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Italic<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Underline<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for StrikeThrough<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Code<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Verbatim<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for RegularLink<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for RadioLink<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for RadioTarget<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for PlainLink<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for AngleLink<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for OrgMacro<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Entity<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for LatexFragment<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for ExportSnippet<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for FootnoteReference<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Citation<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for CitationReference<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for InlineBabelCall<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for InlineSourceBlock<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for LineBreak<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Target<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for StatisticsCookie<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Subscript<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Superscript<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for Timestamp<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> StandardProperties<'s> for PlainText<'s> {
fn get_source(&'s self) -> &'s str {
fn get_source<'b>(&'b self) -> &'s str {
self.source
}
}
impl<'s> Timestamp<'s> {
pub fn get_raw_value(&self) -> &'s str {
self.source.trim_end()
}
}

View File

@@ -3,7 +3,7 @@ pub trait StandardProperties<'s> {
/// Get the slice of the entire AST node.
///
/// This corresponds to :begin to :end in upstream org-mode's standard properties.
fn get_source(&'s self) -> &'s str;
fn get_source<'b>(&'b self) -> &'s str;
// Get the slice of the AST node's contents.
//