feat(headline): support custom keywords

This commit is contained in:
PoiScript 2019-02-15 11:32:52 +08:00
parent 88e1f8d62d
commit c7de340479
2 changed files with 159 additions and 113 deletions

View file

@ -2,6 +2,9 @@
use memchr::memchr2;
const HEADLINE_DEFAULT_KEYWORDS: &'static [&'static str] =
&["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"];
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug)]
pub struct Headline<'a> {
@ -35,10 +38,10 @@ impl<'a> Headline<'a> {
}
#[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 word = &src[0..pos];
if word.as_bytes().iter().all(|&c| c.is_ascii_uppercase()) && word != "COMMENT" {
if keywords.contains(&word) {
Some((word, pos))
} else {
None
@ -74,6 +77,13 @@ impl<'a> Headline<'a> {
/// assert_eq!(hdl.keyword, Some("DONE"));
/// ```
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());
debug_assert!(level > 0);
@ -85,7 +95,7 @@ impl<'a> Headline<'a> {
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;
k
});
@ -153,6 +163,10 @@ impl<'a> Headline<'a> {
}
}
#[cfg(test)]
mod tests {
use super::Headline;
#[test]
fn parse() {
assert_eq!(
@ -235,6 +249,27 @@ fn parse() {
keyword: None,
},
);
assert_eq!(
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]
@ -269,3 +304,4 @@ fn find_level() {
10
);
}
}

View file

@ -200,6 +200,7 @@ pub struct Parser<'a> {
ele_buf: Option<(Element<'a>, usize)>,
obj_buf: Option<(Object<'a>, usize)>,
has_more_item: bool,
keywords: Option<&'a [&'a str]>,
}
impl<'a> Parser<'a> {
@ -212,6 +213,7 @@ impl<'a> Parser<'a> {
ele_buf: None,
obj_buf: None,
has_more_item: false,
keywords: None,
}
}
@ -225,6 +227,10 @@ impl<'a> Parser<'a> {
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> {
let end = Headline::find_level(&self.text[self.off..], std::usize::MAX);
debug_assert!(end <= self.text[self.off..].len());
@ -239,7 +245,11 @@ impl<'a> Parser<'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());
self.stack.push(Container::Headline {
beg: self.off + off,