From 6b0f468f9fc1a62879f644f1d78205c1a2c9da01 Mon Sep 17 00:00:00 2001 From: PoiScript Date: Fri, 28 Jun 2019 18:00:49 +0800 Subject: [PATCH] feat(org): ParseConfig struct --- README.md | 63 ++++++++++--------- src/config.rs | 19 ++++++ src/elements/headline.rs | 129 +++++++++++++++++++++++++++++++-------- src/elements/mod.rs | 2 +- src/lib.rs | 97 ++++++++++++----------------- src/org.rs | 27 +++++++- 6 files changed, 222 insertions(+), 115 deletions(-) create mode 100644 src/config.rs diff --git a/README.md b/README.md index d173c09..d8c3837 100644 --- a/README.md +++ b/README.md @@ -13,14 +13,22 @@ To parse a orgmode string, simply invoking the `Org::parse` function: ```rust use orgize::Org; -let org = Org::parse(r#"* Title 1 -*Section 1* -** Title 2 -_Section 2_ -* Title 3 -/Section 3/ -* Title 4 -=Section 4="#); +Org::parse("* DONE Title :tag:"); +``` + +or `Org::parse_with_config`: + +``` rust +use orgize::{Org, ParseConfig}; + +let org = Org::parse_with_config( + "* TASK Title 1", + ParseConfig { + // custom todo keywords + todo_keywords: &["TASK"], + ..Default::default() + }, +); ``` ## Iter @@ -29,7 +37,9 @@ _Section 2_ a simple wrapper of `Element`. ```rust -for event in org.iter() { +use orgize::Org; + +for event in Org::parse("* DONE Title :tag:").iter() { // handling the event } ``` @@ -43,15 +53,14 @@ You can call the `Org::html_default` function to generate html directly, which uses the `DefaultHtmlHandler` internally: ```rust +use orgize::Org; + let mut writer = Vec::new(); -org.html_default(&mut writer).unwrap(); +Org::parse("* title\n*section*").html_default(&mut writer).unwrap(); assert_eq!( String::from_utf8(writer).unwrap(), - "

Title 1

Section 1

\ -

Title 2

Section 2

\ -

Title 3

Section 3

\ -

Title 4

Section 4

" + "

title

section

" ); ``` @@ -64,6 +73,14 @@ The following code demonstrates how to add a id for every headline and return own error type while rendering. ```rust +use std::convert::From; +use std::io::{Error as IOError, Write}; +use std::string::FromUtf8Error; + +use orgize::export::{html::Escape, DefaultHtmlHandler, HtmlHandler}; +use orgize::{Element, Org}; +use slugify::slugify; + #[derive(Debug)] enum MyError { IO(IOError), @@ -112,23 +129,13 @@ impl HtmlHandler for MyHtmlHandler { } fn main() -> Result<(), MyError> { - let contents = r"* Title 1 -*Section 1* -** Title 2 -_Section 2_ -* Title 3 -/Section 3/ -* Title 4 -=Section 4="; - let mut writer = Vec::new(); - Org::parse(&contents).html(&mut writer, MyHtmlHandler)?; + Org::parse("* title\n*section*").html(&mut writer, MyHtmlHandler)?; + assert_eq!( String::from_utf8(writer)?, - "

Title 1

Section 1

\ -

Title 2

Section 2

\ -

Title 3

Section 3

\ -

Title 4

Section 4

" + "

title

\ +

section

