54 Commits

Author SHA1 Message Date
Tom Alexander
59222c58b1 Publish version 0.1.13.
All checks were successful
rustfmt Build rustfmt has succeeded
clippy Build clippy has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-test Build rust-test has succeeded
2023-12-15 21:10:21 -05:00
Tom Alexander
4d95a7f244 Merge branch 'post_blank'
All checks were successful
clippy Build clippy has succeeded
rustfmt Build rustfmt has succeeded
rust-foreign-document-test Build rust-foreign-document-test has succeeded
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-15 20:11:20 -05:00
Tom Alexander
5a8159eed7 Fix clippy.
All checks were successful
clippy Build clippy 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-12-15 19:57:35 -05:00
Tom Alexander
e24fcb9ded Add dummy values for new fields for plaintext. 2023-12-15 19:54:03 -05:00
Tom Alexander
4b94dc60d2 Fix handling of documents containing only whitespace. 2023-12-15 19:49:12 -05:00
Tom Alexander
2046603d01 Fix handling post blank for org documents. 2023-12-15 19:42:43 -05:00
Tom Alexander
30412361e1 Fix handling fixed width area post-blank inside a list. 2023-12-15 19:37:33 -05:00
Tom Alexander
e846c85188 Fix handling fixed width areas with empty lines in the middle. 2023-12-15 19:17:16 -05:00
Tom Alexander
99b74095e6 Fix heading post-blank. 2023-12-15 19:10:14 -05:00
Tom Alexander
6b802d36bf Implement the new fields for target. 2023-12-15 18:57:19 -05:00
Tom Alexander
33ca43ca40 Remove the old Paragraph::of_text function.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has succeeded
2023-12-15 18:06:48 -05:00
Tom Alexander
f5280a3090 Implement the new fields for bullshitium broken dynamic block. 2023-12-15 18:04:42 -05:00
Tom Alexander
c28d8ccea4 Fix post-blank for headlines containing only whitespace. 2023-12-15 17:59:47 -05:00
Tom Alexander
9690545901 Fix setting contents for broken end bullshitium when there is a paragraph present.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 17:40:08 -05:00
Tom Alexander
eba4fb94cf Implement the new fields for dynamic block. 2023-12-15 17:26:01 -05:00
Tom Alexander
565978225a Implement the new fields for table.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 17:16:49 -05:00
Tom Alexander
cce9ca87fa Fix handling of leading blank lines in greater blocks.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 16:55:47 -05:00
Tom Alexander
683c523ece Implement the new fields for greater block. 2023-12-15 16:15:22 -05:00
Tom Alexander
7a4dc20dc9 Implement the new fields for plain list.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 15:59:00 -05:00
Tom Alexander
022dda06eb Implement the new fields for plain list item. 2023-12-15 15:52:53 -05:00
Tom Alexander
7b88a2d248 Implement the new fields for broken end bullshitium. 2023-12-15 15:40:17 -05:00
Tom Alexander
fce5b92091 Remove leading blank lines from document contents. 2023-12-15 15:30:46 -05:00
Tom Alexander
45a506334c Remove leading blank lines from heading contents. 2023-12-15 15:20:31 -05:00
Tom Alexander
e47901a67f Implement the new fields for node property.
Some checks failed
clippy Build clippy has failed
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has failed
2023-12-15 15:05:03 -05:00
Tom Alexander
7430daa768 Fix handling of property drawers containing only whitespace. 2023-12-15 15:05:03 -05:00
Tom Alexander
6ce25c8a3b Update property drawer empty test to include a variety of whitespace. 2023-12-15 14:25:01 -05:00
Tom Alexander
7b8fa1eb4a Fix get_contents for sections. 2023-12-15 13:21:58 -05:00
Tom Alexander
ffa5349f25 Fix get_contents for headlines. 2023-12-15 13:14:49 -05:00
Tom Alexander
bb472b63cc Implement the new fields for property drawer. 2023-12-15 13:03:42 -05:00
Tom Alexander
57f566a7a1 Implement the new fields for planning. 2023-12-15 12:55:05 -05:00
Tom Alexander
2181993246 Implement the new fields for horizontal rule. 2023-12-15 12:50:01 -05:00
Tom Alexander
60d1ecfa75 Fix fixed width area to not consume trailing line break so it can be part of the post-blank.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 12:45:51 -05:00
Tom Alexander
3962db12a8 Implement the new fields for fixed width area. 2023-12-15 12:29:46 -05:00
Tom Alexander
f192507cd9 Implement the new fields for diary sexp.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 12:10:34 -05:00
Tom Alexander
252be3e001 Fix post blank for timestamp date ranges. 2023-12-15 11:38:52 -05:00
Tom Alexander
28f12a04f7 Implement the new fields for drawer.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 11:22:27 -05:00
Tom Alexander
d6232dc49c Implement the new fields for clock. 2023-12-15 10:49:04 -05:00
Tom Alexander
68a220aa1c Implement the new fields for babel call.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-15 10:45:16 -05:00
Tom Alexander
2e7db0f8bd Implement the new fields for lesser block.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 15:59:56 -05:00
Tom Alexander
175ff1e6c4 Implement the new fields for LaTeX environment.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 14:49:18 -05:00
Tom Alexander
0b42139393 Implement the new fields for inline babel call and inline source block. 2023-12-11 14:47:22 -05:00
Tom Alexander
67a9103b07 Implement the new fields for export snippet. 2023-12-11 14:41:49 -05:00
Tom Alexander
f141a4e186 Implement the new fields for citation.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 14:38:14 -05:00
Tom Alexander
aba29df34c Implement the new fields for org macro. 2023-12-11 14:22:56 -05:00
Tom Alexander
87ce7d7432 Implement the new fields for timestamp. 2023-12-11 14:18:04 -05:00
Tom Alexander
68dccd54b1 Implement the new fields for radio link. 2023-12-11 14:10:27 -05:00
Tom Alexander
4753f4c7c6 Implement the new fields for plain link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 13:08:53 -05:00
Tom Alexander
13c62bf29f Implement the new fields for angle link.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 13:00:43 -05:00
Tom Alexander
670209e9fc Fix post blank for comment. 2023-12-11 12:58:05 -05:00
Tom Alexander
4af0d3141f Implement the new fields for statistics cookie. 2023-12-11 12:51:07 -05:00
Tom Alexander
ab281de3c6 Implement the new fields for latex fragment.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:46:20 -05:00
Tom Alexander
d556d28f49 Implement the new fields for entity. 2023-12-11 12:44:42 -05:00
Tom Alexander
9cfb2fa052 Implement the new fields for keywords.
Some checks failed
clippy Build clippy has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-build Build rust-build has succeeded
rust-test Build rust-test has failed
2023-12-11 12:38:13 -05:00
Tom Alexander
30c03b5529 Implement the new fields for radio target. 2023-12-11 12:27:35 -05:00
50 changed files with 863 additions and 382 deletions

View File

@@ -2,7 +2,7 @@
[package]
name = "organic"
version = "0.1.12"
version = "0.1.13"
authors = ["Tom Alexander <tom@fizz.buzz>"]
description = "An org-mode parser."
edition = "2021"

View File

View File

@@ -0,0 +1,4 @@

View File

@@ -0,0 +1,5 @@
* foo

View File

@@ -1,3 +1,32 @@
* Empty
:PROPERTIES:
:END:
* Single new line
:PROPERTIES:
:END:
* Single line with spaces
:PROPERTIES:
:END:
* Many lines, first line without spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces
:PROPERTIES:
:END:
* Many lines, first line with spaces, later line with spaces
:PROPERTIES:
:END:

View File

@@ -5,3 +5,5 @@
#+call: dolar cat(dog)
#+call: (bat)
#+call:

View File

@@ -0,0 +1,3 @@
: foo
:
: bar

View File

@@ -0,0 +1,6 @@
1. foo
#+begin_src text
#+end_src
2. baz

View File

@@ -0,0 +1,3 @@
<<FOO>> bar
[[FOO][baz]]

