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},
target::Target,
timestamp::{Datetime, Timestamp},
title::Title,
title::{PropertiesMap, Title},
};
use std::borrow::Cow;

View file

@ -1,12 +1,7 @@
//! Headline Title
#[cfg(not(feature = "indexmap"))]
pub type PropertiesMap<K, V> = std::collections::HashMap<K, V>;
#[cfg(feature = "indexmap")]
pub type PropertiesMap<K, V> = indexmap::IndexMap<K, V>;
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>, 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<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, ()> {
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>, 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::<PropertiesMap<_, _>>()
.collect::<PropertiesMap>()
))
)
}
#[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);
}
}

View file

@ -269,7 +269,7 @@ impl OrgHandler<Error> 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:")?;