Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add an OwnedFormatItem to make it easier to store parsed format descriptions in a struct #430

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
119 changes: 118 additions & 1 deletion src/format_description/mod.rs
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -184,3 +193,111 @@ impl PartialEq<FormatItem<'_>> 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<u8>),
/// 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<Self>),
/// 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<Self>),
/// 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<Self>),
}

#[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<Component> for OwnedFormatItem {
fn from(component: Component) -> Self {
Self::Component(component)
}
}

#[cfg(feature = "alloc")]
impl TryFrom<OwnedFormatItem> for Component {
type Error = error::DifferentVariant;

fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
match value {
OwnedFormatItem::Component(component) => Ok(component),
_ => Err(error::DifferentVariant),
}
}
}

#[cfg(feature = "alloc")]
impl From<Vec<OwnedFormatItem>> for OwnedFormatItem {
fn from(items: Vec<OwnedFormatItem>) -> OwnedFormatItem {
OwnedFormatItem::Compound(items)
}
}

#[cfg(feature = "alloc")]
impl TryFrom<OwnedFormatItem> for Vec<OwnedFormatItem> {
type Error = error::DifferentVariant;

fn try_from(value: OwnedFormatItem) -> Result<Self, Self::Error> {
match value {
OwnedFormatItem::Compound(items) => Ok(items),
_ => Err(error::DifferentVariant),
}
}
}

#[cfg(feature = "alloc")]
impl PartialEq<Component> for OwnedFormatItem {
fn eq(&self, rhs: &Component) -> bool {
matches!(self, OwnedFormatItem::Component(component) if component == rhs)
}
}

#[cfg(feature = "alloc")]
impl PartialEq<OwnedFormatItem> for Component {
fn eq(&self, rhs: &OwnedFormatItem) -> bool {
rhs == self
}
}
41 changes: 40 additions & 1 deletion src/formatting/formattable.rs
Expand Up @@ -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,
};
Expand All @@ -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<T: Deref> Formattable for T where T::Target: Formattable {}
Expand Down Expand Up @@ -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<Date>,
time: Option<Time>,
offset: Option<UtcOffset>,
) -> Result<usize, error::Format> {
Ok(match self {
Self::Literal(literal) => write(output, literal.as_ref())?,
Self::Component(component) => format_component(output, *component, date, time, offset)?,
Self::Compound(items) => items.format_into(output, date, time, offset)?,
Self::Optional(item) => item.format_into(output, date, time, offset)?,
Self::First(items) => match items.get(0) {
Some(item) => item.format_into(output, date, time, offset)?,
None => 0,
},
})
}
}

impl<'a> sealed::Sealed for [FormatItem<'a>] {
fn format_into(
&self,
Expand All @@ -88,6 +111,22 @@ impl<'a> sealed::Sealed for [FormatItem<'a>] {
}
}

impl sealed::Sealed for [OwnedFormatItem] {
fn format_into(
&self,
output: &mut impl io::Write,
date: Option<Date>,
time: Option<Time>,
offset: Option<UtcOffset>,
) -> Result<usize, error::Format> {
let mut bytes = 0;
for item in self.iter() {
bytes += item.format_into(output, date, time, offset)?;
}
Ok(bytes)
}
}

impl<T: Deref> sealed::Sealed for T
where
T::Target: sealed::Sealed,
Expand Down
56 changes: 30 additions & 26 deletions tests/integration/formatting.rs
@@ -1,7 +1,7 @@
use std::io::{self, ErrorKind};

use time::format_description::well_known::{Rfc2822, Rfc3339};
use time::format_description::{self, FormatItem};
use time::format_description::{self, FormatItem, OwnedFormatItem};
use time::macros::{date, datetime, format_description as fd, offset, time};
use time::{OffsetDateTime, Time};

Expand Down Expand Up @@ -135,11 +135,9 @@ fn format_time() -> time::Result<()> {
time!(13:02:03.456_789_012).format(format_description)?,
output
);
assert!(
time!(13:02:03.456_789_012)
.format_into(&mut io::sink(), format_description)
.is_ok()
);
assert!(time!(13:02:03.456_789_012)
.format_into(&mut io::sink(), format_description)
.is_ok());
}

assert_eq!(
Expand Down Expand Up @@ -233,11 +231,9 @@ fn format_date() -> time::Result<()> {

for &(format_description, output) in &format_output {
assert_eq!(date!(2019 - 12 - 31).format(format_description)?, output);
assert!(
date!(2019 - 12 - 31)
.format_into(&mut io::sink(), format_description)
.is_ok()
);
assert!(date!(2019 - 12 - 31)
.format_into(&mut io::sink(), format_description)
.is_ok());
}

Ok(())
Expand Down Expand Up @@ -285,11 +281,9 @@ fn format_offset() -> time::Result<()> {

for &(value, format_description, output) in &value_format_output {
assert_eq!(value.format(format_description)?, output);
assert!(
value
.format_into(&mut io::sink(), format_description)
.is_ok()
);
assert!(value
.format_into(&mut io::sink(), format_description)
.is_ok());
}

Ok(())
Expand All @@ -316,11 +310,9 @@ fn format_pdt() -> time::Result<()> {
datetime!(1970-01-01 0:00).format(format_description)?,
"1970-01-01 00:00:00.0"
);
assert!(
datetime!(1970-01-01 0:00)
.format_into(&mut io::sink(), format_description)
.is_ok()
);
assert!(datetime!(1970-01-01 0:00)
.format_into(&mut io::sink(), format_description)
.is_ok());

Ok(())
}
Expand Down Expand Up @@ -348,11 +340,9 @@ fn format_odt() -> time::Result<()> {
datetime!(1970-01-01 0:00 UTC).format(&format_description)?,
"1970-01-01 00:00:00.0 +00:00:00"
);
assert!(
datetime!(1970-01-01 0:00 UTC)
.format_into(&mut io::sink(), &format_description)
.is_ok()
);
assert!(datetime!(1970-01-01 0:00 UTC)
.format_into(&mut io::sink(), &format_description)
.is_ok());

Ok(())
}
Expand Down Expand Up @@ -489,3 +479,17 @@ fn first() -> time::Result<()> {

Ok(())
}

#[test]
fn test_ownedformat_item() -> time::Result<()> {
let owned = {
let format_string = format!("[hour]-[minute]-literal");
let borrowed = format_description::parse(&format_string)?;
borrowed
.into_iter()
.map(|v| v.make_owned())
.collect::<Vec<OwnedFormatItem>>()
};
assert_eq!(Time::MIDNIGHT.format(&owned)?, "00-00-literal");
Ok(())
}