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