feat(config): add done_keywords field

This commit is contained in:
PoiScript 2019-07-30 21:09:29 +08:00
parent 3e82172dfe
commit b429e4f54a
6 changed files with 51 additions and 56 deletions

View file

@ -21,11 +21,11 @@ or `Org::parse_with_config`:
``` rust ``` rust
use orgize::{Org, ParseConfig}; use orgize::{Org, ParseConfig};
let org = Org::parse_with_config( Org::parse_with_config(
"* TASK Title 1", "* TASK Title 1",
ParseConfig { &ParseConfig {
// custom todo keywords // custom todo keywords
todo_keywords: &["TASK"], todo_keywords: vec!["TASK".to_string()],
..Default::default() ..Default::default()
}, },
); );
@ -64,7 +64,7 @@ assert_eq!(
); );
``` ```
## Render html with custom HtmlHandler ## Render html with custom `HtmlHandler`
To customize html rendering, simply implementing `HtmlHandler` trait and passing To customize html rendering, simply implementing `HtmlHandler` trait and passing
it to the `Org::html_with_handler` function. it to the `Org::html_with_handler` function.
@ -101,28 +101,26 @@ impl From<FromUtf8Error> for MyError {
} }
} }
struct MyHtmlHandler; struct MyHtmlHandler(DefaultHtmlHandler);
impl HtmlHandler<MyError> for MyHtmlHandler { impl HtmlHandler<MyError> for MyHtmlHandler {
fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
let mut default_handler = DefaultHtmlHandler;
match element { match element {
Element::Headline(headline) => { Element::Headline(headline) => {
if headline.level > 6 { if headline.level > 6 {
return Err(MyError::Heading); return Err(MyError::Heading);
} else { } else {
let slugify = slugify!(headline.title);
write!( write!(
w, w,
"<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>", "<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>",
headline.level, headline.level,
slugify, slugify!(headline.title),
Escape(headline.title), Escape(headline.title),
)?; )?;
} }
} }
// fallthrough to default handler // fallthrough to default handler
_ => default_handler.start(w, element)?, _ => self.0.start(w, element)?,
} }
Ok(()) Ok(())
} }
@ -130,7 +128,7 @@ impl HtmlHandler<MyError> for MyHtmlHandler {
fn main() -> Result<(), MyError> { fn main() -> Result<(), MyError> {
let mut writer = Vec::new(); let mut writer = Vec::new();
Org::parse("* title\n*section*").html_with_handler(&mut writer, MyHtmlHandler)?; Org::parse("* title\n*section*").html_with_handler(&mut writer, MyHtmlHandler(DefaultHtmlHandler))?;
assert_eq!( assert_eq!(
String::from_utf8(writer)?, String::from_utf8(writer)?,
@ -145,13 +143,13 @@ fn main() -> Result<(), MyError> {
**Note**: as I mentioned above, each element will appears two times while iterating. **Note**: as I mentioned above, each element will appears two times while iterating.
And handler will silently ignores all end events from non-container elements. And handler will silently ignores all end events from non-container elements.
So if you want to change how a non-container element renders, just redefine the start So if you want to change how a non-container element renders, just redefine the `start`
function and leave the end function untouched. function and leave the `end` function unchanged.
## Serde # Serde
`Org` struct have already implemented serde's `Serialize` trait. It means you can `Org` struct have already implemented serde's `Serialize` trait. It means you can
freely serialize it into any format that serde supports such as json: serialize it into any format supported by serde, such as json:
```rust ```rust
use orgize::Org; use orgize::Org;
@ -186,11 +184,11 @@ println!("{}", to_string(&org).unwrap());
## Features ## Features
By now, orgize provides three features: By now, orgize provides two features:
+ `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default. + `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
+ `chrono`: adds the ability to convert `Datetime` into `chrono` struct, disabled by default. + `chrono`: adds the ability to convert `Datetime` into `chrono` structs, disabled by default.
## License ## License

View file

@ -29,28 +29,26 @@ impl From<FromUtf8Error> for MyError {
} }
} }
struct MyHtmlHandler; struct MyHtmlHandler(DefaultHtmlHandler);
impl HtmlHandler<MyError> for MyHtmlHandler { impl HtmlHandler<MyError> for MyHtmlHandler {
fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
let mut default_handler = DefaultHtmlHandler;
match element { match element {
Element::Headline(headline) => { Element::Headline(headline) => {
if headline.level > 6 { if headline.level > 6 {
return Err(MyError::Heading); return Err(MyError::Heading);
} else { } else {
let slugify = slugify!(headline.title);
write!( write!(
w, w,
"<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>", "<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>",
headline.level, headline.level,
slugify, slugify!(headline.title),
Escape(headline.title), Escape(headline.title),
)?; )?;
} }
} }
// fallthrough to default handler // fallthrough to default handler
_ => default_handler.start(w, element)?, _ => self.0.start(w, element)?,
} }
Ok(()) Ok(())
} }
@ -65,7 +63,7 @@ fn main() -> Result<(), MyError> {
let contents = String::from_utf8(fs::read(&args[1])?)?; let contents = String::from_utf8(fs::read(&args[1])?)?;
let mut writer = Vec::new(); let mut writer = Vec::new();
Org::parse(&contents).html_with_handler(&mut writer, MyHtmlHandler)?; Org::parse(&contents).html_with_handler(&mut writer, MyHtmlHandler(DefaultHtmlHandler))?;
println!("{}", String::from_utf8(writer)?); println!("{}", String::from_utf8(writer)?);
} }

View file

@ -2,18 +2,18 @@
/// Parse configuration /// Parse configuration
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ParseConfig<'a> { pub struct ParseConfig {
/// Default headline todo keywords, it shouldn't be changed. /// Headline's TODO keywords, todo type
pub default_todo_keywords: &'a [&'a str], pub todo_keywords: Vec<String>,
/// Custom headline todo keywords /// Headline's TODO keywords, done type
pub todo_keywords: &'a [&'a str], pub done_keywords: Vec<String>,
} }
impl Default for ParseConfig<'_> { impl Default for ParseConfig {
fn default() -> Self { fn default() -> Self {
ParseConfig { ParseConfig {
default_todo_keywords: &["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"], todo_keywords: vec![String::from("TODO")],
todo_keywords: &[], done_keywords: vec![String::from("DONE")],
} }
} }
} }

