feat(headline): support custom keywords
This commit is contained in:
parent
88e1f8d62d
commit
c7de340479
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
use memchr::memchr2;
|
use memchr::memchr2;
|
||||||
|
|
||||||
|
const HEADLINE_DEFAULT_KEYWORDS: &'static [&'static str] =
|
||||||
|
&["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"];
|
||||||
|
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Headline<'a> {
|
pub struct Headline<'a> {
|
||||||
|
@ -35,10 +38,10 @@ impl<'a> Headline<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn parse_keyword(src: &'a str) -> Option<(&'a str, usize)> {
|
fn parse_keyword(src: &'a str, keywords: &'a [&'a str]) -> Option<(&'a str, usize)> {
|
||||||
let pos = memchr2(b' ', b'\n', src.as_bytes()).unwrap_or_else(|| src.len());
|
let pos = memchr2(b' ', b'\n', src.as_bytes()).unwrap_or_else(|| src.len());
|
||||||
let word = &src[0..pos];
|
let word = &src[0..pos];
|
||||||
if word.as_bytes().iter().all(|&c| c.is_ascii_uppercase()) && word != "COMMENT" {
|
if keywords.contains(&word) {
|
||||||
Some((word, pos))
|
Some((word, pos))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -74,6 +77,13 @@ impl<'a> Headline<'a> {
|
||||||
/// assert_eq!(hdl.keyword, Some("DONE"));
|
/// assert_eq!(hdl.keyword, Some("DONE"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn parse(src: &'a str) -> (Headline<'a>, usize, usize) {
|
pub fn parse(src: &'a str) -> (Headline<'a>, usize, usize) {
|
||||||
|
Self::parse_with_keywords(src, HEADLINE_DEFAULT_KEYWORDS)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_with_keywords(
|
||||||
|
src: &'a str,
|
||||||
|
keywords: &'a [&'a str],
|
||||||
|
) -> (Headline<'a>, usize, usize) {
|
||||||
let level = memchr2(b'\n', b' ', src.as_bytes()).unwrap_or_else(|| src.len());
|
let level = memchr2(b'\n', b' ', src.as_bytes()).unwrap_or_else(|| src.len());
|
||||||
|
|
||||||
debug_assert!(level > 0);
|
debug_assert!(level > 0);
|
||||||
|
@ -85,7 +95,7 @@ impl<'a> Headline<'a> {
|
||||||
|
|
||||||
let mut title_start = skip_space!(src, level);
|
let mut title_start = skip_space!(src, level);
|
||||||
|
|
||||||
let keyword = Headline::parse_keyword(&src[title_start..eol]).map(|(k, l)| {
|
let keyword = Headline::parse_keyword(&src[title_start..eol], keywords).map(|(k, l)| {
|
||||||
title_start += l;
|
title_start += l;
|
||||||
k
|
k
|
||||||
});
|
});
|
||||||
|
@ -153,8 +163,12 @@ impl<'a> Headline<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[cfg(test)]
|
||||||
fn parse() {
|
mod tests {
|
||||||
|
use super::Headline;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%:").0,
|
Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%:").0,
|
||||||
Headline {
|
Headline {
|
||||||
|
@ -235,18 +249,39 @@ fn parse() {
|
||||||
keyword: None,
|
keyword: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
assert_eq!(
|
||||||
fn is_commented() {
|
Headline::parse_with_keywords("**** TODO [#A] COMMENT Title :tag:a2%:", &[]).0,
|
||||||
|
Headline {
|
||||||
|
level: 4,
|
||||||
|
priority: None,
|
||||||
|
keyword: None,
|
||||||
|
title: "TODO [#A] COMMENT Title",
|
||||||
|
tags: Some(":tag:a2%:"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Headline::parse_with_keywords("**** TASK [#A] COMMENT Title :tag:a2%:", &["TASK"]).0,
|
||||||
|
Headline {
|
||||||
|
level: 4,
|
||||||
|
priority: Some('A'),
|
||||||
|
keyword: Some("TASK"),
|
||||||
|
title: "COMMENT Title",
|
||||||
|
tags: Some(":tag:a2%:"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_commented() {
|
||||||
assert!(Headline::parse("* COMMENT Title").0.is_commented());
|
assert!(Headline::parse("* COMMENT Title").0.is_commented());
|
||||||
assert!(!Headline::parse("* Title").0.is_commented());
|
assert!(!Headline::parse("* Title").0.is_commented());
|
||||||
assert!(!Headline::parse("* C0MMENT Title").0.is_commented());
|
assert!(!Headline::parse("* C0MMENT Title").0.is_commented());
|
||||||
assert!(!Headline::parse("* comment Title").0.is_commented());
|
assert!(!Headline::parse("* comment Title").0.is_commented());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn is_archived() {
|
fn is_archived() {
|
||||||
assert!(Headline::parse("* Title :ARCHIVE:").0.is_archived());
|
assert!(Headline::parse("* Title :ARCHIVE:").0.is_archived());
|
||||||
assert!(Headline::parse("* Title :tag:ARCHIVE:").0.is_archived());
|
assert!(Headline::parse("* Title :tag:ARCHIVE:").0.is_archived());
|
||||||
assert!(Headline::parse("* Title :ARCHIVE:tag:").0.is_archived());
|
assert!(Headline::parse("* Title :ARCHIVE:tag:").0.is_archived());
|
||||||
|
@ -254,10 +289,10 @@ fn is_archived() {
|
||||||
assert!(!Headline::parse("* Title :ARCHIVED:").0.is_archived());
|
assert!(!Headline::parse("* Title :ARCHIVED:").0.is_archived());
|
||||||
assert!(!Headline::parse("* Title :ARCHIVES:").0.is_archived());
|
assert!(!Headline::parse("* Title :ARCHIVES:").0.is_archived());
|
||||||
assert!(!Headline::parse("* Title :archive:").0.is_archived());
|
assert!(!Headline::parse("* Title :archive:").0.is_archived());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn find_level() {
|
fn find_level() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Headline::find_level(
|
Headline::find_level(
|
||||||
r#"
|
r#"
|
||||||
|
@ -268,4 +303,5 @@ fn find_level() {
|
||||||
),
|
),
|
||||||
10
|
10
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,6 +200,7 @@ pub struct Parser<'a> {
|
||||||
ele_buf: Option<(Element<'a>, usize)>,
|
ele_buf: Option<(Element<'a>, usize)>,
|
||||||
obj_buf: Option<(Object<'a>, usize)>,
|
obj_buf: Option<(Object<'a>, usize)>,
|
||||||
has_more_item: bool,
|
has_more_item: bool,
|
||||||
|
keywords: Option<&'a [&'a str]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
|
@ -212,6 +213,7 @@ impl<'a> Parser<'a> {
|
||||||
ele_buf: None,
|
ele_buf: None,
|
||||||
obj_buf: None,
|
obj_buf: None,
|
||||||
has_more_item: false,
|
has_more_item: false,
|
||||||
|
keywords: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,6 +227,10 @@ impl<'a> Parser<'a> {
|
||||||
self.stack.len()
|
self.stack.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_keywords(&mut self, keywords: &'a [&'a str]) {
|
||||||
|
self.keywords = Some(keywords)
|
||||||
|
}
|
||||||
|
|
||||||
fn next_sec_or_hdl(&mut self) -> Event<'a> {
|
fn next_sec_or_hdl(&mut self) -> Event<'a> {
|
||||||
let end = Headline::find_level(&self.text[self.off..], std::usize::MAX);
|
let end = Headline::find_level(&self.text[self.off..], std::usize::MAX);
|
||||||
debug_assert!(end <= self.text[self.off..].len());
|
debug_assert!(end <= self.text[self.off..].len());
|
||||||
|
@ -239,7 +245,11 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_hdl(&mut self) -> Event<'a> {
|
fn next_hdl(&mut self) -> Event<'a> {
|
||||||
let (hdl, off, end) = Headline::parse(&self.text[self.off..]);
|
let (hdl, off, end) = if let Some(keywords) = self.keywords {
|
||||||
|
Headline::parse_with_keywords(&self.text[self.off..], keywords)
|
||||||
|
} else {
|
||||||
|
Headline::parse(&self.text[self.off..])
|
||||||
|
};
|
||||||
debug_assert!(end <= self.text[self.off..].len());
|
debug_assert!(end <= self.text[self.off..].len());
|
||||||
self.stack.push(Container::Headline {
|
self.stack.push(Container::Headline {
|
||||||
beg: self.off + off,
|
beg: self.off + off,
|
||||||
|
|
Loading…
Reference in a new issue