" ); Ok(()) diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5b829d8 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,19 @@ +//! Parse configuration module + +/// Parse configuration +#[derive(Clone, Debug)] +pub struct ParseConfig<'a> { + /// Default headline todo keywords, it shouldn't be changed. + pub default_todo_keywords: &'a [&'a str], + /// Custom headline todo keywords + pub todo_keywords: &'a [&'a str], +} + +impl Default for ParseConfig<'_> { + fn default() -> Self { + ParseConfig { + default_todo_keywords: &["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"], + todo_keywords: &[], + } + } +} diff --git a/src/elements/headline.rs b/src/elements/headline.rs index 8717baa..ea74e1b 100644 --- a/src/elements/headline.rs +++ b/src/elements/headline.rs @@ -3,8 +3,7 @@ use jetscii::ByteSubstring; use memchr::{memchr, memchr2, memrchr}; -pub const DEFAULT_TODO_KEYWORDS: &[&str] = - &["TODO", "DONE", "NEXT", "WAITING", "LATER", "CANCELLED"]; +use crate::config::ParseConfig; #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "serde", derive(serde::Serialize))] @@ -26,7 +25,10 @@ pub struct Headline<'a> { } impl Headline<'_> { - pub(crate) fn parse<'a>(text: &'a str, keywords: &[&str]) -> (Headline<'a>, usize, usize) { + pub(crate) fn parse<'a>( + text: &'a str, + config: &ParseConfig<'_>, + ) -> (Headline<'a>, usize, usize) { let level = memchr2(b'\n', b' ', text.as_bytes()).unwrap_or_else(|| text.len()); debug_assert!(level > 0); @@ -65,7 +67,8 @@ impl Headline<'_> { let (word, off) = memchr(b' ', tail.as_bytes()) .map(|i| (&tail[0..i], i + 1)) .unwrap_or_else(|| (tail, tail.len())); - if keywords.contains(&word) { + if config.todo_keywords.contains(&word) || config.default_todo_keywords.contains(&word) + { (Some(word), &tail[off..]) } else { (None, tail) @@ -149,7 +152,11 @@ impl Headline<'_> { #[test] fn parse() { assert_eq!( - Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &["DONE"]).0, + Headline::parse( + "**** DONE [#A] COMMENT Title :tag:a2%:", + &ParseConfig::default() + ) + .0, Headline { level: 4, priority: Some('A'), @@ -159,7 +166,11 @@ fn parse() { }, ); assert_eq!( - Headline::parse("**** ToDO [#A] COMMENT Title :tag:a2%:", &["DONE"]).0, + Headline::parse( + "**** ToDO [#A] COMMENT Title :tag:a2%:", + &ParseConfig::default() + ) + .0, Headline { level: 4, priority: None, @@ -169,7 +180,11 @@ fn parse() { }, ); assert_eq!( - Headline::parse("**** T0DO [#A] COMMENT Title :tag:a2%:", &["DONE"]).0, + Headline::parse( + "**** T0DO [#A] COMMENT Title :tag:a2%:", + &ParseConfig::default() + ) + .0, Headline { level: 4, priority: None, @@ -179,7 +194,11 @@ fn parse() { }, ); assert_eq!( - Headline::parse("**** DONE [#1] COMMENT Title :tag:a2%:", &["DONE"]).0, + Headline::parse( + "**** DONE [#1] COMMENT Title :tag:a2%:", + &ParseConfig::default() + ) + .0, Headline { level: 4, priority: None, @@ -189,7 +208,11 @@ fn parse() { }, ); assert_eq!( - Headline::parse("**** DONE [#a] COMMENT Title :tag:a2%:", &["DONE"]).0, + Headline::parse( + "**** DONE [#a] COMMENT Title :tag:a2%:", + &ParseConfig::default() + ) + .0, Headline { level: 4, priority: None, @@ -199,7 +222,11 @@ fn parse() { }, ); assert_eq!( - Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%", &["DONE"]).0, + Headline::parse( + "**** DONE [#A] COMMENT Title :tag:a2%", + &ParseConfig::default() + ) + .0, Headline { level: 4, priority: Some('A'), @@ -209,7 +236,11 @@ fn parse() { }, ); assert_eq!( - Headline::parse("**** DONE [#A] COMMENT Title tag:a2%:", &["DONE"]).0, + Headline::parse( + "**** DONE [#A] COMMENT Title tag:a2%:", + &ParseConfig::default() + ) + .0, Headline { level: 4, priority: Some('A'), @@ -219,7 +250,7 @@ fn parse() { }, ); assert_eq!( - Headline::parse("**** COMMENT Title tag:a2%:", &["DONE"]).0, + Headline::parse("**** COMMENT Title tag:a2%:", &ParseConfig::default()).0, Headline { level: 4, priority: None, @@ -233,7 +264,14 @@ fn parse() { #[test] fn parse_todo_keywords() { assert_eq!( - Headline::parse("**** DONE [#A] COMMENT Title :tag:a2%:", &[]).0, + Headline::parse( + "**** DONE [#A] COMMENT Title :tag:a2%:", + &ParseConfig { + default_todo_keywords: &[], + ..Default::default() + } + ) + .0, Headline { level: 4, priority: None, @@ -243,7 +281,14 @@ fn parse_todo_keywords() { }, ); assert_eq!( - Headline::parse("**** TASK [#A] COMMENT Title :tag:a2%:", &["TASK"]).0, + Headline::parse( + "**** TASK [#A] COMMENT Title :tag:a2%:", + &ParseConfig { + todo_keywords: &["TASK"], + ..Default::default() + } + ) + .0, Headline { level: 4, priority: Some('A'), @@ -256,21 +301,55 @@ fn parse_todo_keywords() { #[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()); + assert!(Headline::parse("* COMMENT Title", &ParseConfig::default()) + .0 + .is_commented()); + assert!(!Headline::parse("* Title", &ParseConfig::default()) + .0 + .is_commented()); + assert!(!Headline::parse("* C0MMENT Title", &ParseConfig::default()) + .0 + .is_commented()); + assert!(!Headline::parse("* comment Title", &ParseConfig::default()) + .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()); + assert!( + Headline::parse("* Title :ARCHIVE:", &ParseConfig::default()) + .0 + .is_archived() + ); + assert!( + Headline::parse("* Title :t:ARCHIVE:", &ParseConfig::default()) + .0 + .is_archived() + ); + assert!( + Headline::parse("* Title :ARCHIVE:t:", &ParseConfig::default()) + .0 + .is_archived() + ); + assert!(!Headline::parse("* Title", &ParseConfig::default()) + .0 + .is_commented()); + assert!( + !Headline::parse("* Title :ARCHIVED:", &ParseConfig::default()) + .0 + .is_archived() + ); + assert!( + !Headline::parse("* Title :ARCHIVES:", &ParseConfig::default()) + .0 + .is_archived() + ); + assert!( + !Headline::parse("* Title :archive:", &ParseConfig::default()) + .0 + .is_archived() + ); } #[test] diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 9044599..3b71b81 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -32,7 +32,7 @@ pub use self::{ dyn_block::DynBlock, fn_def::FnDef, fn_ref::FnRef, - headline::{Headline, DEFAULT_TODO_KEYWORDS}, + headline::Headline, inline_call::InlineCall, inline_src::InlineSrc, keyword::{BabelCall, Keyword}, diff --git a/src/lib.rs b/src/lib.rs index 724ee86..196c506 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,14 +9,24 @@ //! ```rust //! use orgize::Org; //! -//! let org = Org::parse(r#"* Title 1 -//! *Section 1* -//! ** Title 2 -//! _Section 2_ -//! * Title 3 -//! /Section 3/ -//! * Title 4 -//! =Section 4="#); +//! Org::parse("* DONE Title :tag:"); +//! ``` +//! +//! or [`Org::parse_with_config`]: +//! +//! [`Org::parse_with_config`]: org/struct.Org.html#method.parse_with_config +//! +//! ``` rust +//! use orgize::{Org, ParseConfig}; +//! +//! Org::parse_with_config( +//! "* TASK Title 1", +//! ParseConfig { +//! // custom todo keywords +//! todo_keywords: &["TASK"], +//! ..Default::default() +//! }, +//! ); //! ``` //! //! # Iter @@ -29,18 +39,9 @@ //! [`Element`]: elements/enum.Element.html //! //! ```rust -//! # use orgize::Org; -//! # -//! # let org = Org::parse(r#"* Title 1 -//! # *Section 1* -//! # ** Title 2 -//! # _Section 2_ -//! # * Title 3 -//! # /Section 3/ -//! # * Title 4 -//! # =Section 4="#); -//! # -//! for event in org.iter() { +//! use orgize::Org; +//! +//! for event in Org::parse("* DONE Title :tag:").iter() { //! // handling the event //! } //! ``` @@ -60,26 +61,14 @@ //! [`DefaultHtmlHandler`]: export/html/struct.DefaultHtmlHandler.html //! //! ```rust -//! # use orgize::Org; -//! # -//! # let org = Org::parse(r#"* Title 1 -//! # *Section 1* -//! # ** Title 2 -//! # _Section 2_ -//! # * Title 3 -//! # /Section 3/ -//! # * Title 4 -//! # =Section 4="#); -//! # +//! use orgize::Org; +//! //! let mut writer = Vec::new(); -//! org.html_default(&mut writer).unwrap(); +//! Org::parse("* title\n*section*").html_default(&mut writer).unwrap(); //! //! assert_eq!( //! String::from_utf8(writer).unwrap(), -//! "

Title 1

Section 1

\ -//!

Title 2

Section 2

\ -//!

Title 3

Section 3

\ -//!

Title 4

Section 4

" +//! "

title

section

" //! ); //! ``` //! @@ -95,14 +84,14 @@ //! own error type while rendering. //! //! ```rust -//! # use std::convert::From; -//! # use std::io::{Error as IOError, Write}; -//! # use std::string::FromUtf8Error; -//! # -//! # use orgize::export::{html::Escape, DefaultHtmlHandler, HtmlHandler}; -//! # use orgize::{Element, Org}; -//! # use slugify::slugify; -//! # +//! use std::convert::From; +//! use std::io::{Error as IOError, Write}; +//! use std::string::FromUtf8Error; +//! +//! use orgize::export::{html::Escape, DefaultHtmlHandler, HtmlHandler}; +//! use orgize::{Element, Org}; +//! use slugify::slugify; +//! //! #[derive(Debug)] //! enum MyError { //! IO(IOError), @@ -151,23 +140,13 @@ //! } //! //! fn main() -> Result<(), MyError> { -//! let contents = r"* Title 1 -//! *Section 1* -//! ** Title 2 -//! _Section 2_ -//! * Title 3 -//! /Section 3/ -//! * Title 4 -//! =Section 4="; -//! //! let mut writer = Vec::new(); -//! Org::parse(&contents).html(&mut writer, MyHtmlHandler)?; +//! Org::parse("* title\n*section*").html(&mut writer, MyHtmlHandler)?; +//! //! assert_eq!( //! String::from_utf8(writer)?, -//! "

Title 1

Section 1

\ -//!

Title 2

Section 2

\ -//!

Title 3

Section 3

\ -//!

Title 4

Section 4

" +//! "

title

\ +//!

section

" //! ); //! //! Ok(()) @@ -230,6 +209,7 @@ //! //! MIT +pub mod config; pub mod elements; pub mod export; pub mod iter; @@ -237,6 +217,7 @@ pub mod org; #[cfg(feature = "serde")] mod serde; +pub use config::ParseConfig; pub use elements::Element; pub use iter::{Event, Iter}; pub use org::Org; diff --git a/src/org.rs b/src/org.rs index b31f342..5ed9c83 100644 --- a/src/org.rs +++ b/src/org.rs @@ -3,6 +3,7 @@ use jetscii::bytes; use memchr::{memchr, memchr_iter}; use std::io::{Error, Write}; +use crate::config::ParseConfig; use crate::elements::*; use crate::export::{DefaultHtmlHandler, HtmlHandler}; use crate::iter::Iter; @@ -28,7 +29,27 @@ impl<'a> Org<'a> { document, text, }; - org.parse_internal(); + + org.parse_internal(ParseConfig::default()); + + org + } + + pub fn parse_with_config(text: &'a str, config: ParseConfig<'_>) -> Self { + let mut arena = Arena::new(); + let document = arena.new_node(Element::Document { + begin: 0, + end: text.len(), + contents_begin: 0, + contents_end: text.len(), + }); + + let mut org = Org { + arena, + document, + text, + }; + org.parse_internal(config); org } @@ -62,7 +83,7 @@ impl<'a> Org<'a> { self.html(wrtier, DefaultHtmlHandler) } - fn parse_internal(&mut self) { + fn parse_internal(&mut self, config: ParseConfig<'_>) { let mut node = self.document; loop { match self.arena[node].data { @@ -92,7 +113,7 @@ impl<'a> Org<'a> { } } while begin < end { - let (headline, off, end) = Headline::parse(&self.text[begin..end], &[]); + let (headline, off, end) = Headline::parse(&self.text[begin..end], &config); let headline = Element::Headline { headline, begin,