Skip to content

Commit

Permalink
Add FormatItem::Optional
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Oct 18, 2021
1 parent 7227f7a commit 1af1448
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 38 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The format is based on [Keep a Changelog]. This project adheres to [Semantic Ver
- A `const fn default()` has been added to all modifiers that are `struct`s. These methods exist to
permit construction in `const` contexts and may be removed (without being considered a breaking
change) once `impl const Default` is stabilized.
- `FormatItem::Optional`, which will consume the contained value if present but still succeed
otherwise.

## 0.3.3 [2021-09-25]

Expand Down
6 changes: 6 additions & 0 deletions src/format_description/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ pub enum FormatItem<'a> {
/// A series of literals or components that collectively form a partial or complete
/// description.
Compound(&'a [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(&'a Self),
}

#[cfg(feature = "alloc")]
Expand All @@ -67,6 +72,7 @@ impl fmt::Debug for FormatItem<'_> {
FormatItem::Literal(literal) => f.write_str(&String::from_utf8_lossy(literal)),
FormatItem::Component(component) => component.fmt(f),
FormatItem::Compound(compound) => compound.fmt(f),
FormatItem::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/formatting/formattable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ impl<'a> sealed::Sealed for FormatItem<'a> {
Self::Literal(literal) => write(output, literal)?,
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)?,
})
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/parsing/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ impl Parsed {

/// Parse a single [`FormatItem`], mutating the struct. The remaining input is returned as the
/// `Ok` value.
///
/// If a [`FormatItem::Optional`] is passed, parsing will not fail; the input will be returned
/// as-is if the expected format is not present.
pub fn parse_item<'a>(
&mut self,
input: &'a [u8],
Expand All @@ -103,6 +106,7 @@ impl Parsed {
FormatItem::Literal(literal) => Self::parse_literal(input, literal),
FormatItem::Component(component) => self.parse_component(input, *component),
FormatItem::Compound(compound) => self.parse_items(input, compound),
FormatItem::Optional(item) => self.parse_item(input, item).or(Ok(input)),
}
}