View File

@@ -0,0 +1,5 @@
* foo
** bar
* baz

View File

@@ -139,7 +139,7 @@ fn assert_post_blank<'b, 's, S: StandardProperties<'s> + ?Sized>(
.post_blank
.ok_or("Token should have a post-blank.")?;
if rust_post_blank as usize != emacs_post_blank {
Err(format!("Rust post-blank (in chars) {rust_post_blank} does not match emacs post-blank ({emacs_post_blank})", rust_post_blank = rust_post_blank, emacs_post_blank = emacs_post_blank))?;
Err(format!("Rust post-blank {rust_post_blank} does not match emacs post-blank ({emacs_post_blank})", rust_post_blank = rust_post_blank, emacs_post_blank = emacs_post_blank))?;
}
Ok(())
@@ -285,7 +285,7 @@ where
pub(crate) fn compare_children<'b, 's, 'x, RC>(
source: &'s str,
emacs: &'b Token<'s>,
rust_children: &'x Vec<RC>,
rust_children: &'x [RC],
child_status: &mut Vec<DiffEntry<'b, 's>>,
this_status: &mut DiffStatus,
message: &mut Option<String>,

View File

@@ -27,7 +27,7 @@ pub(crate) fn record_event(event_type: EventType, input: OrgSource<'_>) {
pub fn report(original_document: &str) {
let mut db = GLOBAL_DATA.lock().unwrap();
let db = db.get_or_insert_with(HashMap::new);
let mut results: Vec<_> = db.iter().map(|(k, v)| (k, v)).collect();
let mut results: Vec<_> = db.iter().collect();
results.sort_by_key(|(_k, v)| *v);
// This would put the most common at the top, but that is a pain when there is already a lot of output from the parser.
// results.sort_by(|(_ak, av), (_bk, bv)| bv.cmp(av));

View File

@@ -47,7 +47,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
parser_with_context!(parse_angle_link)(context),
))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -59,6 +59,7 @@ pub(crate) fn angle_link<'b, 'g, 'r, 's>(
raw_link: raw_link.into(),
search_option: parsed_link.search_option,
application: parsed_link.application,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -4,7 +4,6 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::anychar;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::opt;
use nom::combinator::peek;
use nom::combinator::recognize;
@@ -43,32 +42,10 @@ where
start_of_line(remaining)?;
let (remaining, _) = tuple((space0, tag("#+"), tag_no_case("call"), tag(":")))(remaining)?;
if let Ok((remaining, (_, line_break))) = tuple((space0, org_line_ending))(remaining) {
let (remaining, _trailing_ws) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
BabelCall {
source: Into::<&str>::into(source),
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(line_break.take(0)),
call: None,
inside_header: None,
arguments: None,
end_header: None,
},
));
}
let (remaining, _ws) = space0(remaining)?;
let (remaining, (value, babel_call_value)) = consumed(babel_call_value)(remaining)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, babel_call_value) = babel_call_value(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -80,17 +57,22 @@ where
context.get_global_settings(),
affiliated_keywords,
),
value: Into::<&str>::into(value).trim_end(),
value: Into::<&str>::into(babel_call_value.value),
call: babel_call_value.call.map(Into::<&str>::into),
inside_header: babel_call_value.inside_header.map(Into::<&str>::into),
arguments: babel_call_value.arguments.map(Into::<&str>::into),
end_header: babel_call_value.end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[derive(Debug)]
struct BabelCallValue<'s> {
/// The entire string to the right of "#+call: " without the trailing line break.
value: OrgSource<'s>,
/// The function name which may contain a line break if there are no headers/arguments.
call: Option<OrgSource<'s>>,
inside_header: Option<OrgSource<'s>>,
arguments: Option<OrgSource<'s>>,
@@ -99,13 +81,45 @@ struct BabelCallValue<'s> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, call) = opt(babel_call_call)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
alt((
babel_call_value_without_headers,
babel_call_value_with_headers,
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value_without_headers<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, value) = babel_call_call_with_headers(input)?;
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
let call = get_consumed(input, remaining);
Ok((
remaining,
BabelCallValue {
value,
call: Some(call),
inside_header: None,
arguments: None,
end_header: None,
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_value_with_headers<'s>(
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, BabelCallValue<'s>> {
let (remaining, call) = opt(babel_call_call_with_headers)(input)?;
let (remaining, inside_header) = opt(inside_header)(remaining)?;
let (remaining, arguments) = opt(arguments)(remaining)?;
let (remaining, end_header) = opt(end_header)(remaining)?;
let value = get_consumed(input, remaining);
let (remaining, _ws) = tuple((space0, org_line_ending))(remaining)?;
Ok((
remaining,
BabelCallValue {
value,
call,
inside_header,
arguments: arguments.flatten(),
@@ -115,14 +129,15 @@ fn babel_call_value<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, BabelCallVal
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn babel_call_call<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
fn babel_call_call_with_headers<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
// When babel call contains no arguments or headers (for example: "#+call: lorem ipsum\n") then the trailing line break is part of the call. Otherwise, it is not.
verify(
recognize(many_till(
anychar,
alt((
peek(recognize(one_of("[("))),
peek(alt((
recognize(one_of("[(")),
recognize(tuple((space0, org_line_ending))),
)),
))),
)),
|s| s.len() > 0,
)(input)
@@ -232,19 +247,3 @@ fn impl_balanced_bracket<
};
Ok((remaining, contents))
}
#[cfg(test)]
mod tests {
use nom::combinator::opt;
use super::*;
#[test]
fn simple_call() -> Result<(), Box<dyn std::error::Error>> {
let input = OrgSource::new("()");
let (remaining, call) = opt(babel_call_call)(input)?;
assert_eq!(Into::<&str>::into(remaining), "()");
assert!(call.is_none());
Ok(())
}
}

View File

@@ -63,6 +63,7 @@ pub(crate) fn broken_end<'b, 'g, 'r, 's>(
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
@@ -73,14 +74,18 @@ pub(crate) fn broken_end<'b, 'g, 'r, 's>(
};
Ok((remaining, paragraph))
} else {
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
input.get_until(lead_in_remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}
@@ -119,6 +124,7 @@ pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
match paragraph.children.first_mut() {
Some(Object::PlainText(plain_text)) => {
plain_text.source = input.get_until_end_of_str(plain_text.source).into();
paragraph.contents = Some(input.get_until_end_of_str(plain_text.source).into());
}
Some(obj) => {
panic!("Unhandled first object type inside bullshitium {:?}", obj);
@@ -129,14 +135,18 @@ pub(crate) fn broken_dynamic_block<'b, 'g, 'r, 's>(
};
Ok((remaining, paragraph))
} else {
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, lead_in_remaining)?;
let body = Into::<&str>::into(input.get_until(lead_in_remaining));
Ok((
remaining,
Paragraph::of_text(
input.get_until(remaining).into(),
input.get_until(lead_in_remaining).into(),
body,
if !body.is_empty() { Some(body) } else { None },
post_blank.map(Into::<&str>::into),
),
))
}

View File

@@ -46,16 +46,22 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
let (remaining, prefix) =
must_balance_bracket(opt(parser_with_context!(global_prefix)(context)))(remaining)?;
let contents_begin = remaining;
let (remaining, references) =
separated_list1(tag(";"), parser_with_context!(citation_reference)(context))(remaining)?;
let contents_end = {
let (rem, _) = opt(tag(";"))(remaining)?;
rem
};
let (remaining, suffix) = must_balance_bracket(opt(map(
tuple((tag(";"), parser_with_context!(global_suffix)(context))),
|(_, suffix)| suffix,
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let contents = contents_begin.get_until(contents_end);
Ok((
remaining,
Citation {
@@ -64,6 +70,8 @@ pub(crate) fn citation<'b, 'g, 'r, 's>(
prefix: prefix.unwrap_or(Vec::new()),
suffix: suffix.unwrap_or(Vec::new()),
children: references,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -40,7 +40,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
let (remaining, (timestamp, duration)) = clock_timestamp(context, remaining)?;
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -54,6 +54,7 @@ pub(crate) fn clock<'b, 'g, 'r, 's>(
} else {
ClockStatus::Running
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -81,7 +82,7 @@ fn clock_timestamp<'b, 'g, 'r, 's>(
|(timestamp, duration)| (timestamp, duration.map(Into::<&str>::into)),
),
map(
parser_with_context!(inactive_timestamp)(context),
parser_with_context!(inactive_timestamp(true))(context),
|timestamp| (timestamp, None),
),
))(input)

View File

@@ -46,7 +46,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let mut value = Vec::with_capacity(remaining_lines.len() + 1);
@@ -67,6 +67,7 @@ pub(crate) fn comment<'b, 'g, 'r, 's>(
Comment {
source: source.into(),
value,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -31,7 +31,7 @@ where
let (remaining, value) = recognize(tuple((tag("%%("), is_not("\r\n"))))(remaining)?;
let (remaining, _eol) = org_line_ending(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -43,6 +43,7 @@ where
affiliated_keywords,
),
value: Into::<&str>::into(value),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use nom::combinator::all_consuming;
use nom::combinator::opt;
use nom::multi::many0;
use nom::InputTake;
use super::headline::heading;
use super::in_buffer_settings::apply_in_buffer_settings;
@@ -181,8 +182,10 @@ fn _document<'b, 'g, 'r, 's>(
let zeroth_section_matcher = parser_with_context!(zeroth_section)(context);
let heading_matcher = parser_with_context!(heading(0))(context);
let (remaining, _blank_lines) = many0(blank_line)(input)?;
let contents_begin = remaining;
let (remaining, zeroth_section) = opt(zeroth_section_matcher)(remaining)?;
let (remaining, children) = many0(heading_matcher)(remaining)?;
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -192,6 +195,11 @@ fn _document<'b, 'g, 'r, 's>(
path: None,
zeroth_section,
children,
contents: if contents.len() > 0 {
Into::<&str>::into(contents)
} else {
Into::<&str>::into(remaining.take(0))
},
},
))
}

View File

@@ -4,6 +4,7 @@ use nom::bytes::complete::tag_no_case;
use nom::bytes::complete::take_while;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::not;
use nom::combinator::recognize;
@@ -12,7 +13,9 @@ use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -21,7 +24,6 @@ use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -30,7 +32,6 @@ use crate::parser::util::WORD_CONSTITUENT_CHARACTERS;
use crate::types::Drawer;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
#[cfg_attr(
feature = "tracing",
@@ -70,29 +71,12 @@ where
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 (remaining, (contents, children)) =
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, children) = match tuple((
not(exit_matcher),
blank_line,
many_till(blank_line, exit_matcher),
))(remaining)
{
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => {
let source = get_consumed(remaining, remain);
let element = Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()));
(remain, vec![element])
}
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, _end) = drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -106,10 +90,34 @@ where
),
drawer_name: drawer_name.into(),
children,
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<Element<'s>>> {
let element_matcher = parser_with_context!(element(true))(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
if let Ok((remaining, (_not_exit, empty_para))) =
tuple((not(exit_matcher), bind_context!(empty_paragraph, context)))(input)
{
return Ok((remaining, vec![Element::Paragraph(empty_para)]));
}
let (remaining, (children, _exit_contents)) = many_till(element_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn name<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
take_while(|c| WORD_CONSTITUENT_CHARACTERS.contains(c) || "-_".contains(c))(input)

View File

@@ -6,20 +6,20 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
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;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::greater_block::leading_blank_lines_end;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -28,7 +28,6 @@ use crate::context::RefContext;
use crate::error::CustomError;
use crate::error::Res;
use crate::parser::element_parser::element;
use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed;
use crate::parser::util::immediate_in_section;
@@ -36,7 +35,6 @@ use crate::parser::util::start_of_line;
use crate::types::DynamicBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
#[cfg_attr(
feature = "tracing",
@@ -81,23 +79,25 @@ where
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)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple((
blank_line,
many0(preceded(not(exit_matcher), blank_line)),
))))(remaining)?;
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
});
let contents_begin = remaining;
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &leading_blank_lines_end,
});
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
children.insert(0, Element::Paragraph(lines));
}
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = dynamic_block_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -111,6 +111,12 @@ where
block_name: name.into(),
parameters: parameters.map(|val| val.into()),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -28,7 +28,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
let (remaining, _) = tag("\\")(input)?;
let (remaining, (entity_definition, entity_name, use_brackets)) = name(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -43,6 +43,7 @@ pub(crate) fn entity<'b, 'g, 'r, 's>(
ascii: entity_definition.ascii,
utf8: entity_definition.utf8,
use_brackets,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -39,7 +39,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
parser_with_context!(contents)(&parser_context),
)))(remaining)?;
let (remaining, _) = tag("@@")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ pub(crate) fn export_snippet<'b, 'g, 'r, 's>(
source: source.into(),
backend: backend_name.into(),
contents: backend_contents.map(|(_colon, backend_contents)| backend_contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -2,12 +2,15 @@ use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::anychar;
use nom::character::complete::space0;
use nom::combinator::map;
use nom::combinator::not;
use nom::combinator::peek;
use nom::combinator::recognize;
use nom::multi::many0;
use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple;
use nom::InputTake;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
@@ -35,28 +38,25 @@ pub(crate) fn fixed_width_area<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let fixed_width_area_line_matcher = parser_with_context!(fixed_width_area_line)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line) = fixed_width_area_line_matcher(remaining)?;
let (remaining, mut remaining_lines) =
many0(preceded(not(exit_matcher), fixed_width_area_line_matcher))(remaining)?;
let (remaining, first_line) = fixed_width_area_line(remaining)?;
let (remaining, remaining_lines) = many0(preceded(
not(tuple((org_line_ending, exit_matcher))),
map(
tuple((org_line_ending, fixed_width_area_line)),
|(_line_ending, line_contents)| line_contents,
),
))(remaining)?;
let (remaining, _trailing_ws) =
let post_blank_begin = remaining;
let (remaining, _first_line_break) = org_line_ending(remaining)?;
let (remaining, _additional_post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let post_blank = get_consumed(post_blank_begin, 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)])
}
value.push(Into::<&str>::into(first_line));
value.extend(remaining_lines.into_iter().map(Into::<&str>::into));
Ok((
remaining,
FixedWidthArea {
@@ -66,25 +66,24 @@ where
affiliated_keywords,
),
value,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
fn fixed_width_area_line<'b, 'g, 'r, 's>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn fixed_width_area_line<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
start_of_line(input)?;
let (remaining, _) = tuple((space0, tag(":")))(input)?;
if let Ok((remaining, line_break)) = org_line_ending(remaining) {
return Ok((remaining, line_break));
if let Ok((_remain, _line_break)) = org_line_ending(remaining) {
return Ok((remaining, remaining.take(0)));
}
let (remaining, _) = tag(" ")(remaining)?;
let (remaining, value) = recognize(many_till(anychar, org_line_ending))(remaining)?;
let (remaining, value) = recognize(many_till(anychar, peek(org_line_ending)))(remaining)?;
Ok((remaining, value))
}

View File

@@ -5,22 +5,21 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
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;
use nom::sequence::preceded;
use nom::sequence::tuple;
use super::affiliated_keyword::parse_affiliated_keywords;
use super::org_source::OrgSource;
use super::paragraph::empty_paragraph;
use super::util::in_section;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::context::bind_context;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ContextMatcher;
@@ -37,7 +36,6 @@ use crate::parser::util::start_of_line;
use crate::types::CenterBlock;
use crate::types::Element;
use crate::types::Keyword;
use crate::types::Paragraph;
use crate::types::QuoteBlock;
use crate::types::SpecialBlock;
@@ -102,7 +100,7 @@ fn center_block<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
@@ -112,12 +110,14 @@ where
Ok((
remaining,
Element::CenterBlock(CenterBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
@@ -135,7 +135,7 @@ fn quote_block<'b, 'g, 'r, 's, AK>(
where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
input,
pre_affiliated_keywords_input,
@@ -145,12 +145,14 @@ where
Ok((
remaining,
Element::QuoteBlock(QuoteBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
@@ -196,7 +198,7 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, parameters) = opt(tuple((space1, parameters)))(input)?;
let (remaining, (source, children)) = greater_block_body(
let (remaining, body) = greater_block_body(
context,
remaining,
pre_affiliated_keywords_input,
@@ -206,18 +208,28 @@ where
Ok((
remaining,
Element::SpecialBlock(SpecialBlock {
source,
source: body.source,
affiliated_keywords: parse_affiliated_keywords(
context.get_global_settings(),
affiliated_keywords,
),
children,
children: body.children,
block_type: name,
parameters: parameters.map(|(_, parameters)| Into::<&str>::into(parameters)),
contents: body.contents,
post_blank: body.post_blank,
}),
))
}
#[derive(Debug)]
struct GreaterBlockBody<'s> {
source: &'s str,
children: Vec<Element<'s>>,
contents: Option<&'s str>,
post_blank: Option<&'s str>,
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
@@ -228,7 +240,7 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
pre_affiliated_keywords_input: OrgSource<'s>,
name: &'c str,
context_name: &'c str,
) -> Res<OrgSource<'s>, (&'s str, Vec<Element<'s>>)> {
) -> Res<OrgSource<'s>, GreaterBlockBody<'s>> {
if in_section(context, context_name) {
return Err(nom::Err::Error(CustomError::Static(
"Cannot nest objects of the same element",
@@ -250,28 +262,43 @@ fn greater_block_body<'c, 'b, 'g, 'r, 's>(
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)?;
let (remaining, leading_blank_lines) = opt(consumed(tuple((
blank_line,
many0(preceded(not(exit_matcher), blank_line)),
))))(remaining)?;
let leading_blank_lines =
leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
Element::Paragraph(Paragraph::of_text(source.into(), first_line.into()))
});
let contents_begin = remaining;
let blank_line_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
class: ExitClass::Alpha,
exit_matcher: &leading_blank_lines_end,
});
let blank_line_context = parser_context.with_additional_node(&blank_line_context);
let (remaining, leading_blank_lines) =
opt(bind_context!(empty_paragraph, &blank_line_context))(remaining)?;
let (remaining, (mut children, _exit_contents)) =
many_till(element_matcher, exit_matcher)(remaining)?;
if let Some(lines) = leading_blank_lines {
children.insert(0, lines);
children.insert(0, Element::Paragraph(lines));
}
let contents = get_consumed(contents_begin, remaining);
let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(pre_affiliated_keywords_input, remaining);
Ok((remaining, (Into::<&str>::into(source), children)))
Ok((
remaining,
GreaterBlockBody {
source: Into::<&str>::into(source),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
@@ -307,3 +334,14 @@ fn _greater_block_end<'b, 'g, 'r, 's, 'c>(
let source = get_consumed(input, remaining);
Ok((remaining, source))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))
)]
pub(crate) fn leading_blank_lines_end<'b, 'g, 'r, 's, 'c>(
_context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, OrgSource<'s>> {
recognize(not(blank_line))(input)
}

View File

@@ -66,6 +66,8 @@ fn _heading<'b, 'g, 'r, 's>(
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
let section_matcher = bind_context!(section, context);
let heading_matcher = bind_context!(heading(pre_headline.star_count), context);
let (contents_begin, _) = opt(many0(blank_line))(remaining)?;
let maybe_post_blank = get_consumed(remaining, contents_begin);
let (remaining, maybe_section) =
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
@@ -82,7 +84,8 @@ fn _heading<'b, 'g, 'r, 's>(
}
children.insert(0, section);
}
let remaining = if children.is_empty() {
let has_children = !children.is_empty();
let remaining = if !has_children {
// Support empty headings
let (remain, _ws) = many0(blank_line)(remaining)?;
remain
@@ -91,6 +94,7 @@ fn _heading<'b, 'g, 'r, 's>(
};
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
let contents = get_consumed(contents_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
@@ -112,6 +116,16 @@ fn _heading<'b, 'g, 'r, 's>(
scheduled,
deadline,
closed,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
post_blank: if has_children {
None
} else {
Some(Into::<&str>::into(maybe_post_blank))
},
},
))
}

View File

@@ -38,7 +38,7 @@ where
space0,
alt((line_ending, eof)),
)))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -49,6 +49,7 @@ where
context.get_global_settings(),
affiliated_keywords,
),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -38,7 +38,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
let (remaining, arguments) = argument(context, remaining)?;
let (remaining, end_header) = opt(parser_with_context!(header)(context))(remaining)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -54,6 +54,7 @@ pub(crate) fn inline_babel_call<'b, 'g, 'r, 's>(
None
},
end_header: end_header.map(Into::<&str>::into),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -38,7 +38,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
let (remaining, language) = lang(context, remaining)?;
let (remaining, parameters) = opt(parser_with_context!(header)(context))(remaining)?;
let (remaining, value) = body(context, remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ pub(crate) fn inline_source_block<'b, 'g, 'r, 's>(
language: language.into(),
parameters: parameters.map(Into::<&str>::into),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -58,6 +58,7 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: "",
post_blank: None,
},
));
}
@@ -71,6 +72,7 @@ fn _filtered_keyword<'s, F: Fn(OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s
affiliated_keywords: AffiliatedKeywords::default(), // To be populated by the caller if this keyword is in a context to support affiliated keywords.
key: parsed_key.into(),
value: parsed_value.into(),
post_blank: None,
},
))
}
@@ -89,12 +91,13 @@ where
AK: IntoIterator<Item = Keyword<'s>>,
{
let (remaining, mut kw) = filtered_keyword(regular_keyword_key)(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
kw.affiliated_keywords =
parse_affiliated_keywords(context.get_global_settings(), affiliated_keywords);
kw.source = Into::<&str>::into(source);
kw.post_blank = post_blank.map(Into::<&str>::into);
Ok((remaining, kw))
}

View File

@@ -57,7 +57,7 @@ where
let (remaining, _end) = latex_environment_end_specialized(&parser_context, remaining)?;
let value_end = remaining;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let value = get_consumed(value_start, value_end);
@@ -70,6 +70,7 @@ where
affiliated_keywords,
),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -39,7 +39,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
parser_with_context!(bordered_dollar_fragment)(context),
))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -47,6 +47,7 @@ pub(crate) fn latex_fragment<'b, 'g, 'r, 's>(
LatexFragment {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -80,22 +80,28 @@ where
let object_matcher = parser_with_context!(standard_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
// Check for a completely empty block
let (remaining, children) = match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining,
vec![Object::PlainText(PlainText {
source: whitespace.into(),
})],
),
Err(_) => {
let (remaining, (children, _exit_contents)) =
many_till(object_matcher, exit_matcher)(remaining)?;
(remaining, children)
}
};
let (remaining, contents, children) =
match consumed(many_till(blank_line, exit_matcher))(remaining) {
Ok((remaining, (whitespace, (_children, _exit_contents)))) => (
remaining,
whitespace,
if whitespace.len() > 0 {
vec![Object::PlainText(PlainText {
source: whitespace.into(),
})]
} else {
Vec::new()
},
),
Err(_) => {
let (remaining, (contents, (children, _exit_contents))) =
consumed(many_till(object_matcher, exit_matcher))(remaining)?;
(remaining, contents, children)
}
};
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -108,6 +114,8 @@ where
),
data: parameters.map(Into::<&str>::into),
children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -144,7 +152,7 @@ where
let (remaining, contents) = parser_with_context!(text_until_exit)(&parser_context)(remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -156,6 +164,7 @@ where
affiliated_keywords,
),
contents: contents.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -205,7 +214,7 @@ where
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
@@ -237,6 +246,7 @@ where
use_labels,
label_format,
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -279,7 +289,7 @@ where
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -293,6 +303,7 @@ where
export_type: export_type.map(Into::<&str>::into),
data: parameters.map(Into::<&str>::into),
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -334,7 +345,7 @@ where
let (remaining, contents) = text_until_exit(&parser_context, remaining)?;
let (remaining, _end) = lesser_block_end_specialized(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
let (switches, number_lines, preserve_indent, retain_labels, use_labels, label_format) = {
@@ -372,6 +383,7 @@ where
use_labels,
label_format,
value: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -32,7 +32,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
let (remaining, macro_args) = opt(parser_with_context!(org_macro_args)(context))(remaining)?;
let (remaining, _) = tag("}}}")(remaining)?;
let macro_value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -47,6 +47,7 @@ pub(crate) fn org_macro<'b, 'g, 'r, 's>(
.map(|arg| arg.into())
.collect(),
value: Into::<&str>::into(macro_value),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -1,6 +1,8 @@
use nom::branch::alt;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
use nom::multi::many1;
@@ -13,6 +15,7 @@ use super::org_source::OrgSource;
use super::util::blank_line;
use super::util::get_consumed;
use super::util::maybe_consume_trailing_whitespace_if_not_exiting;
use super::util::org_line_ending;
use crate::context::parser_with_context;
use crate::context::ContextElement;
use crate::context::ExitClass;
@@ -72,6 +75,57 @@ where
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn empty_paragraph<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Paragraph<'s>> {
// If it is just a single newline then source, contents, and post-blank are "\n".
// If it has multiple newlines then contents is the first "\n" and post-blank is all the new lines.
// If there are any spaces on the first line then post-blank excludes the first line.
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, first_line_with_spaces) =
opt(recognize(tuple((space1, org_line_ending))))(input)?;
let post_blank_begin = remaining;
if let Some(first_line_with_spaces) = first_line_with_spaces {
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line_with_spaces),
Some(Into::<&str>::into(first_line_with_spaces)),
Some(Into::<&str>::into(post_blank)),
),
))
} else {
let (remaining, first_line) = blank_line(remaining)?;
let (remaining, _additional_lines) =
recognize(many_till(blank_line, exit_matcher))(remaining)?;
let post_blank = get_consumed(post_blank_begin, remaining);
let source = get_consumed(input, remaining);
Ok((
remaining,
Paragraph::of_text(
Into::<&str>::into(source),
Into::<&str>::into(first_line),
Some(Into::<&str>::into(first_line)),
Some(Into::<&str>::into(post_blank)),
),
))
}
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
@@ -99,6 +153,7 @@ mod tests {
use crate::context::List;
use crate::parser::element_parser::element;
use crate::parser::org_source::OrgSource;
use crate::parser::paragraph::empty_paragraph;
use crate::types::StandardProperties;
#[test]
@@ -115,4 +170,17 @@ mod tests {
assert_eq!(first_paragraph.get_source(), "foo bar baz\n\n");
assert_eq!(second_paragraph.get_source(), "lorem ipsum");
}
#[test]
fn paragraph_whitespace() {
let input = OrgSource::new("\n");
let global_settings = GlobalSettings::default();
let initial_context = ContextElement::document_context();
let initial_context = Context::new(&global_settings, List::new(&initial_context));
let paragraph_matcher = bind_context!(empty_paragraph, &initial_context);
let (remaining, paragraph) = paragraph_matcher(input).expect("Parse paragraph");
assert_eq!(Into::<&str>::into(remaining), "");
assert_eq!(paragraph.get_source(), "\n");
assert_eq!(paragraph.get_contents(), Some("\n"));
}
}

View File

@@ -54,7 +54,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
let (remaining, _) = pre(context, input)?;
let (remaining, path_plain) = parse_path_plain(context, remaining)?;
peek(parser_with_context!(post)(context))(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -66,6 +66,7 @@ pub(crate) fn plain_link<'b, 'g, 'r, 's>(
raw_link: path_plain.raw_link,
search_option: path_plain.search_option,
application: path_plain.application,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -7,6 +7,7 @@ use nom::character::complete::multispace1;
use nom::character::complete::one_of;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not;
@@ -152,6 +153,7 @@ where
let mut children = Vec::new();
let mut first_item_indentation: Option<IndentationLevel> = None;
let mut first_item_list_type: Option<PlainListType> = None;
let contents_begin = remaining;
let mut remaining = remaining;
// The final list item does not consume trailing blank lines (which instead get consumed by the list). We have three options here:
@@ -195,7 +197,8 @@ where
)));
}
let (remaining, _trailing_ws) =
let contents = get_consumed(contents_begin, remaining);
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -208,6 +211,8 @@ where
),
list_type: first_item_list_type.expect("Plain lists require at least one element."),
children: children.into_iter().map(|(_start, item)| item).collect(),
contents: Some(Into::<&str>::into(contents)),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -265,7 +270,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
let maybe_contentless_item: Res<OrgSource<'_>, ()> =
detect_contentless_item_contents(&parser_context, remaining);
if let Ok((_rem, _ws)) = maybe_contentless_item {
let (remaining, _trailing_ws) = if tuple((
let (remaining, post_blank) = if tuple((
blank_line,
bind_context!(final_item_whitespace_cutoff, context),
))(remaining)
@@ -291,6 +296,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
.unwrap_or(Vec::new()),
pre_blank: 0,
children: Vec::new(),
contents: None,
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
),
));
@@ -301,13 +312,13 @@ fn plain_list_item<'b, 'g, 'r, 's>(
.filter(|b| *b == b'\n')
.count();
let (remaining, (children, _exit_contents)) = many_till(
let (remaining, (contents, (children, _exit_contents))) = consumed(many_till(
include_input(bind_context!(element(true), &parser_context)),
bind_context!(exit_matcher_parser, &parser_context),
)(remaining)?;
))(remaining)?;
// We have to use the parser_context here to include the whitespace cut-off
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(&final_whitespace_context, remaining)?;
let source = get_consumed(input, remaining);
@@ -329,6 +340,12 @@ fn plain_list_item<'b, 'g, 'r, 's>(
pre_blank: PlainListItemPreBlank::try_from(pre_blank)
.expect("pre-blank cannot be larger than 2."),
children: children.into_iter().map(|(_start, item)| item).collect(),
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
),
));

