refactor: PropertiesMap
This commit is contained in:
parent
00b46a278a
commit
abf568b117
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:")?;
|
||||||
|
|
Loading…
Reference in a new issue