View file

@ -27,7 +27,7 @@ pub struct Headline<'a> {
impl Headline<'_> { impl Headline<'_> {
pub(crate) fn parse<'a>( pub(crate) fn parse<'a>(
text: &'a str, text: &'a str,
config: &ParseConfig<'_>, config: &ParseConfig,
) -> (&'a str, Headline<'a>, &'a str) { ) -> (&'a str, Headline<'a>, &'a str) {
let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len()); let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len());
@ -67,7 +67,8 @@ impl Headline<'_> {
let (word, off) = memchr(b' ', tail.as_bytes()) let (word, off) = memchr(b' ', tail.as_bytes())
.map(|i| (&tail[0..i], i + 1)) .map(|i| (&tail[0..i], i + 1))
.unwrap_or_else(|| (tail, tail.len())); .unwrap_or_else(|| (tail, tail.len()));
if config.todo_keywords.contains(&word) || config.default_todo_keywords.contains(&word) if config.todo_keywords.iter().any(|x| x == word)
|| config.done_keywords.iter().any(|x| x == word)
{ {
(Some(word), &tail[off..]) (Some(word), &tail[off..])
} else { } else {
@ -151,7 +152,7 @@ impl Headline<'_> {
#[cfg(test)] #[cfg(test)]
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref CONFIG: ParseConfig<'static> = ParseConfig::default(); static ref CONFIG: ParseConfig = ParseConfig::default();
} }
#[test] #[test]
@ -276,7 +277,7 @@ fn parse_todo_keywords() {
Headline::parse( Headline::parse(
"**** DONE [#A] COMMENT Title :tag:a2%:", "**** DONE [#A] COMMENT Title :tag:a2%:",
&ParseConfig { &ParseConfig {
default_todo_keywords: &[], done_keywords: vec![],
..Default::default() ..Default::default()
} }
), ),
@ -296,7 +297,7 @@ fn parse_todo_keywords() {
Headline::parse( Headline::parse(
"**** TASK [#A] COMMENT Title :tag:a2%:", "**** TASK [#A] COMMENT Title :tag:a2%:",
&ParseConfig { &ParseConfig {
todo_keywords: &["TASK"], todo_keywords: vec!["TASK".to_string()],
..Default::default() ..Default::default()
} }
), ),

View file

@ -21,9 +21,9 @@
//! //!
//! Org::parse_with_config( //! Org::parse_with_config(
//! "* TASK Title 1", //! "* TASK Title 1",
//! ParseConfig { //! &ParseConfig {
//! // custom todo keywords //! // custom todo keywords
//! todo_keywords: &["TASK"], //! todo_keywords: vec!["TASK".to_string()],
//! ..Default::default() //! ..Default::default()
//! }, //! },
//! ); //! );
@ -72,7 +72,7 @@
//! ); //! );
//! ``` //! ```
//! //!
//! # Render html with custom HtmlHandler //! # Render html with custom `HtmlHandler`
//! //!
//! To customize html rendering, simply implementing [`HtmlHandler`] trait and passing //! To customize html rendering, simply implementing [`HtmlHandler`] trait and passing
//! it to the [`Org::html_with_handler`] function. //! it to the [`Org::html_with_handler`] function.
@ -112,28 +112,26 @@
//! } //! }
//! } //! }
//! //!
//! struct MyHtmlHandler; //! struct MyHtmlHandler(DefaultHtmlHandler);
//! //!
//! impl HtmlHandler<MyError> for MyHtmlHandler { //! impl HtmlHandler<MyError> for MyHtmlHandler {
//! fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> { //! fn start<W: Write>(&mut self, mut w: W, element: &Element<'_>) -> Result<(), MyError> {
//! let mut default_handler = DefaultHtmlHandler;
//! match element { //! match element {
//! Element::Headline(headline) => { //! Element::Headline(headline) => {
//! if headline.level > 6 { //! if headline.level > 6 {
//! return Err(MyError::Heading); //! return Err(MyError::Heading);
//! } else { //! } else {
//! let slugify = slugify!(headline.title);
//! write!( //! write!(
//! w, //! w,
//! "<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>", //! "<h{0}><a id=\"{1}\" href=\"#{1}\">{2}</a></h{0}>",
//! headline.level, //! headline.level,
//! slugify, //! slugify!(headline.title),
//! Escape(headline.title), //! Escape(headline.title),
//! )?; //! )?;
//! } //! }
//! } //! }
//! // fallthrough to default handler //! // fallthrough to default handler
//! _ => default_handler.start(w, element)?, //! _ => self.0.start(w, element)?,
//! } //! }
//! Ok(()) //! Ok(())
//! } //! }
@ -141,7 +139,7 @@
//! //!
//! fn main() -> Result<(), MyError> { //! fn main() -> Result<(), MyError> {
//! let mut writer = Vec::new(); //! let mut writer = Vec::new();
//! Org::parse("* title\n*section*").html_with_handler(&mut writer, MyHtmlHandler)?; //! Org::parse("* title\n*section*").html_with_handler(&mut writer, MyHtmlHandler(DefaultHtmlHandler))?;
//! //!
//! assert_eq!( //! assert_eq!(
//! String::from_utf8(writer)?, //! String::from_utf8(writer)?,
@ -156,13 +154,13 @@
//! **Note**: as I mentioned above, each element will appears two times while iterating. //! **Note**: as I mentioned above, each element will appears two times while iterating.
//! And handler will silently ignores all end events from non-container elements. //! And handler will silently ignores all end events from non-container elements.
//! //!
//! So if you want to change how a non-container element renders, just redefine the start //! So if you want to change how a non-container element renders, just redefine the `start`
//! function and leave the end function untouched. //! function and leave the `end` function unchanged.
//! //!
//! # Serde //! # Serde
//! //!
//! `Org` struct have already implemented serde's `Serialize` trait. It means you can //! `Org` struct have already implemented serde's `Serialize` trait. It means you can
//! freely serialize it into any format that serde supports such as json: //! serialize it into any format supported by serde, such as json:
//! //!
//! ```rust //! ```rust
//! use orgize::Org; //! use orgize::Org;
@ -197,11 +195,11 @@
//! //!
//! # Features //! # Features
//! //!
//! By now, orgize provides three features: //! By now, orgize provides two features:
//! //!
//! + `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default. //! + `serde`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
//! //!
//! + `chrono`: adds the ability to convert `Datetime` into `chrono` struct, disabled by default. //! + `chrono`: adds the ability to convert `Datetime` into `chrono` structs, disabled by default.
//! //!
//! # License //! # License
//! //!
@ -209,11 +207,11 @@
#![allow(clippy::range_plus_one)] #![allow(clippy::range_plus_one)]
pub mod config; mod config;
pub mod elements; pub mod elements;
pub mod export; pub mod export;
pub mod iter; mod iter;
pub mod org; mod org;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
mod serde; mod serde;

View file

@ -44,10 +44,10 @@ enum Container<'a> {
impl<'a> Org<'a> { impl<'a> Org<'a> {
pub fn parse(text: &'a str) -> Self { pub fn parse(text: &'a str) -> Self {
Org::parse_with_config(text, ParseConfig::default()) Org::parse_with_config(text, &ParseConfig::default())
} }
pub fn parse_with_config(content: &'a str, config: ParseConfig<'_>) -> Self { pub fn parse_with_config(content: &'a str, config: &ParseConfig) -> Self {
let mut arena = Arena::new(); let mut arena = Arena::new();
let document = arena.new_node(Element::Document); let document = arena.new_node(Element::Document);
@ -238,7 +238,7 @@ fn parse_element<'a>(
} }
if tail.starts_with(':') { if tail.starts_with(':') {
if let Some((tail, drawer, content)) = Drawer::parse(tail) { if let Some((tail, drawer, _content)) = Drawer::parse(tail) {
return Some((tail, arena.new_node(drawer))); return Some((tail, arena.new_node(drawer)));
} }
} }
@ -404,7 +404,7 @@ fn parse_object<'a>(
.ok() .ok()
.map(|(tail, element)| (tail, arena.new_node(element))), .map(|(tail, element)| (tail, arena.new_node(element))),
b'<' => RadioTarget::parse(contents) b'<' => RadioTarget::parse(contents)
.map(|(tail, (radio, content))| (tail, radio)) .map(|(tail, (radio, _content))| (tail, radio))
.or_else(|_| Target::parse(contents)) .or_else(|_| Target::parse(contents))
.or_else(|_| { .or_else(|_| {
Timestamp::parse_active(contents).map(|(tail, timestamp)| (tail, timestamp.into())) Timestamp::parse_active(contents).map(|(tail, timestamp)| (tail, timestamp.into()))