time_macros/format_description/
format_item.rs1use 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 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 trailing_input = "trailing_input": Option<TrailingInput> => trailing_input,
212 },
213 Hour = "hour" {
214 padding = "padding": Option<Padding> => padding,
215 base = "repr": Option<HourBase> => is_12_hour_clock,
216 },
217 Ignore = "ignore" {
218 #[required]
219 count = "count": Option<#[from_str] NonZero<u16>> => count,
220 },
221 Minute = "minute" {
222 padding = "padding": Option<Padding> => padding,
223 },
224 Month = "month" {
225 padding = "padding": Option<Padding> => padding,
226 repr = "repr": Option<MonthRepr> => repr,
227 case_sensitive = "case_sensitive": Option<MonthCaseSensitive> => case_sensitive,
228 },
229 OffsetHour = "offset_hour" {
230 sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
231 padding = "padding": Option<Padding> => padding,
232 },
233 OffsetMinute = "offset_minute" {
234 padding = "padding": Option<Padding> => padding,
235 },
236 OffsetSecond = "offset_second" {
237 padding = "padding": Option<Padding> => padding,
238 },
239 Ordinal = "ordinal" {
240 padding = "padding": Option<Padding> => padding,
241 },
242 Period = "period" {
243 case = "case": Option<PeriodCase> => is_uppercase,
244 case_sensitive = "case_sensitive": Option<PeriodCaseSensitive> => case_sensitive,
245 },
246 Second = "second" {
247 padding = "padding": Option<Padding> => padding,
248 },
249 Subsecond = "subsecond" {
250 digits = "digits": Option<SubsecondDigits> => digits,
251 },
252 UnixTimestamp = "unix_timestamp" {
253 precision = "precision": Option<UnixTimestampPrecision> => precision,
254 sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
255 },
256 Weekday = "weekday" {
257 repr = "repr": Option<WeekdayRepr> => repr,
258 one_indexed = "one_indexed": Option<WeekdayOneIndexed> => one_indexed,
259 case_sensitive = "case_sensitive": Option<WeekdayCaseSensitive> => case_sensitive,
260 },
261 WeekNumber = "week_number" {
262 padding = "padding": Option<Padding> => padding,
263 repr = "repr": Option<WeekNumberRepr> => repr,
264 },
265 Year = "year" {
266 padding = "padding": Option<Padding> => padding,
267 repr = "repr": Option<YearRepr> => repr,
268 range = "range": Option<YearRange> => range,
269 base = "base": Option<YearBase> => iso_week_based,
270 sign_behavior = "sign": Option<SignBehavior> => sign_is_mandatory,
271 },
272 }
273}
274
275macro_rules! target_ty {
276 ($name:ident $type:ty) => {
277 $type
278 };
279 ($name:ident) => {
280 super::public::modifier::$name
281 };
282}
283
284macro_rules! target_value {
286 ($name:ident $variant:ident $value:expr) => {
287 $value
288 };
289 ($name:ident $variant:ident) => {
290 super::public::modifier::$name::$variant
291 };
292}
293
294macro_rules! modifier {
295 ($(
296 enum $name:ident $(($target_ty:ty))? {
297 $(
298 $(#[$attr:meta])?
299 $variant:ident $(($target_value:expr))? = $parse_variant:literal
300 ),* $(,)?
301 }
302 )+) => {$(
303 #[derive(Default)]
304 enum $name {
305 $($(#[$attr])? $variant),*
306 }
307
308 impl $name {
309 fn from_modifier_value(value: &Spanned<&[u8]>) -> Result<Option<Self>, Error> {
311 $(if value.eq_ignore_ascii_case($parse_variant) {
312 return Ok(Some(Self::$variant));
313 })*
314 Err(value.span.error("invalid modifier value"))
315 }
316 }
317
318 impl From<$name> for target_ty!($name $($target_ty)?) {
319 fn from(modifier: $name) -> Self {
320 match modifier {
321 $($name::$variant => target_value!($name $variant $($target_value)?)),*
322 }
323 }
324 }
325 )+};
326}
327
328modifier! {
329 enum HourBase(bool) {
330 Twelve(true) = b"12",
331 #[default]
332 TwentyFour(false) = b"24",
333 }
334
335 enum MonthCaseSensitive(bool) {
336 False(false) = b"false",
337 #[default]
338 True(true) = b"true",
339 }
340
341 enum MonthRepr {
342 #[default]
343 Numerical = b"numerical",
344 Long = b"long",
345 Short = b"short",
346 }
347
348 enum Padding {
349 Space = b"space",
350 #[default]
351 Zero = b"zero",
352 None = b"none",
353 }
354
355 enum PeriodCase(bool) {
356 Lower(false) = b"lower",
357 #[default]
358 Upper(true) = b"upper",
359 }
360
361 enum PeriodCaseSensitive(bool) {
362 False(false) = b"false",
363 #[default]
364 True(true) = b"true",
365 }
366
367 enum SignBehavior(bool) {
368 #[default]
369 Automatic(false) = b"automatic",
370 Mandatory(true) = b"mandatory",
371 }
372
373 enum SubsecondDigits {
374 One = b"1",
375 Two = b"2",
376 Three = b"3",
377 Four = b"4",
378 Five = b"5",
379 Six = b"6",
380 Seven = b"7",
381 Eight = b"8",
382 Nine = b"9",
383 #[default]
384 OneOrMore = b"1+",
385 }
386
387 enum TrailingInput {
388 #[default]
389 Prohibit = b"prohibit",
390 Discard = b"discard",
391 }
392
393 enum UnixTimestampPrecision {
394 #[default]
395 Second = b"second",
396 Millisecond = b"millisecond",
397 Microsecond = b"microsecond",
398 Nanosecond = b"nanosecond",
399 }
400
401 enum WeekNumberRepr {
402 #[default]
403 Iso = b"iso",
404 Sunday = b"sunday",
405 Monday = b"monday",
406 }
407
408 enum WeekdayCaseSensitive(bool) {
409 False(false) = b"false",
410 #[default]
411 True(true) = b"true",
412 }
413
414 enum WeekdayOneIndexed(bool) {
415 False(false) = b"false",
416 #[default]
417 True(true) = b"true",
418 }
419
420 enum WeekdayRepr {
421 Short = b"short",
422 #[default]
423 Long = b"long",
424 Sunday = b"sunday",
425 Monday = b"monday",
426 }
427
428 enum YearBase(bool) {
429 #[default]
430 Calendar(false) = b"calendar",
431 IsoWeek(true) = b"iso_week",
432 }
433
434 enum YearRepr {
435 #[default]
436 Full = b"full",
437 Century = b"century",
438 LastTwo = b"last_two",
439 }
440
441 enum YearRange {
442 Standard = b"standard",
443 #[default]
444 Extended = b"extended",
445 }
446}
447
448fn parse_from_modifier_value<T: FromStr>(value: &Spanned<&[u8]>) -> Result<Option<T>, Error> {
449 str::from_utf8(value)
450 .ok()
451 .and_then(|val| val.parse::<T>().ok())
452 .map(|val| Some(val))
453 .ok_or_else(|| value.span.error("invalid modifier value"))
454}