Expand Down
74 changes: 43 additions & 31 deletions tests/integration/derives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,36 +115,48 @@ fn ord() {

#[test]
fn debug() {
let _ = format!("{:?}", Duration::ZERO);
let _ = format!("{:?}", IndeterminateOffset);
let _ = format!("{:?}", ConversionRange);
let _ = format!("{:?}", TryFromParsed::InsufficientInformation);
let _ = format!("{:?}", Parsed::new());
let _ = format!("{:?}", Instant::now());
let _ = format!("{:?}", error::ParseFromDescription::InvalidComponent("foo"));
let _ = format!("{:?}", error::Format::InvalidComponent("foo"));
let _ = format!("{:?}", well_known::Rfc3339);
let _ = format!("{:?}", component_range_error());
let _ = format!("{:?}", Error::ConversionRange(ConversionRange));
macro_rules! debug_all {
($($x:expr;)*) => {$(
let _ = format!("{:?}", $x);
)*};
}

let _ = format!("{:?}", modifier::Day::default());
let _ = format!("{:?}", modifier::MonthRepr::default());
let _ = format!("{:?}", modifier::Month::default());
let _ = format!("{:?}", modifier::Ordinal::default());
let _ = format!("{:?}", modifier::WeekdayRepr::default());
let _ = format!("{:?}", modifier::Weekday::default());
let _ = format!("{:?}", modifier::WeekNumberRepr::default());
let _ = format!("{:?}", modifier::WeekNumber::default());
let _ = format!("{:?}", modifier::YearRepr::default());
let _ = format!("{:?}", modifier::Year::default());
let _ = format!("{:?}", modifier::Hour::default());
let _ = format!("{:?}", modifier::Minute::default());
let _ = format!("{:?}", modifier::Period::default());
let _ = format!("{:?}", modifier::Second::default());
let _ = format!("{:?}", modifier::SubsecondDigits::default());
let _ = format!("{:?}", modifier::Subsecond::default());
let _ = format!("{:?}", modifier::OffsetHour::default());
let _ = format!("{:?}", modifier::OffsetMinute::default());
let _ = format!("{:?}", modifier::OffsetSecond::default());
let _ = format!("{:?}", modifier::Padding::default());
debug_all! {
Duration::ZERO;
IndeterminateOffset;
ConversionRange;
TryFromParsed::InsufficientInformation;
Parsed::new();
Instant::now();
error::ParseFromDescription::InvalidComponent("foo");
error::Format::InvalidComponent("foo");
well_known::Rfc3339;
component_range_error();
Error::ConversionRange(ConversionRange);

modifier::Day::default();
modifier::MonthRepr::default();
modifier::Month::default();
modifier::Ordinal::default();
modifier::WeekdayRepr::default();
modifier::Weekday::default();
modifier::WeekNumberRepr::default();
modifier::WeekNumber::default();
modifier::YearRepr::default();
modifier::Year::default();
modifier::Hour::default();
modifier::Minute::default();
modifier::Period::default();
modifier::Second::default();
modifier::SubsecondDigits::default();
modifier::Subsecond::default();
modifier::OffsetHour::default();
modifier::OffsetMinute::default();
modifier::OffsetSecond::default();
modifier::Padding::default();

FormatItem::Literal(b"abcdef");
FormatItem::Compound(&[FormatItem::Component(Component::Day(modifier::Day::default()))]);
FormatItem::Optional(&FormatItem::Compound(&[]));
}
}
7 changes: 1 addition & 6 deletions tests/integration/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@ use time::error::{
ComponentRange, ConversionRange, DifferentVariant, Error, Format, IndeterminateOffset,
InvalidFormatDescription, Parse, ParseFromDescription, TryFromParsed,
};
use time::format_description::{self, modifier, Component, FormatItem};
use time::macros::format_description;
use time::parsing::Parsed;
use time::{Date, Time};
use time::{format_description, Date, Time};

macro_rules! assert_display_eq {
($a:expr, $b:expr $(,)?) => {
Expand Down Expand Up @@ -60,10 +59,6 @@ fn invalid_literal() -> ParseFromDescription {

#[test]
fn debug() {
assert_eq!(format!("{:?}", FormatItem::Literal(b"abcdef")), "abcdef");
assert_dbg_reflexive!(FormatItem::Compound(&[FormatItem::Component(
Component::Day(modifier::Day::default())
)]));
assert_dbg_reflexive!(Parse::from(ParseFromDescription::InvalidComponent("a")));
assert_dbg_reflexive!(invalid_format_description());
assert_dbg_reflexive!(DifferentVariant);
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,10 @@ fn failed_write() -> time::Result<()> {
};
assert_err(Time::MIDNIGHT.format_into(bytes!(0), fd!("foo")));
assert_err(Time::MIDNIGHT.format_into(bytes!(0), &FormatItem::Compound(fd!("foo"))));
assert_err(Time::MIDNIGHT.format_into(
bytes!(0),
&FormatItem::Optional(&FormatItem::Compound(fd!("foo"))),
));
assert_err(OffsetDateTime::UNIX_EPOCH.format_into(bytes!(0), &Rfc3339));
assert_err(OffsetDateTime::UNIX_EPOCH.format_into(bytes!(4), &Rfc3339));
assert_err(OffsetDateTime::UNIX_EPOCH.format_into(bytes!(5), &Rfc3339));
Expand Down
30 changes: 29 additions & 1 deletion tests/integration/parsing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use core::convert::{TryFrom, TryInto};
use std::num::NonZeroU8;

use time::format_description::well_known::Rfc3339;
use time::format_description::{modifier, Component};
use time::format_description::{modifier, Component, FormatItem};
use time::macros::{date, datetime, time};
use time::parsing::Parsed;
use time::{
Expand Down Expand Up @@ -871,3 +872,30 @@ fn parse_components() -> time::Result<()> {

Ok(())
}

#[test]
fn parse_optional() -> time::Result<()> {
// Ensure full parsing works as expected.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&FormatItem::Optional(&FormatItem::Compound(&fd::parse("[year]-[month]-[day]")?)),
)?;
assert!(remaining_input.is_empty());
assert_eq!(parsed.year(), Some(2021));
assert_eq!(parsed.month(), Some(Month::January));
assert_eq!(parsed.day().map(NonZeroU8::get), Some(2));

// Ensure a successful partial parse *does not* mutate `parsed`.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01",
&FormatItem::Optional(&FormatItem::Compound(&fd::parse("[year]-[month]-[day]")?)),
)?;
assert_eq!(remaining_input, b"2021-01");
assert!(parsed.year().is_none());
assert!(parsed.month().is_none());
assert!(parsed.day().is_none());

Ok(())
}

0 comments on commit 1af1448

Please sign in to comment.