From f2d0a1dd2de660add7a436b2293cde877d11f45c Mon Sep 17 00:00:00 2001 From: PoiScript Date: Sat, 10 Aug 2019 20:17:39 +0800 Subject: [PATCH] feat(elements): `into_owned` function --- src/elements/block.rs | 70 +++++++++++++++++ src/elements/clock.rs | 27 +++++++ src/elements/cookie.rs | 6 ++ src/elements/drawer.rs | 6 ++ src/elements/dyn_block.rs | 7 ++ src/elements/fn_def.rs | 6 ++ src/elements/fn_ref.rs | 7 ++ src/elements/inline_call.rs | 9 +++ src/elements/inline_src.rs | 8 ++ src/elements/keyword.rs | 18 +++++ src/elements/link.rs | 7 ++ src/elements/list.rs | 6 ++ src/elements/macros.rs | 7 ++ src/elements/mod.rs | 95 ++++++++++++++++------- src/elements/planning.rs | 8 ++ src/elements/radio_target.rs | 52 +++++-------- src/elements/snippet.rs | 7 ++ src/elements/table.rs | 13 ++++ src/elements/target.rs | 6 ++ src/elements/timestamp.rs | 141 +++++++++++++++++++++++++---------- src/elements/title.rs | 20 +++++ src/export/html.rs | 2 +- src/export/org.rs | 2 +- src/org.rs | 28 +------ src/parsers.rs | 48 ++++++++++-- 25 files changed, 475 insertions(+), 131 deletions(-) diff --git a/src/elements/block.rs b/src/elements/block.rs index 1a4356e..eed973a 100644 --- a/src/elements/block.rs +++ b/src/elements/block.rs @@ -12,6 +12,15 @@ pub struct SpecialBlock<'a> { pub name: Cow<'a, str>, } +impl SpecialBlock<'_> { + pub fn into_owned(self) -> SpecialBlock<'static> { + SpecialBlock { + name: self.name.into_owned().into(), + parameters: self.parameters.map(Into::into).map(Cow::Owned), + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] @@ -19,6 +28,14 @@ pub struct QuoteBlock<'a> { pub parameters: Option>, } +impl QuoteBlock<'_> { + pub fn into_owned(self) -> QuoteBlock<'static> { + QuoteBlock { + parameters: self.parameters.map(Into::into).map(Cow::Owned), + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] @@ -26,6 +43,14 @@ pub struct CenterBlock<'a> { pub parameters: Option>, } +impl CenterBlock<'_> { + pub fn into_owned(self) -> CenterBlock<'static> { + CenterBlock { + parameters: self.parameters.map(Into::into).map(Cow::Owned), + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] @@ -33,6 +58,14 @@ pub struct VerseBlock<'a> { pub parameters: Option>, } +impl VerseBlock<'_> { + pub fn into_owned(self) -> VerseBlock<'static> { + VerseBlock { + parameters: self.parameters.map(Into::into).map(Cow::Owned), + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] @@ -41,6 +74,15 @@ pub struct CommentBlock<'a> { pub contents: Cow<'a, str>, } +impl CommentBlock<'_> { + pub fn into_owned(self) -> CommentBlock<'static> { + CommentBlock { + data: self.data.map(Into::into).map(Cow::Owned), + contents: self.contents.into_owned().into(), + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] @@ -49,6 +91,15 @@ pub struct ExampleBlock<'a> { pub contents: Cow<'a, str>, } +impl ExampleBlock<'_> { + pub fn into_owned(self) -> ExampleBlock<'static> { + ExampleBlock { + data: self.data.map(Into::into).map(Cow::Owned), + contents: self.contents.into_owned().into(), + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] @@ -57,6 +108,15 @@ pub struct ExportBlock<'a> { pub contents: Cow<'a, str>, } +impl ExportBlock<'_> { + pub fn into_owned(self) -> ExportBlock<'static> { + ExportBlock { + data: self.data.into_owned().into(), + contents: self.contents.into_owned().into(), + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] @@ -66,6 +126,16 @@ pub struct SourceBlock<'a> { pub arguments: Cow<'a, str>, } +impl SourceBlock<'_> { + pub fn into_owned(self) -> SourceBlock<'static> { + SourceBlock { + language: self.language.into_owned().into(), + arguments: self.arguments.into_owned().into(), + contents: self.contents.into_owned().into(), + } + } +} + pub(crate) fn parse_block_element(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> { let (input, name) = preceded(tag_no_case("#+BEGIN_"), alpha1)(input)?; let (input, args) = line(input)?; diff --git a/src/elements/clock.rs b/src/elements/clock.rs index 5455699..fda4678 100644 --- a/src/elements/clock.rs +++ b/src/elements/clock.rs @@ -90,6 +90,33 @@ impl Clock<'_> { } } + pub fn into_onwed(self) -> Clock<'static> { + match self { + Clock::Closed { + start, + end, + repeater, + delay, + duration, + } => Clock::Closed { + start: start.into_owned(), + end: end.into_owned(), + repeater: repeater.map(Into::into).map(Cow::Owned), + delay: delay.map(Into::into).map(Cow::Owned), + duration: duration.into_owned().into(), + }, + Clock::Running { + start, + repeater, + delay, + } => Clock::Running { + start: start.into_owned(), + repeater: repeater.map(Into::into).map(Cow::Owned), + delay: delay.map(Into::into).map(Cow::Owned), + }, + } + } + /// returns `true` if the clock is running pub fn is_running(&self) -> bool { match self { diff --git a/src/elements/cookie.rs b/src/elements/cookie.rs index 7dcea8a..c4b1fb8 100644 --- a/src/elements/cookie.rs +++ b/src/elements/cookie.rs @@ -35,6 +35,12 @@ impl Cookie<'_> { }, )) } + + pub fn into_owned(self) -> Cookie<'static> { + Cookie { + value: self.value.into_owned().into(), + } + } } #[test] diff --git a/src/elements/drawer.rs b/src/elements/drawer.rs index a2ce9b8..2fc44d6 100644 --- a/src/elements/drawer.rs +++ b/src/elements/drawer.rs @@ -30,6 +30,12 @@ impl Drawer<'_> { Ok((input, (Drawer { name: name.into() }, contents))) } + + pub fn into_owned(self) -> Drawer<'static> { + Drawer { + name: self.name.into_owned().into(), + } + } } #[test] diff --git a/src/elements/dyn_block.rs b/src/elements/dyn_block.rs index ca46813..6581bcd 100644 --- a/src/elements/dyn_block.rs +++ b/src/elements/dyn_block.rs @@ -43,6 +43,13 @@ impl DynBlock<'_> { ), )) } + + pub fn into_owned(self) -> DynBlock<'static> { + DynBlock { + block_name: self.block_name.into_owned().into(), + arguments: self.arguments.map(Into::into).map(Cow::Owned), + } + } } #[test] diff --git a/src/elements/fn_def.rs b/src/elements/fn_def.rs index 541c99b..13aad70 100644 --- a/src/elements/fn_def.rs +++ b/src/elements/fn_def.rs @@ -35,6 +35,12 @@ impl FnDef<'_> { ), )) } + + pub fn into_owned(self) -> FnDef<'static> { + FnDef { + label: self.label.into_owned().into(), + } + } } #[test] diff --git a/src/elements/fn_ref.rs b/src/elements/fn_ref.rs index e1c0d98..b8dee48 100644 --- a/src/elements/fn_ref.rs +++ b/src/elements/fn_ref.rs @@ -50,6 +50,13 @@ impl FnRef<'_> { }, )) } + + pub fn into_owned(self) -> FnRef<'static> { + FnRef { + label: self.label.into_owned().into(), + definition: self.definition.map(Into::into).map(Cow::Owned), + } + } } #[test] diff --git a/src/elements/inline_call.rs b/src/elements/inline_call.rs index 44c3ca6..dfbc0b2 100644 --- a/src/elements/inline_call.rs +++ b/src/elements/inline_call.rs @@ -49,6 +49,15 @@ impl InlineCall<'_> { }, )) } + + pub fn into_owned(self) -> InlineCall<'static> { + InlineCall { + name: self.name.into_owned().into(), + arguments: self.arguments.into_owned().into(), + inside_header: self.inside_header.map(Into::into).map(Cow::Owned), + end_header: self.end_header.map(Into::into).map(Cow::Owned), + } + } } #[test] diff --git a/src/elements/inline_src.rs b/src/elements/inline_src.rs index 62e84dc..54e75f6 100644 --- a/src/elements/inline_src.rs +++ b/src/elements/inline_src.rs @@ -40,6 +40,14 @@ impl InlineSrc<'_> { }, )) } + + pub fn into_owned(self) -> InlineSrc<'static> { + InlineSrc { + lang: self.lang.into_owned().into(), + options: self.options.map(Into::into).map(Cow::Owned), + body: self.body.into_owned().into(), + } + } } #[test] diff --git a/src/elements/keyword.rs b/src/elements/keyword.rs index 3604f46..13c61c2 100644 --- a/src/elements/keyword.rs +++ b/src/elements/keyword.rs @@ -19,6 +19,16 @@ pub struct Keyword<'a> { pub value: Cow<'a, str>, } +impl Keyword<'_> { + pub fn into_owned(self) -> Keyword<'static> { + Keyword { + key: self.key.into_owned().into(), + optional: self.optional.map(Into::into).map(Cow::Owned), + value: self.value.into_owned().into(), + } + } +} + #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] #[derive(Debug)] @@ -26,6 +36,14 @@ pub struct BabelCall<'a> { pub value: Cow<'a, str>, } +impl BabelCall<'_> { + pub fn into_owned(self) -> BabelCall<'static> { + BabelCall { + value: self.value.into_owned().into(), + } + } +} + pub(crate) fn parse_keyword(input: &str) -> IResult<&str, (&str, Option<&str>, &str)> { let (input, _) = tag("#+")(input)?; let (input, key) = take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '[')(input)?; diff --git a/src/elements/link.rs b/src/elements/link.rs index b69da70..7f73b16 100644 --- a/src/elements/link.rs +++ b/src/elements/link.rs @@ -38,6 +38,13 @@ impl Link<'_> { }, )) } + + pub fn into_owned(self) -> Link<'static> { + Link { + path: self.path.into_owned().into(), + desc: self.desc.map(Into::into).map(Cow::Owned), + } + } } #[test] diff --git a/src/elements/list.rs b/src/elements/list.rs index cb20541..8425e1a 100644 --- a/src/elements/list.rs +++ b/src/elements/list.rs @@ -106,6 +106,12 @@ impl ListItem<'_> { &text[off..], ) } + + pub fn into_owned(self) -> ListItem<'static> { + ListItem { + bullet: self.bullet.into_owned().into(), + } + } } #[inline] diff --git a/src/elements/macros.rs b/src/elements/macros.rs index 2ed0f3b..d972b0e 100644 --- a/src/elements/macros.rs +++ b/src/elements/macros.rs @@ -35,6 +35,13 @@ impl Macros<'_> { }, )) } + + pub fn into_owned(self) -> Macros<'static> { + Macros { + name: self.name.into_owned().into(), + arguments: self.arguments.map(Into::into).map(Cow::Owned), + } + } } #[test] diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 44789e4..2639237 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -24,8 +24,8 @@ mod timestamp; mod title; pub(crate) use self::{ - block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword, rule::parse_rule, - table::parse_table_el, + block::parse_block_element, emphasis::parse_emphasis, keyword::parse_keyword, + radio_target::parse_radio_target, rule::parse_rule, table::parse_table_el, }; pub use self::{ @@ -46,7 +46,6 @@ pub use self::{ list::{List, ListItem}, macros::Macros, planning::Planning, - radio_target::RadioTarget, snippet::Snippet, table::{Table, TableRow}, target::Target, @@ -74,7 +73,7 @@ pub enum Element<'a> { Section, Clock(Clock<'a>), Cookie(Cookie<'a>), - RadioTarget(RadioTarget), + RadioTarget, Drawer(Drawer<'a>), Document, DynBlock(DynBlock<'a>), @@ -89,7 +88,7 @@ pub enum Element<'a> { ListItem(ListItem<'a>), Macros(Macros<'a>), Snippet(Snippet<'a>), - Text { value: &'a str }, + Text { value: Cow<'a, str> }, Paragraph, Rule, Timestamp(Timestamp<'a>), @@ -110,29 +109,76 @@ pub enum Element<'a> { impl Element<'_> { pub fn is_container(&self) -> bool { + use Element::*; + match self { - Element::SpecialBlock(_) - | Element::QuoteBlock(_) - | Element::CenterBlock(_) - | Element::VerseBlock(_) - | Element::Bold - | Element::Document - | Element::DynBlock(_) - | Element::Headline - | Element::Italic - | Element::List(_) - | Element::ListItem(_) - | Element::Paragraph - | Element::Section - | Element::Strike - | Element::Underline - | Element::Title(_) - | Element::Table(_) - | Element::TableRow(_) - | Element::TableCell => true, + SpecialBlock(_) | QuoteBlock(_) | CenterBlock(_) | VerseBlock(_) | Bold | Document + | DynBlock(_) | Headline | Italic | List(_) | ListItem(_) | Paragraph | Section + | Strike | Underline | Title(_) | Table(_) | TableRow(_) | TableCell => true, _ => false, } } + + pub fn into_owned(self) -> Element<'static> { + use Element::*; + + match self { + SpecialBlock(e) => SpecialBlock(e.into_owned()), + QuoteBlock(e) => QuoteBlock(e.into_owned()), + CenterBlock(e) => CenterBlock(e.into_owned()), + VerseBlock(e) => VerseBlock(e.into_owned()), + CommentBlock(e) => CommentBlock(e.into_owned()), + ExampleBlock(e) => ExampleBlock(e.into_owned()), + ExportBlock(e) => ExportBlock(e.into_owned()), + SourceBlock(e) => SourceBlock(e.into_owned()), + BabelCall(e) => BabelCall(e.into_owned()), + Section => Section, + Clock(e) => Clock(e.into_onwed()), + Cookie(e) => Cookie(e.into_owned()), + RadioTarget => RadioTarget, + Drawer(e) => Drawer(e.into_owned()), + Document => Document, + DynBlock(e) => DynBlock(e.into_owned()), + FnDef(e) => FnDef(e.into_owned()), + FnRef(e) => FnRef(e.into_owned()), + Headline => Headline, + InlineCall(e) => InlineCall(e.into_owned()), + InlineSrc(e) => InlineSrc(e.into_owned()), + Keyword(e) => Keyword(e.into_owned()), + Link(e) => Link(e.into_owned()), + List(e) => List(e), + ListItem(e) => ListItem(e.into_owned()), + Macros(e) => Macros(e.into_owned()), + Snippet(e) => Snippet(e.into_owned()), + Text { value } => Text { + value: value.into_owned().into(), + }, + Paragraph => Paragraph, + Rule => Rule, + Timestamp(e) => Timestamp(e.into_owned()), + Target(e) => Target(e.into_owned()), + Bold => Bold, + Strike => Strike, + Italic => Italic, + Underline => Underline, + Verbatim { value } => Verbatim { + value: value.into_owned().into(), + }, + Code { value } => Code { + value: value.into_owned().into(), + }, + Comment { value } => Comment { + value: value.into_owned().into(), + }, + FixedWidth { value } => FixedWidth { + value: value.into_owned().into(), + }, + Title(e) => Title(e.into_owned()), + Table(e) => Table(e.into_owned()), + TableRow(e) => TableRow(e), + TableCell => TableCell, + } + } } macro_rules! impl_from { @@ -181,7 +227,6 @@ impl_from!( Table, Title, VerseBlock; - RadioTarget, List, TableRow ); diff --git a/src/elements/planning.rs b/src/elements/planning.rs index 95603f1..91b482c 100644 --- a/src/elements/planning.rs +++ b/src/elements/planning.rs @@ -64,6 +64,14 @@ impl Planning<'_> { )) } } + + pub fn into_owned(self) -> Planning<'static> { + Planning { + deadline: self.deadline.map(|x| x.into_owned()), + scheduled: self.scheduled.map(|x| x.into_owned()), + closed: self.closed.map(|x| x.into_owned()), + } + } } #[test] diff --git a/src/elements/radio_target.rs b/src/elements/radio_target.rs index 105e7ad..84c14c5 100644 --- a/src/elements/radio_target.rs +++ b/src/elements/radio_target.rs @@ -5,44 +5,30 @@ use nom::{ IResult, }; -use crate::elements::Element; - // TODO: text-markup, entities, latex-fragments, subscript and superscript -#[cfg_attr(test, derive(PartialEq))] -#[cfg_attr(feature = "ser", derive(serde::Serialize))] -#[derive(Debug)] -pub struct RadioTarget; -impl RadioTarget { - #[inline] - pub(crate) fn parse(input: &str) -> IResult<&str, (Element, &str)> { - let (input, contents) = delimited( - tag("<<<"), - verify( - take_while(|c: char| c != '<' && c != '\n' && c != '>'), - |s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '), - ), - tag(">>>"), - )(input)?; +#[inline] +pub(crate) fn parse_radio_target(input: &str) -> IResult<&str, &str> { + let (input, contents) = delimited( + tag("<<<"), + verify( + take_while(|c: char| c != '<' && c != '\n' && c != '>'), + |s: &str| s.starts_with(|c| c != ' ') && s.ends_with(|c| c != ' '), + ), + tag(">>>"), + )(input)?; - Ok((input, (Element::RadioTarget(RadioTarget), contents))) - } + Ok((input, contents)) } #[test] fn parse() { - assert_eq!( - RadioTarget::parse("<<>>"), - Ok(("", (Element::RadioTarget(RadioTarget), "target"))) - ); - assert_eq!( - RadioTarget::parse("<<>>"), - Ok(("", (Element::RadioTarget(RadioTarget), "tar get"))) - ); - assert!(RadioTarget::parse("<<>>").is_err()); - assert!(RadioTarget::parse("<<< target>>>").is_err()); - assert!(RadioTarget::parse("<<>>").is_err()); - assert!(RadioTarget::parse("<<get>>>").is_err()); - assert!(RadioTarget::parse("<<>>").is_err()); - assert!(RadioTarget::parse("<<>").is_err()); + assert_eq!(parse_radio_target("<<>>"), Ok(("", "target"))); + assert_eq!(parse_radio_target("<<>>"), Ok(("", "tar get"))); + assert!(parse_radio_target("<<>>").is_err()); + assert!(parse_radio_target("<<< target>>>").is_err()); + assert!(parse_radio_target("<<>>").is_err()); + assert!(parse_radio_target("<<get>>>").is_err()); + assert!(parse_radio_target("<<>>").is_err()); + assert!(parse_radio_target("<<>").is_err()); } diff --git a/src/elements/snippet.rs b/src/elements/snippet.rs index 02c2f0f..3ae17a7 100644 --- a/src/elements/snippet.rs +++ b/src/elements/snippet.rs @@ -35,6 +35,13 @@ impl Snippet<'_> { }, )) } + + pub fn into_owned(self) -> Snippet<'static> { + Snippet { + name: self.name.into_owned().into(), + value: self.value.into_owned().into(), + } + } } #[test] diff --git a/src/elements/table.rs b/src/elements/table.rs index 6820b89..b56cca5 100644 --- a/src/elements/table.rs +++ b/src/elements/table.rs @@ -18,6 +18,19 @@ pub enum Table<'a> { TableEl { value: Cow<'a, str> }, } +impl Table<'_> { + pub fn into_owned(self) -> Table<'static> { + match self { + Table::Org { tblfm } => Table::Org { + tblfm: tblfm.map(Into::into).map(Cow::Owned), + }, + Table::TableEl { value } => Table::TableEl { + value: value.into_owned().into(), + }, + } + } +} + #[derive(Debug)] #[cfg_attr(test, derive(PartialEq))] #[cfg_attr(feature = "ser", derive(serde::Serialize))] diff --git a/src/elements/target.rs b/src/elements/target.rs index fc06a11..cf6f125 100644 --- a/src/elements/target.rs +++ b/src/elements/target.rs @@ -33,6 +33,12 @@ impl Target<'_> { }, )) } + + pub fn into_owned(self) -> Target<'static> { + Target { + target: self.target.into_owned().into(), + } + } } #[test] diff --git a/src/elements/timestamp.rs b/src/elements/timestamp.rs index 890d982..9115dba 100644 --- a/src/elements/timestamp.rs +++ b/src/elements/timestamp.rs @@ -30,47 +30,17 @@ pub struct Datetime<'a> { pub minute: Option, } -fn parse_time(input: &str) -> IResult<&str, (u8, u8)> { - let (input, hour) = map_res(take_while_m_n(1, 2, |c: char| c.is_ascii_digit()), |num| { - u8::from_str_radix(num, 10) - })(input)?; - let (input, _) = tag(":")(input)?; - let (input, minute) = map_res(take(2usize), |num| u8::from_str_radix(num, 10))(input)?; - Ok((input, (hour, minute))) -} - -fn parse_datetime(input: &str) -> IResult<&str, Datetime<'_>> { - let parse_u8 = |num| u8::from_str_radix(num, 10); - - let (input, year) = map_res(take(4usize), |num| u16::from_str_radix(num, 10))(input)?; - let (input, _) = tag("-")(input)?; - let (input, month) = map_res(take(2usize), parse_u8)(input)?; - let (input, _) = tag("-")(input)?; - let (input, day) = map_res(take(2usize), parse_u8)(input)?; - let (input, _) = space1(input)?; - let (input, dayname) = take_while(|c: char| { - !c.is_ascii_whitespace() - && !c.is_ascii_digit() - && c != '+' - && c != '-' - && c != ']' - && c != '>' - })(input)?; - let (input, (hour, minute)) = map(opt(preceded(space1, parse_time)), |time| { - (time.map(|t| t.0), time.map(|t| t.1)) - })(input)?; - - Ok(( - input, +impl Datetime<'_> { + pub fn into_owned(self) -> Datetime<'static> { Datetime { - year, - month, - day, - dayname: dayname.into(), - hour, - minute, - }, - )) + year: self.year, + month: self.month, + day: self.day, + dayname: self.dayname.into_owned().into(), + hour: self.hour, + minute: self.minute, + } + } } #[cfg(feature = "chrono")] @@ -281,6 +251,97 @@ impl Timestamp<'_> { }, )) } + + pub fn into_owned(self) -> Timestamp<'static> { + match self { + Timestamp::Active { + start, + repeater, + delay, + } => Timestamp::Active { + start: start.into_owned(), + repeater: repeater.map(Into::into).map(Cow::Owned), + delay: delay.map(Into::into).map(Cow::Owned), + }, + Timestamp::Inactive { + start, + repeater, + delay, + } => Timestamp::Inactive { + start: start.into_owned(), + repeater: repeater.map(Into::into).map(Cow::Owned), + delay: delay.map(Into::into).map(Cow::Owned), + }, + Timestamp::ActiveRange { + start, + end, + repeater, + delay, + } => Timestamp::ActiveRange { + start: start.into_owned(), + end: end.into_owned(), + repeater: repeater.map(Into::into).map(Cow::Owned), + delay: delay.map(Into::into).map(Cow::Owned), + }, + Timestamp::InactiveRange { + start, + end, + repeater, + delay, + } => Timestamp::InactiveRange { + start: start.into_owned(), + end: end.into_owned(), + repeater: repeater.map(Into::into).map(Cow::Owned), + delay: delay.map(Into::into).map(Cow::Owned), + }, + Timestamp::Diary { value } => Timestamp::Diary { + value: value.into_owned().into(), + }, + } + } +} + +fn parse_time(input: &str) -> IResult<&str, (u8, u8)> { + let (input, hour) = map_res(take_while_m_n(1, 2, |c: char| c.is_ascii_digit()), |num| { + u8::from_str_radix(num, 10) + })(input)?; + let (input, _) = tag(":")(input)?; + let (input, minute) = map_res(take(2usize), |num| u8::from_str_radix(num, 10))(input)?; + Ok((input, (hour, minute))) +} + +fn parse_datetime(input: &str) -> IResult<&str, Datetime<'_>> { + let parse_u8 = |num| u8::from_str_radix(num, 10); + + let (input, year) = map_res(take(4usize), |num| u16::from_str_radix(num, 10))(input)?; + let (input, _) = tag("-")(input)?; + let (input, month) = map_res(take(2usize), parse_u8)(input)?; + let (input, _) = tag("-")(input)?; + let (input, day) = map_res(take(2usize), parse_u8)(input)?; + let (input, _) = space1(input)?; + let (input, dayname) = take_while(|c: char| { + !c.is_ascii_whitespace() + && !c.is_ascii_digit() + && c != '+' + && c != '-' + && c != ']' + && c != '>' + })(input)?; + let (input, (hour, minute)) = map(opt(preceded(space1, parse_time)), |time| { + (time.map(|t| t.0), time.map(|t| t.1)) + })(input)?; + + Ok(( + input, + Datetime { + year, + month, + day, + dayname: dayname.into(), + hour, + minute, + }, + )) } // TODO diff --git a/src/elements/title.rs b/src/elements/title.rs index a799cb4..d6a5231 100644 --- a/src/elements/title.rs +++ b/src/elements/title.rs @@ -71,6 +71,26 @@ impl Title<'_> { ), )) } + + pub fn into_owned(self) -> Title<'static> { + Title { + level: self.level, + priority: self.priority, + tags: self + .tags + .into_iter() + .map(|s| s.into_owned().into()) + .collect(), + keyword: self.keyword.map(Into::into).map(Cow::Owned), + raw: self.raw.into_owned().into(), + planning: self.planning.map(|p| Box::new(p.into_owned())), + properties: self + .properties + .into_iter() + .map(|(k, v)| (k.into_owned().into(), v.into_owned().into())) + .collect(), + } + } } fn parse_headline<'a>( diff --git a/src/export/html.rs b/src/export/html.rs index 8001e2f..60f654b 100644 --- a/src/export/html.rs +++ b/src/export/html.rs @@ -101,7 +101,7 @@ pub trait HtmlHandler> { Escape(link.desc.as_ref().unwrap_or(&link.path)), )?, Macros(_macros) => (), - RadioTarget(_radio_target) => (), + RadioTarget => (), Snippet(snippet) => { if snippet.name.eq_ignore_ascii_case("HTML") { write!(w, "{}", snippet.value)?; diff --git a/src/export/org.rs b/src/export/org.rs index 6fb8f89..7131df0 100644 --- a/src/export/org.rs +++ b/src/export/org.rs @@ -81,7 +81,7 @@ pub trait OrgHandler> { write!(&mut w, "]")?; } Macros(_macros) => (), - RadioTarget(_radio_target) => (), + RadioTarget => (), Snippet(snippet) => write!(w, "@@{}:{}@@", snippet.name, snippet.value)?, Target(_target) => (), Text { value } => write!(w, "{}", value)?, diff --git a/src/org.rs b/src/org.rs index a3e43fe..471b39c 100644 --- a/src/org.rs +++ b/src/org.rs @@ -5,7 +5,7 @@ use crate::config::ParseConfig; use crate::elements::Element; use crate::export::*; use crate::node::HeadlineNode; -use crate::parsers::*; +use crate::parsers::{parse_container, Container}; pub struct Org<'a> { pub(crate) arena: Arena>, @@ -27,31 +27,7 @@ impl Org<'_> { let mut arena = Arena::new(); let node = arena.new_node(Element::Document); - let containers = &mut vec![Container::Document { content, node }]; - - while let Some(container) = containers.pop() { - match container { - Container::Document { content, node } => { - parse_section_and_headlines(&mut arena, content, node, containers); - } - Container::Headline { content, node } => { - parse_headline_content(&mut arena, content, node, containers, config); - } - Container::Block { content, node } => { - parse_blocks(&mut arena, content, node, containers); - } - Container::Inline { content, node } => { - parse_inlines(&mut arena, content, node, containers); - } - Container::List { - content, - node, - indent, - } => { - parse_list_items(&mut arena, content, indent, node, containers); - } - } - } + parse_container(&mut arena, Container::Document { content, node }, config); Org { arena, root: node } } diff --git a/src/parsers.rs b/src/parsers.rs index 182b0fa..287321b 100644 --- a/src/parsers.rs +++ b/src/parsers.rs @@ -1,6 +1,7 @@ // parser related functions use std::borrow::Cow; +use std::marker::PhantomData; use indextree::{Arena, NodeId}; use jetscii::bytes; @@ -80,6 +81,38 @@ pub enum Container<'a> { }, } +pub fn parse_container<'a, T: ElementArena<'a>>( + arena: &mut T, + container: Container<'a>, + config: &ParseConfig, +) { + let containers = &mut vec![container]; + + while let Some(container) = containers.pop() { + match container { + Container::Document { content, node } => { + parse_section_and_headlines(arena, content, node, containers); + } + Container::Headline { content, node } => { + parse_headline_content(arena, content, node, containers, config); + } + Container::Block { content, node } => { + parse_blocks(arena, content, node, containers); + } + Container::Inline { content, node } => { + parse_inlines(arena, content, node, containers); + } + Container::List { + content, + node, + indent, + } => { + parse_list_items(arena, content, indent, node, containers); + } + } + } +} + pub fn parse_headline_content<'a, T: ElementArena<'a>>( arena: &mut T, content: &'a str, @@ -397,7 +430,12 @@ pub fn parse_inlines<'a, T: ElementArena<'a>>( macro_rules! insert_text { ($value:expr) => { - arena.insert_before_last_child(Element::Text { value: $value }, parent); + arena.insert_before_last_child( + Element::Text { + value: $value.into(), + }, + parent, + ); pos = 0; }; } @@ -449,7 +487,7 @@ pub fn parse_inlines<'a, T: ElementArena<'a>>( } if !text.is_empty() { - arena.push_element(Element::Text { value: text }, parent); + arena.push_element(Element::Text { value: text.into() }, parent); } } @@ -478,8 +516,8 @@ pub fn parse_inline<'a, T: ElementArena<'a>>( } } b'<' => { - if let Ok((tail, (radio, _content))) = RadioTarget::parse(contents) { - arena.push_element(radio, parent); + if let Ok((tail, _content)) = parse_radio_target(contents) { + arena.push_element(Element::RadioTarget, parent); return Some(tail); } else if let Ok((tail, target)) = Target::parse(contents) { arena.push_element(target, parent); @@ -717,7 +755,7 @@ pub fn take_one_word(input: &str) -> IResult<&str, &str> { } #[test] -fn test_skip_empty_lines() { +pub fn test_skip_empty_lines() { assert_eq!(skip_empty_lines("foo"), "foo"); assert_eq!(skip_empty_lines(" foo"), " foo"); assert_eq!(skip_empty_lines(" \nfoo\n"), "foo\n");