View File

@@ -33,7 +33,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
many1(parser_with_context!(planning_parameter)(context))(remaining)?;
let (remaining, _trailing_ws) = tuple((space0, org_line_ending))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -62,6 +62,7 @@ pub(crate) fn planning<'b, 'g, 'r, 's>(
scheduled,
deadline,
closed,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -6,6 +6,7 @@ use nom::character::complete::anychar;
use nom::character::complete::line_ending;
use nom::character::complete::space0;
use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof;
use nom::combinator::opt;
use nom::combinator::recognize;
@@ -64,14 +65,11 @@ pub(crate) fn property_drawer<'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 node_property_matcher = parser_with_context!(node_property)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(remaining)?;
let (remaining, (contents, children)) =
consumed(parser_with_context!(children)(&parser_context))(remaining)?;
let (remaining, _end) = property_drawer_end(&parser_context, remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -80,10 +78,31 @@ pub(crate) fn property_drawer<'b, 'g, 'r, 's>(
PropertyDrawer {
source: source.into(),
children,
contents: if contents.len() > 0 {
Some(contents.into())
} else {
None
},
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn children<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
) -> Res<OrgSource<'s>, Vec<NodeProperty<'s>>> {
let node_property_matcher = parser_with_context!(node_property)(context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(context);
let (remaining, (children, _exit_contents)) =
many_till(node_property_matcher, exit_matcher)(input)?;
Ok((remaining, children))
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(_context))

View File

@@ -39,7 +39,7 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
let rematched_target = rematch_target(context, radio_target, input);
if let Ok((remaining, rematched_target)) = rematched_target {
let path = get_consumed(input, remaining);
let (remaining, _) = space0(remaining)?;
let (remaining, post_blank) = space0(remaining)?;
let source = get_consumed(input, remaining);
return Ok((
remaining,
@@ -47,6 +47,11 @@ pub(crate) fn radio_link<'b, 'g, 'r, 's>(
source: source.into(),
children: rematched_target,
path: path.into(),
post_blank: if post_blank.len() > 0 {
Some(Into::<&str>::into(post_blank))
} else {
None
},
},
));
}
@@ -134,7 +139,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
))(remaining)?;
let (remaining, _closing) = tag(">>>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -142,6 +147,7 @@ pub(crate) fn radio_target<'b, 'g, 'r, 's>(
RadioTarget {
source: source.into(),
value: raw_value.into(),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
))

View File

@@ -1,4 +1,3 @@
use nom::combinator::consumed;
use nom::combinator::opt;
use nom::combinator::recognize;
use nom::combinator::verify;
@@ -59,12 +58,12 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
many0(blank_line),
)))(input)?;
let (remaining, (contents, (mut children, _exit_contents))) = consumed(verify(
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || comment_and_property_drawer_element.is_some()
},
))(remaining)?;
)(remaining)?;
if let Some((comment, property_drawer, _ws)) = comment_and_property_drawer_element {
children.insert(0, Element::PropertyDrawer(property_drawer));
@@ -81,7 +80,6 @@ pub(crate) fn zeroth_section<'b, 'g, 'r, 's>(
remaining,
Section {
source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
children,
},
@@ -118,12 +116,12 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
remaining = remain;
input = remain;
}
let (remaining, (contents, (mut children, _exit_contents))) = consumed(verify(
let (remaining, (mut children, _exit_contents)) = verify(
many_till(element_matcher, exit_matcher),
|(children, _exit_contents)| {
!children.is_empty() || property_drawer_element.is_some() || planning_element.is_some()
},
))(remaining)?;
)(remaining)?;
if let Some(ele) = property_drawer_element.map(Element::PropertyDrawer) {
children.insert(0, ele);
}
@@ -139,7 +137,6 @@ pub(crate) fn section<'b, 'g, 'r, 's>(
remaining,
Section {
source: source.into(),
contents: Some(contents.into()),
post_blank: post_blank.map(Into::<&str>::into),
children,
},

View File

@@ -40,7 +40,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
tag("%]"),
)))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -48,6 +48,7 @@ fn percent_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -68,7 +69,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
tag("]"),
)))(input)?;
let value = get_consumed(input, remaining);
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -76,6 +77,7 @@ fn fraction_statistics_cookie<'b, 'g, 'r, 's>(
StatisticsCookie {
source: source.into(),
value: value.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -3,6 +3,7 @@ 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::consumed;
use nom::combinator::not;
use nom::combinator::opt;
use nom::combinator::peek;
@@ -67,13 +68,13 @@ where
let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, (children, _exit_contents)) =
many_till(org_mode_table_row_matcher, exit_matcher)(remaining)?;
let (remaining, (contents, (children, _exit_contents))) =
consumed(many_till(org_mode_table_row_matcher, exit_matcher))(remaining)?;
let (remaining, formulas) =
many0(parser_with_context!(table_formula_keyword)(context))(remaining)?;
let (remaining, _trailing_ws) =
let (remaining, post_blank) =
maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -87,6 +88,8 @@ where
),
formulas,
children,
contents: Into::<&str>::into(contents),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -150,6 +153,7 @@ fn org_mode_table_row_rule<'b, 'g, 'r, 's>(
TableRow {
source: source.into(),
children: Vec::new(),
contents: None,
},
))
}
@@ -164,8 +168,8 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
) -> Res<OrgSource<'s>, TableRow<'s>> {
start_of_line(input)?;
let (remaining, _) = tuple((space0, tag("|")))(input)?;
let (remaining, children) =
many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?;
let (remaining, (contents, children)) =
consumed(many1(parser_with_context!(org_mode_table_cell)(context)))(remaining)?;
let (remaining, _tail) = recognize(tuple((space0, org_line_ending)))(remaining)?;
let source = get_consumed(input, remaining);
Ok((
@@ -173,6 +177,11 @@ fn org_mode_table_row_regular<'b, 'g, 'r, 's>(
TableRow {
source: source.into(),
children,
contents: if contents.len() > 0 {
Some(Into::<&str>::into(contents))
} else {
None
},
},
))
}
@@ -194,12 +203,12 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
parser_with_context!(table_cell_set_object)(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
let (remaining, _) = space0(input)?;
let (remaining, (children, _exit_contents)) = verify(
let (remaining, (contents, (children, _exit_contents))) = consumed(verify(
many_till(table_cell_set_object_matcher, exit_matcher),
|(children, exit_contents)| {
!children.is_empty() || Into::<&str>::into(exit_contents).ends_with('|')
},
)(remaining)?;
))(remaining)?;
let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?;
@@ -210,6 +219,7 @@ fn org_mode_table_cell<'b, 'g, 'r, 's>(
TableCell {
source: source.into(),
children,
contents: Into::<&str>::into(contents),
},
))
}

