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

View file

@ -144,7 +144,7 @@ pub struct Parser<'a> {
off: usize,
ele_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> {
@ -157,12 +157,12 @@ impl<'a> Parser<'a> {
off: 0,
ele_buf: None,
obj_buf: None,
keywords: DEFAULT_KEYWORDS,
todo_keywords: DEFAULT_TODO_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 {
text,
stack: Vec::new(),
@ -170,7 +170,7 @@ impl<'a> Parser<'a> {
off: 0,
ele_buf: None,
obj_buf: None,
keywords,
todo_keywords,
}
}
@ -184,9 +184,9 @@ impl<'a> Parser<'a> {
self.stack.len()
}
/// set keywords
pub fn set_keywords(&mut self, keywords: &'a [&'a str]) {
self.keywords = keywords;
/// set todo keywords
pub fn set_todo_keywords(&mut self, todo_keywords: &'a [&'a str]) {
self.todo_keywords = todo_keywords;
}
/// set text
@ -210,7 +210,7 @@ impl<'a> Parser<'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.off += off;
Event::HeadlineBeg(hdl)

View file

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