feat(config): define todo_keywords
in tuple
This commit is contained in:
parent
3b646aa7a5
commit
2b8d2590ff
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
A Rust library for parsing orgmode files.
|
A Rust library for parsing orgmode files.
|
||||||
|
|
||||||
Live demo: https://orgize.herokuapp.com/
|
[Live demo](https://orgize.herokuapp.com/)
|
||||||
|
|
||||||
## Parse
|
## Parse
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ Org::parse_with_config(
|
||||||
"* TASK Title 1",
|
"* TASK Title 1",
|
||||||
&ParseConfig {
|
&ParseConfig {
|
||||||
// custom todo keywords
|
// custom todo keywords
|
||||||
todo_keywords: vec!["TASK".to_string()],
|
todo_keywords: (vec!["TASK".to_string()], vec![]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -157,7 +157,7 @@ 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 unchanged.
|
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
|
||||||
serialize it into any format supported by serde, such as json:
|
serialize it into any format supported by serde, such as json:
|
||||||
|
@ -195,7 +195,7 @@ println!("{}", to_string(&org).unwrap());
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
By now, orgize provides two features:
|
By now, orgize provides three features:
|
||||||
|
|
||||||
+ `ser`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
+ `ser`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
//! Parse configuration module
|
|
||||||
|
|
||||||
/// Parse configuration
|
/// Parse configuration
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ParseConfig {
|
pub struct ParseConfig {
|
||||||
/// Headline's todo keywords, todo type
|
/// Headline's todo keywords
|
||||||
pub todo_keywords: Vec<String>,
|
pub todo_keywords: (Vec<String>, Vec<String>),
|
||||||
/// Headline's todo keywords, done type
|
|
||||||
pub done_keywords: Vec<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ParseConfig {
|
impl Default for ParseConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ParseConfig {
|
ParseConfig {
|
||||||
todo_keywords: vec![String::from("TODO")],
|
todo_keywords: (vec![String::from("TODO")], vec![String::from("DONE")]),
|
||||||
done_keywords: vec![String::from("DONE")],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub use self::{
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
/// Orgize Element Enum
|
/// Element Enum
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
|
|
|
@ -9,7 +9,7 @@ use nom::{
|
||||||
IResult,
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Orgize Datetime Struct
|
/// Datetime Struct
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
#[cfg_attr(feature = "ser", derive(serde::Serialize))]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -14,9 +14,11 @@ use nom::{
|
||||||
Err, IResult,
|
Err, IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::config::ParseConfig;
|
use crate::{
|
||||||
use crate::elements::{drawer::parse_drawer, Planning, Timestamp};
|
config::ParseConfig,
|
||||||
use crate::parsers::{line, skip_empty_lines, take_one_word};
|
elements::{drawer::parse_drawer, Planning, Timestamp},
|
||||||
|
parsers::{line, skip_empty_lines, take_one_word},
|
||||||
|
};
|
||||||
|
|
||||||
/// Title Elemenet
|
/// Title Elemenet
|
||||||
#[cfg_attr(test, derive(PartialEq))]
|
#[cfg_attr(test, derive(PartialEq))]
|
||||||
|
@ -28,10 +30,10 @@ pub struct Title<'a> {
|
||||||
/// Headline priority cookie
|
/// Headline priority cookie
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub priority: Option<char>,
|
pub priority: Option<char>,
|
||||||
/// Headline title tags, including the sparated colons
|
/// Headline title tags
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Vec::is_empty"))]
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Vec::is_empty"))]
|
||||||
pub tags: Vec<Cow<'a, str>>,
|
pub tags: Vec<Cow<'a, str>>,
|
||||||
/// Headline title keyword
|
/// Headline todo keyword
|
||||||
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "ser", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub keyword: Option<Cow<'a, str>>,
|
pub keyword: Option<Cow<'a, str>>,
|
||||||
/// Raw headline's text, without the stars and the tags
|
/// Raw headline's text, without the stars and the tags
|
||||||
|
@ -130,8 +132,8 @@ fn parse_title<'a, E: ParseError<&'a str>>(
|
||||||
let (input, keyword) = opt(preceded(
|
let (input, keyword) = opt(preceded(
|
||||||
space1,
|
space1,
|
||||||
verify(take_one_word, |s: &str| {
|
verify(take_one_word, |s: &str| {
|
||||||
config.todo_keywords.iter().any(|x| x == s)
|
config.todo_keywords.0.iter().any(|x| x == s)
|
||||||
|| config.done_keywords.iter().any(|x| x == s)
|
|| config.todo_keywords.1.iter().any(|x| x == s)
|
||||||
}),
|
}),
|
||||||
))(input)?;
|
))(input)?;
|
||||||
|
|
||||||
|
@ -353,7 +355,7 @@ fn parse_title_() {
|
||||||
parse_title::<VerboseError<&str>>(
|
parse_title::<VerboseError<&str>>(
|
||||||
"**** DONE Title",
|
"**** DONE Title",
|
||||||
&ParseConfig {
|
&ParseConfig {
|
||||||
done_keywords: vec![],
|
todo_keywords: (vec![], vec![]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -377,7 +379,7 @@ fn parse_title_() {
|
||||||
parse_title::<VerboseError<&str>>(
|
parse_title::<VerboseError<&str>>(
|
||||||
"**** TASK [#A] Title",
|
"**** TASK [#A] Title",
|
||||||
&ParseConfig {
|
&ParseConfig {
|
||||||
todo_keywords: vec!["TASK".to_string()],
|
todo_keywords: (vec!["TASK".to_string()], vec![]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,19 +6,32 @@ use jetscii::{bytes, BytesConst};
|
||||||
use crate::elements::Element;
|
use crate::elements::Element;
|
||||||
use crate::export::write_datetime;
|
use crate::export::write_datetime;
|
||||||
|
|
||||||
|
/// A wrapper for escaping sensitive characters in html.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use orgize::export::html::Escape;
|
||||||
|
///
|
||||||
|
/// assert_eq!(format!("{}", Escape("< < <")), "< < <");
|
||||||
|
/// assert_eq!(
|
||||||
|
/// format!("{}", Escape("<script>alert('Hello XSS')</script>")),
|
||||||
|
/// "<script>alert('Hello XSS')</script>"
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
pub struct Escape<S: AsRef<str>>(pub S);
|
pub struct Escape<S: AsRef<str>>(pub S);
|
||||||
|
|
||||||
impl<S: AsRef<str>> fmt::Display for Escape<S> {
|
impl<S: AsRef<str>> fmt::Display for Escape<S> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
let bytes = self.0.as_ref().as_bytes();
|
|
||||||
|
let content = self.0.as_ref();
|
||||||
|
let bytes = content.as_bytes();
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref ESCAPE_BYTES: BytesConst = bytes!(b'<', b'>', b'&', b'\'', b'"');
|
static ref ESCAPE_BYTES: BytesConst = bytes!(b'<', b'>', b'&', b'\'', b'"');
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Some(off) = ESCAPE_BYTES.find(&bytes[pos..]) {
|
while let Some(off) = ESCAPE_BYTES.find(&bytes[pos..]) {
|
||||||
write!(f, "{}", &self.0.as_ref()[pos..pos + off])?;
|
write!(f, "{}", &content[pos..pos + off])?;
|
||||||
|
|
||||||
pos += off + 1;
|
pos += off + 1;
|
||||||
|
|
||||||
|
@ -26,13 +39,13 @@ impl<S: AsRef<str>> fmt::Display for Escape<S> {
|
||||||
b'<' => write!(f, "<")?,
|
b'<' => write!(f, "<")?,
|
||||||
b'>' => write!(f, ">")?,
|
b'>' => write!(f, ">")?,
|
||||||
b'&' => write!(f, "&")?,
|
b'&' => write!(f, "&")?,
|
||||||
b'\'' => write!(f, "'")?,
|
b'\'' => write!(f, "'")?,
|
||||||
b'"' => write!(f, """)?,
|
b'"' => write!(f, """)?,
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
write!(f, "{}", &self.0.as_ref()[pos..])
|
write!(f, "{}", &content[pos..])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
pub mod html;
|
pub mod html;
|
||||||
pub mod org;
|
pub mod org;
|
||||||
|
|
||||||
pub use html::*;
|
pub use html::{DefaultHtmlHandler, Escape as HtmlEscape, HtmlHandler};
|
||||||
pub use org::*;
|
pub use org::{DefaultOrgHandler, OrgHandler};
|
||||||
|
|
||||||
use std::io::{Error, Write};
|
use std::io::{Error, Write};
|
||||||
|
|
||||||
|
|
|
@ -63,11 +63,10 @@ impl Document {
|
||||||
self.0.last_child(org)
|
self.0.last_child(org)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_section_content<'a, S: Into<Cow<'a, str>>>(
|
pub fn set_section_content<'a, S>(&mut self, content: S, org: &mut Org<'a>)
|
||||||
&mut self,
|
where
|
||||||
content: S,
|
S: Into<Cow<'a, str>>,
|
||||||
org: &mut Org<'a>,
|
{
|
||||||
) {
|
|
||||||
let sec_n = if let Some(sec_n) = self.0.sec_n {
|
let sec_n = if let Some(sec_n) = self.0.sec_n {
|
||||||
let children: Vec<_> = sec_n.children(&org.arena).collect();
|
let children: Vec<_> = sec_n.children(&org.arena).collect();
|
||||||
for child in children {
|
for child in children {
|
||||||
|
@ -188,6 +187,7 @@ impl Headline {
|
||||||
Element::Section => Some(n),
|
Element::Section => Some(n),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
|
||||||
Headline {
|
Headline {
|
||||||
lvl,
|
lvl,
|
||||||
hdl_n,
|
hdl_n,
|
||||||
|
@ -226,7 +226,10 @@ impl Headline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_title_content<'a, S: Into<Cow<'a, str>>>(self, content: S, org: &mut Org<'a>) {
|
pub fn set_title_content<'a, S>(self, content: S, org: &mut Org<'a>)
|
||||||
|
where
|
||||||
|
S: Into<Cow<'a, str>>,
|
||||||
|
{
|
||||||
let content = content.into();
|
let content = content.into();
|
||||||
|
|
||||||
let children: Vec<_> = self.ttl_n.children(&org.arena).collect();
|
let children: Vec<_> = self.ttl_n.children(&org.arena).collect();
|
||||||
|
@ -258,11 +261,10 @@ impl Headline {
|
||||||
org.debug_validate();
|
org.debug_validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_section_content<'a, S: Into<Cow<'a, str>>>(
|
pub fn set_section_content<'a, S>(&mut self, content: S, org: &mut Org<'a>)
|
||||||
&mut self,
|
where
|
||||||
content: S,
|
S: Into<Cow<'a, str>>,
|
||||||
org: &mut Org<'a>,
|
{
|
||||||
) {
|
|
||||||
let sec_n = if let Some(sec_n) = self.sec_n {
|
let sec_n = if let Some(sec_n) = self.sec_n {
|
||||||
let children: Vec<_> = sec_n.children(&org.arena).collect();
|
let children: Vec<_> = sec_n.children(&org.arena).collect();
|
||||||
for child in children {
|
for child in children {
|
||||||
|
@ -471,3 +473,21 @@ impl Headline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Org<'_> {
|
||||||
|
/// Return a `Document`
|
||||||
|
pub fn document(&self) -> Document {
|
||||||
|
Document::from_org(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return an iterator of `Headline`
|
||||||
|
pub fn headlines(&self) -> impl Iterator<Item = Headline> + '_ {
|
||||||
|
self.root
|
||||||
|
.descendants(&self.arena)
|
||||||
|
.skip(1)
|
||||||
|
.filter_map(move |node| match &self.arena[node].get() {
|
||||||
|
Element::Headline { level } => Some(Headline::from_node(node, *level, self)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! A Rust library for parsing orgmode files.
|
//! A Rust library for parsing orgmode files.
|
||||||
//!
|
//!
|
||||||
//! Live demo: https://orgize.herokuapp.com/
|
//! [Live demo](https://orgize.herokuapp.com/)
|
||||||
//!
|
//!
|
||||||
//! # Parse
|
//! # Parse
|
||||||
//!
|
//!
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
//! "* TASK Title 1",
|
//! "* TASK Title 1",
|
||||||
//! &ParseConfig {
|
//! &ParseConfig {
|
||||||
//! // custom todo keywords
|
//! // custom todo keywords
|
||||||
//! todo_keywords: vec!["TASK".to_string()],
|
//! todo_keywords: (vec!["TASK".to_string()], vec![]),
|
||||||
//! ..Default::default()
|
//! ..Default::default()
|
||||||
//! },
|
//! },
|
||||||
//! );
|
//! );
|
||||||
|
@ -206,7 +206,7 @@
|
||||||
//!
|
//!
|
||||||
//! # Features
|
//! # Features
|
||||||
//!
|
//!
|
||||||
//! By now, orgize provides two features:
|
//! By now, orgize provides three features:
|
||||||
//!
|
//!
|
||||||
//! + `ser`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
//! + `ser`: adds the ability to serialize `Org` and other elements using `serde`, enabled by default.
|
||||||
//!
|
//!
|
||||||
|
|
35
src/org.rs
35
src/org.rs
|
@ -5,7 +5,6 @@ use crate::{
|
||||||
config::{ParseConfig, DEFAULT_CONFIG},
|
config::{ParseConfig, DEFAULT_CONFIG},
|
||||||
elements::Element,
|
elements::Element,
|
||||||
export::{DefaultHtmlHandler, DefaultOrgHandler, HtmlHandler, OrgHandler},
|
export::{DefaultHtmlHandler, DefaultOrgHandler, HtmlHandler, OrgHandler},
|
||||||
headline::{Document, Headline},
|
|
||||||
parsers::{parse_container, Container},
|
parsers::{parse_container, Container},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,13 +34,13 @@ impl<'a> Org<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Org struct from parsing `text`, using a custom ParseConfig
|
/// Create a new Org struct from parsing `text`, using a custom ParseConfig
|
||||||
pub fn parse_with_config(content: &'a str, config: &ParseConfig) -> Org<'a> {
|
pub fn parse_with_config(text: &'a str, config: &ParseConfig) -> Org<'a> {
|
||||||
let mut org = Org::new();
|
let mut org = Org::new();
|
||||||
|
|
||||||
parse_container(
|
parse_container(
|
||||||
&mut org.arena,
|
&mut org.arena,
|
||||||
Container::Document {
|
Container::Document {
|
||||||
content,
|
content: text,
|
||||||
node: org.root,
|
node: org.root,
|
||||||
},
|
},
|
||||||
config,
|
config,
|
||||||
|
@ -52,22 +51,6 @@ impl<'a> Org<'a> {
|
||||||
org
|
org
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a `Document`
|
|
||||||
pub fn document(&self) -> Document {
|
|
||||||
Document::from_org(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return an iterator of `Headline`
|
|
||||||
pub fn headlines<'b>(&'b self) -> impl Iterator<Item = Headline> + 'b {
|
|
||||||
self.root
|
|
||||||
.descendants(&self.arena)
|
|
||||||
.skip(1)
|
|
||||||
.filter_map(move |node| match &self.arena[node].get() {
|
|
||||||
Element::Headline { level } => Some(Headline::from_node(node, *level, self)),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a refrence to underlay arena
|
/// Return a refrence to underlay arena
|
||||||
pub fn arena(&self) -> &Arena<Element<'a>> {
|
pub fn arena(&self) -> &Arena<Element<'a>> {
|
||||||
&self.arena
|
&self.arena
|
||||||
|
@ -86,8 +69,11 @@ impl<'a> Org<'a> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn html<W: Write>(&self, wrtier: W) -> Result<(), Error> {
|
pub fn html<W>(&self, writer: W) -> Result<(), Error>
|
||||||
self.html_with_handler(wrtier, &mut DefaultHtmlHandler)
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
self.html_with_handler(writer, &mut DefaultHtmlHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn html_with_handler<W, H, E>(&self, mut writer: W, handler: &mut H) -> Result<(), E>
|
pub fn html_with_handler<W, H, E>(&self, mut writer: W, handler: &mut H) -> Result<(), E>
|
||||||
|
@ -106,8 +92,11 @@ impl<'a> Org<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn org<W: Write>(&self, wrtier: W) -> Result<(), Error> {
|
pub fn org<W>(&self, writer: W) -> Result<(), Error>
|
||||||
self.org_with_handler(wrtier, &mut DefaultOrgHandler)
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
self.org_with_handler(writer, &mut DefaultOrgHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn org_with_handler<W, H, E>(&self, mut writer: W, handler: &mut H) -> Result<(), E>
|
pub fn org_with_handler<W, H, E>(&self, mut writer: W, handler: &mut H) -> Result<(), E>
|
||||||
|
|
|
@ -362,22 +362,22 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
parent: NodeId,
|
parent: NodeId,
|
||||||
containers: &mut Vec<Container<'a>>,
|
containers: &mut Vec<Container<'a>>,
|
||||||
name: Cow<'a, str>,
|
name: Cow<'a, str>,
|
||||||
args: Option<Cow<'a, str>>,
|
parameters: Option<Cow<'a, str>>,
|
||||||
content: &'a str,
|
content: &'a str,
|
||||||
) {
|
) {
|
||||||
match &*name.to_uppercase() {
|
match &*name.to_uppercase() {
|
||||||
"CENTER" => {
|
"CENTER" => {
|
||||||
let node = arena.append_element(CenterBlock { parameters: args }, parent);
|
let node = arena.append_element(CenterBlock { parameters }, parent);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
}
|
}
|
||||||
"QUOTE" => {
|
"QUOTE" => {
|
||||||
let node = arena.append_element(QuoteBlock { parameters: args }, parent);
|
let node = arena.append_element(QuoteBlock { parameters }, parent);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
}
|
}
|
||||||
"COMMENT" => {
|
"COMMENT" => {
|
||||||
arena.append_element(
|
arena.append_element(
|
||||||
CommentBlock {
|
CommentBlock {
|
||||||
data: args,
|
data: parameters,
|
||||||
contents: content.into(),
|
contents: content.into(),
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
|
@ -386,7 +386,7 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
"EXAMPLE" => {
|
"EXAMPLE" => {
|
||||||
arena.append_element(
|
arena.append_element(
|
||||||
ExampleBlock {
|
ExampleBlock {
|
||||||
data: args,
|
data: parameters,
|
||||||
contents: content.into(),
|
contents: content.into(),
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
|
@ -395,14 +395,14 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
"EXPORT" => {
|
"EXPORT" => {
|
||||||
arena.append_element(
|
arena.append_element(
|
||||||
ExportBlock {
|
ExportBlock {
|
||||||
data: args.unwrap_or_default(),
|
data: parameters.unwrap_or_default(),
|
||||||
contents: content.into(),
|
contents: content.into(),
|
||||||
},
|
},
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"SRC" => {
|
"SRC" => {
|
||||||
let (language, arguments) = match &args {
|
let (language, arguments) = match ¶meters {
|
||||||
Some(Cow::Borrowed(args)) => {
|
Some(Cow::Borrowed(args)) => {
|
||||||
let (language, arguments) =
|
let (language, arguments) =
|
||||||
args.split_at(args.find(' ').unwrap_or_else(|| args.len()));
|
args.split_at(args.find(' ').unwrap_or_else(|| args.len()));
|
||||||
|
@ -421,17 +421,11 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
"VERSE" => {
|
"VERSE" => {
|
||||||
let node = arena.append_element(VerseBlock { parameters: args }, parent);
|
let node = arena.append_element(VerseBlock { parameters }, parent);
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let node = arena.append_element(
|
let node = arena.append_element(SpecialBlock { parameters, name }, parent);
|
||||||
SpecialBlock {
|
|
||||||
parameters: args,
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
parent,
|
|
||||||
);
|
|
||||||
containers.push(Container::Block { content, node });
|
containers.push(Container::Block { content, node });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -439,7 +433,7 @@ pub fn match_block<'a, T: ElementArena<'a>>(
|
||||||
|
|
||||||
struct InlinePositions<'a> {
|
struct InlinePositions<'a> {
|
||||||
bytes: &'a [u8],
|
bytes: &'a [u8],
|
||||||
position: usize,
|
pos: usize,
|
||||||
next: Option<usize>,
|
next: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,7 +441,7 @@ impl InlinePositions<'_> {
|
||||||
fn new(bytes: &[u8]) -> InlinePositions {
|
fn new(bytes: &[u8]) -> InlinePositions {
|
||||||
InlinePositions {
|
InlinePositions {
|
||||||
bytes,
|
bytes,
|
||||||
position: 0,
|
pos: 0,
|
||||||
next: Some(0),
|
next: Some(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -463,16 +457,16 @@ impl Iterator for InlinePositions<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.next.take().or_else(|| {
|
self.next.take().or_else(|| {
|
||||||
PRE_BYTES.find(&self.bytes[self.position..]).map(|i| {
|
PRE_BYTES.find(&self.bytes[self.pos..]).map(|i| {
|
||||||
self.position += i + 1;
|
self.pos += i + 1;
|
||||||
|
|
||||||
match self.bytes[self.position - 1] {
|
match self.bytes[self.pos - 1] {
|
||||||
b'{' => {
|
b'{' => {
|
||||||
self.next = Some(self.position);
|
self.next = Some(self.pos);
|
||||||
self.position - 1
|
self.pos - 1
|
||||||
}
|
}
|
||||||
b' ' | b'(' | b'\'' | b'"' | b'\n' => self.position,
|
b' ' | b'(' | b'\'' | b'"' | b'\n' => self.pos,
|
||||||
_ => self.position - 1,
|
_ => self.pos - 1,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -683,7 +677,9 @@ pub fn line<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, &str, E
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eol<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, &str, E> {
|
pub fn eol<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&str, &str, E> {
|
||||||
verify(line, |s: &str| s.trim().is_empty())(input)
|
verify(line, |s: &str| {
|
||||||
|
s.as_bytes().iter().all(|c| c.is_ascii_whitespace())
|
||||||
|
})(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_lines_while(predicate: impl Fn(&str) -> bool) -> impl Fn(&str) -> (&str, &str) {
|
pub fn take_lines_while(predicate: impl Fn(&str) -> bool) -> impl Fn(&str) -> (&str, &str) {
|
||||||
|
@ -708,7 +704,7 @@ pub fn take_lines_while(predicate: impl Fn(&str) -> bool) -> impl Fn(&str) -> (&
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip_empty_lines(input: &str) -> &str {
|
pub fn skip_empty_lines(input: &str) -> &str {
|
||||||
take_lines_while(|line| line.trim().is_empty())(input).0
|
take_lines_while(|line| line.as_bytes().iter().all(|c| c.is_ascii_whitespace()))(input).0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_headline(input: &str) -> Option<(&str, (&str, usize))> {
|
pub fn parse_headline(input: &str) -> Option<(&str, (&str, usize))> {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use indextree::NodeId;
|
use indextree::NodeId;
|
||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use crate::elements::{Element, Table, TableRow, Title};
|
use crate::elements::{Element, Table, TableRow};
|
||||||
use crate::Org;
|
use crate::Org;
|
||||||
|
|
||||||
/// Validation Error
|
/// Validation Error
|
||||||
|
@ -104,8 +104,8 @@ impl Org<'_> {
|
||||||
errors.push(ValidationError::ExpectedChildren { at: node_id });
|
errors.push(ValidationError::ExpectedChildren { at: node_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Element::Title(Title { raw, .. }) => {
|
Element::Title(title) => {
|
||||||
if !raw.is_empty() && node.first_child().is_none() {
|
if !title.raw.is_empty() && node.first_child().is_none() {
|
||||||
errors.push(ValidationError::ExpectedChildren { at: node_id });
|
errors.push(ValidationError::ExpectedChildren { at: node_id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue