diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-02 18:36:06 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-02 18:36:06 -0600 |
| commit | 8cdfa445d6629ffef4cb84967ff7017654045bc2 (patch) | |
| tree | 22f0b0907c024c78d26a731e2e1f5219407d8102 /vendor/time/src/format_description/parse | |
| parent | 4351c74c7c5f97156bc94d3a8549b9940ac80e3f (diff) | |
chore: add vendor directory
Diffstat (limited to 'vendor/time/src/format_description/parse')
| -rw-r--r-- | vendor/time/src/format_description/parse/ast.rs | 384 | ||||
| -rw-r--r-- | vendor/time/src/format_description/parse/format_item.rs | 549 | ||||
| -rw-r--r-- | vendor/time/src/format_description/parse/lexer.rs | 284 | ||||
| -rw-r--r-- | vendor/time/src/format_description/parse/mod.rs | 262 | ||||
| -rw-r--r-- | vendor/time/src/format_description/parse/strftime.rs | 487 |
5 files changed, 1966 insertions, 0 deletions
diff --git a/vendor/time/src/format_description/parse/ast.rs b/vendor/time/src/format_description/parse/ast.rs new file mode 100644 index 00000000..cf13de89 --- /dev/null +++ b/vendor/time/src/format_description/parse/ast.rs @@ -0,0 +1,384 @@ +//! AST for parsing format descriptions. + +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; +use core::iter; + +use super::{lexer, unused, Error, Location, Spanned, SpannedValue, Unused}; +use crate::internal_macros::bug; + +/// One part of a complete format description. +pub(super) enum Item<'a> { + /// A literal string, formatted and parsed as-is. + /// + /// This should never be present inside a nested format description. + Literal(Spanned<&'a [u8]>), + /// A sequence of brackets. The first acts as the escape character. + /// + /// This should never be present if the lexer has `BACKSLASH_ESCAPE` set to `true`. + EscapedBracket { + /// The first bracket. + _first: Unused<Location>, + /// The second bracket. + _second: Unused<Location>, + }, + /// Part of a type, along with its modifiers. + Component { + /// Where the opening bracket was in the format string. + _opening_bracket: Unused<Location>, + /// Whitespace between the opening bracket and name. + _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>, + /// The name of the component. + name: Spanned<&'a [u8]>, + /// The modifiers for the component. + modifiers: Box<[Modifier<'a>]>, + /// Whitespace between the modifiers and closing bracket. + _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>, + /// Where the closing bracket was in the format string. + _closing_bracket: Unused<Location>, + }, + /// An optional sequence of items. + Optional { + /// Where the opening bracket was in the format string. + opening_bracket: Location, + /// Whitespace between the opening bracket and "optional". + _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>, + /// The "optional" keyword. + _optional_kw: Unused<Spanned<&'a [u8]>>, + /// Whitespace between the "optional" keyword and the opening bracket. + _whitespace: Unused<Spanned<&'a [u8]>>, + /// The items within the optional sequence. + nested_format_description: NestedFormatDescription<'a>, + /// Where the closing bracket was in the format string. + closing_bracket: Location, + }, + /// The first matching parse of a sequence of items. + First { + /// Where the opening bracket was in the format string. + opening_bracket: Location, + /// Whitespace between the opening bracket and "first". + _leading_whitespace: Unused<Option<Spanned<&'a [u8]>>>, + /// The "first" keyword. + _first_kw: Unused<Spanned<&'a [u8]>>, + /// Whitespace between the "first" keyword and the opening bracket. + _whitespace: Unused<Spanned<&'a [u8]>>, + /// The sequences of items to try. + nested_format_descriptions: Box<[NestedFormatDescription<'a>]>, + /// Where the closing bracket was in the format string. + closing_bracket: Location, + }, +} + +/// A format description that is nested within another format description. +pub(super) struct NestedFormatDescription<'a> { + /// Where the opening bracket was in the format string. + pub(super) _opening_bracket: Unused<Location>, + /// The items within the nested format description. + pub(super) items: Box<[Item<'a>]>, + /// Where the closing bracket was in the format string. + pub(super) _closing_bracket: Unused<Location>, + /// Whitespace between the closing bracket and the next item. + pub(super) _trailing_whitespace: Unused<Option<Spanned<&'a [u8]>>>, +} + +/// A modifier for a component. +pub(super) struct Modifier<'a> { + /// Whitespace preceding the modifier. + pub(super) _leading_whitespace: Unused<Spanned<&'a [u8]>>, + /// The key of the modifier. + pub(super) key: Spanned<&'a [u8]>, + /// Where the colon of the modifier was in the format string. + pub(super) _colon: Unused<Location>, + /// The value of the modifier. + pub(super) value: Spanned<&'a [u8]>, +} + +/// Parse the provided tokens into an AST. +pub(super) fn parse< + 'item: 'iter, + 'iter, + I: Iterator<Item = Result<lexer::Token<'item>, Error>>, + const VERSION: usize, +>( + tokens: &'iter mut lexer::Lexed<I>, +) -> impl Iterator<Item = Result<Item<'item>, Error>> + 'iter { + validate_version!(VERSION); + parse_inner::<_, false, VERSION>(tokens) +} + +/// Parse the provided tokens into an AST. The const generic indicates whether the resulting +/// [`Item`] will be used directly or as part of a [`NestedFormatDescription`]. +fn parse_inner< + 'item, + I: Iterator<Item = Result<lexer::Token<'item>, Error>>, + const NESTED: bool, + const VERSION: usize, +>( + tokens: &mut lexer::Lexed<I>, +) -> impl Iterator<Item = Result<Item<'item>, Error>> + '_ { + validate_version!(VERSION); + iter::from_fn(move || { + if NESTED && tokens.peek_closing_bracket().is_some() { + return None; + } + + let next = match tokens.next()? { + Ok(token) => token, + Err(err) => return Some(Err(err)), + }; + + Some(match next { + lexer::Token::Literal(Spanned { value: _, span: _ }) if NESTED => { + bug!("literal should not be present in nested description") + } + lexer::Token::Literal(value) => Ok(Item::Literal(value)), + lexer::Token::Bracket { + kind: lexer::BracketKind::Opening, + location, + } => { + if version!(..=1) { + if let Some(second_location) = tokens.next_if_opening_bracket() { + Ok(Item::EscapedBracket { + _first: unused(location), + _second: unused(second_location), + }) + } else { + parse_component::<_, VERSION>(location, tokens) + } + } else { + parse_component::<_, VERSION>(location, tokens) + } + } + lexer::Token::Bracket { + kind: lexer::BracketKind::Closing, + location: _, + } if NESTED => { + bug!("closing bracket should be caught by the `if` statement") + } + lexer::Token::Bracket { + kind: lexer::BracketKind::Closing, + location: _, + } => { + bug!("closing bracket should have been consumed by `parse_component`") + } + lexer::Token::ComponentPart { + kind: _, // whitespace is significant in nested components + value, + } if NESTED => Ok(Item::Literal(value)), + lexer::Token::ComponentPart { kind: _, value: _ } => { + bug!("component part should have been consumed by `parse_component`") + } + }) + }) +} + +/// Parse a component. This assumes that the opening bracket has already been consumed. +fn parse_component< + 'a, + I: Iterator<Item = Result<lexer::Token<'a>, Error>>, + const VERSION: usize, +>( + opening_bracket: Location, + tokens: &mut lexer::Lexed<I>, +) -> Result<Item<'a>, Error> { + validate_version!(VERSION); + let leading_whitespace = tokens.next_if_whitespace(); + + let Some(name) = tokens.next_if_not_whitespace() else { + let span = match leading_whitespace { + Some(Spanned { value: _, span }) => span, + None => opening_bracket.to_self(), + }; + return Err(Error { + _inner: unused(span.error("expected component name")), + public: crate::error::InvalidFormatDescription::MissingComponentName { + index: span.start.byte as usize, + }, + }); + }; + + if *name == b"optional" { + let Some(whitespace) = tokens.next_if_whitespace() else { + return Err(Error { + _inner: unused(name.span.error("expected whitespace after `optional`")), + public: crate::error::InvalidFormatDescription::Expected { + what: "whitespace after `optional`", + index: name.span.end.byte as usize, + }, + }); + }; + + let nested = parse_nested::<_, VERSION>(whitespace.span.end, tokens)?; + + let Some(closing_bracket) = tokens.next_if_closing_bracket() else { + return Err(Error { + _inner: unused(opening_bracket.error("unclosed bracket")), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte as usize, + }, + }); + }; + + return Ok(Item::Optional { + opening_bracket, + _leading_whitespace: unused(leading_whitespace), + _optional_kw: unused(name), + _whitespace: unused(whitespace), + nested_format_description: nested, + closing_bracket, + }); + } + + if *name == b"first" { + let Some(whitespace) = tokens.next_if_whitespace() else { + return Err(Error { + _inner: unused(name.span.error("expected whitespace after `first`")), + public: crate::error::InvalidFormatDescription::Expected { + what: "whitespace after `first`", + index: name.span.end.byte as usize, + }, + }); + }; + + let mut nested_format_descriptions = Vec::new(); + while let Ok(description) = parse_nested::<_, VERSION>(whitespace.span.end, tokens) { + nested_format_descriptions.push(description); + } + + let Some(closing_bracket) = tokens.next_if_closing_bracket() else { + return Err(Error { + _inner: unused(opening_bracket.error("unclosed bracket")), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte as usize, + }, + }); + }; + + return Ok(Item::First { + opening_bracket, + _leading_whitespace: unused(leading_whitespace), + _first_kw: unused(name), + _whitespace: unused(whitespace), + nested_format_descriptions: nested_format_descriptions.into_boxed_slice(), + closing_bracket, + }); + } + + let mut modifiers = Vec::new(); + let trailing_whitespace = loop { + let Some(whitespace) = tokens.next_if_whitespace() else { + break None; + }; + + // This is not necessary for proper parsing, but provides a much better error when a nested + // description is used where it's not allowed. + if let Some(location) = tokens.next_if_opening_bracket() { + return Err(Error { + _inner: unused( + location + .to_self() + .error("modifier must be of the form `key:value`"), + ), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from("["), + index: location.byte as usize, + }, + }); + } + + let Some(Spanned { value, span }) = tokens.next_if_not_whitespace() else { + break Some(whitespace); + }; + + let Some(colon_index) = value.iter().position(|&b| b == b':') else { + return Err(Error { + _inner: unused(span.error("modifier must be of the form `key:value`")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value).into_owned(), + index: span.start.byte as usize, + }, + }); + }; + let key = &value[..colon_index]; + let value = &value[colon_index + 1..]; + + if key.is_empty() { + return Err(Error { + _inner: unused(span.shrink_to_start().error("expected modifier key")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.start.byte as usize, + }, + }); + } + if value.is_empty() { + return Err(Error { + _inner: unused(span.shrink_to_end().error("expected modifier value")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::new(), + index: span.shrink_to_end().start.byte as usize, + }, + }); + } + + modifiers.push(Modifier { + _leading_whitespace: unused(whitespace), + key: key.spanned(span.shrink_to_before(colon_index as u32)), + _colon: unused(span.start.offset(colon_index as u32)), + value: value.spanned(span.shrink_to_after(colon_index as u32)), + }); + }; + + let Some(closing_bracket) = tokens.next_if_closing_bracket() else { + return Err(Error { + _inner: unused(opening_bracket.error("unclosed bracket")), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte as usize, + }, + }); + }; + + Ok(Item::Component { + _opening_bracket: unused(opening_bracket), + _leading_whitespace: unused(leading_whitespace), + name, + modifiers: modifiers.into_boxed_slice(), + _trailing_whitespace: unused(trailing_whitespace), + _closing_bracket: unused(closing_bracket), + }) +} + +/// Parse a nested format description. The location provided is the the most recent one consumed. +fn parse_nested<'a, I: Iterator<Item = Result<lexer::Token<'a>, Error>>, const VERSION: usize>( + last_location: Location, + tokens: &mut lexer::Lexed<I>, +) -> Result<NestedFormatDescription<'a>, Error> { + validate_version!(VERSION); + let Some(opening_bracket) = tokens.next_if_opening_bracket() else { + return Err(Error { + _inner: unused(last_location.error("expected opening bracket")), + public: crate::error::InvalidFormatDescription::Expected { + what: "opening bracket", + index: last_location.byte as usize, + }, + }); + }; + let items = parse_inner::<_, true, VERSION>(tokens).collect::<Result<_, _>>()?; + let Some(closing_bracket) = tokens.next_if_closing_bracket() else { + return Err(Error { + _inner: unused(opening_bracket.error("unclosed bracket")), + public: crate::error::InvalidFormatDescription::UnclosedOpeningBracket { + index: opening_bracket.byte as usize, + }, + }); + }; + let trailing_whitespace = tokens.next_if_whitespace(); + + Ok(NestedFormatDescription { + _opening_bracket: unused(opening_bracket), + items, + _closing_bracket: unused(closing_bracket), + _trailing_whitespace: unused(trailing_whitespace), + }) +} diff --git a/vendor/time/src/format_description/parse/format_item.rs b/vendor/time/src/format_description/parse/format_item.rs new file mode 100644 index 00000000..d401daa5 --- /dev/null +++ b/vendor/time/src/format_description/parse/format_item.rs @@ -0,0 +1,549 @@ +//! Typed, validated representation of a parsed format description. + +use alloc::boxed::Box; +use alloc::string::String; +use core::num::NonZeroU16; +use core::str::{self, FromStr}; + +use super::{ast, unused, Error, Span, Spanned}; +use crate::internal_macros::bug; + +/// Parse an AST iterator into a sequence of format items. +pub(super) fn parse<'a>( + ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>, +) -> impl Iterator<Item = Result<Item<'a>, Error>> { + ast_items.map(|ast_item| ast_item.and_then(Item::from_ast)) +} + +/// A description of how to format and parse one part of a type. +pub(super) enum Item<'a> { + /// A literal string. + Literal(&'a [u8]), + /// Part of a type, along with its modifiers. + Component(Component), + /// A sequence of optional items. + Optional { + /// The items themselves. + value: Box<[Self]>, + /// The span of the full sequence. + span: Span, + }, + /// The first matching parse of a sequence of format descriptions. + First { + /// The sequence of format descriptions. + value: Box<[Box<[Self]>]>, + /// The span of the full sequence. + span: Span, + }, +} + +impl Item<'_> { + /// Parse an AST item into a format item. + pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> { + Ok(match ast_item { + ast::Item::Component { + _opening_bracket: _, + _leading_whitespace: _, + name, + modifiers, + _trailing_whitespace: _, + _closing_bracket: _, + } => Item::Component(component_from_ast(&name, &modifiers)?), + ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value), + ast::Item::EscapedBracket { + _first: _, + _second: _, + } => Item::Literal(b"["), + ast::Item::Optional { + opening_bracket, + _leading_whitespace: _, + _optional_kw: _, + _whitespace: _, + nested_format_description, + closing_bracket, + } => { + let items = nested_format_description + .items + .into_vec() + .into_iter() + .map(Item::from_ast) + .collect::<Result<_, _>>()?; + Item::Optional { + value: items, + span: opening_bracket.to(closing_bracket), + } + } + ast::Item::First { + opening_bracket, + _leading_whitespace: _, + _first_kw: _, + _whitespace: _, + nested_format_descriptions, + closing_bracket, + } => { + let items = nested_format_descriptions + .into_vec() + .into_iter() + .map(|nested_format_description| { + nested_format_description + .items + .into_vec() + .into_iter() + .map(Item::from_ast) + .collect() + }) + .collect::<Result<_, _>>()?; + Item::First { + value: items, + span: opening_bracket.to(closing_bracket), + } + } + }) + } +} + +impl<'a> TryFrom<Item<'a>> for crate::format_description::BorrowedFormatItem<'a> { + type Error = Error; + + fn try_from(item: Item<'a>) -> Result<Self, Self::Error> { + match item { + Item::Literal(literal) => Ok(Self::Literal(literal)), + Item::Component(component) => Ok(Self::Component(component.into())), + Item::Optional { value: _, span } => Err(Error { + _inner: unused(span.error( + "optional items are not supported in runtime-parsed format descriptions", + )), + public: crate::error::InvalidFormatDescription::NotSupported { + what: "optional item", + context: "runtime-parsed format descriptions", + index: span.start.byte as usize, + }, + }), + Item::First { value: _, span } => Err(Error { + _inner: unused(span.error( + "'first' items are not supported in runtime-parsed format descriptions", + )), + public: crate::error::InvalidFormatDescription::NotSupported { + what: "'first' item", + context: "runtime-parsed format descriptions", + index: span.start.byte as usize, + }, + }), + } + } +} + +impl From<Item<'_>> for crate::format_description::OwnedFormatItem { + fn from(item: Item<'_>) -> Self { + match item { + Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()), + Item::Component(component) => Self::Component(component.into()), + Item::Optional { value, span: _ } => Self::Optional(Box::new(value.into())), + Item::First { value, span: _ } => { + Self::First(value.into_vec().into_iter().map(Into::into).collect()) + } + } + } +} + +impl<'a> From<Box<[Item<'a>]>> for crate::format_description::OwnedFormatItem { + fn from(items: Box<[Item<'a>]>) -> Self { + let items = items.into_vec(); + match <[_; 1]>::try_from(items) { + Ok([item]) => item.into(), + Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()), + } + } +} + +/// Declare the `Component` struct. +macro_rules! component_definition { + (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; + (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; + (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* }; + (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? }; + + ($vis:vis enum $name:ident { + $($variant:ident = $parse_variant:literal {$( + $(#[$required:tt])? + $field:ident = $parse_field:literal: + Option<$(#[$from_str:tt])? $field_type:ty> + => $target_field:ident + ),* $(,)?}),* $(,)? + }) => { + $vis enum $name { + $($variant($variant),)* + } + + $($vis struct $variant { + $($field: Option<$field_type>),* + })* + + $(impl $variant { + /// Parse the component from the AST, given its modifiers. + fn with_modifiers( + modifiers: &[ast::Modifier<'_>], + _component_span: Span, + ) -> Result<Self, Error> + { + // rustc will complain if the modifier is empty. + #[allow(unused_mut)] + let mut this = Self { + $($field: None),* + }; + + for modifier in modifiers { + $(#[allow(clippy::string_lit_as_bytes)] + if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) { + this.$field = component_definition!(@if_from_str $($from_str)? + then { + parse_from_modifier_value::<$field_type>(&modifier.value)? + } else { + <$field_type>::from_modifier_value(&modifier.value)? + }); + continue; + })* + return Err(Error { + _inner: unused(modifier.key.span.error("invalid modifier key")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(*modifier.key).into_owned(), + index: modifier.key.span.start.byte as usize, + } + }); + } + + $(component_definition! { @if_required $($required)? then { + if this.$field.is_none() { + return Err(Error { + _inner: unused(_component_span.error("missing required modifier")), + public: + crate::error::InvalidFormatDescription::MissingRequiredModifier { + name: $parse_field, + index: _component_span.start.byte as usize, + } + }); + } + }})* + + Ok(this) + } + })* + + impl From<$name> for crate::format_description::Component { + fn from(component: $name) -> Self { + match component {$( + $name::$variant($variant { $($field),* }) => { + $crate::format_description::component::Component::$variant( + $crate::format_description::modifier::$variant {$( + $target_field: component_definition! { @if_required $($required)? + then { + match $field { + Some(value) => value.into(), + None => bug!("required modifier was not set"), + } + } else { + $field.unwrap_or_default().into() + } + } + ),*} + ) + } + )*} + } + } + + /// Parse a component from the AST, given its name and modifiers. + fn component_from_ast( + name: &Spanned<&[u8]>, + modifiers: &[ast::Modifier<'_>], + ) -> Result<Component, Error> { + $(#[allow(clippy::string_lit_as_bytes)] + if name.eq_ignore_ascii_case($parse_variant.as_bytes()) { + return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?)); + })* + Err(Error { + _inner: unused(name.span.error("invalid component")), + public: crate::error::InvalidFormatDescription::InvalidComponentName { + name: String::from_utf8_lossy(name).into_owned(), + index: name.span.start.byte as usize, + }, + }) + } + } +} + +// Keep in alphabetical order. +component_definition! { + pub(super) enum Component { + Day = "day" { + padding = "padding": Option<Padding> => padding, + }, + End = "end" {}, + Hour = "hour" { + padding = "padding": Option<Padding> => padding, + base = "repr": Option<HourBase> => is_12_hour_clock, + }, + Ignore = "ignore" { + #[required] + count = "count": Option<#[from_str] NonZeroU16> => count, + }, + Minute = "minute" { + padding = "padding": Option<Padding> => padding, + }, + Month = "month" { + padding = "padding": Option<Padding> => padding, + repr = "repr": Option<MonthRepr> => repr, + case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive, + }, + OffsetHour = "offset_hour" { + sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, + padding = "padding": Option<Padding> => padding, + }, + OffsetMinute = "offset_minute" { + padding = "padding": Option<Padding> => padding, + }, + OffsetSecond = "offset_second" { + padding = "padding": Option<Padding> => padding, + }, + Ordinal = "ordinal" { + padding = "padding": Option<Padding> => padding, + }, + Period = "period" { + case = "case": Option<PeriodCase> => is_uppercase, + case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive, + }, + Second = "second" { + padding = "padding": Option<Padding> => padding, + }, + Subsecond = "subsecond" { + digits = "digits": Option<SubsecondDigits> => digits, + }, + UnixTimestamp = "unix_timestamp" { + precision = "precision": Option<UnixTimestampPrecision> => precision, + sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, + }, + Weekday = "weekday" { + repr = "repr": Option<WeekdayRepr> => repr, + one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed, + case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive, + }, + WeekNumber = "week_number" { + padding = "padding": Option<Padding> => padding, + repr = "repr": Option<WeekNumberRepr> => repr, + }, + Year = "year" { + padding = "padding": Option<Padding> => padding, + repr = "repr": Option<YearRepr> => repr, + range = "range": Option<YearRange> => range, + base = "base": Option<YearBase> => iso_week_based, + sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory, + }, + } +} + +/// Get the target type for a given enum. +macro_rules! target_ty { + ($name:ident $type:ty) => { + $type + }; + ($name:ident) => { + $crate::format_description::modifier::$name + }; +} + +/// Get the target value for a given enum. +macro_rules! target_value { + ($name:ident $variant:ident $value:expr) => { + $value + }; + ($name:ident $variant:ident) => { + $crate::format_description::modifier::$name::$variant + }; +} + +/// Declare the various modifiers. +/// +/// For the general case, ordinary syntax can be used. Note that you _must_ declare a default +/// variant. The only significant change is that the string representation of the variant must be +/// provided after the variant name. For example, `Numerical = b"numerical"` declares a variant +/// named `Numerical` with the string representation `b"numerical"`. This is the value that will be +/// used when parsing the modifier. The value is not case sensitive. +/// +/// If the type in the public API does not have the same name as the type in the internal +/// representation, then the former must be specified in parenthesis after the internal name. For +/// example, `HourBase(bool)` has an internal name "HourBase", but is represented as a boolean in +/// the public API. +/// +/// By default, the internal variant name is assumed to be the same as the public variant name. If +/// this is not the case, the qualified path to the variant must be specified in parenthesis after +/// the internal variant name. For example, `Twelve(true)` has an internal variant name "Twelve", +/// but is represented as `true` in the public API. +macro_rules! modifier { + ($( + enum $name:ident $(($target_ty:ty))? { + $( + $(#[$attr:meta])? + $variant:ident $(($target_value:expr))? = $parse_variant:literal + ),* $(,)? + } + )+) => {$( + #[derive(Default)] + enum $name { + $($(#[$attr])? $variant),* + } + + impl $name { + /// Parse the modifier from its string representation. + fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> { + $(if value.eq_ignore_ascii_case($parse_variant) { + return Ok(Some(Self::$variant)); + })* + Err(Error { + _inner: unused(value.span.error("invalid modifier value")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value).into_owned(), + index: value.span.start.byte as usize, + }, + }) + } + } + + impl From<$name> for target_ty!($name $($target_ty)?) { + fn from(modifier: $name) -> Self { + match modifier { + $($name::$variant => target_value!($name $variant $($target_value)?)),* + } + } + } + )+}; +} + +// Keep in alphabetical order. +modifier! { + enum HourBase(bool) { + Twelve(true) = b"12", + #[default] + TwentyFour(false) = b"24", + } + + enum MonthCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum MonthRepr { + #[default] + Numerical = b"numerical", + Long = b"long", + Short = b"short", + } + + enum Padding { + Space = b"space", + #[default] + Zero = b"zero", + None = b"none", + } + + enum PeriodCase(bool) { + Lower(false) = b"lower", + #[default] + Upper(true) = b"upper", + } + + enum PeriodCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum SignBehavior(bool) { + #[default] + Automatic(false) = b"automatic", + Mandatory(true) = b"mandatory", + } + + enum SubsecondDigits { + One = b"1", + Two = b"2", + Three = b"3", + Four = b"4", + Five = b"5", + Six = b"6", + Seven = b"7", + Eight = b"8", + Nine = b"9", + #[default] + OneOrMore = b"1+", + } + + enum UnixTimestampPrecision { + #[default] + Second = b"second", + Millisecond = b"millisecond", + Microsecond = b"microsecond", + Nanosecond = b"nanosecond", + } + + enum WeekNumberRepr { + #[default] + Iso = b"iso", + Sunday = b"sunday", + Monday = b"monday", + } + + enum WeekdayCaseSensitive(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum WeekdayOneIndexed(bool) { + False(false) = b"false", + #[default] + True(true) = b"true", + } + + enum WeekdayRepr { + Short = b"short", + #[default] + Long = b"long", + Sunday = b"sunday", + Monday = b"monday", + } + + enum YearBase(bool) { + #[default] + Calendar(false) = b"calendar", + IsoWeek(true) = b"iso_week", + } + + enum YearRepr { + #[default] + Full = b"full", + Century = b"century", + LastTwo = b"last_two", + } + + enum YearRange { + Standard = b"standard", + #[default] + Extended = b"extended", + } +} + +/// Parse a modifier value using `FromStr`. Requires the modifier value to be valid UTF-8. +fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> { + str::from_utf8(value) + .ok() + .and_then(|val| val.parse::<T>().ok()) + .map(|val| Some(val)) + .ok_or_else(|| Error { + _inner: unused(value.span.error("invalid modifier value")), + public: crate::error::InvalidFormatDescription::InvalidModifier { + value: String::from_utf8_lossy(value).into_owned(), + index: value.span.start.byte as usize, + }, + }) +} diff --git a/vendor/time/src/format_description/parse/lexer.rs b/vendor/time/src/format_description/parse/lexer.rs new file mode 100644 index 00000000..a63722e1 --- /dev/null +++ b/vendor/time/src/format_description/parse/lexer.rs @@ -0,0 +1,284 @@ +//! Lexer for parsing format descriptions. + +use core::iter; + +use super::{attach_location, unused, Error, Location, Spanned, SpannedValue}; + +/// An iterator over the lexed tokens. +pub(super) struct Lexed<I: Iterator> { + /// The internal iterator. + iter: iter::Peekable<I>, +} + +impl<I: Iterator> Iterator for Lexed<I> { + type Item = I::Item; + + fn next(&mut self) -> Option<Self::Item> { + self.iter.next() + } +} + +impl<'iter, 'token: 'iter, I: Iterator<Item = Result<Token<'token>, Error>> + 'iter> Lexed<I> { + /// Peek at the next item in the iterator. + pub(super) fn peek(&mut self) -> Option<&I::Item> { + self.iter.peek() + } + + /// Consume the next token if it is whitespace. + pub(super) fn next_if_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> { + if let Some(&Ok(Token::ComponentPart { + kind: ComponentKind::Whitespace, + value, + })) = self.peek() + { + self.next(); // consume + Some(value) + } else { + None + } + } + + /// Consume the next token if it is a component item that is not whitespace. + pub(super) fn next_if_not_whitespace(&mut self) -> Option<Spanned<&'token [u8]>> { + if let Some(&Ok(Token::ComponentPart { + kind: ComponentKind::NotWhitespace, + value, + })) = self.peek() + { + self.next(); // consume + Some(value) + } else { + None + } + } + + /// Consume the next token if it is an opening bracket. + pub(super) fn next_if_opening_bracket(&mut self) -> Option<Location> { + if let Some(&Ok(Token::Bracket { + kind: BracketKind::Opening, + location, + })) = self.peek() + { + self.next(); // consume + Some(location) + } else { + None + } + } + + /// Peek at the next token if it is a closing bracket. + pub(super) fn peek_closing_bracket(&'iter mut self) -> Option<&'iter Location> { + if let Some(Ok(Token::Bracket { + kind: BracketKind::Closing, + location, + })) = self.peek() + { + Some(location) + } else { + None + } + } + + /// Consume the next token if it is a closing bracket. + pub(super) fn next_if_closing_bracket(&mut self) -> Option<Location> { + if let Some(&Ok(Token::Bracket { + kind: BracketKind::Closing, + location, + })) = self.peek() + { + self.next(); // consume + Some(location) + } else { + None + } + } +} + +/// A token emitted by the lexer. There is no semantic meaning at this stage. +pub(super) enum Token<'a> { + /// A literal string, formatted and parsed as-is. + Literal(Spanned<&'a [u8]>), + /// An opening or closing bracket. May or may not be the start or end of a component. + Bracket { + /// Whether the bracket is opening or closing. + kind: BracketKind, + /// Where the bracket was in the format string. + location: Location, + }, + /// One part of a component. This could be its name, a modifier, or whitespace. + ComponentPart { + /// Whether the part is whitespace or not. + kind: ComponentKind, + /// The part itself. + value: Spanned<&'a [u8]>, + }, +} + +/// What type of bracket is present. +pub(super) enum BracketKind { + /// An opening bracket: `[` + Opening, + /// A closing bracket: `]` + Closing, +} + +/// Indicates whether the component is whitespace or not. +pub(super) enum ComponentKind { + Whitespace, + NotWhitespace, +} + +/// Parse the string into a series of [`Token`]s. +/// +/// `VERSION` controls the version of the format description that is being parsed. Currently, this +/// must be 1 or 2. +/// +/// - When `VERSION` is 1, `[[` is the only escape sequence, resulting in a literal `[`. +/// - When `VERSION` is 2, all escape sequences begin with `\`. The only characters that may +/// currently follow are `\`, `[`, and `]`, all of which result in the literal character. All +/// other characters result in a lex error. +pub(super) fn lex<const VERSION: usize>( + mut input: &[u8], +) -> Lexed<impl Iterator<Item = Result<Token<'_>, Error>>> { + validate_version!(VERSION); + + let mut depth: u8 = 0; + let mut iter = attach_location(input.iter()).peekable(); + let mut second_bracket_location = None; + + let iter = iter::from_fn(move || { + // The flag is only set when version is zero. + if version!(..=1) { + // There is a flag set to emit the second half of an escaped bracket pair. + if let Some(location) = second_bracket_location.take() { + return Some(Ok(Token::Bracket { + kind: BracketKind::Opening, + location, + })); + } + } + + Some(Ok(match iter.next()? { + // possible escape sequence + (b'\\', backslash_loc) if version!(2..) => { + match iter.next() { + Some((b'\\' | b'[' | b']', char_loc)) => { + // The escaped character is emitted as-is. + let char = &input[1..2]; + input = &input[2..]; + if depth == 0 { + Token::Literal(char.spanned(backslash_loc.to(char_loc))) + } else { + Token::ComponentPart { + kind: ComponentKind::NotWhitespace, + value: char.spanned(backslash_loc.to(char_loc)), + } + } + } + Some((_, loc)) => { + return Some(Err(Error { + _inner: unused(loc.error("invalid escape sequence")), + public: crate::error::InvalidFormatDescription::Expected { + what: "valid escape sequence", + index: loc.byte as usize, + }, + })); + } + None => { + return Some(Err(Error { + _inner: unused(backslash_loc.error("unexpected end of input")), + public: crate::error::InvalidFormatDescription::Expected { + what: "valid escape sequence", + index: backslash_loc.byte as usize, + }, + })); + } + } + } + // potentially escaped opening bracket + (b'[', location) if version!(..=1) => { + if let Some((_, second_location)) = iter.next_if(|&(&byte, _)| byte == b'[') { + // Escaped bracket. Store the location of the second so we can emit it later. + second_bracket_location = Some(second_location); + input = &input[2..]; + } else { + // opening bracket + depth += 1; + input = &input[1..]; + } + + Token::Bracket { + kind: BracketKind::Opening, + location, + } + } + // opening bracket + (b'[', location) => { + depth += 1; + input = &input[1..]; + + Token::Bracket { + kind: BracketKind::Opening, + location, + } + } + // closing bracket + (b']', location) if depth > 0 => { + depth -= 1; + input = &input[1..]; + + Token::Bracket { + kind: BracketKind::Closing, + location, + } + } + // literal + (_, start_location) if depth == 0 => { + let mut bytes = 1; + let mut end_location = start_location; + + while let Some((_, location)) = + iter.next_if(|&(&byte, _)| !((version!(2..) && byte == b'\\') || byte == b'[')) + { + end_location = location; + bytes += 1; + } + + let value = &input[..bytes]; + input = &input[bytes..]; + + Token::Literal(value.spanned(start_location.to(end_location))) + } + // component part + (byte, start_location) => { + let mut bytes = 1; + let mut end_location = start_location; + let is_whitespace = byte.is_ascii_whitespace(); + + while let Some((_, location)) = iter.next_if(|&(byte, _)| { + !matches!(byte, b'\\' | b'[' | b']') + && is_whitespace == byte.is_ascii_whitespace() + }) { + end_location = location; + bytes += 1; + } + + let value = &input[..bytes]; + input = &input[bytes..]; + + Token::ComponentPart { + kind: if is_whitespace { + ComponentKind::Whitespace + } else { + ComponentKind::NotWhitespace + }, + value: value.spanned(start_location.to(end_location)), + } + } + })) + }); + + Lexed { + iter: iter.peekable(), + } +} diff --git a/vendor/time/src/format_description/parse/mod.rs b/vendor/time/src/format_description/parse/mod.rs new file mode 100644 index 00000000..d058594d --- /dev/null +++ b/vendor/time/src/format_description/parse/mod.rs @@ -0,0 +1,262 @@ +//! Parser for format descriptions. + +use alloc::boxed::Box; +use alloc::vec::Vec; + +pub use self::strftime::{parse_strftime_borrowed, parse_strftime_owned}; +use crate::{error, format_description}; + +/// A helper macro to make version restrictions simpler to read and write. +macro_rules! version { + ($range:expr) => { + $range.contains(&VERSION) + }; +} + +/// A helper macro to statically validate the version (when used as a const parameter). +macro_rules! validate_version { + ($version:ident) => { + let _ = $crate::format_description::parse::Version::<$version>::IS_VALID; + }; +} + +mod ast; +mod format_item; +mod lexer; +mod strftime; + +/// A struct that is used to ensure that the version is valid. +struct Version<const N: usize>; +impl<const N: usize> Version<N> { + /// A constant that panics if the version is not valid. This results in a post-monomorphization + /// error. + const IS_VALID: () = assert!(N >= 1 && N <= 2); +} + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). +/// +/// This function exists for backward compatibility reasons. It is equivalent to calling +/// `parse_borrowed::<1>(s)`. In the future, this function will be deprecated in favor of +/// `parse_borrowed`. +pub fn parse( + s: &str, +) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> { + parse_borrowed::<1>(s) +} + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format +/// description is provided as the const parameter. **It is recommended to use version 2.** +pub fn parse_borrowed<const VERSION: usize>( + s: &str, +) -> Result<Vec<format_description::BorrowedFormatItem<'_>>, error::InvalidFormatDescription> { + validate_version!(VERSION); + let mut lexed = lexer::lex::<VERSION>(s.as_bytes()); + let ast = ast::parse::<_, VERSION>(&mut lexed); + let format_items = format_item::parse(ast); + Ok(format_items + .map(|res| res.and_then(TryInto::try_into)) + .collect::<Result<_, _>>()?) +} + +/// Parse a sequence of items from the format description. +/// +/// The syntax for the format description can be found in [the +/// book](https://time-rs.github.io/book/api/format-description.html). The version of the format +/// description is provided as the const parameter. +/// +/// Unlike [`parse`], this function returns [`OwnedFormatItem`], which owns its contents. This means +/// that there is no lifetime that needs to be handled. **It is recommended to use version 2.** +/// +/// [`OwnedFormatItem`]: crate::format_description::OwnedFormatItem +pub fn parse_owned<const VERSION: usize>( + s: &str, +) -> Result<format_description::OwnedFormatItem, error::InvalidFormatDescription> { + validate_version!(VERSION); + let mut lexed = lexer::lex::<VERSION>(s.as_bytes()); + let ast = ast::parse::<_, VERSION>(&mut lexed); + let format_items = format_item::parse(ast); + let items = format_items.collect::<Result<Box<_>, _>>()?; + Ok(items.into()) +} + +/// Attach [`Location`] information to each byte in the iterator. +fn attach_location<'item>( + iter: impl Iterator<Item = &'item u8>, +) -> impl Iterator<Item = (&'item u8, Location)> { + let mut byte_pos = 0; + + iter.map(move |byte| { + let location = Location { byte: byte_pos }; + byte_pos += 1; + (byte, location) + }) +} + +/// A location within a string. +#[derive(Clone, Copy)] +struct Location { + /// The zero-indexed byte of the string. + byte: u32, +} + +impl Location { + /// Create a new [`Span`] from `self` to `other`. + const fn to(self, end: Self) -> Span { + Span { start: self, end } + } + + /// Create a new [`Span`] consisting entirely of `self`. + const fn to_self(self) -> Span { + Span { + start: self, + end: self, + } + } + + /// Offset the location by the provided amount. + /// + /// Note that this assumes the resulting location is on the same line as the original location. + #[must_use = "this does not modify the original value"] + const fn offset(&self, offset: u32) -> Self { + Self { + byte: self.byte + offset, + } + } + + /// Create an error with the provided message at this location. + const fn error(self, message: &'static str) -> ErrorInner { + ErrorInner { + _message: message, + _span: Span { + start: self, + end: self, + }, + } + } +} + +/// A start and end point within a string. +#[derive(Clone, Copy)] +struct Span { + start: Location, + end: Location, +} + +impl Span { + /// Obtain a `Span` pointing at the start of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_start(&self) -> Self { + Self { + start: self.start, + end: self.start, + } + } + + /// Obtain a `Span` pointing at the end of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_end(&self) -> Self { + Self { + start: self.end, + end: self.end, + } + } + + /// Obtain a `Span` that ends before the provided position of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_before(&self, pos: u32) -> Self { + Self { + start: self.start, + end: Location { + byte: self.start.byte + pos - 1, + }, + } + } + + /// Obtain a `Span` that starts after provided position to the end of the pre-existing span. + #[must_use = "this does not modify the original value"] + const fn shrink_to_after(&self, pos: u32) -> Self { + Self { + start: Location { + byte: self.start.byte + pos + 1, + }, + end: self.end, + } + } + + /// Create an error with the provided message at this span. + const fn error(self, message: &'static str) -> ErrorInner { + ErrorInner { + _message: message, + _span: self, + } + } +} + +/// A value with an associated [`Span`]. +#[derive(Clone, Copy)] +struct Spanned<T> { + /// The value. + value: T, + /// Where the value was in the format string. + span: Span, +} + +impl<T> core::ops::Deref for Spanned<T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} + +/// Helper trait to attach a [`Span`] to a value. +trait SpannedValue: Sized { + /// Attach a [`Span`] to a value. + fn spanned(self, span: Span) -> Spanned<Self>; +} + +impl<T> SpannedValue for T { + fn spanned(self, span: Span) -> Spanned<Self> { + Spanned { value: self, span } + } +} + +/// The internal error type. +struct ErrorInner { + /// The message displayed to the user. + _message: &'static str, + /// Where the error originated. + _span: Span, +} + +/// A complete error description. +struct Error { + /// The internal error. + _inner: Unused<ErrorInner>, + /// The error needed for interoperability with the rest of `time`. + public: error::InvalidFormatDescription, +} + +impl From<Error> for error::InvalidFormatDescription { + fn from(error: Error) -> Self { + error.public + } +} + +/// A value that may be used in the future, but currently is not. +/// +/// This struct exists so that data can semantically be passed around without _actually_ passing it +/// around. This way the data still exists if it is needed in the future. +// `PhantomData` is not used directly because we don't want to introduce any trait implementations. +struct Unused<T>(core::marker::PhantomData<T>); + +/// Indicate that a value is currently unused. +fn unused<T>(_: T) -> Unused<T> { + Unused(core::marker::PhantomData) +} diff --git a/vendor/time/src/format_description/parse/strftime.rs b/vendor/time/src/format_description/parse/strftime.rs new file mode 100644 index 00000000..5fcaf184 --- /dev/null +++ b/vendor/time/src/format_description/parse/strftime.rs @@ -0,0 +1,487 @@ +use alloc::string::String; +use alloc::vec::Vec; +use core::iter; + +use crate::error::InvalidFormatDescription; +use crate::format_description::parse::{ + attach_location, unused, Error, ErrorInner, Location, Spanned, SpannedValue, Unused, +}; +use crate::format_description::{self, modifier, BorrowedFormatItem, Component}; + +/// Parse a sequence of items from the [`strftime` format description][strftime docs]. +/// +/// The only heap allocation required is for the `Vec` itself. All components are bound to the +/// lifetime of the input. +/// +/// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html +#[doc(alias = "parse_strptime_borrowed")] +pub fn parse_strftime_borrowed( + s: &str, +) -> Result<Vec<BorrowedFormatItem<'_>>, InvalidFormatDescription> { + let tokens = lex(s.as_bytes()); + let items = into_items(tokens).collect::<Result<_, _>>()?; + Ok(items) +} + +/// Parse a sequence of items from the [`strftime` format description][strftime docs]. +/// +/// This requires heap allocation for some owned items. +/// +/// [strftime docs]: https://man7.org/linux/man-pages/man3/strftime.3.html +#[doc(alias = "parse_strptime_owned")] +pub fn parse_strftime_owned( + s: &str, +) -> Result<format_description::OwnedFormatItem, InvalidFormatDescription> { + parse_strftime_borrowed(s).map(Into::into) +} + +#[derive(Debug, Clone, Copy, PartialEq)] +enum Padding { + /// The default padding for a numeric component. Indicated by no character. + Default, + /// Pad a numeric component with spaces. Indicated by an underscore. + Spaces, + /// Do not pad a numeric component. Indicated by a hyphen. + None, + /// Pad a numeric component with zeroes. Indicated by a zero. + Zeroes, +} + +enum Token<'a> { + Literal(Spanned<&'a [u8]>), + Component { + _percent: Unused<Location>, + padding: Spanned<Padding>, + component: Spanned<u8>, + }, +} + +fn lex(mut input: &[u8]) -> iter::Peekable<impl Iterator<Item = Result<Token<'_>, Error>>> { + let mut iter = attach_location(input.iter()).peekable(); + + iter::from_fn(move || { + Some(Ok(match iter.next()? { + (b'%', percent_loc) => match iter.next() { + Some((padding @ (b'_' | b'-' | b'0'), padding_loc)) => { + let padding = match padding { + b'_' => Padding::Spaces, + b'-' => Padding::None, + b'0' => Padding::Zeroes, + _ => unreachable!(), + }; + let (&component, component_loc) = iter.next()?; + input = &input[3..]; + Token::Component { + _percent: unused(percent_loc), + padding: padding.spanned(padding_loc.to_self()), + component: component.spanned(component_loc.to_self()), + } + } + Some((&component, component_loc)) => { + input = &input[2..]; + let span = component_loc.to_self(); + Token::Component { + _percent: unused(percent_loc), + padding: Padding::Default.spanned(span), + component: component.spanned(span), + } + } + None => { + return Some(Err(Error { + _inner: unused(percent_loc.error("unexpected end of input")), + public: InvalidFormatDescription::Expected { + what: "valid escape sequence", + index: percent_loc.byte as usize, + }, + })); + } + }, + (_, start_location) => { + let mut bytes = 1; + let mut end_location = start_location; + + while let Some((_, location)) = iter.next_if(|&(&byte, _)| byte != b'%') { + end_location = location; + bytes += 1; + } + + let value = &input[..bytes]; + input = &input[bytes..]; + + Token::Literal(value.spanned(start_location.to(end_location))) + } + })) + }) + .peekable() +} + +fn into_items<'iter, 'token: 'iter>( + mut tokens: iter::Peekable<impl Iterator<Item = Result<Token<'token>, Error>> + 'iter>, +) -> impl Iterator<Item = Result<BorrowedFormatItem<'token>, Error>> + 'iter { + iter::from_fn(move || { + let next = match tokens.next()? { + Ok(token) => token, + Err(err) => return Some(Err(err)), + }; + + Some(match next { + Token::Literal(spanned) => Ok(BorrowedFormatItem::Literal(*spanned)), + Token::Component { + _percent, + padding, + component, + } => parse_component(padding, component), + }) + }) +} + +fn parse_component( + padding: Spanned<Padding>, + component: Spanned<u8>, +) -> Result<BorrowedFormatItem<'static>, Error> { + let padding_or_default = |padding: Padding, default| match padding { + Padding::Default => default, + Padding::Spaces => modifier::Padding::Space, + Padding::None => modifier::Padding::None, + Padding::Zeroes => modifier::Padding::Zero, + }; + + /// Helper macro to create a component. + macro_rules! component { + ($name:ident { $($inner:tt)* }) => { + BorrowedFormatItem::Component(Component::$name(modifier::$name { + $($inner)* + })) + } + } + + Ok(match *component { + b'%' => BorrowedFormatItem::Literal(b"%"), + b'a' => component!(Weekday { + repr: modifier::WeekdayRepr::Short, + one_indexed: true, + case_sensitive: true, + }), + b'A' => component!(Weekday { + repr: modifier::WeekdayRepr::Long, + one_indexed: true, + case_sensitive: true, + }), + b'b' | b'h' => component!(Month { + repr: modifier::MonthRepr::Short, + padding: modifier::Padding::Zero, + case_sensitive: true, + }), + b'B' => component!(Month { + repr: modifier::MonthRepr::Long, + padding: modifier::Padding::Zero, + case_sensitive: true, + }), + b'c' => BorrowedFormatItem::Compound(&[ + component!(Weekday { + repr: modifier::WeekdayRepr::Short, + one_indexed: true, + case_sensitive: true, + }), + BorrowedFormatItem::Literal(b" "), + component!(Month { + repr: modifier::MonthRepr::Short, + padding: modifier::Padding::Zero, + case_sensitive: true, + }), + BorrowedFormatItem::Literal(b" "), + component!(Day { + padding: modifier::Padding::Space + }), + BorrowedFormatItem::Literal(b" "), + component!(Hour { + padding: modifier::Padding::Zero, + is_12_hour_clock: false, + }), + BorrowedFormatItem::Literal(b":"), + component!(Minute { + padding: modifier::Padding::Zero, + }), + BorrowedFormatItem::Literal(b":"), + component!(Second { + padding: modifier::Padding::Zero, + }), + BorrowedFormatItem::Literal(b" "), + component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::Full, + range: modifier::YearRange::Extended, + iso_week_based: false, + sign_is_mandatory: false, + }), + ]), + b'C' => component!(Year { + padding: padding_or_default(*padding, modifier::Padding::Zero), + repr: modifier::YearRepr::Century, + range: modifier::YearRange::Extended, + iso_week_based: false, + sign_is_mandatory: false, + }), + b'd' => component!(Day { + padding: padding_or_default(*padding, modifier::Padding::Zero), + }), + b'D' => BorrowedFormatItem::Compound(&[ + component!(Month { + repr: modifier::MonthRepr::Numerical, + padding: modifier::Padding::Zero, + case_sensitive: true, + }), + BorrowedFormatItem::Literal(b"/"), + component!(Day { + padding: modifier::Padding::Zero, + }), + BorrowedFormatItem::Literal(b"/"), + component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::LastTwo, + range: modifier::YearRange::Extended, + iso_week_based: false, + sign_is_mandatory: false, + }), + ]), + b'e' => component!(Day { + padding: padding_or_default(*padding, modifier::Padding::Space), + }), + b'F' => BorrowedFormatItem::Compound(&[ + component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::Full, + range: modifier::YearRange::Extended, + iso_week_based: false, + sign_is_mandatory: false, + }), + BorrowedFormatItem::Literal(b"-"), + component!(Month { + padding: modifier::Padding::Zero, + repr: modifier::MonthRepr::Numerical, + case_sensitive: true, + }), + BorrowedFormatItem::Literal(b"-"), + component!(Day { + padding: modifier::Padding::Zero, + }), + ]), + b'g' => component!(Year { + padding: padding_or_default(*padding, modifier::Padding::Zero), + repr: modifier::YearRepr::LastTwo, + range: modifier::YearRange::Extended, + iso_week_based: true, + sign_is_mandatory: false, + }), + b'G' => component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::Full, + range: modifier::YearRange::Extended, + iso_week_based: true, + sign_is_mandatory: false, + }), + b'H' => component!(Hour { + padding: padding_or_default(*padding, modifier::Padding::Zero), + is_12_hour_clock: false, + }), + b'I' => component!(Hour { + padding: padding_or_default(*padding, modifier::Padding::Zero), + is_12_hour_clock: true, + }), + b'j' => component!(Ordinal { + padding: padding_or_default(*padding, modifier::Padding::Zero), + }), + b'k' => component!(Hour { + padding: padding_or_default(*padding, modifier::Padding::Space), + is_12_hour_clock: false, + }), + b'l' => component!(Hour { + padding: padding_or_default(*padding, modifier::Padding::Space), + is_12_hour_clock: true, + }), + b'm' => component!(Month { + padding: padding_or_default(*padding, modifier::Padding::Zero), + repr: modifier::MonthRepr::Numerical, + case_sensitive: true, + }), + b'M' => component!(Minute { + padding: padding_or_default(*padding, modifier::Padding::Zero), + }), + b'n' => BorrowedFormatItem::Literal(b"\n"), + b'O' => { + return Err(Error { + _inner: unused(ErrorInner { + _message: "unsupported modifier", + _span: component.span, + }), + public: InvalidFormatDescription::NotSupported { + what: "modifier", + context: "", + index: component.span.start.byte as usize, + }, + }) + } + b'p' => component!(Period { + is_uppercase: true, + case_sensitive: true + }), + b'P' => component!(Period { + is_uppercase: false, + case_sensitive: true + }), + b'r' => BorrowedFormatItem::Compound(&[ + component!(Hour { + padding: modifier::Padding::Zero, + is_12_hour_clock: true, + }), + BorrowedFormatItem::Literal(b":"), + component!(Minute { + padding: modifier::Padding::Zero, + }), + BorrowedFormatItem::Literal(b":"), + component!(Second { + padding: modifier::Padding::Zero, + }), + BorrowedFormatItem::Literal(b" "), + component!(Period { + is_uppercase: true, + case_sensitive: true, + }), + ]), + b'R' => BorrowedFormatItem::Compound(&[ + component!(Hour { + padding: modifier::Padding::Zero, + is_12_hour_clock: false, + }), + BorrowedFormatItem::Literal(b":"), + component!(Minute { + padding: modifier::Padding::Zero, + }), + ]), + b's' => component!(UnixTimestamp { + precision: modifier::UnixTimestampPrecision::Second, + sign_is_mandatory: false, + }), + b'S' => component!(Second { + padding: padding_or_default(*padding, modifier::Padding::Zero), + }), + b't' => BorrowedFormatItem::Literal(b"\t"), + b'T' => BorrowedFormatItem::Compound(&[ + component!(Hour { + padding: modifier::Padding::Zero, + is_12_hour_clock: false, + }), + BorrowedFormatItem::Literal(b":"), + component!(Minute { + padding: modifier::Padding::Zero, + }), + BorrowedFormatItem::Literal(b":"), + component!(Second { + padding: modifier::Padding::Zero, + }), + ]), + b'u' => component!(Weekday { + repr: modifier::WeekdayRepr::Monday, + one_indexed: true, + case_sensitive: true, + }), + b'U' => component!(WeekNumber { + padding: padding_or_default(*padding, modifier::Padding::Zero), + repr: modifier::WeekNumberRepr::Sunday, + }), + b'V' => component!(WeekNumber { + padding: padding_or_default(*padding, modifier::Padding::Zero), + repr: modifier::WeekNumberRepr::Iso, + }), + b'w' => component!(Weekday { + repr: modifier::WeekdayRepr::Sunday, + one_indexed: true, + case_sensitive: true, + }), + b'W' => component!(WeekNumber { + padding: padding_or_default(*padding, modifier::Padding::Zero), + repr: modifier::WeekNumberRepr::Monday, + }), + b'x' => BorrowedFormatItem::Compound(&[ + component!(Month { + repr: modifier::MonthRepr::Numerical, + padding: modifier::Padding::Zero, + case_sensitive: true, + }), + BorrowedFormatItem::Literal(b"/"), + component!(Day { + padding: modifier::Padding::Zero + }), + BorrowedFormatItem::Literal(b"/"), + component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::LastTwo, + range: modifier::YearRange::Extended, + iso_week_based: false, + sign_is_mandatory: false, + }), + ]), + b'X' => BorrowedFormatItem::Compound(&[ + component!(Hour { + padding: modifier::Padding::Zero, + is_12_hour_clock: false, + }), + BorrowedFormatItem::Literal(b":"), + component!(Minute { + padding: modifier::Padding::Zero, + }), + BorrowedFormatItem::Literal(b":"), + component!(Second { + padding: modifier::Padding::Zero, + }), + ]), + b'y' => component!(Year { + padding: padding_or_default(*padding, modifier::Padding::Zero), + repr: modifier::YearRepr::LastTwo, + range: modifier::YearRange::Extended, + iso_week_based: false, + sign_is_mandatory: false, + }), + b'Y' => component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::Full, + range: modifier::YearRange::Extended, + iso_week_based: false, + sign_is_mandatory: false, + }), + b'z' => BorrowedFormatItem::Compound(&[ + component!(OffsetHour { + sign_is_mandatory: true, + padding: modifier::Padding::Zero, + }), + component!(OffsetMinute { + padding: modifier::Padding::Zero, + }), + ]), + b'Z' => { + return Err(Error { + _inner: unused(ErrorInner { + _message: "unsupported component", + _span: component.span, + }), + public: InvalidFormatDescription::NotSupported { + what: "component", + context: "", + index: component.span.start.byte as usize, + }, + }) + } + _ => { + return Err(Error { + _inner: unused(ErrorInner { + _message: "invalid component", + _span: component.span, + }), + public: InvalidFormatDescription::InvalidComponentName { + name: String::from_utf8_lossy(&[*component]).into_owned(), + index: component.span.start.byte as usize, + }, + }) + } + }) +} |
