time_macros/format_description/
format_item.rs

1use std::num::NonZeroU16;
2use std::str::{self, FromStr};
3
4use super::{ast, unused, Error, Span, Spanned, 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    Component(Component),
15    Optional {
16        value: Box<[Self]>,
17        _span: Unused<Span>,
18    },
19    First {
20        value: Box<[Box<[Self]>]>,
21        _span: Unused<Span>,
22    },
23}
24
25impl Item<'_> {
26    pub(super) fn from_ast(ast_item: ast::Item<'_>) -> Result<Item<'_>, Error> {
27        Ok(match ast_item {
28            ast::Item::Component {
29                _opening_bracket: _,
30                _leading_whitespace: _,
31                name,
32                modifiers,
33                _trailing_whitespace: _,
34                _closing_bracket: _,
35            } => Item::Component(component_from_ast(&name, &modifiers)?),
36            ast::Item::Literal(Spanned { value, span: _ }) => Item::Literal(value),
37            ast::Item::EscapedBracket {
38                _first: _,
39                _second: _,
40            } => Item::Literal(b"["),
41            ast::Item::Optional {
42                opening_bracket,
43                _leading_whitespace: _,
44                _optional_kw: _,
45                _whitespace: _,
46                nested_format_description,
47                closing_bracket,
48            } => {
49                let items = nested_format_description
50                    .items
51                    .into_vec()
52                    .into_iter()
53                    .map(Item::from_ast)
54                    .collect::<Result<_, _>>()?;
55                Item::Optional {
56                    value: items,
57                    _span: unused(opening_bracket.to(closing_bracket)),
58                }
59            }
60            ast::Item::First {
61                opening_bracket,
62                _leading_whitespace: _,
63                _first_kw: _,
64                _whitespace: _,
65                nested_format_descriptions,
66                closing_bracket,
67            } => {
68                let items = nested_format_descriptions
69                    .into_vec()
70                    .into_iter()
71                    .map(|nested_format_description| {
72                        nested_format_description
73                            .items
74                            .into_vec()
75                            .into_iter()
76                            .map(Item::from_ast)
77                            .collect()
78                    })
79                    .collect::<Result<_, _>>()?;
80                Item::First {
81                    value: items,
82                    _span: unused(opening_bracket.to(closing_bracket)),
83                }
84            }
85        })
86    }
87}
88
89impl From<Item<'_>> for crate::format_description::public::OwnedFormatItem {
90    fn from(item: Item<'_>) -> Self {
91        match item {
92            Item::Literal(literal) => Self::Literal(literal.to_vec().into_boxed_slice()),
93            Item::Component(component) => Self::Component(component.into()),
94            Item::Optional { value, _span: _ } => Self::Optional(Box::new(value.into())),
95            Item::First { value, _span: _ } => {
96                Self::First(value.into_vec().into_iter().map(Into::into).collect())
97            }
98        }
99    }
100}
101
102impl<'a> From<Box<[Item<'a>]>> for crate::format_description::public::OwnedFormatItem {
103    fn from(items: Box<[Item<'a>]>) -> Self {
104        let items = items.into_vec();
105        match <[_; 1]>::try_from(items) {
106            Ok([item]) => item.into(),
107            Err(vec) => Self::Compound(vec.into_iter().map(Into::into).collect()),
108        }
109    }
110}
111
112macro_rules! component_definition {
113    (@if_required required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
114    (@if_required then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
115    (@if_from_str from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($then)* };
116    (@if_from_str then { $($then:tt)* } $(else { $($else:tt)* })?) => { $($($else)*)? };
117
118    ($vis:vis enum $name:ident {
119        $($variant:ident = $parse_variant:literal {$(
120            $(#[$required:tt])?
121            $field:ident = $parse_field:literal:
122            Option<$(#[$from_str:tt])? $field_type:ty>
123            => $target_field:ident
124        ),* $(,)?}),* $(,)?
125    }) => {
126        $vis enum $name {
127            $($variant($variant),)*
128        }
129
130        $($vis struct $variant {
131            $($field: Option<$field_type>),*
132        })*
133
134        $(impl $variant {
135            fn with_modifiers(
136                modifiers: &[ast::Modifier<'_>],
137                _component_span: Span,
138            ) -> Result<Self, Error>
139            {
140                #[allow(unused_mut)]
141                let mut this = Self {
142                    $($field: None),*
143                };
144
145                for modifier in modifiers {
146                    $(#[allow(clippy::string_lit_as_bytes)]
147                    if modifier.key.eq_ignore_ascii_case($parse_field.as_bytes()) {
148                        this.$field = component_definition!(@if_from_str $($from_str)?
149                            then {
150                                parse_from_modifier_value::<$field_type>(&modifier.value)?
151                            } else {
152                                <$field_type>::from_modifier_value(&modifier.value)?
153                            });
154                        continue;
155                    })*
156                    return Err(modifier.key.span.error("invalid modifier key"));
157                }
158
159                $(component_definition! { @if_required $($required)? then {
160                    if this.$field.is_none() {
161                        return Err(_component_span.error("missing required modifier"));
162                    }
163                }})*
164
165                Ok(this)
166            }
167        })*
168
169        impl From<$name> for crate::format_description::public::Component {
170            fn from(component: $name) -> Self {
171                match component {$(
172                    $name::$variant($variant { $($field),* }) => {
173                        $crate::format_description::public::Component::$variant(
174                            super::public::modifier::$variant {$(
175                                $target_field: component_definition! { @if_required $($required)?
176                                    then {
177                                        match $field {
178                                            Some(value) => value.into(),
179                                            None => bug!("required modifier was not set"),
180                                        }
181                                    } else {
182                                        $field.unwrap_or_default().into()
183                                    }
184                                }
185                            ),*}
186                        )
187                    }
188                )*}
189            }
190        }
191
192        fn component_from_ast(
193            name: &Spanned<&[u8]>,
194            modifiers: &[ast::Modifier<'_>],
195        ) -> Result<Component, Error> {
196            $(#[allow(clippy::string_lit_as_bytes)]
197            if name.eq_ignore_ascii_case($parse_variant.as_bytes()) {
198                return Ok(Component::$variant($variant::with_modifiers(&modifiers, name.span)?));
199            })*
200            Err(name.span.error("invalid component"))
201        }
202    }
203}
204
205component_definition! {
206    pub(super) enum Component {
207        Day = "day" {
208            padding = "padding": Option<Padding> => padding,
209        },
210        End = "end" {},
211        Hour = "hour" {
212            padding = "padding": Option<Padding> => padding,
213            base = "repr": Option<HourBase> => is_12_hour_clock,
214        },
215        Ignore = "ignore" {
216            #[required]
217            count = "count": Option<#[from_str] NonZeroU16> => count,
218        },
219        Minute = "minute" {
220            padding = "padding": Option<Padding> => padding,
221        },
222        Month = "month" {
223            padding = "padding": Option<Padding> => padding,
224            repr = "repr": Option<MonthRepr> => repr,
225            case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
226        },
227        OffsetHour = "offset_hour" {
228            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
229            padding = "padding": Option<Padding> => padding,
230        },
231        OffsetMinute = "offset_minute" {
232            padding = "padding": Option<Padding> => padding,
233        },
234        OffsetSecond = "offset_second" {
235            padding = "padding": Option<Padding> => padding,
236        },
237        Ordinal = "ordinal" {
238            padding = "padding": Option<Padding> => padding,
239        },
240        Period = "period" {
241            case = "case": Option<PeriodCase> => is_uppercase,
242            case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
243        },
244        Second = "second" {
245            padding = "padding": Option<Padding> => padding,
246        },
247        Subsecond = "subsecond" {
248            digits = "digits": Option<SubsecondDigits> => digits,
249        },
250        UnixTimestamp = "unix_timestamp" {
251            precision = "precision": Option<UnixTimestampPrecision> => precision,
252            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
253        },
254        Weekday = "weekday" {
255            repr = "repr": Option<WeekdayRepr> => repr,
256            one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed,
257            case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
258        },
259        WeekNumber = "week_number" {
260            padding = "padding": Option<Padding> => padding,
261            repr = "repr": Option<WeekNumberRepr> => repr,
262        },
263        Year = "year" {
264            padding = "padding": Option<Padding> => padding,
265            repr = "repr": Option<YearRepr> => repr,
266            range = "range": Option<YearRange> => range,
267            base = "base": Option<YearBase> => iso_week_based,
268            sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
269        },
270    }
271}
272
273macro_rules! target_ty {
274    ($name:ident $type:ty) => {
275        $type
276    };
277    ($name:ident) => {
278        super::public::modifier::$name
279    };
280}
281
282/// Get the target value for a given enum.
283macro_rules! target_value {
284    ($name:ident $variant:ident $value:expr) => {
285        $value
286    };
287    ($name:ident $variant:ident) => {
288        super::public::modifier::$name::$variant
289    };
290}
291
292macro_rules! modifier {
293    ($(
294        enum $name:ident $(($target_ty:ty))? {
295            $(
296                $(#[$attr:meta])?
297                $variant:ident $(($target_value:expr))? = $parse_variant:literal
298            ),* $(,)?
299        }
300    )+) => {$(
301        #[derive(Default)]
302        enum $name {
303            $($(#[$attr])? $variant),*
304        }
305
306        impl $name {
307            /// Parse the modifier from its string representation.
308            fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
309                $(if value.eq_ignore_ascii_case($parse_variant) {
310                    return Ok(Some(Self::$variant));
311                })*
312                Err(value.span.error("invalid modifier value"))
313            }
314        }
315
316        impl From<$name> for target_ty!($name $($target_ty)?) {
317            fn from(modifier: $name) -> Self {
318                match modifier {
319                    $($name::$variant => target_value!($name $variant $($target_value)?)),*
320                }
321            }
322        }
323    )+};
324}
325
326modifier! {
327    enum HourBase(bool) {
328        Twelve(true) = b"12",
329        #[default]
330        TwentyFour(false) = b"24",
331    }
332
333    enum MonthCaseSensitive(bool) {
334        False(false) = b"false",
335        #[default]
336        True(true) = b"true",
337    }
338
339    enum MonthRepr {
340        #[default]
341        Numerical = b"numerical",
342        Long = b"long",
343        Short = b"short",
344    }
345
346    enum Padding {
347        Space = b"space",
348        #[default]
349        Zero = b"zero",
350        None = b"none",
351    }
352
353    enum PeriodCase(bool) {
354        Lower(false) = b"lower",
355        #[default]
356        Upper(true) = b"upper",
357    }
358
359    enum PeriodCaseSensitive(bool) {
360        False(false) = b"false",
361        #[default]
362        True(true) = b"true",
363    }
364
365    enum SignBehavior(bool) {
366        #[default]
367        Automatic(false) = b"automatic",
368        Mandatory(true) = b"mandatory",
369    }
370
371    enum SubsecondDigits {
372        One = b"1",
373        Two = b"2",
374        Three = b"3",
375        Four = b"4",
376        Five = b"5",
377        Six = b"6",
378        Seven = b"7",
379        Eight = b"8",
380        Nine = b"9",
381        #[default]
382        OneOrMore = b"1+",
383    }
384
385    enum UnixTimestampPrecision {
386        #[default]
387        Second = b"second",
388        Millisecond = b"millisecond",
389        Microsecond = b"microsecond",
390        Nanosecond = b"nanosecond",
391    }
392
393    enum WeekNumberRepr {
394        #[default]
395        Iso = b"iso",
396        Sunday = b"sunday",
397        Monday = b"monday",
398    }
399
400    enum WeekdayCaseSensitive(bool) {
401        False(false) = b"false",
402        #[default]
403        True(true) = b"true",
404    }
405
406    enum WeekdayOneIndexed(bool) {
407        False(false) = b"false",
408        #[default]
409        True(true) = b"true",
410    }
411
412    enum WeekdayRepr {
413        Short = b"short",
414        #[default]
415        Long = b"long",
416        Sunday = b"sunday",
417        Monday = b"monday",
418    }
419
420    enum YearBase(bool) {
421        #[default]
422        Calendar(false) = b"calendar",
423        IsoWeek(true) = b"iso_week",
424    }
425
426    enum YearRepr {
427        #[default]
428        Full = b"full",
429        Century = b"century",
430        LastTwo = b"last_two",
431    }
432
433    enum YearRange {
434        Standard = b"standard",
435        #[default]
436        Extended = b"extended",
437    }
438}
439
440fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
441    str::from_utf8(value)
442        .ok()
443        .and_then(|val| val.parse::<T>().ok())
444        .map(|val| Some(val))
445        .ok_or_else(|| value.span.error("invalid modifier value"))
446}