From abf568b1177f76572f8d5656cc2639ce01fb1f10 Mon Sep 17 00:00:00 2001 From: PoiScript Date: Mon, 8 Nov 2021 15:34:04 +0800 Subject: [PATCH] refactor: PropertiesMap --- src/elements/mod.rs | 2 +- src/elements/title.rs | 107 +++++++++++++++++++++++++++++------------- src/export/org.rs | 2 +- 3 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/elements/mod.rs b/src/elements/mod.rs index ff99f76..7beec74 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -50,7 +50,7 @@ pub use self::{ table::{Table, TableCell, TableRow}, target::Target, timestamp::{Datetime, Timestamp}, - title::Title, + title::{PropertiesMap, Title}, }; use std::borrow::Cow; diff --git a/src/elements/title.rs b/src/elements/title.rs index 0e663a7..0edc3d6 100644 --- a/src/elements/title.rs +++ b/src/elements/title.rs @@ -1,12 +1,7 @@ //! Headline Title -#[cfg(not(feature = "indexmap"))] -pub type PropertiesMap = std::collections::HashMap; - -#[cfg(feature = "indexmap")] -pub type PropertiesMap = indexmap::IndexMap; - -use std::borrow::Cow; +use std::collections::HashMap; +use std::{borrow::Cow, iter::FromIterator}; use memchr::memrchr2; use nom::{ @@ -52,7 +47,7 @@ pub struct Title<'a> { feature = "ser", serde(skip_serializing_if = "PropertiesMap::is_empty") )] - pub properties: PropertiesMap, Cow<'a, str>>, + pub properties: PropertiesMap<'a>, /// Numbers of blank lines between last title's line and next non-blank line /// or buffer's end pub post_blank: usize, @@ -107,11 +102,7 @@ impl Title<'_> { 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(), + properties: self.properties.into_owned(), post_blank: self.post_blank, } } @@ -132,6 +123,60 @@ impl Default for Title<'_> { } } +/// Properties +#[derive(Default, Debug, Clone)] +#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(feature = "ser", derive(serde::Serialize))] +pub struct PropertiesMap<'a> { + pub pairs: Vec<(Cow<'a, str>, Cow<'a, str>)>, +} + +impl<'a> PropertiesMap<'a> { + pub fn new() -> Self { + PropertiesMap { pairs: Vec::new() } + } + + pub fn is_empty(&self) -> bool { + self.pairs.is_empty() + } + + pub fn iter(&self) -> impl Iterator, Cow<'a, str>)> { + self.pairs.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator, Cow<'a, str>)> { + self.pairs.iter_mut() + } + + pub fn into_iter(self) -> impl Iterator, Cow<'a, str>)> { + self.pairs.into_iter() + } + + pub fn into_hash_map(self) -> HashMap, Cow<'a, str>> { + self.pairs.into_iter().collect() + } + + #[cfg(feature = "indexmap")] + pub fn into_index_map(self) -> indexmap::IndexMap, Cow<'a, str>> { + self.pairs.into_iter().collect() + } + + pub fn into_owned(self) -> PropertiesMap<'static> { + self.pairs + .into_iter() + .map(|(k, v)| (k.into_owned().into(), v.into_owned().into())) + .collect() + } +} + +impl<'a> FromIterator<(Cow<'a, str>, Cow<'a, str>)> for PropertiesMap<'a> { + fn from_iter, Cow<'a, str>)>>(iter: T) -> Self { + let mut map = PropertiesMap::new(); + map.pairs.extend(iter); + map + } +} + fn white_spaces_or_eol(input: &str) -> IResult<&str, &str, ()> { alt((space1, line_ending))(input) } @@ -212,18 +257,16 @@ fn is_tag_line(input: &str) -> bool { } #[inline] -fn parse_properties_drawer( - input: &str, -) -> IResult<&str, PropertiesMap, Cow<'_, str>>, ()> { +fn parse_properties_drawer(input: &str) -> IResult<&str, PropertiesMap<'_>, ()> { let (input, (drawer, content)) = parse_drawer_without_blank(input.trim_start())?; if drawer.name != "PROPERTIES" { return Err(Err::Error(make_error(input, ErrorKind::Tag))); } let (_, map) = fold_many0( parse_node_property, - || PropertiesMap::new(), - |mut acc: PropertiesMap<_, _>, (name, value)| { - acc.insert(name.into(), value.into()); + PropertiesMap::new, + |mut acc: PropertiesMap, (name, value)| { + acc.pairs.push((name.into(), value.into())); acc }, )(content)?; @@ -461,20 +504,21 @@ fn parse_properties_drawer_() { "", vec![("CUSTOM_ID".into(), "id".into())] .into_iter() - .collect::>() + .collect::() )) ) } #[test] +#[cfg(feature = "indexmap")] fn preserve_properties_drawer_order() { - let mut properties = Vec::default(); + let mut vec = Vec::default(); // Use a large number of properties to reduce false pass rate, since HashMap // is non-deterministic. There are roughly 10^18 possible derangements of this sequence. for i in 0..20 { // Avoid alphabetic or numeric order. let j = (i + 7) % 20; - properties.push(( + vec.push(( Cow::Owned(format!( "{}{}", if i % 3 == 0 { @@ -491,20 +535,17 @@ fn preserve_properties_drawer_order() { } let mut s = String::default(); - for (k, v) in &properties { + + for (k, v) in &vec { s += &format!(" :{}: {}\n", k, v); } + let drawer = format!(" :PROPERTIES:\n{}:END:\n", &s); - let mut parsed: Vec<(_, _)> = parse_properties_drawer(&drawer) - .unwrap() - .1 - .into_iter() - .collect(); - #[cfg(not(feature = "indexmap"))] - parsed.sort(); - #[cfg(not(feature = "indexmap"))] - properties.sort(); + let map = parse_properties_drawer(&drawer).unwrap().1.into_index_map(); - assert_eq!(parsed, properties); + // indexmap should be in the same order as vector + for (left, right) in vec.iter().zip(map) { + assert_eq!(left, &right); + } } diff --git a/src/export/org.rs b/src/export/org.rs index 7e860d8..44c0a5c 100644 --- a/src/export/org.rs +++ b/src/export/org.rs @@ -269,7 +269,7 @@ impl OrgHandler for DefaultOrgHandler { } if !title.properties.is_empty() { writeln!(&mut w, ":PROPERTIES:")?; - for (key, value) in &title.properties { + for (key, value) in title.properties.iter() { writeln!(&mut w, ":{}: {}", key, value)?; } writeln!(&mut w, ":END:")?;