refactor: PropertiesMap

This commit is contained in:
PoiScript 2021-11-08 15:34:04 +08:00
parent 00b46a278a
commit abf568b117
No known key found for this signature in database
GPG key ID: 22C2B1249D99985E
3 changed files with 76 additions and 35 deletions

View file

@ -50,7 +50,7 @@ pub use self::{
table::{Table, TableCell, TableRow}, table::{Table, TableCell, TableRow},
target::Target, target::Target,
timestamp::{Datetime, Timestamp}, timestamp::{Datetime, Timestamp},
title::Title, title::{PropertiesMap, Title},
}; };
use std::borrow::Cow; use std::borrow::Cow;

View file

@ -1,12 +1,7 @@
//! Headline Title //! Headline Title
#[cfg(not(feature = "indexmap"))] use std::collections::HashMap;
pub type PropertiesMap<K, V> = std::collections::HashMap<K, V>; use std::{borrow::Cow, iter::FromIterator};
#[cfg(feature = "indexmap")]
pub type PropertiesMap<K, V> = indexmap::IndexMap<K, V>;
use std::borrow::Cow;
use memchr::memrchr2; use memchr::memrchr2;
use nom::{ use nom::{
@ -52,7 +47,7 @@ pub struct Title<'a> {
feature = "ser", feature = "ser",
serde(skip_serializing_if = "PropertiesMap::is_empty") serde(skip_serializing_if = "PropertiesMap::is_empty")
)] )]
pub properties: PropertiesMap<Cow<'a, str>, Cow<'a, str>>, pub properties: PropertiesMap<'a>,
/// Numbers of blank lines between last title's line and next non-blank line /// Numbers of blank lines between last title's line and next non-blank line
/// or buffer's end /// or buffer's end
pub post_blank: usize, pub post_blank: usize,
@ -107,11 +102,7 @@ impl Title<'_> {
keyword: self.keyword.map(Into::into).map(Cow::Owned), keyword: self.keyword.map(Into::into).map(Cow::Owned),
raw: self.raw.into_owned().into(), raw: self.raw.into_owned().into(),
planning: self.planning.map(|p| Box::new(p.into_owned())), planning: self.planning.map(|p| Box::new(p.into_owned())),
properties: self properties: self.properties.into_owned(),
.properties
.into_iter()
.map(|(k, v)| (k.into_owned().into(), v.into_owned().into()))
.collect(),
post_blank: self.post_blank, 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<Item = &(Cow<'a, str>, Cow<'a, str>)> {
self.pairs.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (Cow<'a, str>, Cow<'a, str>)> {
self.pairs.iter_mut()
}
pub fn into_iter(self) -> impl Iterator<Item = (Cow<'a, str>, Cow<'a, str>)> {
self.pairs.into_iter()
}
pub fn into_hash_map(self) -> HashMap<Cow<'a, str>, Cow<'a, str>> {
self.pairs.into_iter().collect()
}
#[cfg(feature = "indexmap")]
pub fn into_index_map(self) -> indexmap::IndexMap<Cow<'a, str>, 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<T: IntoIterator<Item = (Cow<'a, str>, 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, ()> { fn white_spaces_or_eol(input: &str) -> IResult<&str, &str, ()> {
alt((space1, line_ending))(input) alt((space1, line_ending))(input)
} }
@ -212,18 +257,16 @@ fn is_tag_line(input: &str) -> bool {
} }
#[inline] #[inline]
fn parse_properties_drawer( fn parse_properties_drawer(input: &str) -> IResult<&str, PropertiesMap<'_>, ()> {
input: &str,
) -> IResult<&str, PropertiesMap<Cow<'_, str>, Cow<'_, str>>, ()> {
let (input, (drawer, content)) = parse_drawer_without_blank(input.trim_start())?; let (input, (drawer, content)) = parse_drawer_without_blank(input.trim_start())?;
if drawer.name != "PROPERTIES" { if drawer.name != "PROPERTIES" {
return Err(Err::Error(make_error(input, ErrorKind::Tag))); return Err(Err::Error(make_error(input, ErrorKind::Tag)));
} }
let (_, map) = fold_many0( let (_, map) = fold_many0(
parse_node_property, parse_node_property,
|| PropertiesMap::new(), PropertiesMap::new,
|mut acc: PropertiesMap<_, _>, (name, value)| { |mut acc: PropertiesMap, (name, value)| {
acc.insert(name.into(), value.into()); acc.pairs.push((name.into(), value.into()));
acc acc
}, },
)(content)?; )(content)?;
@ -461,20 +504,21 @@ fn parse_properties_drawer_() {
"", "",
vec![("CUSTOM_ID".into(), "id".into())] vec![("CUSTOM_ID".into(), "id".into())]
.into_iter() .into_iter()
.collect::<PropertiesMap<_, _>>() .collect::<PropertiesMap>()
)) ))
) )
} }
#[test] #[test]
#[cfg(feature = "indexmap")]
fn preserve_properties_drawer_order() { 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 // 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. // is non-deterministic. There are roughly 10^18 possible derangements of this sequence.
for i in 0..20 { for i in 0..20 {
// Avoid alphabetic or numeric order. // Avoid alphabetic or numeric order.
let j = (i + 7) % 20; let j = (i + 7) % 20;
properties.push(( vec.push((
Cow::Owned(format!( Cow::Owned(format!(
"{}{}", "{}{}",
if i % 3 == 0 { if i % 3 == 0 {
@ -491,20 +535,17 @@ fn preserve_properties_drawer_order() {
} }
let mut s = String::default(); let mut s = String::default();
for (k, v) in &properties {
for (k, v) in &vec {
s += &format!(" :{}: {}\n", k, v); s += &format!(" :{}: {}\n", k, v);
} }
let drawer = format!(" :PROPERTIES:\n{}:END:\n", &s); 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"))] let map = parse_properties_drawer(&drawer).unwrap().1.into_index_map();
parsed.sort();
#[cfg(not(feature = "indexmap"))]
properties.sort();
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);
}
} }

View file

@ -269,7 +269,7 @@ impl OrgHandler<Error> for DefaultOrgHandler {
} }
if !title.properties.is_empty() { if !title.properties.is_empty() {
writeln!(&mut w, ":PROPERTIES:")?; 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, ":{}: {}", key, value)?;
} }
writeln!(&mut w, ":END:")?; writeln!(&mut w, ":END:")?;