View File

@@ -46,7 +46,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
)));
}
let (remaining, _) = tag(">>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -55,6 +55,7 @@ pub(crate) fn target<'b, 'g, 'r, 's>(
Target {
source: source.into(),
value: body.into(),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -53,8 +53,8 @@ pub(crate) fn timestamp<'b, 'g, 'r, 's>(
parser_with_context!(inactive_time_range_timestamp)(context),
parser_with_context!(active_date_range_timestamp)(context),
parser_with_context!(inactive_date_range_timestamp)(context),
parser_with_context!(active_timestamp)(context),
parser_with_context!(inactive_timestamp)(context),
parser_with_context!(active_timestamp(true))(context),
parser_with_context!(inactive_timestamp(true))(context),
))(input)
}
@@ -69,7 +69,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
let (remaining, _) = tag("<%%(")(input)?;
let (remaining, _body) = sexp(context, remaining)?;
let (remaining, _) = tag(")>")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -85,6 +85,7 @@ fn diary_timestamp<'b, 'g, 'r, 's>(
end_time: None,
repeater: None,
warning_delay: None,
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -125,13 +126,23 @@ fn sexp_end<'b, 'g, 'r, 's>(
alt((tag(")>"), recognize(one_of(">\n"))))(input)
}
const fn active_timestamp(
allow_post_blank: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
move |context, input| impl_active_timestamp(context, input, allow_post_blank)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
fn active_timestamp<'b, 'g, 'r, 's>(
fn impl_active_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
allow_post_blank: bool,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("<")(input)?;
let (remaining, start) = date(context, remaining)?;
@@ -159,8 +170,11 @@ fn active_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, post_blank) = if allow_post_blank {
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
} else {
(remaining, None)
};
let source = get_consumed(input, remaining);
Ok((
@@ -175,17 +189,28 @@ fn active_timestamp<'b, 'g, 'r, 's>(
end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
pub(crate) const fn inactive_timestamp(
allow_post_blank: bool,
) -> impl for<'b, 'g, 'r, 's> Fn(
RefContext<'b, 'g, 'r, 's>,
OrgSource<'s>,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
move |context, input| impl_inactive_timestamp(context, input, allow_post_blank)
}
#[cfg_attr(
feature = "tracing",
tracing::instrument(ret, level = "debug", skip(context))
)]
pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
fn impl_inactive_timestamp<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>,
input: OrgSource<'s>,
allow_post_blank: bool,
) -> Res<OrgSource<'s>, Timestamp<'s>> {
let (remaining, _) = tag("[")(input)?;
let (remaining, start) = date(context, remaining)?;
@@ -213,8 +238,11 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let (remaining, post_blank) = if allow_post_blank {
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?
} else {
(remaining, None)
};
let source = get_consumed(input, remaining);
Ok((
@@ -229,6 +257,7 @@ pub(crate) fn inactive_timestamp<'b, 'g, 'r, 's>(
end_time: time.map(|(_, time)| time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -241,12 +270,12 @@ 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) = impl_active_timestamp(context, input, false)?;
// 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) = impl_active_timestamp(context, remaining, false)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -264,6 +293,7 @@ fn active_date_range_timestamp<'b, 'g, 'r, 's>(
warning_delay: first_timestamp
.warning_delay
.or(second_timestamp.warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -302,7 +332,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag(">")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -318,6 +348,7 @@ fn active_time_range_timestamp<'b, 'g, 'r, 's>(
end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -330,12 +361,12 @@ pub(crate) 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) = impl_inactive_timestamp(context, input, false)?;
// 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) = impl_inactive_timestamp(context, remaining, false)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -354,6 +385,7 @@ pub(crate) fn inactive_date_range_timestamp<'b, 'g, 'r, 's>(
warning_delay: first_timestamp
.warning_delay
.or(second_timestamp.warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}
@@ -392,7 +424,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
)))(remaining)?;
let (remaining, _) = tag("]")(remaining)?;
let (remaining, _trailing_whitespace) =
let (remaining, post_blank) =
maybe_consume_object_trailing_whitespace_if_not_exiting(context, remaining)?;
let source = get_consumed(input, remaining);
@@ -408,6 +440,7 @@ pub(crate) fn inactive_time_range_timestamp<'b, 'g, 'r, 's>(
end_time: Some(second_time),
repeater: repeater.map(|(_, repeater)| repeater),
warning_delay: warning_delay.map(|(_, warning_delay)| warning_delay),
post_blank: post_blank.map(Into::<&str>::into),
},
))
}

View File

@@ -1,6 +1,5 @@
use std::path::PathBuf;
use super::remove_trailing::RemoveTrailing;
use super::Element;
use super::NodeProperty;
use super::Object;
@@ -18,6 +17,7 @@ pub struct Document<'s> {
pub path: Option<PathBuf>,
pub zeroth_section: Option<Section<'s>>,
pub children: Vec<Heading<'s>>,
pub contents: &'s str,
}
#[derive(Debug)]
@@ -35,12 +35,13 @@ pub struct Heading<'s> {
pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct Section<'s> {
pub source: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
pub children: Vec<Element<'s>>,
}
@@ -63,35 +64,11 @@ impl<'s> StandardProperties<'s> for Document<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
let post_blank = self.get_post_blank();
let mut content_lines = self
.source
.split_inclusive('\n')
.remove_trailing(post_blank);
let first_line = content_lines.next();
let last_line = content_lines.last().or(first_line);
match (first_line, last_line) {
(None, None) => None,
(None, Some(_)) | (Some(_), None) => unreachable!(),
(Some(first_line), Some(last_line)) => {
let first_line_offset = first_line.as_ptr() as usize;
let last_line_offset = last_line.as_ptr() as usize + last_line.len();
let source_offset = self.source.as_ptr() as usize;
debug_assert!(super::lesser_element::is_slice_of(self.source, first_line));
debug_assert!(super::lesser_element::is_slice_of(self.source, last_line));
Some(
&self.source[(first_line_offset - source_offset)
..(last_line_offset - first_line_offset)],
)
}
}
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
self.into_iter()
.last()
.map(|child| child.get_post_blank())
.unwrap_or(0)
0
}
}
@@ -101,7 +78,7 @@ impl<'s> StandardProperties<'s> for Section<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
self.contents
Some(self.source)
}
fn get_post_blank(&self) -> PostBlank {
@@ -119,39 +96,15 @@ impl<'s> StandardProperties<'s> for Heading<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
let first_child = self.children.first();
let last_child = self.children.last();
match (first_child, last_child) {
(None, None) => None,
(None, Some(_)) | (Some(_), None) => unreachable!(),
(Some(first_child), Some(last_child)) => {
let first_child_offset = first_child.get_source().as_ptr() as usize;
let last_child_offset = {
let last_child_source = last_child.get_source();
last_child_source.as_ptr() as usize + last_child_source.len()
};
let source_offset = self.source.as_ptr() as usize;
debug_assert!(super::lesser_element::is_slice_of(
self.source,
first_child.get_source()
));
debug_assert!(super::lesser_element::is_slice_of(
self.source,
last_child.get_source()
));
Some(
&self.source[(first_child_offset - source_offset)
..(last_child_offset - first_child_offset)],
)
}
}
self.contents
}
fn get_post_blank(&self) -> PostBlank {
self.children
.last()
.map(|child| child.get_post_blank())
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}

