refactor(headline): store headline tags into Vec

This commit is contained in:
PoiScript 2019-04-24 18:12:51 +08:00
parent 56e289fb48
commit d9053d992d
3 changed files with 151 additions and 183 deletions

View file

@ -3,7 +3,7 @@
use jetscii::ByteSubstring; use jetscii::ByteSubstring;
use memchr::{memchr, memchr2, memrchr}; use memchr::{memchr, memchr2, memrchr};
pub(crate) const DEFAULT_KEYWORDS: &[&str] = pub(crate) const DEFAULT_TODO_KEYWORDS: &[&str] =
&["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"]; &["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"];
#[cfg_attr(test, derive(PartialEq))] #[cfg_attr(test, derive(PartialEq))]
@ -14,7 +14,7 @@ pub struct Headline<'a> {
/// priority cookie /// priority cookie
pub priority: Option<char>, pub priority: Option<char>,
/// headline tags, including the sparated colons /// headline tags, including the sparated colons
pub tags: Option<&'a str>, pub tags: Vec<&'a str>,
/// headline title /// headline title
pub title: &'a str, pub title: &'a str,
/// headline keyword /// headline keyword
@ -48,7 +48,7 @@ impl<'a> Headline<'a> {
keyword: None, keyword: None,
priority: None, priority: None,
title: "", title: "",
tags: None, tags: Vec::new(),
}, },
off, off,
end, end,
@ -86,12 +86,12 @@ impl<'a> Headline<'a> {
let (title, tags) = if let Some(i) = memrchr(b' ', tail.as_bytes()) { let (title, tags) = if let Some(i) = memrchr(b' ', tail.as_bytes()) {
let last = &tail[i + 1..]; let last = &tail[i + 1..];
if last.len() > 2 && last.starts_with(':') && last.ends_with(':') { if last.len() > 2 && last.starts_with(':') && last.ends_with(':') {
(tail[..i].trim(), Some(last)) (tail[..i].trim(), last)
} else { } else {
(tail, None) (tail, "")
} }
} else { } else {
(tail, None) (tail, "")
}; };
( (
@ -100,7 +100,7 @@ impl<'a> Headline<'a> {
keyword, keyword,
priority, priority,
title, title,
tags, tags: tags.split(':').filter(|s| !s.is_empty()).collect(),
}, },
off, off,
end, end,
@ -138,99 +138,96 @@ impl<'a> Headline<'a> {
/// checks if this headline is "archived" /// checks if this headline is "archived"
pub fn is_archived(&self) -> bool { pub fn is_archived(&self) -> bool {
self.tags self.tags.contains(&"ARCHIVE")
.map(|tags| tags[1..].split_terminator(':').any(|t| t == "ARCHIVE"))
.unwrap_or(false)
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test] #[test]
fn parse() { fn parse() {
assert_eq!( assert_eq!(
Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%:", DEFAULT_KEYWORDS).0, Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%:", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: Some('A'), priority: Some('A'),
keyword: Some("TODO"), keyword: Some("TODO"),
title: "COMMENT Title", title: "COMMENT Title",
tags: Some(":tag:a2%:"), tags: vec!["tag", "a2%"],
}, },
); );
assert_eq!( assert_eq!(
Headline::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", DEFAULT_KEYWORDS).0, Headline::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: None, priority: None,
tags: Some(":tag:a2%:"), tags: vec!["tag", "a2%"],
title: "ToDO [#A] COMMENT Title", title: "ToDO [#A] COMMENT Title",
keyword: None, keyword: None,
}, },
); );
assert_eq!( assert_eq!(
Headline::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", DEFAULT_KEYWORDS).0, Headline::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: None, priority: None,
tags: Some(":tag:a2%:"), tags: vec!["tag", "a2%"],
title: "T0DO [#A] COMMENT Title", title: "T0DO [#A] COMMENT Title",
keyword: None, keyword: None,
}, },
); );
assert_eq!( assert_eq!(
Headline::parse("**** TODO [#1] COMMENT Title :tag:a2%:", DEFAULT_KEYWORDS).0, Headline::parse("**** TODO [#1] COMMENT Title :tag:a2%:", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: None, priority: None,
tags: Some(":tag:a2%:"), tags: vec!["tag", "a2%"],
title: "[#1] COMMENT Title", title: "[#1] COMMENT Title",
keyword: Some("TODO") keyword: Some("TODO")
}, },
); );
assert_eq!( assert_eq!(
Headline::parse("**** TODO [#a] COMMENT Title :tag:a2%:", DEFAULT_KEYWORDS).0, Headline::parse("**** TODO [#a] COMMENT Title :tag:a2%:", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: None, priority: None,
tags: Some(":tag:a2%:"), tags: vec!["tag", "a2%"],
title: "[#a] COMMENT Title", title: "[#a] COMMENT Title",
keyword: Some("TODO") keyword: Some("TODO")
}, },
); );
assert_eq!( assert_eq!(
Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%", DEFAULT_KEYWORDS).0, Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: Some('A'), priority: Some('A'),
tags: None, tags: Vec::new(),
title: "COMMENT Title :tag:a2%", title: "COMMENT Title :tag:a2%",
keyword: Some("TODO"), keyword: Some("TODO"),
}, },
); );
assert_eq!( assert_eq!(
Headline::parse("**** TODO [#A] COMMENT Title tag:a2%:", DEFAULT_KEYWORDS).0, Headline::parse("**** TODO [#A] COMMENT Title tag:a2%:", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: Some('A'), priority: Some('A'),
tags: None, tags: Vec::new(),
title: "COMMENT Title tag:a2%:", title: "COMMENT Title tag:a2%:",
keyword: Some("TODO"), keyword: Some("TODO"),
}, },
); );
assert_eq!( assert_eq!(
Headline::parse("**** COMMENT Title tag:a2%:", DEFAULT_KEYWORDS).0, Headline::parse("**** COMMENT Title tag:a2%:", &["TODO"]).0,
Headline { Headline {
level: 4, level: 4,
priority: None, priority: None,
tags: None, tags: Vec::new(),
title: "COMMENT Title tag:a2%:", title: "COMMENT Title tag:a2%:",
keyword: None, keyword: None,
}, },
); );
}
#[test]
fn parse_todo_keywords() {
assert_eq!( assert_eq!(
Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%:", &[]).0, Headline::parse("**** TODO [#A] COMMENT Title :tag:a2%:", &[]).0,
Headline { Headline {
@ -238,7 +235,7 @@ mod tests {
priority: None, priority: None,
keyword: None, keyword: None,
title: "TODO [#A] COMMENT Title", title: "TODO [#A] COMMENT Title",
tags: Some(":tag:a2%:"), tags: vec!["tag", "a2%"],
}, },
); );
assert_eq!( assert_eq!(
@ -248,63 +245,34 @@ mod tests {
priority: Some('A'), priority: Some('A'),
keyword: Some("TASK"), keyword: Some("TASK"),
title: "COMMENT Title", title: "COMMENT Title",
tags: Some(":tag:a2%:"), tags: vec!["tag", "a2%"],
}, },
); );
} }
#[test] #[test]
fn is_commented() { fn is_commented() {
assert!(Headline::parse("* COMMENT Title", DEFAULT_KEYWORDS) assert!(Headline::parse("* COMMENT Title", &[]).0.is_commented());
.0 assert!(!Headline::parse("* Title", &[]).0.is_commented());
.is_commented()); assert!(!Headline::parse("* C0MMENT Title", &[]).0.is_commented());
assert!(!Headline::parse("* Title", DEFAULT_KEYWORDS) assert!(!Headline::parse("* comment Title", &[]).0.is_commented());
.0
.is_commented());
assert!(!Headline::parse("* C0MMENT Title", DEFAULT_KEYWORDS)
.0
.is_commented());
assert!(!Headline::parse("* comment Title", DEFAULT_KEYWORDS)
.0
.is_commented());
} }
#[test] #[test]
fn is_archived() { fn is_archived() {
assert!(Headline::parse("* Title :ARCHIVE:", DEFAULT_KEYWORDS) assert!(Headline::parse("* Title :ARCHIVE:", &[]).0.is_archived());
.0 assert!(Headline::parse("* Title :t:ARCHIVE:", &[]).0.is_archived());
.is_archived()); assert!(Headline::parse("* Title :ARCHIVE:t:", &[]).0.is_archived());
assert!(Headline::parse("* Title :tag:ARCHIVE:", DEFAULT_KEYWORDS) assert!(!Headline::parse("* Title", &[]).0.is_commented());
.0 assert!(!Headline::parse("* Title :ARCHIVED:", &[]).0.is_archived());
.is_archived()); assert!(!Headline::parse("* Title :ARCHIVES:", &[]).0.is_archived());
assert!(Headline::parse("* Title :ARCHIVE:tag:", DEFAULT_KEYWORDS) assert!(!Headline::parse("* Title :archive:", &[]).0.is_archived());
.0
.is_archived());
assert!(!Headline::parse("* Title", DEFAULT_KEYWORDS)
.0
.is_commented());
assert!(!Headline::parse("* Title :ARCHIVED:", DEFAULT_KEYWORDS)
.0
.is_archived());
assert!(!Headline::parse("* Title :ARCHIVES:", DEFAULT_KEYWORDS)
.0
.is_archived());
assert!(!Headline::parse("* Title :archive:", DEFAULT_KEYWORDS)
.0
.is_archived());
} }
#[test] #[test]
fn find_level() { fn find_level() {
assert_eq!( assert_eq!(
Headline::find_level( Headline::find_level("\n** Title\n* Title\n** Title\n", 1),
r#" "\n** Title\n".len()
** Title
* Title
** Title"#,
1
),
10
); );
} }
}

