feat(org): ParseConfig struct

This commit is contained in:
PoiScript 2019-06-28 18:00:49 +08:00
parent b847eb9285
commit 6b0f468f9f
6 changed files with 222 additions and 115 deletions

View file

@ -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(),
"<main><h1>Title 1</h1><section><p><b>Section 1</b></p></section>\
<h2>Title 2</h2><section><p><u>Section 2</u></p></section>\
<h1>Title 3</h1><section><p><i>Section 3</i></p></section>\
<h1>Title 4</h1><section><p><code>Section 4</code></p></section></main>"
"<main><h1>title</h1><section><p><b>section</b></p></section></main>"
);
```
@ -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<MyError> 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)?,
"<main><h1><a id=\"title-1\" href=\"#title-1\">Title 1</a></h1><section><p><b>Section 1</b></p></section>\
<h2><a id=\"title-2\" href=\"#title-2\">Title 2</a></h2><section><p><u>Section 2</u></p></section>\
<h1><a id=\"title-3\" href=\"#title-3\">Title 3</a></h1><section><p><i>Section 3</i></p></section>\
<h1><a id=\"title-4\" href=\"#title-4\">Title 4</a></h1><section><p><code>Section 4</code></p></section></main>"
"<main><h1><a id=\"title\" href=\"#title\">title</a></h1>\
<section><p><b>section</b></p></section></main>"
);
Ok(())

19
src/config.rs Normal file
View file

@ -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: &[],
}
}
}

View file

@ -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]

View file

@ -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},

View file

@ -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(),
//! "<main><h1>Title 1</h1><section><p><b>Section 1</b></p></section>\
//! <h2>Title 2</h2><section><p><u>Section 2</u></p></section>\
//! <h1>Title 3</h1><section><p><i>Section 3</i></p></section>\
//! <h1>Title 4</h1><section><p><code>Section 4</code></p></section></main>"
//! "<main><h1>title</h1><section><p><b>section</b></p></section></main>"
//! );
//! ```
//!
@ -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)?,
//! "<main><h1><a id=\"title-1\" href=\"#title-1\">Title 1</a></h1><section><p><b>Section 1</b></p></section>\
//! <h2><a id=\"title-2\" href=\"#title-2\">Title 2</a></h2><section><p><u>Section 2</u></p></section>\
//! <h1><a id=\"title-3\" href=\"#title-3\">Title 3</a></h1><section><p><i>Section 3</i></p></section>\
//! <h1><a id=\"title-4\" href=\"#title-4\">Title 4</a></h1><section><p><code>Section 4</code></p></section></main>"
//! "<main><h1><a id=\"title\" href=\"#title\">title</a></h1>\
//! <section><p><b>section</b></p></section></main>"
//! );
//!
//! 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;

View file

@ -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,