View File

@@ -13,6 +13,8 @@ pub struct PlainList<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub list_type: PlainListType,
pub children: Vec<PlainListItem<'s>>,
pub contents: Option<&'s str>, // TODO: Can contents ever be None?
pub post_blank: Option<&'s str>,
}
#[derive(Debug, Copy, Clone)]
@@ -35,6 +37,8 @@ pub struct PlainListItem<'s> {
pub tag: Vec<Object<'s>>,
pub pre_blank: PlainListItemPreBlank,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
pub type PlainListItemCounter = u16;
@@ -52,6 +56,8 @@ pub struct CenterBlock<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -59,6 +65,8 @@ pub struct QuoteBlock<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -68,6 +76,8 @@ pub struct SpecialBlock<'s> {
pub block_type: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -77,6 +87,8 @@ pub struct DynamicBlock<'s> {
pub block_name: &'s str,
pub parameters: Option<&'s str>,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -95,12 +107,16 @@ pub struct Drawer<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub drawer_name: &'s str,
pub children: Vec<Element<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct PropertyDrawer<'s> {
pub source: &'s str,
pub children: Vec<NodeProperty<'s>>,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -116,12 +132,15 @@ pub struct Table<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub formulas: Vec<Keyword<'s>>,
pub children: Vec<TableRow<'s>>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct TableRow<'s> {
pub source: &'s str,
pub children: Vec<TableCell<'s>>,
pub contents: Option<&'s str>,
}
#[derive(Debug)]
@@ -136,11 +155,15 @@ impl<'s> StandardProperties<'s> for PlainList<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -150,11 +173,15 @@ impl<'s> StandardProperties<'s> for PlainListItem<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -164,11 +191,15 @@ impl<'s> StandardProperties<'s> for CenterBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -178,11 +209,15 @@ impl<'s> StandardProperties<'s> for QuoteBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -192,11 +227,15 @@ impl<'s> StandardProperties<'s> for SpecialBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -206,11 +245,15 @@ impl<'s> StandardProperties<'s> for DynamicBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -238,11 +281,15 @@ impl<'s> StandardProperties<'s> for Drawer<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -252,11 +299,15 @@ impl<'s> StandardProperties<'s> for PropertyDrawer<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -266,11 +317,11 @@ impl<'s> StandardProperties<'s> for NodeProperty<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
0
}
}
@@ -280,11 +331,15 @@ impl<'s> StandardProperties<'s> for Table<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -294,11 +349,11 @@ impl<'s> StandardProperties<'s> for TableRow<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
self.contents
}
fn get_post_blank(&self) -> PostBlank {
todo!()
0
}
}