View file

@ -144,7 +144,7 @@ pub struct Parser<'a> {
off: usize, off: usize,
ele_buf: Option<(Event<'a>, usize, usize, usize)>, ele_buf: Option<(Event<'a>, usize, usize, usize)>,
obj_buf: Option<(Event<'a>, usize, usize, usize)>, obj_buf: Option<(Event<'a>, usize, usize, usize)>,
keywords: &'a [&'a str], todo_keywords: &'a [&'a str],
} }
impl<'a> Parser<'a> { impl<'a> Parser<'a> {
@ -157,12 +157,12 @@ impl<'a> Parser<'a> {
off: 0, off: 0,
ele_buf: None, ele_buf: None,
obj_buf: None, obj_buf: None,
keywords: DEFAULT_KEYWORDS, todo_keywords: DEFAULT_TODO_KEYWORDS,
} }
} }
/// creates a new parser from string, with the specified keywords /// creates a new parser from string, with the specified keywords
pub fn with_keywrods(text: &'a str, keywords: &'a [&'a str]) -> Parser<'a> { pub fn with_todo_keywrods(text: &'a str, todo_keywords: &'a [&'a str]) -> Parser<'a> {
Parser { Parser {
text, text,
stack: Vec::new(), stack: Vec::new(),
@ -170,7 +170,7 @@ impl<'a> Parser<'a> {
off: 0, off: 0,
ele_buf: None, ele_buf: None,
obj_buf: None, obj_buf: None,
keywords, todo_keywords,
} }
} }
@ -184,9 +184,9 @@ impl<'a> Parser<'a> {
self.stack.len() self.stack.len()
} }
/// set keywords /// set todo keywords
pub fn set_keywords(&mut self, keywords: &'a [&'a str]) { pub fn set_todo_keywords(&mut self, todo_keywords: &'a [&'a str]) {
self.keywords = keywords; self.todo_keywords = todo_keywords;
} }
/// set text /// set text
@ -210,7 +210,7 @@ impl<'a> Parser<'a> {
} }
fn next_headline(&mut self, text: &'a str) -> Event<'a> { fn next_headline(&mut self, text: &'a str) -> Event<'a> {
let (hdl, off, end) = Headline::parse(text, self.keywords); let (hdl, off, end) = Headline::parse(text, self.todo_keywords);
self.push_stack(Container::Headline(self.off + off), end, end); self.push_stack(Container::Headline(self.off + off), end, end);
self.off += off; self.off += off;
Event::HeadlineBeg(hdl) Event::HeadlineBeg(hdl)

View file

@ -1,5 +1,5 @@
use crate::elements::{fn_def, Keyword}; use crate::elements::{fn_def, Keyword};
use crate::headline::{Headline, DEFAULT_KEYWORDS}; use crate::headline::{Headline, DEFAULT_TODO_KEYWORDS};
use memchr::memchr; use memchr::memchr;
type Headlines<'a> = Vec<Headline<'a>>; type Headlines<'a> = Vec<Headline<'a>>;
@ -13,7 +13,7 @@ pub fn metadata(src: &str) -> (Headlines<'_>, Keywords<'_>, Footnotes<'_>) {
if line.starts_with('*') { if line.starts_with('*') {
let level = memchr(b' ', line.as_bytes()).unwrap_or_else(|| line.len()); let level = memchr(b' ', line.as_bytes()).unwrap_or_else(|| line.len());
if line.as_bytes()[0..level].iter().all(|&c| c == b'*') { if line.as_bytes()[0..level].iter().all(|&c| c == b'*') {
headlines.push(Headline::parse(line, DEFAULT_KEYWORDS).0) headlines.push(Headline::parse(line, DEFAULT_TODO_KEYWORDS).0)
} }
} else if line.starts_with("#+") { } else if line.starts_with("#+") {
if let Some((key, _, value, _)) = Keyword::parse(line) { if let Some((key, _, value, _)) = Keyword::parse(line) {