Skip to content

Commit

Permalink
Implement FormatItem::First
Browse files Browse the repository at this point in the history
  • Loading branch information
jhpratt committed Oct 25, 2021
1 parent 32ed115 commit 9514bd0
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ The format is based on [Keep a Changelog]. This project adheres to [Semantic Ver
change) once `impl const Default` is stabilized.
- `FormatItem::Optional`, which will consume the contained value if present but still succeed
otherwise.
- `FormatItem::First`, which will consume the first successful parse, ignoring any prior errors.

### Fixed

Expand Down
5 changes: 5 additions & 0 deletions src/format_description/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ pub enum FormatItem<'a> {
///
/// This variant has no effect on formatting, as the value is guaranteed to be present.
Optional(&'a 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(&'a [Self]),
}

#[cfg(feature = "alloc")]
Expand All @@ -73,6 +77,7 @@ impl fmt::Debug for FormatItem<'_> {
FormatItem::Component(component) => component.fmt(f),
FormatItem::Compound(compound) => compound.fmt(f),
FormatItem::Optional(item) => f.debug_tuple("Optional").field(item).finish(),
FormatItem::First(items) => f.debug_tuple("First").field(items).finish(),
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/formatting/formattable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ impl<'a> sealed::Sealed for FormatItem<'a> {
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 {
[] => 0,
[item, ..] => item.format_into(output, date, time, offset)?,
},
})
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/parsing/parsed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,24 @@ impl Parsed {
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)),
FormatItem::First(items) => {
let mut first_err = None;

for item in items.iter() {
match self.parse_item(input, item) {
Ok(remaining_input) => return Ok(remaining_input),
Err(err) if first_err.is_none() => first_err = Some(err),
Err(_) => {}
}
}

match first_err {
Some(err) => Err(err),
// This location will be reached if the slice is empty, skipping the `for` loop.
// As this case is expected to be uncommon, there's no need to check up front.
None => Ok(input),
}
}
}
}

Expand Down
1 change: 1 addition & 0 deletions tests/integration/derives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,6 @@ fn debug() {
FormatItem::Literal(b"abcdef");
FormatItem::Compound(&[FormatItem::Component(Component::Day(modifier::Day::default()))]);
FormatItem::Optional(&FormatItem::Compound(&[]));
FormatItem::First(&[]);
}
}
14 changes: 14 additions & 0 deletions tests/integration/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ fn insufficient_type_information() {
assert_insufficient_type_information(Time::MIDNIGHT.format(&Rfc3339));
assert_insufficient_type_information(date!(2021 - 001).format(&Rfc3339));
assert_insufficient_type_information(datetime!(2021 - 001 0:00).format(&Rfc3339));
assert_insufficient_type_information(
Time::MIDNIGHT.format(&FormatItem::First(&[FormatItem::Compound(fd!("[year]"))])),
);
}

#[test]
Expand Down Expand Up @@ -425,3 +428,14 @@ fn failed_write() -> time::Result<()> {

Ok(())
}

#[test]
fn first() -> time::Result<()> {
assert_eq!(Time::MIDNIGHT.format(&FormatItem::First(&[]))?, "");
assert_eq!(
Time::MIDNIGHT.format(&FormatItem::First(&[FormatItem::Compound(fd!("[hour]"))]))?,
"00"
);

Ok(())
}
52 changes: 52 additions & 0 deletions tests/integration/parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -911,3 +911,55 @@ fn parse_optional() -> time::Result<()> {

Ok(())
}

#[test]
fn parse_first() -> time::Result<()> {
// Ensure the first item is parsed correctly.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&FormatItem::First(&[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 an empty slice is a no-op success.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(b"2021-01-02", &FormatItem::First(&[]))?;
assert_eq!(remaining_input, b"2021-01-02");
assert!(parsed.year().is_none());
assert!(parsed.month().is_none());
assert!(parsed.day().is_none());

// Ensure success when the first item fails.
let mut parsed = Parsed::new();
let remaining_input = parsed.parse_item(
b"2021-01-02",
&FormatItem::First(&[
FormatItem::Compound(&fd::parse("[period]")?),
FormatItem::Compound(&fd::parse("x")?),
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 the first error is returned.
let mut parsed = Parsed::new();
let err = parsed
.parse_item(
b"2021-01-02",
&FormatItem::First(&[
FormatItem::Compound(&fd::parse("[period]")?),
FormatItem::Compound(&fd::parse("x")?),
]),
)
.unwrap_err();
assert_eq!(err, error::ParseFromDescription::InvalidComponent("period"));

Ok(())
}

0 comments on commit 9514bd0

Please sign in to comment.