55 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
36 changed files with 2191 additions and 824 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

@@ -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>

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

@@ -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)?
@@ -223,8 +222,8 @@ pub(crate) fn get_property_quoted_string<'s, 'x>(
/// This uses the elisp convention of nil == false, non-nil == true.
///
/// Returns false if key is not found.
pub(crate) fn get_property_boolean<'s, 'x>(
emacs: &'s Token<'s>,
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)?
@@ -233,3 +232,23 @@ pub(crate) fn get_property_boolean<'s, 'x>(
.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

@@ -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,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

@@ -20,9 +20,9 @@ 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;

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

@@ -34,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;
@@ -55,6 +56,9 @@ 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, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = parser_with_context!(section)(context);
@@ -65,6 +69,14 @@ fn _heading<'b, 'g, 'r, 's>(
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() {
@@ -94,6 +106,9 @@ fn _heading<'b, 'g, 'r, 's>(
is_comment: pre_headline.comment.is_some(),
is_archived,
is_footnote_section: pre_headline.is_footnote_section,
scheduled,
deadline,
closed,
},
))
}
@@ -107,6 +122,7 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
/// 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,

View File

@@ -11,8 +11,8 @@ use super::OrgSource;
use crate::context::HeadlineLevelFilter;
use crate::error::CustomError;
use crate::error::Res;
use crate::iter::AstNode;
use crate::settings::GlobalSettings;
use crate::types::AstNode;
use crate::types::Document;
use crate::types::Keyword;

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

@@ -4,6 +4,7 @@ use super::Element;
use super::GetStandardProperties;
use super::Object;
use super::StandardProperties;
use super::Timestamp;
pub type PriorityCookie = u8;
pub type HeadlineLevel = u16;
@@ -29,6 +30,9 @@ pub struct Heading<'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)]
@@ -50,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,
@@ -59,19 +63,19 @@ 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
}
}

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

@@ -44,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>,
@@ -82,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>,
}
@@ -98,62 +111,80 @@ 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
}
}
@@ -169,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
}
}

12
src/types/macros.rs Normal file
View File

@@ -0,0 +1,12 @@
/// Write the implementation of From<> to convert a borrow of the type to an AstNode
macro_rules! to_ast_node {
($inp:ty, $enum:expr) => {
impl<'r, 's> From<$inp> for AstNode<'r, 's> {
fn from(value: $inp) -> Self {
$enum(value)
}
}
};
}
pub(crate) use to_ast_node;

View File

@@ -1,11 +1,14 @@
mod ast_node;
mod document;
mod element;
mod get_standard_properties;
mod greater_element;
mod lesser_element;
mod macros;
mod object;
mod source;
mod standard_properties;
pub(crate) use ast_node::AstNode;
pub use document::Document;
pub use document::DocumentElement;
pub use document::Heading;
@@ -15,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;
@@ -28,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;
@@ -51,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;
@@ -66,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.
//