View File

@@ -35,12 +35,14 @@ pub struct Paragraph<'s> {
pub struct Comment<'s> {
pub source: &'s str,
pub value: Vec<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct TableCell<'s> {
pub source: &'s str,
pub children: Vec<Object<'s>>,
pub contents: &'s str,
}
#[derive(Debug)]
@@ -49,6 +51,8 @@ pub struct VerseBlock<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub data: Option<&'s str>,
pub children: Vec<Object<'s>>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -56,6 +60,7 @@ pub struct CommentBlock<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
pub type CharOffsetInLine = u16;
@@ -79,6 +84,7 @@ pub struct ExampleBlock<'s> {
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -88,6 +94,7 @@ pub struct ExportBlock<'s> {
pub export_type: Option<&'s str>,
pub data: Option<&'s str>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -103,6 +110,7 @@ pub struct SrcBlock<'s> {
pub use_labels: bool,
pub label_format: Option<&'s str>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -117,6 +125,7 @@ pub struct Clock<'s> {
pub timestamp: Timestamp<'s>,
pub duration: Option<&'s str>,
pub status: ClockStatus,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -124,6 +133,7 @@ pub struct DiarySexp<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -132,6 +142,7 @@ pub struct Planning<'s> {
pub scheduled: Option<Timestamp<'s>>,
pub deadline: Option<Timestamp<'s>>,
pub closed: Option<Timestamp<'s>>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -139,12 +150,14 @@ pub struct FixedWidthArea<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub value: Vec<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct HorizontalRule<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -153,6 +166,7 @@ pub struct Keyword<'s> {
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub key: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -164,6 +178,7 @@ pub struct BabelCall<'s> {
pub inside_header: Option<&'s str>,
pub arguments: Option<&'s str>,
pub end_header: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -171,6 +186,7 @@ pub struct LatexEnvironment<'s> {
pub source: &'s str,
pub affiliated_keywords: AffiliatedKeywords<'s>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
/// A line number used in switches to lesser blocks.
@@ -188,11 +204,16 @@ impl<'s> Paragraph<'s> {
/// Generate a paragraph of the passed in text with no additional properties.
///
/// This is used for elements that support an "empty" content like greater blocks.
pub(crate) fn of_text(source: &'s str, body: &'s str) -> Self {
pub(crate) fn of_text(
source: &'s str,
body: &'s str,
contents: Option<&'s str>,
post_blank: Option<&'s str>,
) -> Self {
Paragraph {
source,
contents: None, // TODO
post_blank: None, // TODO
contents,
post_blank,
affiliated_keywords: AffiliatedKeywords::default(),
children: vec![Object::PlainText(PlainText { source: body })],
}
@@ -223,11 +244,11 @@ impl<'s> StandardProperties<'s> for TableCell<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
todo!()
0
}
}
@@ -241,7 +262,11 @@ impl<'s> StandardProperties<'s> for Comment<'s> {
}
fn get_post_blank(&self) -> PostBlank {
0
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -251,11 +276,15 @@ impl<'s> StandardProperties<'s> for VerseBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> StandardProperties<'s> for CommentBlock<'s> {
@@ -264,11 +293,15 @@ impl<'s> StandardProperties<'s> for CommentBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
@@ -277,11 +310,15 @@ impl<'s> StandardProperties<'s> for ExampleBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> StandardProperties<'s> for ExportBlock<'s> {
@@ -290,11 +327,15 @@ impl<'s> StandardProperties<'s> for ExportBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
impl<'s> StandardProperties<'s> for SrcBlock<'s> {
@@ -303,11 +344,15 @@ impl<'s> StandardProperties<'s> for SrcBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -317,11 +362,15 @@ impl<'s> StandardProperties<'s> for Clock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -331,11 +380,15 @@ impl<'s> StandardProperties<'s> for DiarySexp<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -345,11 +398,15 @@ impl<'s> StandardProperties<'s> for Planning<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -359,11 +416,15 @@ impl<'s> StandardProperties<'s> for FixedWidthArea<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -373,11 +434,15 @@ impl<'s> StandardProperties<'s> for HorizontalRule<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -387,11 +452,15 @@ impl<'s> StandardProperties<'s> for Keyword<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -401,11 +470,15 @@ impl<'s> StandardProperties<'s> for BabelCall<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -415,11 +488,15 @@ impl<'s> StandardProperties<'s> for LatexEnvironment<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.lines().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -437,13 +514,7 @@ impl<'s> Comment<'s> {
impl<'s> FixedWidthArea<'s> {
pub fn get_value(&self) -> String {
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
self.value.join("\n")
}
}

View File

@@ -120,6 +120,7 @@ pub struct RegularLink<'s> {
pub struct RadioTarget<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
@@ -127,6 +128,7 @@ pub struct RadioTarget<'s> {
pub struct RadioLink<'s> {
pub source: &'s str,
pub path: &'s str,
pub post_blank: Option<&'s str>,
pub children: Vec<Object<'s>>,
}
@@ -138,6 +140,7 @@ pub struct PlainLink<'s> {
pub raw_link: &'s str,
pub search_option: Option<&'s str>,
pub application: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -156,6 +159,7 @@ pub struct AngleLink<'s> {
/// This does not take into account the post-processing that you would get from the upstream emacs org-mode AST. Use `get_search_option` for an equivalent value.
pub search_option: Option<&'s str>,
pub application: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -173,6 +177,7 @@ pub struct OrgMacro<'s> {
pub args: Vec<&'s str>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -186,12 +191,14 @@ pub struct Entity<'s> {
// Skipping latin1 because it is detrimental to the future. If anyone out there is using latin1, take a long look in the mirror and change your ways.
pub utf8: &'s str,
pub use_brackets: bool,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct LatexFragment<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -199,6 +206,7 @@ pub struct ExportSnippet<'s> {
pub source: &'s str,
pub backend: &'s str,
pub contents: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -217,6 +225,8 @@ pub struct Citation<'s> {
pub prefix: Vec<Object<'s>>,
pub suffix: Vec<Object<'s>>,
pub children: Vec<CitationReference<'s>>,
pub contents: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -235,6 +245,7 @@ pub struct InlineBabelCall<'s> {
pub inside_header: Option<&'s str>,
pub arguments: Option<&'s str>,
pub end_header: Option<&'s str>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -243,6 +254,7 @@ pub struct InlineSourceBlock<'s> {
pub language: &'s str,
pub parameters: Option<&'s str>,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -254,12 +266,14 @@ pub struct LineBreak<'s> {
pub struct Target<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
pub struct StatisticsCookie<'s> {
pub source: &'s str,
pub value: &'s str,
pub post_blank: Option<&'s str>,
}
#[derive(Debug)]
@@ -292,6 +306,7 @@ pub struct Timestamp<'s> {
pub end_time: Option<Time<'s>>,
pub repeater: Option<Repeater>,
pub warning_delay: Option<WarningDelay>,
pub post_blank: Option<&'s str>,
}
#[derive(Debug, Clone)]
@@ -665,11 +680,15 @@ impl<'s> StandardProperties<'s> for RadioLink<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
Some(self.path)
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -679,11 +698,15 @@ impl<'s> StandardProperties<'s> for RadioTarget<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
Some(self.value)
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -693,11 +716,15 @@ impl<'s> StandardProperties<'s> for PlainLink<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -707,11 +734,15 @@ impl<'s> StandardProperties<'s> for AngleLink<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -721,11 +752,15 @@ impl<'s> StandardProperties<'s> for OrgMacro<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -735,11 +770,15 @@ impl<'s> StandardProperties<'s> for Entity<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -749,11 +788,15 @@ impl<'s> StandardProperties<'s> for LatexFragment<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -763,11 +806,15 @@ impl<'s> StandardProperties<'s> for ExportSnippet<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -795,11 +842,15 @@ impl<'s> StandardProperties<'s> for Citation<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
Some(self.contents)
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -809,11 +860,11 @@ impl<'s> StandardProperties<'s> for CitationReference<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
0
}
}
@@ -823,11 +874,15 @@ impl<'s> StandardProperties<'s> for InlineBabelCall<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -837,11 +892,15 @@ impl<'s> StandardProperties<'s> for InlineSourceBlock<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -865,11 +924,15 @@ impl<'s> StandardProperties<'s> for Target<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -879,11 +942,15 @@ impl<'s> StandardProperties<'s> for StatisticsCookie<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|post_blank| post_blank.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -929,11 +996,15 @@ impl<'s> StandardProperties<'s> for Timestamp<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
None
}
fn get_post_blank(&self) -> PostBlank {
todo!()
self.post_blank
.map(|text| text.chars().count())
.unwrap_or(0)
.try_into()
.expect("Too much post-blank to fit into a PostBlank.")
}
}
@@ -943,11 +1014,13 @@ impl<'s> StandardProperties<'s> for PlainText<'s> {
}
fn get_contents<'b>(&'b self) -> Option<&'s str> {
todo!()
// This field does not actually exist in emacs for plaintext
Some(self.source)
}
fn get_post_blank(&self) -> PostBlank {
todo!()
// This field does not actually exist in emacs for plaintext
0
}
}