Compare commits

...

6 Commits

Author SHA1 Message Date
Tom Alexander
444d6758aa
Handle leading blank lines in greater blocks.
Some checks failed
rust-test Build rust-test has succeeded
rust-build Build rust-build has succeeded
rust-foreign-document-test Build rust-foreign-document-test has failed
2023-09-15 21:03:35 -04:00
Tom Alexander
6c7203410e
Add a test showing we're not handling leading blank lines in greater blocks. 2023-09-15 17:02:41 -04:00
Tom Alexander
bfe67b1f75
Parse plain list item checkboxes. 2023-09-15 16:09:57 -04:00
Tom Alexander
fd41ad9c29
Pretend dos line endings do not exist. 2023-09-15 14:13:17 -04:00
Tom Alexander
7f751d4f28
Allow no digit in repeater in timestamp. 2023-09-15 13:12:54 -04:00
Tom Alexander
52a4dab67c
Use the timestamp parser in planning.
Previously we did not support inactive timestamps in planning. This fixes that.
2023-09-15 12:45:19 -04:00
9 changed files with 95 additions and 34 deletions

View File

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

View File

@ -14,7 +14,9 @@ use crate::LocalFileAccessInterface;
pub fn run_anonymous_compare<P: AsRef<str>>( pub fn run_anonymous_compare<P: AsRef<str>>(
org_contents: P, org_contents: P,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
let org_contents = org_contents.as_ref(); // TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings.
let org_contents = org_contents.as_ref().replace("\r\n", "\n");
let org_contents = org_contents.as_str();
eprintln!("Using emacs version: {}", get_emacs_version()?.trim()); eprintln!("Using emacs version: {}", get_emacs_version()?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim()); eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim());
let rust_parsed = parse(org_contents)?; let rust_parsed = parse(org_contents)?;
@ -44,6 +46,8 @@ pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn st
.parent() .parent()
.ok_or("Should be contained inside a directory.")?; .ok_or("Should be contained inside a directory.")?;
let org_contents = std::fs::read_to_string(org_path)?; let org_contents = std::fs::read_to_string(org_path)?;
// TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings.
let org_contents = org_contents.replace("\r\n", "\n");
let org_contents = org_contents.as_str(); let org_contents = org_contents.as_str();
let file_access_interface = LocalFileAccessInterface { let file_access_interface = LocalFileAccessInterface {
working_directory: Some(parent_directory.to_path_buf()), working_directory: Some(parent_directory.to_path_buf()),

View File

@ -8,6 +8,7 @@ use super::util::assert_name;
use super::util::get_property; use super::util::get_property;
use crate::types::AngleLink; use crate::types::AngleLink;
use crate::types::Bold; use crate::types::Bold;
use crate::types::CheckboxType;
use crate::types::Citation; use crate::types::Citation;
use crate::types::CitationReference; use crate::types::CitationReference;
use crate::types::Clock; use crate::types::Clock;
@ -799,7 +800,26 @@ fn compare_plain_list_item<'s>(
contents_status, contents_status,
)?); )?);
// TODO: compare :bullet :checkbox :counter :pre-blank // TODO: compare :bullet :counter :pre-blank
// Compare checkbox
let checkbox = get_property(emacs, ":checkbox")?
.map(Token::as_atom)
.map_or(Ok(None), |r| r.map(Some))?
.unwrap_or("nil");
match (checkbox, &rust.checkbox) {
("nil", None) => {}
("off", Some((CheckboxType::Off, _))) => {}
("trans", Some((CheckboxType::Trans, _))) => {}
("on", Some((CheckboxType::On, _))) => {}
_ => {
this_status = DiffStatus::Bad;
message = Some(format!(
"Checkbox mismatch (emacs != rust) {:?} != {:?}",
checkbox, rust.checkbox
));
}
};
Ok(DiffResult { Ok(DiffResult {
status: this_status, status: this_status,

View File

@ -4,11 +4,14 @@ use nom::bytes::complete::tag_no_case;
use nom::character::complete::line_ending; use nom::character::complete::line_ending;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::consumed;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::verify; use nom::combinator::verify;
use nom::multi::many0;
use nom::multi::many_till; use nom::multi::many_till;
use nom::sequence::preceded;
use nom::sequence::tuple; use nom::sequence::tuple;
use super::org_source::OrgSource; use super::org_source::OrgSource;
@ -80,25 +83,23 @@ pub(crate) fn greater_block<'b, 'g, 'r, 's>(
let element_matcher = parser_with_context!(element(true))(&parser_context); let element_matcher = parser_with_context!(element(true))(&parser_context);
let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context);
// Check for a completely empty block not(exit_matcher)(remaining)?;
let (remaining, children) = match tuple(( let (remaining, leading_blank_lines) = opt(consumed(tuple((
not(exit_matcher),
blank_line, blank_line,
many_till(blank_line, exit_matcher), many0(preceded(not(exit_matcher), blank_line)),
))(remaining) ))))(remaining)?;
{ let leading_blank_lines =
Ok((remain, (_not_immediate_exit, first_line, (_trailing_whitespace, _exit_contents)))) => { leading_blank_lines.map(|(source, (first_line, _remaining_lines))| {
let mut element = Element::Paragraph(Paragraph::of_text(first_line.into())); let mut element = Element::Paragraph(Paragraph::of_text(first_line.into()));
let source = get_consumed(remaining, remain);
element.set_source(source.into()); element.set_source(source.into());
(remain, vec![element]) element
} });
Err(_) => { let (remaining, (mut children, _exit_contents)) =
let (remaining, (children, _exit_contents)) = many_till(element_matcher, exit_matcher)(remaining)?;
many_till(element_matcher, exit_matcher)(remaining)?; if let Some(lines) = leading_blank_lines {
(remaining, children) children.insert(0, lines);
} }
};
let (remaining, _end) = exit_with_name(&parser_context, remaining)?; let (remaining, _end) = exit_with_name(&parser_context, remaining)?;
// Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block // Not checking if parent exit matcher is causing exit because the greater_block_end matcher asserts we matched a full greater block

View File

@ -7,6 +7,7 @@ use nom::character::complete::one_of;
use nom::character::complete::space0; use nom::character::complete::space0;
use nom::character::complete::space1; use nom::character::complete::space1;
use nom::combinator::eof; use nom::combinator::eof;
use nom::combinator::map;
use nom::combinator::not; use nom::combinator::not;
use nom::combinator::opt; use nom::combinator::opt;
use nom::combinator::peek; use nom::combinator::peek;
@ -35,7 +36,9 @@ use crate::parser::util::blank_line;
use crate::parser::util::exit_matcher_parser; use crate::parser::util::exit_matcher_parser;
use crate::parser::util::get_consumed; use crate::parser::util::get_consumed;
use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting;
use crate::parser::util::org_space;
use crate::parser::util::start_of_line; use crate::parser::util::start_of_line;
use crate::types::CheckboxType;
use crate::types::Object; use crate::types::Object;
use crate::types::PlainList; use crate::types::PlainList;
use crate::types::PlainListItem; use crate::types::PlainListItem;
@ -160,7 +163,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
tag("]"), tag("]"),
)))(remaining)?; )))(remaining)?;
// TODO: parse checkbox let (remaining, maybe_checkbox) = opt(tuple((space1, item_checkbox)))(remaining)?;
let (remaining, maybe_tag) = let (remaining, maybe_tag) =
opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?; opt(tuple((space1, parser_with_context!(item_tag)(context))))(remaining)?;
@ -178,6 +181,7 @@ fn plain_list_item<'b, 'g, 'r, 's>(
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
checkbox: None,
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
@ -227,6 +231,8 @@ fn plain_list_item<'b, 'g, 'r, 's>(
source: source.into(), source: source.into(),
indentation: indent_level, indentation: indent_level,
bullet: bull.into(), bullet: bull.into(),
checkbox: maybe_checkbox
.map(|(_, (checkbox_type, source))| (checkbox_type, Into::<&str>::into(source))),
tag: maybe_tag tag: maybe_tag
.map(|(_ws, item_tag)| item_tag) .map(|(_ws, item_tag)| item_tag)
.unwrap_or(Vec::new()), .unwrap_or(Vec::new()),
@ -389,6 +395,18 @@ fn item_tag_post_gap<'b, 'g, 'r, 's>(
)(input) )(input)
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn item_checkbox<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, (CheckboxType, OrgSource<'s>)> {
alt((
map(
recognize(tuple((tag("["), org_space, tag("]")))),
|capture| (CheckboxType::Off, capture),
),
map(tag("[-]"), |capture| (CheckboxType::Trans, capture)),
map(tag("[X]"), |capture| (CheckboxType::On, capture)),
))(input)
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
fn detect_contentless_item_contents<'b, 'g, 'r, 's>( fn detect_contentless_item_contents<'b, 'g, 'r, 's>(
context: RefContext<'b, 'g, 'r, 's>, context: RefContext<'b, 'g, 'r, 's>,

View File

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

View File

@ -1,6 +1,7 @@
use nom::branch::alt; use nom::branch::alt;
use nom::bytes::complete::tag; use nom::bytes::complete::tag;
use nom::character::complete::anychar; use nom::character::complete::anychar;
use nom::character::complete::digit0;
use nom::character::complete::digit1; use nom::character::complete::digit1;
use nom::character::complete::one_of; use nom::character::complete::one_of;
use nom::character::complete::space1; use nom::character::complete::space1;
@ -414,7 +415,7 @@ fn repeater<'b, 'g, 'r, 's>(
// ++ for catch-up type // ++ for catch-up type
// .+ for restart type // .+ for restart type
let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?; let (remaining, _mark) = alt((tag("++"), tag("+"), tag(".+")))(input)?;
let (remaining, _value) = digit1(remaining)?; let (remaining, _value) = digit0(remaining)?;
// h = hour, d = day, w = week, m = month, y = year // h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?; let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);
@ -429,7 +430,7 @@ fn warning_delay<'b, 'g, 'r, 's>(
// - for all type // - for all type
// -- for first type // -- for first type
let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?; let (remaining, _mark) = alt((tag("--"), tag("-")))(input)?;
let (remaining, _value) = digit1(remaining)?; let (remaining, _value) = digit0(remaining)?;
// h = hour, d = day, w = week, m = month, y = year // h = hour, d = day, w = week, m = month, y = year
let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?; let (remaining, _unit) = recognize(one_of("hdwmy"))(remaining)?;
let source = get_consumed(input, remaining); let source = get_consumed(input, remaining);

View File

@ -15,10 +15,18 @@ pub struct PlainListItem<'s> {
pub source: &'s str, pub source: &'s str,
pub indentation: usize, pub indentation: usize,
pub bullet: &'s str, pub bullet: &'s str,
pub checkbox: Option<(CheckboxType, &'s str)>,
pub tag: Vec<Object<'s>>, pub tag: Vec<Object<'s>>,
pub children: Vec<Element<'s>>, pub children: Vec<Element<'s>>,
} }
#[derive(Debug)]
pub enum CheckboxType {
On,
Trans,
Off,
}
#[derive(Debug)] #[derive(Debug)]
pub struct GreaterBlock<'s> { pub struct GreaterBlock<'s> {
pub source: &'s str, pub source: &'s str,

View File

@ -11,6 +11,7 @@ pub use document::PriorityCookie;
pub use document::Section; pub use document::Section;
pub use document::TodoKeywordType; pub use document::TodoKeywordType;
pub use element::Element; pub use element::Element;
pub use greater_element::CheckboxType;
pub use greater_element::Drawer; pub use greater_element::Drawer;
pub use greater_element::DynamicBlock; pub use greater_element::DynamicBlock;
pub use greater_element::FootnoteDefinition; pub use greater_element::FootnoteDefinition;