From 34091c5cbf4605e0bfbb97bbea56126cd8449348 Mon Sep 17 00:00:00 2001 From: James Brown Date: Tue, 18 Jan 2022 17:23:02 -0800 Subject: [PATCH] add an OwnedFormatItem to make it easier to store parsed format descriptions in a struct --- src/format_description/mod.rs | 119 +++++++++++++++++++++++++++++++- src/formatting/formattable.rs | 41 ++++++++++- tests/integration/formatting.rs | 56 ++++++++------- 3 files changed, 188 insertions(+), 28 deletions(-) diff --git a/src/format_description/mod.rs b/src/format_description/mod.rs index 8832c3442..ce19bbec7 100644 --- a/src/format_description/mod.rs +++ b/src/format_description/mod.rs @@ -11,7 +11,7 @@ pub mod modifier; pub(crate) mod parse; #[cfg(feature = "alloc")] -use alloc::string::String; +use alloc::{string::String, vec::Vec}; use core::convert::TryFrom; #[cfg(feature = "alloc")] use core::fmt; @@ -114,6 +114,15 @@ pub enum FormatItem<'a> { First(&'a [Self]), } +impl<'a> FormatItem<'a> { + /// Create an OwnedFormatItem for this FormatItem. + /// Due to the self-referential nature of this struct, std::borrow::ToOwned cannot be used. + #[cfg(feature = "alloc")] + pub fn make_owned(&self) -> OwnedFormatItem { + self.into() + } +} + #[cfg(feature = "alloc")] impl fmt::Debug for FormatItem<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -184,3 +193,111 @@ impl PartialEq> for &[FormatItem<'_>] { rhs == self } } + +/// A complete description of how to format and parse a type which owns its components. +#[non_exhaustive] +#[cfg(feature = "alloc")] +#[derive(Clone, PartialEq, Eq)] +pub enum OwnedFormatItem { + /// Bytes that are formatted as-is. + /// + /// **Note**: If you call the `format` method that returns a `String`, these bytes will be + /// passed through `String::from_utf8_lossy`. + Literal(Vec), + /// A minimal representation of a single non-literal item. + Component(Component), + /// A series of literals or components that collectively form a partial or complete + /// description. + Compound(Vec), + /// A `FormatItem` that may or may not be present when parsing. If parsing fails, there will be + /// no effect on the resulting `struct`. + /// + /// This variant has no effect on formatting, as the value is guaranteed to be present. + Optional(Box), + /// A series of `FormatItem`s where, when parsing, the first successful parse is used. When + /// formatting, the first element of the slice is used. An empty slice is a no-op when + /// formatting or parsing. + First(Vec), +} + +#[cfg(feature = "alloc")] +impl fmt::Debug for OwnedFormatItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OwnedFormatItem::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)), + OwnedFormatItem::Component(component) => component.fmt(f), + OwnedFormatItem::Compound(compound) => compound.fmt(f), + OwnedFormatItem::Optional(item) => f.debug_tuple("Optional").field(item).finish(), + OwnedFormatItem::First(items) => f.debug_tuple("First").field(items).finish(), + } + } +} + +#[cfg(feature = "alloc")] +impl<'a> From<&FormatItem<'a>> for OwnedFormatItem { + fn from(fi: &FormatItem<'a>) -> Self { + match fi { + FormatItem::Literal(lit) => OwnedFormatItem::Literal(lit.to_vec()), + FormatItem::Component(c) => OwnedFormatItem::Component(*c), + FormatItem::Compound(l) => { + OwnedFormatItem::Compound(l.into_iter().map(|i| i.into()).collect()) + } + FormatItem::Optional(item) => OwnedFormatItem::Optional(Box::new((*item).into())), + FormatItem::First(items) => { + OwnedFormatItem::First(items.into_iter().map(|i| i.into()).collect()) + } + } + } +} + +#[cfg(feature = "alloc")] +impl From for OwnedFormatItem { + fn from(component: Component) -> Self { + Self::Component(component) + } +} + +#[cfg(feature = "alloc")] +impl TryFrom for Component { + type Error = error::DifferentVariant; + + fn try_from(value: OwnedFormatItem) -> Result { + match value { + OwnedFormatItem::Component(component) => Ok(component), + _ => Err(error::DifferentVariant), + } + } +} + +#[cfg(feature = "alloc")] +impl From> for OwnedFormatItem { + fn from(items: Vec) -> OwnedFormatItem { + OwnedFormatItem::Compound(items) + } +} + +#[cfg(feature = "alloc")] +impl TryFrom for Vec { + type Error = error::DifferentVariant; + + fn try_from(value: OwnedFormatItem) -> Result { + match value { + OwnedFormatItem::Compound(items) => Ok(items), + _ => Err(error::DifferentVariant), + } + } +} + +#[cfg(feature = "alloc")] +impl PartialEq for OwnedFormatItem { + fn eq(&self, rhs: &Component) -> bool { + matches!(self, OwnedFormatItem::Component(component) if component == rhs) + } +} + +#[cfg(feature = "alloc")] +impl PartialEq for Component { + fn eq(&self, rhs: &OwnedFormatItem) -> bool { + rhs == self + } +} diff --git a/src/formatting/formattable.rs b/src/formatting/formattable.rs index da2db0e14..6a13c76cf 100644 --- a/src/formatting/formattable.rs +++ b/src/formatting/formattable.rs @@ -4,7 +4,7 @@ use core::ops::Deref; use std::io; use crate::format_description::well_known::{Rfc2822, Rfc3339}; -use crate::format_description::FormatItem; +use crate::format_description::{FormatItem, OwnedFormatItem}; use crate::formatting::{ format_component, format_number_pad_zero, write, MONTH_NAMES, WEEKDAY_NAMES, }; @@ -14,7 +14,9 @@ use crate::{error, Date, Time, UtcOffset}; #[cfg_attr(__time_03_docs, doc(notable_trait))] pub trait Formattable: sealed::Sealed {} impl Formattable for FormatItem<'_> {} +impl Formattable for OwnedFormatItem {} impl Formattable for [FormatItem<'_>] {} +impl Formattable for [OwnedFormatItem] {} impl Formattable for Rfc3339 {} impl Formattable for Rfc2822 {} impl Formattable for T where T::Target: Formattable {} @@ -72,6 +74,27 @@ impl<'a> sealed::Sealed for FormatItem<'a> { } } +impl sealed::Sealed for OwnedFormatItem { + fn format_into( + &self, + output: &mut impl io::Write, + date: Option, + time: Option