Merge branch 'headline_properties'
This commit is contained in:
commit
2a003b85fd
@ -0,0 +1,2 @@
|
||||
<<<Footnotes>>> and stuff
|
||||
* Footnotes
|
@ -0,0 +1,3 @@
|
||||
* FOOTNOTES
|
||||
* Footnotes
|
||||
* footnotes
|
@ -0,0 +1,2 @@
|
||||
* Footnotes
|
||||
* Footnotes
|
@ -0,0 +1,3 @@
|
||||
* Foo
|
||||
* Footnotes :foo:bar:
|
||||
* Footnotes and stuff
|
@ -9,6 +9,7 @@ use super::sexp::unquote;
|
||||
use super::sexp::Token;
|
||||
use super::util::compare_standard_properties;
|
||||
use super::util::get_property;
|
||||
use super::util::get_property_boolean;
|
||||
use super::util::get_property_quoted_string;
|
||||
use super::util::get_property_unquoted_atom;
|
||||
use crate::types::AngleLink;
|
||||
@ -714,7 +715,29 @@ fn compare_heading<'s>(
|
||||
(None, false) | (Some(_), true) => {}
|
||||
}
|
||||
|
||||
// TODO: Compare :pre-blank :raw-value :footnote-section-p :scheduled :closed
|
||||
// Compare raw-value
|
||||
let raw_value = get_property_quoted_string(emacs, ":raw-value")?
|
||||
.ok_or("Headlines should have :raw-value.")?;
|
||||
let rust_raw_value = rust.get_raw_value();
|
||||
if raw_value != rust_raw_value {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"raw-value mismatch (emacs != rust) {:?} != {:?}",
|
||||
raw_value, rust_raw_value
|
||||
));
|
||||
}
|
||||
|
||||
// Compare footnote-section-p
|
||||
let footnote_section = get_property_boolean(emacs, ":footnote-section-p")?;
|
||||
if footnote_section != rust.is_footnote_section {
|
||||
this_status = DiffStatus::Bad;
|
||||
message = Some(format!(
|
||||
"footnote section mismatch (emacs != rust) {:?} != {:?}",
|
||||
footnote_section, rust.is_footnote_section
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: Compare :pre-blank :scheduled :closed
|
||||
//
|
||||
// :scheduled and :closed seem to only appear when the headline has a planning
|
||||
|
||||
|
@ -217,3 +217,19 @@ pub(crate) fn get_property_quoted_string<'s, 'x>(
|
||||
.map(unquote)
|
||||
.map_or(Ok(None), |r| r.map(Some))?)
|
||||
}
|
||||
|
||||
/// Get a named property containing a boolean value.
|
||||
///
|
||||
/// This uses the elisp convention of nil == false, non-nil == true.
|
||||
///
|
||||
/// Returns false if key is not found.
|
||||
pub(crate) fn get_property_boolean<'s, 'x>(
|
||||
emacs: &'s Token<'s>,
|
||||
key: &'x str,
|
||||
) -> Result<bool, Box<dyn std::error::Error>> {
|
||||
Ok(get_property(emacs, key)?
|
||||
.map(Token::as_atom)
|
||||
.map_or(Ok(None), |r| r.map(Some))?
|
||||
.unwrap_or("nil")
|
||||
!= "nil")
|
||||
}
|
||||
|
@ -27,6 +27,11 @@ pub struct GlobalSettings<'g, 's> {
|
||||
///
|
||||
/// Corresponds to org-odd-levels-only elisp variable.
|
||||
pub odd_levels_only: HeadlineLevelFilter,
|
||||
|
||||
/// If a headline title matches this string exactly, then that section will become a "footnote section".
|
||||
///
|
||||
/// Corresponds to org-footnote-section elisp variable.
|
||||
pub footnote_section: &'g str,
|
||||
}
|
||||
|
||||
pub const DEFAULT_TAB_WIDTH: IndentationLevel = 8;
|
||||
@ -43,6 +48,7 @@ impl<'g, 's> GlobalSettings<'g, 's> {
|
||||
list_allow_alphabetical: false,
|
||||
tab_width: DEFAULT_TAB_WIDTH,
|
||||
odd_levels_only: HeadlineLevelFilter::default(),
|
||||
footnote_section: "Footnotes",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ use nom::bytes::complete::tag;
|
||||
use nom::character::complete::anychar;
|
||||
use nom::character::complete::space0;
|
||||
use nom::character::complete::space1;
|
||||
use nom::combinator::consumed;
|
||||
use nom::combinator::map;
|
||||
use nom::combinator::not;
|
||||
use nom::combinator::opt;
|
||||
@ -55,20 +56,9 @@ fn _heading<'b, 'g, 'r, 's>(
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<OrgSource<'s>, Heading<'s>> {
|
||||
not(|i| context.check_exit_matcher(i))(input)?;
|
||||
let (
|
||||
remaining,
|
||||
(
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword,
|
||||
maybe_priority,
|
||||
maybe_comment,
|
||||
title,
|
||||
heading_tags,
|
||||
),
|
||||
) = headline(context, input, parent_star_count)?;
|
||||
let (remaining, pre_headline) = headline(context, input, parent_star_count)?;
|
||||
let section_matcher = parser_with_context!(section)(context);
|
||||
let heading_matcher = parser_with_context!(heading(star_count))(context);
|
||||
let heading_matcher = parser_with_context!(heading(pre_headline.star_count))(context);
|
||||
let (remaining, maybe_section) =
|
||||
opt(map(section_matcher, DocumentElement::Section))(remaining)?;
|
||||
let (remaining, _ws) = opt(tuple((start_of_line, many0(blank_line))))(remaining)?;
|
||||
@ -84,23 +74,26 @@ fn _heading<'b, 'g, 'r, 's>(
|
||||
} else {
|
||||
remaining
|
||||
};
|
||||
let is_archived = heading_tags.contains(&"ARCHIVE");
|
||||
let is_archived = pre_headline.tags.contains(&"ARCHIVE");
|
||||
|
||||
let source = get_consumed(input, remaining);
|
||||
Ok((
|
||||
remaining,
|
||||
Heading {
|
||||
source: source.into(),
|
||||
level: headline_level,
|
||||
todo_keyword: maybe_todo_keyword.map(|(todo_keyword_type, todo_keyword)| {
|
||||
level: pre_headline.headline_level,
|
||||
todo_keyword: pre_headline
|
||||
.todo_keyword
|
||||
.map(|(todo_keyword_type, todo_keyword)| {
|
||||
(todo_keyword_type, Into::<&str>::into(todo_keyword))
|
||||
}),
|
||||
priority_cookie: maybe_priority.map(|(_, priority)| priority),
|
||||
title,
|
||||
tags: heading_tags,
|
||||
priority_cookie: pre_headline.priority_cookie.map(|(_, priority)| priority),
|
||||
title: pre_headline.title,
|
||||
tags: pre_headline.tags,
|
||||
children,
|
||||
is_comment: maybe_comment.is_some(),
|
||||
is_comment: pre_headline.comment.is_some(),
|
||||
is_archived,
|
||||
is_footnote_section: pre_headline.is_footnote_section,
|
||||
},
|
||||
))
|
||||
}
|
||||
@ -111,23 +104,26 @@ pub(crate) fn detect_headline<'s>(input: OrgSource<'s>) -> Res<OrgSource<'s>, ()
|
||||
Ok((input, ()))
|
||||
}
|
||||
|
||||
/// Fields from a not-yet-fully-parsed Headline.
|
||||
///
|
||||
/// This struct exists to give names to the fields of a partially-parsed Headline to avoid returning a large tuple of nameless fields.
|
||||
struct PreHeadline<'s> {
|
||||
headline_level: HeadlineLevel,
|
||||
star_count: HeadlineLevel,
|
||||
todo_keyword: Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||
priority_cookie: Option<(OrgSource<'s>, PriorityCookie)>,
|
||||
comment: Option<OrgSource<'s>>,
|
||||
title: Vec<Object<'s>>,
|
||||
tags: Vec<&'s str>,
|
||||
is_footnote_section: bool,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
|
||||
fn headline<'b, 'g, 'r, 's>(
|
||||
context: RefContext<'b, 'g, 'r, 's>,
|
||||
input: OrgSource<'s>,
|
||||
parent_star_count: HeadlineLevel,
|
||||
) -> Res<
|
||||
OrgSource<'s>,
|
||||
(
|
||||
HeadlineLevel,
|
||||
HeadlineLevel,
|
||||
Option<(TodoKeywordType, OrgSource<'s>)>,
|
||||
Option<(OrgSource<'s>, PriorityCookie)>,
|
||||
Option<OrgSource<'s>>,
|
||||
Vec<Object<'s>>,
|
||||
Vec<&'s str>,
|
||||
),
|
||||
> {
|
||||
) -> Res<OrgSource<'s>, PreHeadline<'s>> {
|
||||
let parser_context = ContextElement::ExitMatcherNode(ExitMatcherNode {
|
||||
class: ExitClass::Document,
|
||||
exit_matcher: &headline_title_end,
|
||||
@ -159,30 +155,43 @@ fn headline<'b, 'g, 'r, 's>(
|
||||
|
||||
let (remaining, maybe_title) = opt(tuple((
|
||||
space1,
|
||||
many1(parser_with_context!(standard_set_object)(&parser_context)),
|
||||
consumed(many1(parser_with_context!(standard_set_object)(
|
||||
&parser_context,
|
||||
))),
|
||||
)))(remaining)?;
|
||||
|
||||
let (remaining, maybe_tags) = opt(tuple((space0, tags)))(remaining)?;
|
||||
|
||||
let (remaining, _) = tuple((space0, org_line_ending))(remaining)?;
|
||||
|
||||
let is_footnote_section = maybe_title
|
||||
.as_ref()
|
||||
.map(|(_, (raw_title, _))| raw_title)
|
||||
.map(|raw_title| {
|
||||
Into::<&str>::into(raw_title) == context.get_global_settings().footnote_section
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok((
|
||||
remaining,
|
||||
(
|
||||
PreHeadline {
|
||||
headline_level,
|
||||
star_count,
|
||||
maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||
maybe_priority,
|
||||
maybe_comment.map(|(_, comment, _)| comment),
|
||||
maybe_title.map(|(_, title)| title).unwrap_or(Vec::new()),
|
||||
maybe_tags
|
||||
todo_keyword: maybe_todo_keyword.map(|(_, todo, _)| todo),
|
||||
priority_cookie: maybe_priority,
|
||||
comment: maybe_comment.map(|(_, comment, _)| comment),
|
||||
title: maybe_title
|
||||
.map(|(_, (_, title))| title)
|
||||
.unwrap_or(Vec::new()),
|
||||
tags: maybe_tags
|
||||
.map(|(_ws, tags)| {
|
||||
tags.into_iter()
|
||||
.map(|single_tag| Into::<&str>::into(single_tag))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or(Vec::new()),
|
||||
),
|
||||
is_footnote_section,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,7 @@ pub struct Heading<'s> {
|
||||
pub children: Vec<DocumentElement<'s>>,
|
||||
pub is_comment: bool,
|
||||
pub is_archived: bool,
|
||||
pub is_footnote_section: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -74,3 +75,15 @@ impl<'s> StandardProperties<'s> for Heading<'s> {
|
||||
self.source
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Heading<'s> {
|
||||
pub fn get_raw_value(&self) -> String {
|
||||
// TODO: I think this could just return a string slice instead of an owned string.
|
||||
let title_source: String = self
|
||||
.title
|
||||
.iter()
|
||||
.map(|obj| obj.get_standard_properties().get_source())
|
||||
.collect();
|
||||
title_source
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user