Skip to main content

time_macros/format_description/
format_item.rs

1use std::num::NonZero;
2use std::str::{self, FromStr};
3
4use super::{Error, Span, Spanned, Unused, ast, unused};
5
6pub(super) fn parse<'a>(
7    ast_items: impl Iterator<Item = Result<ast::Item<'a>, Error>>,
8) -> impl Iterator<Item = Result<Item<'a>, Error>> {
9    ast_items.map(|ast_item| ast_item.and_then(Item::from_ast))
10}
11
12pub(super) enum Item<'a> {
13    Literal(&'a [u8]),
14    StringLiteral(&'a str),
15    Component(Component),
16    Optional {
17        value: Box<[Self]>,
18        _span: Unused<Span>,
19    },
20    First {
21        value: Box<[Box<[Self]>]>,
22        _span: Unused<Span>,
23    },
24}
25
26impl Item<'_> {
27    pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
28        Ok(match ast_item {
29            ast::Item::Component {
30                _opening_bracket: _,
31                _leading_whitespace: _,
32                name,
33                modifiers,
34                _trailing_whitespace: _,
35                _closing_bracket: _,
36            } => Item::Component(component_from_ast(&name, &modifiers)?),
37            ast::Item::Literal(Spanned { value, span: _ }) => {
38                if let Ok(value) = str::from_utf8(value) {
39                    Item::StringLiteral(value)
40                } else {
41                    Item::Literal(value)
42                }
43            }
44            ast::Item::EscapedBracket {
45                _first: _,
46                _second: _,
47            } => Item::StringLiteral("["),
48            ast::Item::Optional {
49                opening_bracket,
50                _leading_whitespace: _,
51                _optional_kw: _,
52                _whitespace: _,
53                nested_format_description,
54                closing_bracket,
55            } => {
56                let items = nested_format_description
57                    .items
58                    .into_vec()
59                    .into_iter()
60                    .map(Item::from_ast)
61                    .collect::<Result<_, _>>()?;
62                Item::Optional {
63                    value: items,
64                    _span: unused(opening_bracket.to(closing_bracket)),
65                }
66            }
67            ast::Item::First {
68                opening_bracket,
69                _leading_whitespace: _,
70                _first_kw: _,
71                _whitespace: _,
72                nested_format_descriptions,
73                closing_bracket,
74            } => {
75                let items = nested_format_descriptions
76                    .into_vec()
77                    .into_iter()
78                    .map(|nested_format_description| {
79                        nested_format_description
80                            .items
81                            .into_vec()
82                            .into_iter()
83                            .map(Item::from_ast)
84                            .collect()
85                    })
86                    .collect::<Result<_, _>>()?;
87                Item::First {
88                    value: items,
89                    _span: unused(opening_bracket.to(closing_bracket)),
90                }
91            }
92        })
93    }
94}
95
96impl From<Item<'_>> for crate::format_description::public::OwnedFormatItem {
97    fn from(item: Item<'_>) -> Self {
98        match item {
99            Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
100            Item::StringLiteral(string) => Self::StringLiteral(string.to_owned().into_boxed_str()),
101            Item::Component(component) => Self::Component(component.into()),
102            Item::Optional { value, _span: _ } => Self::Optional(Box::new(value.into())),
103            Item::First { value, _span: _ } => {
104                Self::First(value.into_vec().into_iter().map(Into::into).collect())
105            }
106        }
107    }
108}
109
110impl<'a> From<Box<[Item<'a>]>> for crate::format_description::public::OwnedFormatItem {
111    fn from(items: Box<[Item<'a>]>) -> Self {
112        let items = items.into_vec();
113        match <[_; 1]>::try_from(items) {
114            Ok([item]) => item.into(),
115            Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
116        }
117    }
118}
119
120macro_rules! component_definition {
121    (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
122    (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
123    (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
124    (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
125
126    ($vis:vis enum $name:ident {
127        $($variant:ident = $parse_variant:literal {$(
128            $(#[$required:tt])?
129            $field:ident = $parse_field:literal:
130            Option<$(#[$from_str:tt])? $field_type:ty>
131        ),* $(,)?}),* $(,)?
132    }) => {
133        $vis enum $name {
134            $($variant($variant),)*
135        }
136
137        $($vis struct $variant {
138            $($field: Option<$field_type>),*
139        })*
140
141        $(impl $variant {
142            fn with_modifiers(
143                modifiers: &[ast::Modifier<'_>],
144                _component_span: Span,
145            ) -> Result<Self, Error>
146            {
147                #[allow(unused_mut)]
148                let mut this = Self {
149                    $($field: None),*
150                };
151
152                for modifier in modifiers {
153                    $(#[allow(clippy::string_lit_as_bytes)]
154                    if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
155                        this.$field = component_definition!(@if_from_str $($from_str)?
156                            then {
157                                parse_from_modifier_value::<$field_type>(&modifier.value)?
158                            } else {
159                                <$field_type>::from_modifier_value(&modifier.value)?
160                            });
161                        continue;
162                    })*
163                    return Err(modifier.key.span.error("invalid modifier key"));
164                }
165
166                $(component_definition! { @if_required $($required)? then {
167                    if this.$field.is_none() {
168                        return Err(_component_span.error("missing required modifier"));
169                    }
170                }})*
171
172                Ok(this)
173            }
174        })*
175
176        fn component_from_ast(
177            name: &Spanned<&[u8]>,
178            modifiers: &[ast::Modifier<'_>],
179        ) -> Result<Component, Error> {
180            $(#[allow(clippy::string_lit_as_bytes)]
181            if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
182                return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
183            })*
184            Err(name.span.error("invalid component"))
185        }
186    }
187}
188
189component_definition! {
190    pub(super) enum Component {
191        Day = "day" {
192            padding = "padding": Option<Padding>,
193        },
194        End = "end" {
195            trailing_input = "trailing_input": Option<TrailingInput>,
196        },
197        Hour = "hour" {
198            padding = "padding": Option<Padding>,
199            base = "repr": Option<HourBase>,
200        },
201        Ignore = "ignore" {
202            #[required]
203            count = "count": Option<#[from_str] NonZero<u16>>,
204        },
205        Minute = "minute" {
206            padding = "padding": Option<Padding>,
207        },
208        Month = "month" {
209            padding = "padding": Option<Padding>,
210            repr = "repr": Option<MonthRepr>,
211            case_sensitive = "case_sensitive": Option<MonthCaseSensitive>,
212        },
213        OffsetHour = "offset_hour" {
214            sign_behavior = "sign": Option<SignBehavior>,
215            padding = "padding": Option<Padding>,
216        },
217        OffsetMinute = "offset_minute" {
218            padding = "padding": Option<Padding>,
219        },
220        OffsetSecond = "offset_second" {
221            padding = "padding": Option<Padding>,
222        },
223        Ordinal = "ordinal" {
224            padding = "padding": Option<Padding>,
225        },
226        Period = "period" {
227            case = "case": Option<PeriodCase>,
228            case_sensitive = "case_sensitive": Option<PeriodCaseSensitive>,
229        },
230        Second = "second" {
231            padding = "padding": Option<Padding>,
232        },
233        Subsecond = "subsecond" {
234            digits = "digits": Option<SubsecondDigits>,
235        },
236        UnixTimestamp = "unix_timestamp" {
237            precision = "precision": Option<UnixTimestampPrecision>,
238            sign_behavior = "sign": Option<SignBehavior>,
239        },
240        Weekday = "weekday" {
241            repr = "repr": Option<WeekdayRepr>,
242            one_indexed = "one_indexed": Option<WeekdayOneIndexed>,
243            case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive>,
244        },
245        WeekNumber = "week_number" {
246            padding = "padding": Option<Padding>,
247            repr = "repr": Option<WeekNumberRepr>,
248        },
249        Year = "year" {
250            padding = "padding": Option<Padding>,
251            repr = "repr": Option<YearRepr>,
252            range = "range": Option<YearRange>,
253            base = "base": Option<YearBase>,
254            sign_behavior = "sign": Option<SignBehavior>,
255        },
256    }
257}
258
259impl From<Component> for crate::format_description::public::Component {
260    #[inline]
261    fn from(component: Component) -> Self {
262        use crate::format_description::public::modifier;
263        match component {
264            Component::Day(Day { padding }) => Self::Day(modifier::Day {
265                padding: padding.unwrap_or_default().into(),
266            }),
267            Component::End(End { trailing_input }) => Self::End(modifier::End {
268                trailing_input: trailing_input.unwrap_or_default().into(),
269            }),
270            Component::Hour(Hour { padding, base }) => match base.unwrap_or_default() {
271                HourBase::Twelve => Self::Hour12(modifier::Hour12 {
272                    padding: padding.unwrap_or_default().into(),
273                }),
274                HourBase::TwentyFour => Self::Hour24(modifier::Hour24 {
275                    padding: padding.unwrap_or_default().into(),
276                }),
277            },
278            Component::Ignore(Ignore { count }) => Self::Ignore(modifier::Ignore {
279                count: match count {
280                    Some(value) => value,
281                    None => bug!("required modifier was not set"),
282                },
283            }),
284            Component::Minute(Minute { padding }) => Self::Minute(modifier::Minute {
285                padding: padding.unwrap_or_default().into(),
286            }),
287            Component::Month(Month {
288                padding,
289                repr,
290                case_sensitive,
291            }) => match repr.unwrap_or_default() {
292                MonthRepr::Numerical => Self::MonthNumerical(modifier::MonthNumerical {
293                    padding: padding.unwrap_or_default().into(),
294                }),
295                MonthRepr::Long => Self::MonthLong(modifier::MonthLong {
296                    case_sensitive: case_sensitive.unwrap_or_default().into(),
297                }),
298                MonthRepr::Short => Self::MonthShort(modifier::MonthShort {
299                    case_sensitive: case_sensitive.unwrap_or_default().into(),
300                }),
301            },
302            Component::OffsetHour(OffsetHour {
303                sign_behavior,
304                padding,
305            }) => Self::OffsetHour(modifier::OffsetHour {
306                sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
307                padding: padding.unwrap_or_default().into(),
308            }),
309            Component::OffsetMinute(OffsetMinute { padding }) => {
310                Self::OffsetMinute(modifier::OffsetMinute {
311                    padding: padding.unwrap_or_default().into(),
312                })
313            }
314            Component::OffsetSecond(OffsetSecond { padding }) => {
315                Self::OffsetSecond(modifier::OffsetSecond {
316                    padding: padding.unwrap_or_default().into(),
317                })
318            }
319            Component::Ordinal(Ordinal { padding }) => Self::Ordinal(modifier::Ordinal {
320                padding: padding.unwrap_or_default().into(),
321            }),
322            Component::Period(Period {
323                case,
324                case_sensitive,
325            }) => Self::Period(modifier::Period {
326                is_uppercase: case.unwrap_or_default().into(),
327                case_sensitive: case_sensitive.unwrap_or_default().into(),
328            }),
329            Component::Second(Second { padding }) => Self::Second(modifier::Second {
330                padding: padding.unwrap_or_default().into(),
331            }),
332            Component::Subsecond(Subsecond { digits }) => Self::Subsecond(modifier::Subsecond {
333                digits: digits.unwrap_or_default().into(),
334            }),
335            Component::UnixTimestamp(UnixTimestamp {
336                precision,
337                sign_behavior,
338            }) => match precision.unwrap_or_default() {
339                UnixTimestampPrecision::Second => {
340                    Self::UnixTimestampSecond(modifier::UnixTimestampSecond {
341                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
342                    })
343                }
344                UnixTimestampPrecision::Millisecond => {
345                    Self::UnixTimestampMillisecond(modifier::UnixTimestampMillisecond {
346                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
347                    })
348                }
349                UnixTimestampPrecision::Microsecond => {
350                    Self::UnixTimestampMicrosecond(modifier::UnixTimestampMicrosecond {
351                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
352                    })
353                }
354                UnixTimestampPrecision::Nanosecond => {
355                    Self::UnixTimestampNanosecond(modifier::UnixTimestampNanosecond {
356                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
357                    })
358                }
359            },
360            Component::Weekday(Weekday {
361                repr,
362                one_indexed,
363                case_sensitive,
364            }) => match repr.unwrap_or_default() {
365                WeekdayRepr::Short => Self::WeekdayShort(modifier::WeekdayShort {
366                    case_sensitive: case_sensitive.unwrap_or_default().into(),
367                }),
368                WeekdayRepr::Long => Self::WeekdayLong(modifier::WeekdayLong {
369                    case_sensitive: case_sensitive.unwrap_or_default().into(),
370                }),
371                WeekdayRepr::Sunday => Self::WeekdaySunday(modifier::WeekdaySunday {
372                    one_indexed: one_indexed.unwrap_or_default().into(),
373                }),
374                WeekdayRepr::Monday => Self::WeekdayMonday(modifier::WeekdayMonday {
375                    one_indexed: one_indexed.unwrap_or_default().into(),
376                }),
377            },
378            Component::WeekNumber(WeekNumber { padding, repr }) => match repr.unwrap_or_default() {
379                WeekNumberRepr::Iso => Self::WeekNumberIso(modifier::WeekNumberIso {
380                    padding: padding.unwrap_or_default().into(),
381                }),
382                WeekNumberRepr::Sunday => Self::WeekNumberSunday(modifier::WeekNumberSunday {
383                    padding: padding.unwrap_or_default().into(),
384                }),
385                WeekNumberRepr::Monday => Self::WeekNumberMonday(modifier::WeekNumberMonday {
386                    padding: padding.unwrap_or_default().into(),
387                }),
388            },
389            Component::Year(Year {
390                padding,
391                repr,
392                range,
393                base,
394                sign_behavior,
395            }) => match (
396                base.unwrap_or_default(),
397                repr.unwrap_or_default(),
398                range.unwrap_or_default(),
399            ) {
400                (YearBase::Calendar, YearRepr::Full, YearRange::Extended) => {
401                    Self::CalendarYearFullExtendedRange(modifier::CalendarYearFullExtendedRange {
402                        padding: padding.unwrap_or_default().into(),
403                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
404                    })
405                }
406                (YearBase::Calendar, YearRepr::Full, _) => {
407                    Self::CalendarYearFullStandardRange(modifier::CalendarYearFullStandardRange {
408                        padding: padding.unwrap_or_default().into(),
409                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
410                    })
411                }
412                (YearBase::Calendar, YearRepr::Century, YearRange::Extended) => {
413                    Self::CalendarYearCenturyExtendedRange(
414                        modifier::CalendarYearCenturyExtendedRange {
415                            padding: padding.unwrap_or_default().into(),
416                            sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
417                        },
418                    )
419                }
420                (YearBase::Calendar, YearRepr::Century, _) => {
421                    Self::CalendarYearCenturyStandardRange(
422                        modifier::CalendarYearCenturyStandardRange {
423                            padding: padding.unwrap_or_default().into(),
424                            sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
425                        },
426                    )
427                }
428                (YearBase::IsoWeek, YearRepr::Full, YearRange::Extended) => {
429                    Self::IsoYearFullExtendedRange(modifier::IsoYearFullExtendedRange {
430                        padding: padding.unwrap_or_default().into(),
431                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
432                    })
433                }
434                (YearBase::IsoWeek, YearRepr::Full, _) => {
435                    Self::IsoYearFullStandardRange(modifier::IsoYearFullStandardRange {
436                        padding: padding.unwrap_or_default().into(),
437                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
438                    })
439                }
440                (YearBase::IsoWeek, YearRepr::Century, YearRange::Extended) => {
441                    Self::IsoYearCenturyExtendedRange(modifier::IsoYearCenturyExtendedRange {
442                        padding: padding.unwrap_or_default().into(),
443                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
444                    })
445                }
446                (YearBase::IsoWeek, YearRepr::Century, _) => {
447                    Self::IsoYearCenturyStandardRange(modifier::IsoYearCenturyStandardRange {
448                        padding: padding.unwrap_or_default().into(),
449                        sign_is_mandatory: sign_behavior.unwrap_or_default().into(),
450                    })
451                }
452                (YearBase::Calendar, YearRepr::LastTwo, _) => {
453                    Self::CalendarYearLastTwo(modifier::CalendarYearLastTwo {
454                        padding: padding.unwrap_or_default().into(),
455                    })
456                }
457                (YearBase::IsoWeek, YearRepr::LastTwo, _) => {
458                    Self::IsoYearLastTwo(modifier::IsoYearLastTwo {
459                        padding: padding.unwrap_or_default().into(),
460                    })
461                }
462            },
463        }
464    }
465}
466
467macro_rules! target_ty {
468    ($name:ident $type:ty) => {
469        $type
470    };
471    ($name:ident) => {
472        super::public::modifier::$name
473    };
474}
475
476/// Get the target value for a given enum.
477macro_rules! target_value {
478    ($name:ident $variant:ident $value:expr) => {
479        $value
480    };
481    ($name:ident $variant:ident) => {
482        super::public::modifier::$name::$variant
483    };
484}
485
486macro_rules! if_not_parse_only {
487    (@parse_only $($x:tt)*) => {};
488    ($($x:tt)*) => { $($x)* };
489}
490
491macro_rules! modifier {
492    ($(
493        $(@$instruction:ident)? enum $name:ident $(($target_ty:ty))? {
494            $(
495                $(#[$attr:meta])?
496                $variant:ident $(($target_value:expr))? = $parse_variant:literal
497            ),* $(,)?
498        }
499    )+) => {$(
500        #[derive(Default)]
501        enum $name {
502            $($(#[$attr])? $variant),*
503        }
504
505        impl $name {
506            /// Parse the modifier from its string representation.
507            fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
508                $(if value.eq_ignore_ascii_case($parse_variant) {
509                    return Ok(Some(Self::$variant));
510                })*
511                Err(value.span.error("invalid modifier value"))
512            }
513        }
514
515        if_not_parse_only! { $(@$instruction)?
516            impl From<$name> for target_ty!($name $($target_ty)?) {
517                fn from(modifier: $name) -> Self {
518                    match modifier {
519                        $($name::$variant => target_value!($name $variant $($target_value)?)),*
520                    }
521                }
522            }
523        }
524    )+};
525}
526
527modifier! {
528    enum HourBase(bool) {
529        Twelve(true) = b"12",
530        #[default]
531        TwentyFour(false) = b"24",
532    }
533
534    enum MonthCaseSensitive(bool) {
535        False(false) = b"false",
536        #[default]
537        True(true) = b"true",
538    }
539
540    @parse_only enum MonthRepr {
541        #[default]
542        Numerical = b"numerical",
543        Long = b"long",
544        Short = b"short",
545    }
546
547    enum Padding {
548        Space = b"space",
549        #[default]
550        Zero = b"zero",
551        None = b"none",
552    }
553
554    enum PeriodCase(bool) {
555        Lower(false) = b"lower",
556        #[default]
557        Upper(true) = b"upper",
558    }
559
560    enum PeriodCaseSensitive(bool) {
561        False(false) = b"false",
562        #[default]
563        True(true) = b"true",
564    }
565
566    enum SignBehavior(bool) {
567        #[default]
568        Automatic(false) = b"automatic",
569        Mandatory(true) = b"mandatory",
570    }
571
572    enum SubsecondDigits {
573        One = b"1",
574        Two = b"2",
575        Three = b"3",
576        Four = b"4",
577        Five = b"5",
578        Six = b"6",
579        Seven = b"7",
580        Eight = b"8",
581        Nine = b"9",
582        #[default]
583        OneOrMore = b"1+",
584    }
585
586    enum TrailingInput {
587        #[default]
588        Prohibit = b"prohibit",
589        Discard = b"discard",
590    }
591
592    @parse_only enum UnixTimestampPrecision {
593        #[default]
594        Second = b"second",
595        Millisecond = b"millisecond",
596        Microsecond = b"microsecond",
597        Nanosecond = b"nanosecond",
598    }
599
600    @parse_only enum WeekNumberRepr {
601        #[default]
602        Iso = b"iso",
603        Sunday = b"sunday",
604        Monday = b"monday",
605    }
606
607    enum WeekdayCaseSensitive(bool) {
608        False(false) = b"false",
609        #[default]
610        True(true) = b"true",
611    }
612
613    enum WeekdayOneIndexed(bool) {
614        False(false) = b"false",
615        #[default]
616        True(true) = b"true",
617    }
618
619    @parse_only enum WeekdayRepr {
620        Short = b"short",
621        #[default]
622        Long = b"long",
623        Sunday = b"sunday",
624        Monday = b"monday",
625    }
626
627    enum YearBase(bool) {
628        #[default]
629        Calendar(false) = b"calendar",
630        IsoWeek(true) = b"iso_week",
631    }
632
633    @parse_only enum YearRepr {
634        #[default]
635        Full = b"full",
636        Century = b"century",
637        LastTwo = b"last_two",
638    }
639
640    @parse_only enum YearRange {
641        Standard = b"standard",
642        #[default]
643        Extended = b"extended",
644    }
645}
646
647fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
648    str::from_utf8(value)
649        .ok()
650        .and_then(|val| val.parse::<T>().ok())
651        .map(|val| Some(val))
652        .ok_or_else(|| value.span.error("invalid modifier value"))
653}