Compare heading todo keywords.
This only handles the default case where the only valid TODO keywords are TODO and DONE.
This commit is contained in:
parent
9cc5e63c1b
commit
3e143796f7
@ -2,6 +2,7 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use super::util::assert_bounds;
|
use super::util::assert_bounds;
|
||||||
use super::util::assert_name;
|
use super::util::assert_name;
|
||||||
|
use crate::parser::sexp::unquote;
|
||||||
use crate::parser::sexp::Token;
|
use crate::parser::sexp::Token;
|
||||||
use crate::parser::AngleLink;
|
use crate::parser::AngleLink;
|
||||||
use crate::parser::Bold;
|
use crate::parser::Bold;
|
||||||
@ -60,7 +61,6 @@ use crate::parser::Timestamp;
|
|||||||
use crate::parser::Underline;
|
use crate::parser::Underline;
|
||||||
use crate::parser::Verbatim;
|
use crate::parser::Verbatim;
|
||||||
use crate::parser::VerseBlock;
|
use crate::parser::VerseBlock;
|
||||||
use crate::parser::sexp::unquote;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DiffResult {
|
pub struct DiffResult {
|
||||||
@ -336,6 +336,45 @@ fn compare_heading<'s>(
|
|||||||
this_status = DiffStatus::Bad;
|
this_status = DiffStatus::Bad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare tags
|
||||||
|
let emacs_tags = get_tags_from_heading(emacs)?;
|
||||||
|
let emacs_tags: HashSet<_> = emacs_tags.iter().map(|val| val.as_str()).collect();
|
||||||
|
let rust_tags: HashSet<&str> = rust.tags.iter().map(|val| *val).collect();
|
||||||
|
let difference: Vec<&str> = emacs_tags
|
||||||
|
.symmetric_difference(&rust_tags)
|
||||||
|
.map(|val| *val)
|
||||||
|
.collect();
|
||||||
|
if !difference.is_empty() {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
message = Some(format!("Mismatched tags: {}", difference.join(", ")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare todo-keyword
|
||||||
|
let todo_keyword = {
|
||||||
|
let children = emacs.as_list()?;
|
||||||
|
let attributes_child = children
|
||||||
|
.iter()
|
||||||
|
.nth(1)
|
||||||
|
.ok_or("Should have an attributes child.")?;
|
||||||
|
let attributes_map = attributes_child.as_map()?;
|
||||||
|
let todo_keyword = attributes_map
|
||||||
|
.get(":todo-keyword")
|
||||||
|
.ok_or("Missing :todo-keyword attribute.");
|
||||||
|
todo_keyword?.as_atom()?
|
||||||
|
};
|
||||||
|
match (todo_keyword, rust.todo_keyword, unquote(todo_keyword)) {
|
||||||
|
("nil", None, _) => {}
|
||||||
|
(_, Some(rust_todo), Ok(emacs_todo)) if emacs_todo == rust_todo => {}
|
||||||
|
(emacs_todo, rust_todo, _) => {
|
||||||
|
this_status = DiffStatus::Bad;
|
||||||
|
message = Some(format!(
|
||||||
|
"(emacs != rust) {:?} != {:?}",
|
||||||
|
emacs_todo, rust_todo
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compare title
|
||||||
let title = {
|
let title = {
|
||||||
let children = emacs.as_list()?;
|
let children = emacs.as_list()?;
|
||||||
let attributes_child = children
|
let attributes_child = children
|
||||||
@ -351,20 +390,10 @@ fn compare_heading<'s>(
|
|||||||
for (emacs_child, rust_child) in title.as_list()?.iter().zip(rust.title.iter()) {
|
for (emacs_child, rust_child) in title.as_list()?.iter().zip(rust.title.iter()) {
|
||||||
child_status.push(compare_object(source, emacs_child, rust_child)?);
|
child_status.push(compare_object(source, emacs_child, rust_child)?);
|
||||||
}
|
}
|
||||||
let emacs_tags = get_tags_from_heading(emacs)?;
|
|
||||||
let emacs_tags: HashSet<_> = emacs_tags.iter().map(|val| val.as_str()).collect();
|
|
||||||
let rust_tags: HashSet<&str> = rust.tags.iter().map(|val| *val).collect();
|
|
||||||
let difference: Vec<&str> = emacs_tags
|
|
||||||
.symmetric_difference(&rust_tags)
|
|
||||||
.map(|val| *val)
|
|
||||||
.collect();
|
|
||||||
if !difference.is_empty() {
|
|
||||||
this_status = DiffStatus::Bad;
|
|
||||||
message = Some(format!("Mismatched tags: {}", difference.join(", ")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Compare todo-keyword, level, priority
|
// TODO: Compare todo-type, level, priority
|
||||||
|
|
||||||
|
// Compare section
|
||||||
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {
|
||||||
match rust_child {
|
match rust_child {
|
||||||
DocumentElement::Heading(rust_heading) => {
|
DocumentElement::Heading(rust_heading) => {
|
||||||
@ -405,8 +434,14 @@ fn get_tags_from_heading<'s>(
|
|||||||
};
|
};
|
||||||
let tags = {
|
let tags = {
|
||||||
let tags = tags.as_list()?;
|
let tags = tags.as_list()?;
|
||||||
let strings = tags.iter().map(Token::as_atom).collect::<Result<Vec<&str>, _>>()?;
|
let strings = tags
|
||||||
strings.into_iter().map(unquote).collect::<Result<HashSet<String>, _>>()?
|
.iter()
|
||||||
|
.map(Token::as_atom)
|
||||||
|
.collect::<Result<Vec<&str>, _>>()?;
|
||||||
|
strings
|
||||||
|
.into_iter()
|
||||||
|
.map(unquote)
|
||||||
|
.collect::<Result<HashSet<String>, _>>()?
|
||||||
};
|
};
|
||||||
Ok(tags)
|
Ok(tags)
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,8 @@ pub struct Document<'s> {
|
|||||||
pub struct Heading<'s> {
|
pub struct Heading<'s> {
|
||||||
pub source: &'s str,
|
pub source: &'s str,
|
||||||
pub stars: usize,
|
pub stars: usize,
|
||||||
|
pub todo_keyword: Option<&'s str>,
|
||||||
|
// TODO: add todo-type enum
|
||||||
pub title: Vec<Object<'s>>,
|
pub title: Vec<Object<'s>>,
|
||||||
pub tags: Vec<&'s str>,
|
pub tags: Vec<&'s str>,
|
||||||
pub children: Vec<DocumentElement<'s>>,
|
pub children: Vec<DocumentElement<'s>>,
|
||||||
@ -271,7 +273,8 @@ fn heading<'r, 's>(
|
|||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||||
not(|i| context.check_exit_matcher(i))(input)?;
|
not(|i| context.check_exit_matcher(i))(input)?;
|
||||||
let (remaining, (star_count, _ws, title, heading_tags)) = headline(context, input)?;
|
let (remaining, (star_count, _ws, maybe_todo_keyword, title, heading_tags)) =
|
||||||
|
headline(context, input)?;
|
||||||
let section_matcher = parser_with_context!(section)(context);
|
let section_matcher = parser_with_context!(section)(context);
|
||||||
let heading_matcher = parser_with_context!(heading)(context);
|
let heading_matcher = parser_with_context!(heading)(context);
|
||||||
let (remaining, children) = many0(alt((
|
let (remaining, children) = many0(alt((
|
||||||
@ -287,6 +290,8 @@ fn heading<'r, 's>(
|
|||||||
Heading {
|
Heading {
|
||||||
source: source.into(),
|
source: source.into(),
|
||||||
stars: star_count,
|
stars: star_count,
|
||||||
|
todo_keyword: maybe_todo_keyword
|
||||||
|
.map(|(todo_keyword, _ws)| Into::<&str>::into(todo_keyword)),
|
||||||
title,
|
title,
|
||||||
tags: heading_tags,
|
tags: heading_tags,
|
||||||
children,
|
children,
|
||||||
@ -298,7 +303,16 @@ fn heading<'r, 's>(
|
|||||||
fn headline<'r, 's>(
|
fn headline<'r, 's>(
|
||||||
context: Context<'r, 's>,
|
context: Context<'r, 's>,
|
||||||
input: OrgSource<'s>,
|
input: OrgSource<'s>,
|
||||||
) -> Res<OrgSource<'s>, (usize, OrgSource<'s>, Vec<Object<'s>>, Vec<&'s str>)> {
|
) -> Res<
|
||||||
|
OrgSource<'s>,
|
||||||
|
(
|
||||||
|
usize,
|
||||||
|
OrgSource<'s>,
|
||||||
|
Option<(OrgSource<'s>, OrgSource<'s>)>,
|
||||||
|
Vec<Object<'s>>,
|
||||||
|
Vec<&'s str>,
|
||||||
|
),
|
||||||
|
> {
|
||||||
let parser_context =
|
let parser_context =
|
||||||
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||||
class: ExitClass::Document,
|
class: ExitClass::Document,
|
||||||
@ -306,10 +320,14 @@ fn headline<'r, 's>(
|
|||||||
}));
|
}));
|
||||||
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
let standard_set_object_matcher = parser_with_context!(standard_set_object)(&parser_context);
|
||||||
|
|
||||||
let (remaining, (_sol, star_count, ws, title, maybe_tags, _ws, _line_ending)) = tuple((
|
let (
|
||||||
|
remaining,
|
||||||
|
(_sol, star_count, ws, maybe_todo_keyword, title, maybe_tags, _ws, _line_ending),
|
||||||
|
) = tuple((
|
||||||
start_of_line,
|
start_of_line,
|
||||||
many1_count(tag("*")),
|
many1_count(tag("*")),
|
||||||
space1,
|
space1,
|
||||||
|
opt(tuple((heading_keyword, space1))),
|
||||||
many1(standard_set_object_matcher),
|
many1(standard_set_object_matcher),
|
||||||
opt(tuple((space0, tags))),
|
opt(tuple((space0, tags))),
|
||||||
space0,
|
space0,
|
||||||
@ -320,6 +338,7 @@ fn headline<'r, 's>(
|
|||||||
(
|
(
|
||||||
star_count,
|
star_count,
|
||||||
ws,
|
ws,
|
||||||
|
maybe_todo_keyword,
|
||||||
title,
|
title,
|
||||||
maybe_tags
|
maybe_tags
|
||||||
.map(|(_ws, tags)| {
|
.map(|(_ws, tags)| {
|
||||||
@ -357,6 +376,12 @@ fn single_tag<'r, 's>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>>
|
|||||||
})))(input)
|
})))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||||
|
fn heading_keyword<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, OrgSource<'s>> {
|
||||||
|
// TODO: This should take into account the value of "#+TODO:" ref https://orgmode.org/manual/Per_002dfile-keywords.html and possibly the configurable variable org-todo-keywords ref https://orgmode.org/manual/Workflow-states.html. Case is significant.
|
||||||
|
alt((tag("TODO"), tag("DONE")))(input)
|
||||||
|
}
|
||||||
|
|
||||||
impl<'s> Document<'s> {
|
impl<'s> Document<'s> {
|
||||||
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
pub fn iter_tokens<'r>(&'r self) -> impl Iterator<Item = Token<'r, 's>> {
|
||||||
AllTokensIterator::new(Token::Document(self))
|
AllTokensIterator::new(Token::Document(self))
|
||||||
|
Loading…
Reference in New Issue
Block a user