#![deny( anonymous_parameters, clippy::all, const_err, illegal_floating_point_literal_pattern, late_bound_lifetime_arguments, path_statements, patterns_in_fns_without_body, rust_2018_idioms, trivial_casts, trivial_numeric_casts, unreachable_pub, unsafe_code, unused_extern_crates )] #![warn( clippy::dbg_macro, clippy::decimal_literal_representation, clippy::get_unwrap, clippy::nursery, clippy::print_stdout, clippy::todo, clippy::unimplemented, clippy::unnested_or_patterns, clippy::unwrap_used, clippy::use_debug, single_use_lifetimes, unused_qualifications, variant_size_differences )] #![allow( clippy::missing_const_for_fn, // useless in proc macro clippy::redundant_pub_crate, // suggests bad style clippy::option_if_let_else, // suggests terrible code )] #[macro_use] mod quote; mod date; mod datetime; mod error; #[cfg(any(feature = "formatting", feature = "parsing"))] mod format_description; mod helpers; mod offset; #[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))] mod serde_format_description; mod time; mod to_tokens; use proc_macro::TokenStream; #[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))] use proc_macro::TokenTree; use self::error::Error; macro_rules! impl_macros { ($($name:ident)*) => {$( #[proc_macro] pub fn $name(input: TokenStream) -> TokenStream { use crate::to_tokens::ToTokenTree; let mut iter = input.into_iter().peekable(); match $name::parse(&mut iter) { Ok(value) => match iter.peek() { Some(tree) => Error::UnexpectedToken { tree: tree.clone() }.to_compile_error(), None => TokenStream::from(value.into_token_tree()), }, Err(err) => err.to_compile_error(), } } )*}; } impl_macros![date datetime offset time]; #[cfg(any(feature = "formatting", feature = "parsing"))] #[proc_macro] pub fn format_description(input: TokenStream) -> TokenStream { (|| { let (span, string) = helpers::get_string_literal(input)?; let items = format_description::parse(&string, span)?; Ok(quote! {{ const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = &[#S( items .into_iter() .map(|item| quote! { #S(item), }) .collect::() )]; DESCRIPTION }}) })() .unwrap_or_else(|err: Error| err.to_compile_error()) } #[cfg(all(feature = "serde", any(feature = "formatting", feature = "parsing")))] #[proc_macro] pub fn serde_format_description(input: TokenStream) -> TokenStream { (|| { let mut tokens = input.into_iter().peekable(); // First, an identifier (the desired module name) let mod_name = match tokens.next() { Some(TokenTree::Ident(ident)) => Ok(ident), Some(tree) => Err(Error::UnexpectedToken { tree }), None => Err(Error::UnexpectedEndOfInput), }?; // Followed by a comma helpers::consume_punct(',', &mut tokens)?; // Then, the type to create serde serializers for (e.g., `OffsetDateTime`). let formattable = match tokens.next() { Some(tree @ TokenTree::Ident(_)) => Ok(tree), Some(tree) => Err(Error::UnexpectedToken { tree }), None => Err(Error::UnexpectedEndOfInput), }?; // Another comma helpers::consume_punct(',', &mut tokens)?; // We now have two options. The user can either provide a format description as a string or // they can provide a path to a format description. If the latter, all remaining tokens are // assumed to be part of the path. let (format, raw_format_string) = match tokens.peek() { // string literal Some(TokenTree::Literal(_)) => { let (span, format_string) = helpers::get_string_literal(tokens.collect())?; let items = format_description::parse(&format_string, span)?; let items: TokenStream = items.into_iter().map(|item| quote! { #S(item), }).collect(); let items = quote! { &[#S(items)] }; ( items, Some(String::from_utf8_lossy(&format_string).into_owned()), ) } // path Some(_) => ( quote! {{ // We can't just do `super::path` because the path could be an absolute path. In // that case, we'd be generating `super::::path`, which is invalid. Even if we // took that into account, it's not possible to know if it's an external crate, // which would just require emitting `path` directly. By taking this approach, // we can leave it to the compiler to do the actual resolution. mod __path_hack { pub(super) use super::super::*; pub(super) use #S(tokens.collect::()) as FORMAT; } __path_hack::FORMAT }}, None, ), None => return Err(Error::UnexpectedEndOfInput), }; Ok(serde_format_description::build( mod_name, formattable, format, raw_format_string, )) })() .unwrap_or_else(|err: Error| err.to_compile_error_standalone()) }