1use std::num::NonZero;
2use std::str::{self, FromStr};
3
4use super::{Error, Location, OptionExt as _, Span, Spanned, Unused, ast, unused};
5use crate::FormatDescriptionVersion;
6use crate::format_description::{SpannedValue as _, public};
7
8macro_rules! parse_modifiers {
9 ($version:expr, $modifiers:expr, struct {}) => {{
10 struct Parsed {}
11 drop(($version, $modifiers));
12 Ok(Parsed {})
13 }};
14 ($version:expr, $modifiers:expr, struct { $($field:ident : $modifier:ident),* $(,)? }) => {
15 'block: {
16 struct Parsed {
17 $($field: Option<Spanned<<$modifier as ModifierValue>::Type>>),*
18 }
19
20 let mut parsed = Parsed {
21 $($field: None),*
22 };
23
24 for modifier in $modifiers {
25 $(if ident_eq($version, *modifier.key, stringify!($field)) {
26 if parsed.$field.is_some() {
27 break 'block Err(modifier.key.span.error("duplicate modifier key"));
28 }
29 match <$modifier>::from_modifier_value($version, &modifier.value) {
30 Ok(value) => {
31 parsed.$field = Some(
32 <<$modifier as ModifierValue>::Type>::from(value)
33 .spanned(modifier.value.span)
34 )
35 },
36 Err(err) => break 'block Err(err),
37 }
38 continue;
39 })*
40 break 'block Err(modifier.key.span.error("invalid modifier key"));
41 }
42
43 Ok(parsed)
44 }
45 };
46}
47
48fn ident_eq(version: FormatDescriptionVersion, provided: &str, expected: &str) -> bool {
49 if version.is_at_least_v3() {
50 provided == expected
51 } else {
52 provided.len() == expected.len()
53 && core::iter::zip(provided.bytes(), expected.bytes())
54 .all(|(p, e)| p.to_ascii_lowercase() == e)
55 }
56}
57
58pub(super) fn parse<'a>(
59 ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
60) -> impl Iterator<Item = Result<Item<'a>, Error>> {
61 ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
62}
63
64pub(super) enum Item<'a> {
65 Literal(&'a [u8]),
66 StringLiteral(&'a str),
67 Component(Component),
68 Optional {
69 value: Vec<Self>,
70 format: Spanned<bool>,
71 _span: Unused<Span>,
72 },
73 First {
74 value: Vec<Vec<Self>>,
75 _span: Unused<Span>,
76 },
77}
78
79impl<'a> Item<'a> {
80 pub(super) fn from_ast(ast_item: ast::Item<'a>) -> Result<Self, Error> {
81 Ok(match ast_item {
82 ast::Item::Literal {
83 version,
84 value: Spanned { value, span },
85 } => {
86 if let Ok(value) = str::from_utf8(value) {
87 Item::StringLiteral(value)
88 } else if version.is_at_least_v3() {
89 return Err(span.error("v3 format descriptions must be valid UTF-8"));
90 } else {
91 Item::Literal(value)
92 }
93 }
94 ast::Item::Component {
95 version,
96 opening_bracket,
97 _leading_whitespace: _,
98 name,
99 modifiers,
100 nested_format_descriptions,
101 _trailing_whitespace: _,
102 closing_bracket,
103 } => {
104 if let Some(first_nested_fd) = nested_format_descriptions.first()
105 && first_nested_fd.leading_whitespace.is_none()
106 {
107 return Err(Span {
108 start: opening_bracket,
109 end: closing_bracket,
110 }
111 .error("missing leading whitespace before nested format description"));
112 }
113
114 if ident_eq(version, *name, "optional") {
115 Self::optional_from_parts(
116 version,
117 opening_bracket,
118 &modifiers,
119 nested_format_descriptions,
120 closing_bracket,
121 )?
122 } else if ident_eq(version, *name, "first") {
123 let _modifiers = parse_modifiers!(version, modifiers, struct {})?;
124
125 if version.is_at_least_v3() && nested_format_descriptions.is_empty() {
126 return Err(Span {
127 start: opening_bracket,
128 end: closing_bracket,
129 }
130 .error(
131 "the `first` component requires at least one nested format description",
132 ));
133 }
134
135 let items = nested_format_descriptions
136 .into_iter()
137 .map(|nested_format_description| {
138 nested_format_description
139 .items
140 .into_iter()
141 .map(Item::from_ast)
142 .collect()
143 })
144 .collect::<Result<_, _>>()?;
145
146 Item::First {
147 value: items,
148 _span: unused(opening_bracket.to(closing_bracket)),
149 }
150 } else {
151 if !nested_format_descriptions.is_empty() {
152 return Err(Span {
153 start: opening_bracket,
154 end: closing_bracket,
155 }
156 .error("this component does not support nested format descriptions"));
157 }
158
159 Item::Component(component_from_ast(version, &name, &modifiers)?)
160 }
161 }
162 })
163 }
164
165 fn optional_from_parts(
166 version: FormatDescriptionVersion,
167 opening_bracket: Location,
168 modifiers: &[ast::Modifier<'a>],
169 nested_format_descriptions: Vec<ast::NestedFormatDescription<'a>>,
170 closing_bracket: Location,
171 ) -> Result<Self, Error> {
172 let modifiers = parse_modifiers!(version, modifiers, struct {
173 format: OptionalFormat,
174 })?;
175
176 let nested_format_description = if let Some(second_fd) = nested_format_descriptions.get(1) {
177 return Err(Span {
178 start: second_fd.opening_bracket,
179 end: second_fd.closing_bracket,
180 }
181 .error("the `optional` component only allows a single nested format description"));
182 } else if let Ok([nested_format_description]) =
183 <[_; 1]>::try_from(nested_format_descriptions)
184 {
185 nested_format_description
186 } else {
187 return Err(Span {
188 start: opening_bracket,
189 end: closing_bracket,
190 }
191 .error("missing nested format description for `optional` component"));
192 };
193
194 let format = modifiers.format.transpose().map(|val| val.unwrap_or(true));
195 let items = nested_format_description
196 .items
197 .into_iter()
198 .map(Item::from_ast)
199 .collect::<Result<_, _>>()?;
200
201 if version.is_at_most_v2() && !*format {
202 return Err(format
203 .span
204 .error("v1 and v2 format descriptions must format optional items"));
205 }
206
207 Ok(Item::Optional {
208 value: items,
209 format,
210 _span: unused(opening_bracket.to(closing_bracket)),
211 })
212 }
213}
214
215impl TryFrom<(FormatDescriptionVersion, Item<'_>)> for public::OwnedFormatItemInner {
216 type Error = Error;
217
218 fn try_from(
219 (version, item): (FormatDescriptionVersion, Item<'_>),
220 ) -> Result<Self, Self::Error> {
221 Ok(match item {
222 Item::Literal(literal) => Self::Literal(literal.to_vec()),
223 Item::StringLiteral(string) => Self::StringLiteral(string.to_owned()),
224 Item::Component(component) => Self::Component((version, component).try_into()?),
225 Item::Optional {
226 format,
227 value,
228 _span: _,
229 } => Self::Optional {
230 format: *format,
231 item: Box::new((version, value).try_into()?),
232 },
233 Item::First { value, _span: _ } => Self::First(
234 value
235 .into_iter()
236 .map(|item| (version, item).try_into())
237 .collect::<Result<_, _>>()?,
238 ),
239 })
240 }
241}
242
243impl<'a> TryFrom<(FormatDescriptionVersion, Vec<Item<'a>>)> for public::OwnedFormatItemInner {
244 type Error = Error;
245
246 fn try_from(
247 (version, items): (FormatDescriptionVersion, Vec<Item<'a>>),
248 ) -> Result<Self, Self::Error> {
249 Ok(Self::Compound(
250 items
251 .into_iter()
252 .map(|item| (version, item).try_into())
253 .collect::<Result<_, _>>()?,
254 ))
255 }
256}
257
258macro_rules! component_definition {
259 (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
260 (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
261 (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
262 (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
263
264 ($vis:vis enum $name:ident {
265 $($variant:ident = $parse_variant:literal {$(
266 $(#[$required:tt])?
267 $field:ident = $parse_field:literal:
268 Option<$(#[$from_str:tt])? $field_type:ty>
269 ),* $(,)?}),* $(,)?
270 }) => {
271 $vis enum $name {
272 $($variant($variant),)*
273 }
274
275 $($vis struct $variant {
276 $($field: Spanned<Option<$field_type>>),*
277 })*
278
279 $(impl $variant {
280 fn with_modifiers(
281 version: FormatDescriptionVersion,
282 modifiers: &[ast::Modifier<'_>],
283 _component_span: Span,
284 ) -> Result<Self, Error>
285 {
286 #[allow(unused_mut)]
287 let mut this = Self {
288 $($field: None.spanned(Span::dummy())),*
289 };
290
291 for modifier in modifiers {
292 $(#[allow(clippy::string_lit_as_bytes)]
293 if ident_eq(version, *modifier.key, $parse_field) {
294 this.$field = Some(
295 component_definition!(@if_from_str $($from_str)?
296 then {
297 parse_from_modifier_value::<$field_type>(&modifier.value)?
298 } else {
299 <$field_type>::from_modifier_value(version, &modifier.value)?
300 }
301 )
302 ).spanned(modifier.key_value_span());
303 continue;
304 })*
305 return Err(modifier.key.span.error("invalid modifier key"));
306 }
307
308 $(component_definition! { @if_required $($required)? then {
309 if this.$field.is_none() {
310 return Err(_component_span.error("missing required modifier"));
311 }
312 }})*
313
314 Ok(this)
315 }
316 })*
317
318 fn component_from_ast(
319 version: FormatDescriptionVersion,
320 name: &Spanned<&str>,
321 modifiers: &[ast::Modifier<'_>],
322 ) -> Result<Component, Error> {
323 $(#[allow(clippy::string_lit_as_bytes)]
324 if ident_eq(version, &name, $parse_variant) {
325 return Ok(Component::$variant(
326 $variant::with_modifiers(version, &modifiers, name.span)?
327 ));
328 })*
329 Err(name.span.error("invalid component"))
330 }
331 }
332}
333
334component_definition! {
335 pub(super) enum Component {
336 Day = "day" {
337 padding = "padding": Option<Padding>,
338 },
339 End = "end" {
340 trailing_input = "trailing_input": Option<TrailingInput>,
341 },
342 Hour = "hour" {
343 padding = "padding": Option<Padding>,
344 base = "repr": Option<HourBase>,
345 },
346 Ignore = "ignore" {
347 #[required]
348 count = "count": Option<#[from_str] NonZero<u16>>,
349 },
350 Minute = "minute" {
351 padding = "padding": Option<Padding>,
352 },
353 Month = "month" {
354 padding = "padding": Option<Padding>,
355 repr = "repr": Option<MonthRepr>,
356 case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
357 },
358 OffsetHour = "offset_hour" {
359 sign_behavior = "sign": Option<SignBehavior>,
360 padding = "padding": Option<Padding>,
361 },
362 OffsetMinute = "offset_minute" {
363 padding = "padding": Option<Padding>,
364 },
365 OffsetSecond = "offset_second" {
366 padding = "padding": Option<Padding>,
367 },
368 Ordinal = "ordinal" {
369 padding = "padding": Option<Padding>,
370 },
371 Period = "period" {
372 case = "case": Option<PeriodCase>,
373 case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
374 },
375 Second = "second" {
376 padding = "padding": Option<Padding>,
377 },
378 Subsecond = "subsecond" {
379 digits = "digits": Option<SubsecondDigits>,
380 },
381 UnixTimestamp = "unix_timestamp" {
382 precision = "precision": Option<UnixTimestampPrecision>,
383 sign_behavior = "sign": Option<SignBehavior>,
384 },
385 Weekday = "weekday" {
386 repr = "repr": Option<WeekdayRepr>,
387 one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
388 case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
389 },
390 WeekNumber = "week_number" {
391 padding = "padding": Option<Padding>,
392 repr = "repr": Option<WeekNumberRepr>,
393 },
394 Year = "year" {
395 padding = "padding": Option<Padding>,
396 repr = "repr": Option<YearRepr>,
397 range = "range": Option<YearRange>,
398 base = "base": Option<YearBase>,
399 sign_behavior = "sign": Option<SignBehavior>,
400 },
401 }
402}
403
404impl TryFrom<(FormatDescriptionVersion, Component)> for public::Component {
405 type Error = Error;
406
407 #[inline]
408 fn try_from(
409 (version, component): (FormatDescriptionVersion, Component),
410 ) -> Result<Self, Self::Error> {
411 macro_rules! reject_modifier {
412 ($modifier:ident, $modifier_str:literal, $context:literal) => {
413 if version.is_at_least_v3() && $modifier.value.is_some() {
414 return Err($modifier.span.error(concat!(
415 "the '",
416 $modifier_str,
417 "' modifier is not valid ",
418 $context
419 )));
420 }
421 };
422 }
423
424 use crate::format_description::public::modifier;
425 Ok(match component {
426 Component::Day(Day { padding }) => Self::Day(modifier::Day {
427 padding: padding.unwrap_or_default().into(),
428 }),
429 Component::End(End { trailing_input }) => Self::End(modifier::End {
430 trailing_input: trailing_input.unwrap_or_default().into(),
431 }),
432 Component::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
433 HourBase::Twelve => Self::Hour12(modifier::Hour12 {
434 padding: padding.unwrap_or_default().into(),
435 }),
436 HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
437 padding: padding.unwrap_or_default().into(),
438 }),
439 },
440 Component::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
441 count: match *count {
442 Some(value) => value,
443 None => bug!("required modifier was not set"),
444 },
445 }),
446 Component::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
447 padding: padding.unwrap_or_default().into(),
448 }),
449 Component::Month(Month {
450 padding,
451 repr,
452 case_sensitive,
453 }) => match repr.unwrap_or_default() {
454 MonthRepr::Numerical => {
455 reject_modifier!(case_sensitive, "case_sensitive", "for numerical month");
456 Self::MonthNumerical(modifier::MonthNumerical {
457 padding: padding.unwrap_or_default().into(),
458 })
459 }
460 MonthRepr::Long => {
461 reject_modifier!(padding, "padding", "for long month");
462 Self::MonthLong(modifier::MonthLong {
463 case_sensitive: case_sensitive.unwrap_or_default().into(),
464 })
465 }
466 MonthRepr::Short => {
467 reject_modifier!(padding, "padding", "for short month");
468 Self::MonthShort(modifier::MonthShort {
469 case_sensitive: case_sensitive.unwrap_or_default().into(),
470 })
471 }
472 },
473 Component::OffsetHour(OffsetHour {
474 sign_behavior,
475 padding,
476 }) => Self::OffsetHour(modifier::OffsetHour {
477 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
478 padding: padding.unwrap_or_default().into(),
479 }),
480 Component::OffsetMinute(OffsetMinute { padding }) => {
481 Self::OffsetMinute(modifier::OffsetMinute {
482 padding: padding.unwrap_or_default().into(),
483 })
484 }
485 Component::OffsetSecond(OffsetSecond { padding }) => {
486 Self::OffsetSecond(modifier::OffsetSecond {
487 padding: padding.unwrap_or_default().into(),
488 })
489 }
490 Component::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
491 padding: padding.unwrap_or_default().into(),
492 }),
493 Component::Period(Period {
494 case,
495 case_sensitive,
496 }) => Self::Period(modifier::Period {
497 is_uppercase: case.unwrap_or_default().into(),
498 case_sensitive: case_sensitive.unwrap_or_default().into(),
499 }),
500 Component::Second(Second { padding }) => Self::Second(modifier::Second {
501 padding: padding.unwrap_or_default().into(),
502 }),
503 Component::Subsecond(Subsecond { digits }) => Self::Subsecond(modifier::Subsecond {
504 digits: digits.unwrap_or_default().into(),
505 }),
506 Component::UnixTimestamp(UnixTimestamp {
507 precision,
508 sign_behavior,
509 }) => match precision.unwrap_or_default() {
510 UnixTimestampPrecision::Second => {
511 Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
512 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
513 })
514 }
515 UnixTimestampPrecision::Millisecond => {
516 Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
517 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
518 })
519 }
520 UnixTimestampPrecision::Microsecond => {
521 Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
522 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
523 })
524 }
525 UnixTimestampPrecision::Nanosecond => {
526 Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
527 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
528 })
529 }
530 },
531 Component::Weekday(Weekday {
532 repr,
533 one_indexed,
534 case_sensitive,
535 }) => match repr.unwrap_or_default() {
536 WeekdayRepr::Short => {
537 reject_modifier!(one_indexed, "one_indexed", "for short weekday");
538 Self::WeekdayShort(modifier::WeekdayShort {
539 case_sensitive: case_sensitive.unwrap_or_default().into(),
540 })
541 }
542 WeekdayRepr::Long => {
543 reject_modifier!(one_indexed, "one_indexed", "for long weekday");
544 Self::WeekdayLong(modifier::WeekdayLong {
545 case_sensitive: case_sensitive.unwrap_or_default().into(),
546 })
547 }
548 WeekdayRepr::Sunday => {
549 reject_modifier!(case_sensitive, "case_sensitive", "for numerical weekday");
550 Self::WeekdaySunday(modifier::WeekdaySunday {
551 one_indexed: one_indexed.unwrap_or_default().into(),
552 })
553 }
554 WeekdayRepr::Monday => {
555 reject_modifier!(case_sensitive, "case_sensitive", "for numerical weekday");
556 Self::WeekdayMonday(modifier::WeekdayMonday {
557 one_indexed: one_indexed.unwrap_or_default().into(),
558 })
559 }
560 },
561 Component::WeekNumber(WeekNumber { padding, repr }) => match repr.unwrap_or_default() {
562 WeekNumberRepr::Iso => Self::WeekNumberIso(modifier::WeekNumberIso {
563 padding: padding.unwrap_or_default().into(),
564 }),
565 WeekNumberRepr::Sunday => Self::WeekNumberSunday(modifier::WeekNumberSunday {
566 padding: padding.unwrap_or_default().into(),
567 }),
568 WeekNumberRepr::Monday => Self::WeekNumberMonday(modifier::WeekNumberMonday {
569 padding: padding.unwrap_or_default().into(),
570 }),
571 },
572 Component::Year(Year {
573 padding,
574 repr,
575 range,
576 base,
577 sign_behavior,
578 }) => {
579 #[cfg(not(feature = "large-dates"))]
580 reject_modifier!(
581 range,
582 "range",
583 "when the `large-dates` feature is not enabled"
584 );
585
586 let computed_range = if version.is_at_least_v3() {
589 range.unwrap_or(YearRange::Standard)
590 } else {
591 range.unwrap_or_default()
592 };
593
594 match (
595 base.unwrap_or_default(),
596 repr.unwrap_or_default(),
597 computed_range,
598 ) {
599 #[cfg(feature = "large-dates")]
600 (YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
601 Self::CalendarYearFullExtendedRange(
602 modifier::CalendarYearFullExtendedRange {
603 padding: padding.unwrap_or_default().into(),
604 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
605 },
606 )
607 }
608 (YearBase::Calendar, YearRepr::Full, _) => Self::CalendarYearFullStandardRange(
609 modifier::CalendarYearFullStandardRange {
610 padding: padding.unwrap_or_default().into(),
611 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
612 },
613 ),
614 #[cfg(feature = "large-dates")]
615 (YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
616 Self::CalendarYearCenturyExtendedRange(
617 modifier::CalendarYearCenturyExtendedRange {
618 padding: padding.unwrap_or_default().into(),
619 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
620 },
621 )
622 }
623 (YearBase::Calendar, YearRepr::Century, _) => {
624 Self::CalendarYearCenturyStandardRange(
625 modifier::CalendarYearCenturyStandardRange {
626 padding: padding.unwrap_or_default().into(),
627 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
628 },
629 )
630 }
631 #[cfg(feature = "large-dates")]
632 (YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
633 Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
634 padding: padding.unwrap_or_default().into(),
635 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
636 })
637 }
638 (YearBase::IsoWeek, YearRepr::Full, _) => {
639 Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
640 padding: padding.unwrap_or_default().into(),
641 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
642 })
643 }
644 #[cfg(feature = "large-dates")]
645 (YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
646 Self::IsoYearCenturyExtendedRange(modifier::IsoYearCenturyExtendedRange {
647 padding: padding.unwrap_or_default().into(),
648 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
649 })
650 }
651 (YearBase::IsoWeek, YearRepr::Century, _) => {
652 Self::IsoYearCenturyStandardRange(modifier::IsoYearCenturyStandardRange {
653 padding: padding.unwrap_or_default().into(),
654 sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
655 })
656 }
657 (YearBase::Calendar, YearRepr::LastTwo, _) => {
658 #[cfg(feature = "large-dates")]
659 reject_modifier!(range, "range", "when `repr:last_two` is used");
660 reject_modifier!(sign_behavior, "sign", "when `repr:last_two` is used");
661 Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
662 padding: padding.unwrap_or_default().into(),
663 })
664 }
665 (YearBase::IsoWeek, YearRepr::LastTwo, _) => {
666 #[cfg(feature = "large-dates")]
667 reject_modifier!(range, "range", "when `repr:last_two` is used");
668 reject_modifier!(sign_behavior, "sign", "when `repr:last_two` is used");
669 Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
670 padding: padding.unwrap_or_default().into(),
671 })
672 }
673 }
674 }
675 })
676 }
677}
678
679macro_rules! target_ty {
680 ($name:ident $type:ty) => {
681 $type
682 };
683 ($name:ident) => {
684 super::public::modifier::$name
685 };
686}
687
688macro_rules! target_value {
690 ($name:ident $variant:ident $value:expr) => {
691 $value
692 };
693 ($name:ident $variant:ident) => {
694 super::public::modifier::$name::$variant
695 };
696}
697
698macro_rules! if_not_parse_only {
699 (@parse_only $($x:tt)*) => {};
700 ($($x:tt)*) => { $($x)* };
701}
702
703trait ModifierValue {
704 type Type;
705}
706
707macro_rules! modifier {
708 ($(
709 $(@$instruction:ident)? enum $name:ident $(($target_ty:ty))? {
710 $(
711 $(#[$attr:meta])?
712 $variant:ident $(($target_value:expr))? = $parse_variant:literal
713 ),* $(,)?
714 }
715 )+) => {$(
716 #[derive(Default, Clone, Copy)]
717 enum $name {
718 $($(#[$attr])? $variant),*
719 }
720
721 impl $name {
722 fn from_modifier_value(
724 version: FormatDescriptionVersion,
725 value: &Spanned<&str>,
726 ) -> Result<Self, Error>
727 {
728 $(if ident_eq(version, &value, $parse_variant) {
729 return Ok(Self::$variant);
730 })*
731 Err(value.span.error("invalid modifier value"))
732 }
733 }
734
735 if_not_parse_only! { $(@$instruction)?
736 impl ModifierValue for $name {
737 type Type = target_ty!($name $($target_ty)?);
738 }
739
740 impl From<$name> for <$name as ModifierValue>::Type {
741 #[inline]
742 fn from(modifier: $name) -> Self {
743 match modifier {
744 $($name::$variant => target_value!($name $variant $($target_value)?)),*
745 }
746 }
747 }
748 }
749 )+};
750}
751
752modifier! {
753 enum HourBase(bool) {
754 Twelve(true) = "12",
755 #[default]
756 TwentyFour(false) = "24",
757 }
758
759 enum MonthCaseSensitive(bool) {
760 False(false) = "false",
761 #[default]
762 True(true) = "true",
763 }
764
765 @parse_only enum MonthRepr {
766 #[default]
767 Numerical = "numerical",
768 Long = "long",
769 Short = "short",
770 }
771
772 enum OptionalFormat(bool) {
773 False(false) = "false",
774 #[default]
775 True(true) = "true",
776 }
777
778 enum Padding {
779 Space = "space",
780 #[default]
781 Zero = "zero",
782 None = "none",
783 }
784
785 enum PeriodCase(bool) {
786 Lower(false) = "lower",
787 #[default]
788 Upper(true) = "upper",
789 }
790
791 enum PeriodCaseSensitive(bool) {
792 False(false) = "false",
793 #[default]
794 True(true) = "true",
795 }
796
797 enum SignBehavior(bool) {
798 #[default]
799 Automatic(false) = "automatic",
800 Mandatory(true) = "mandatory",
801 }
802
803 enum SubsecondDigits {
804 One = "1",
805 Two = "2",
806 Three = "3",
807 Four = "4",
808 Five = "5",
809 Six = "6",
810 Seven = "7",
811 Eight = "8",
812 Nine = "9",
813 #[default]
814 OneOrMore = "1+",
815 }
816
817 enum TrailingInput {
818 #[default]
819 Prohibit = "prohibit",
820 Discard = "discard",
821 }
822
823 @parse_only enum UnixTimestampPrecision {
824 #[default]
825 Second = "second",
826 Millisecond = "millisecond",
827 Microsecond = "microsecond",
828 Nanosecond = "nanosecond",
829 }
830
831 @parse_only enum WeekNumberRepr {
832 #[default]
833 Iso = "iso",
834 Sunday = "sunday",
835 Monday = "monday",
836 }
837
838 enum WeekdayCaseSensitive(bool) {
839 False(false) = "false",
840 #[default]
841 True(true) = "true",
842 }
843
844 enum WeekdayOneIndexed(bool) {
845 False(false) = "false",
846 #[default]
847 True(true) = "true",
848 }
849
850 @parse_only enum WeekdayRepr {
851 Short = "short",
852 #[default]
853 Long = "long",
854 Sunday = "sunday",
855 Monday = "monday",
856 }
857
858 enum YearBase(bool) {
859 #[default]
860 Calendar(false) = "calendar",
861 IsoWeek(true) = "iso_week",
862 }
863
864 @parse_only enum YearRepr {
865 #[default]
866 Full = "full",
867 Century = "century",
868 LastTwo = "last_two",
869 }
870
871 @parse_only enum YearRange {
875 Standard = "standard",
876 #[default]
877 Extended = "extended",
878 }
879}
880
881fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&str>) -> Result<T, Error> {
882 value
883 .parse::<T>()
884 .map_err(|_| value.span.error("invalid modifier value"))
885}