diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:47:55 +0000 |
commit | 2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4 (patch) | |
tree | 033cc839730fda84ff08db877037977be94e5e3a /vendor/time-macros/src/lib.rs | |
parent | Initial commit. (diff) | |
download | cargo-2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4.tar.xz cargo-2aadc03ef15cb5ca5cc2af8a7c08e070742f0ac4.zip |
Adding upstream version 0.70.1+ds1.upstream/0.70.1+ds1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/time-macros/src/lib.rs')
-rw-r--r-- | vendor/time-macros/src/lib.rs | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/vendor/time-macros/src/lib.rs b/vendor/time-macros/src/lib.rs new file mode 100644 index 0000000..d9e4693 --- /dev/null +++ b/vendor/time-macros/src/lib.rs @@ -0,0 +1,253 @@ +#![allow( + clippy::missing_const_for_fn, // irrelevant for proc macros + clippy::missing_docs_in_private_items, // TODO remove + clippy::std_instead_of_core, // irrelevant for proc macros + clippy::std_instead_of_alloc, // irrelevant for proc macros + clippy::alloc_instead_of_core, // irrelevant for proc macros + missing_docs, // TODO remove +)] + +#[allow(unused_macros)] +macro_rules! bug { + () => { compile_error!("provide an error message to help fix a possible bug") }; + ($descr:literal $($rest:tt)?) => { + unreachable!(concat!("internal error: ", $descr) $($rest)?) + } +} + +#[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; + +#[cfg(any(feature = "formatting", feature = "parsing"))] +use std::iter::Peekable; + +use proc_macro::TokenStream; +#[cfg(any(feature = "formatting", feature = "parsing"))] +use proc_macro::{Ident, 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"))] +enum FormatDescriptionVersion { + V1, + V2, +} + +#[cfg(any(feature = "formatting", feature = "parsing"))] +enum VersionOrModuleName { + Version(FormatDescriptionVersion), + #[cfg_attr(not(feature = "serde"), allow(unused_tuple_struct_fields))] + ModuleName(Ident), +} + +#[cfg(any(feature = "formatting", feature = "parsing"))] +fn parse_format_description_version<const NO_EQUALS_IS_MOD_NAME: bool>( + iter: &mut Peekable<proc_macro::token_stream::IntoIter>, +) -> Result<Option<VersionOrModuleName>, Error> { + let version_ident = match iter.peek() { + Some(TokenTree::Ident(ident)) if ident.to_string() == "version" => match iter.next() { + Some(TokenTree::Ident(ident)) => ident, + _ => unreachable!(), + }, + _ => return Ok(None), + }; + match iter.peek() { + Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => iter.next(), + _ if NO_EQUALS_IS_MOD_NAME => { + return Ok(Some(VersionOrModuleName::ModuleName(version_ident))); + } + Some(token) => { + return Err(Error::Custom { + message: "expected `=`".into(), + span_start: Some(token.span()), + span_end: Some(token.span()), + }); + } + None => { + return Err(Error::Custom { + message: "expected `=`".into(), + span_start: None, + span_end: None, + }); + } + }; + let version_literal = match iter.next() { + Some(TokenTree::Literal(literal)) => literal, + Some(token) => { + return Err(Error::Custom { + message: "expected 1 or 2".into(), + span_start: Some(token.span()), + span_end: Some(token.span()), + }); + } + None => { + return Err(Error::Custom { + message: "expected 1 or 2".into(), + span_start: None, + span_end: None, + }); + } + }; + let version = match version_literal.to_string().as_str() { + "1" => FormatDescriptionVersion::V1, + "2" => FormatDescriptionVersion::V2, + _ => { + return Err(Error::Custom { + message: "invalid format description version".into(), + span_start: Some(version_literal.span()), + span_end: Some(version_literal.span()), + }); + } + }; + helpers::consume_punct(',', iter)?; + + Ok(Some(VersionOrModuleName::Version(version))) +} + +#[cfg(any(feature = "formatting", feature = "parsing"))] +#[proc_macro] +pub fn format_description(input: TokenStream) -> TokenStream { + (|| { + let mut input = input.into_iter().peekable(); + let version = match parse_format_description_version::<false>(&mut input)? { + Some(VersionOrModuleName::Version(version)) => Some(version), + None => None, + // This branch should never occur here, as `false` is the provided as a const parameter. + Some(VersionOrModuleName::ModuleName(_)) => bug!("branch should never occur"), + }; + let (span, string) = helpers::get_string_literal(input)?; + let items = format_description::parse_with_version(version, &string, span)?; + + Ok(quote! {{ + const DESCRIPTION: &[::time::format_description::FormatItem<'_>] = &[#S( + items + .into_iter() + .map(|item| quote! { #S(item), }) + .collect::<TokenStream>() + )]; + 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, the optional format description version. + let version = parse_format_description_version::<true>(&mut tokens)?; + let (version, mod_name) = match version { + Some(VersionOrModuleName::ModuleName(module_name)) => (None, Some(module_name)), + Some(VersionOrModuleName::Version(version)) => (Some(version), None), + None => (None, None), + }; + + // Next, an identifier (the desired module name) + // Only parse this if it wasn't parsed when attempting to get the version. + let mod_name = match mod_name { + Some(mod_name) => mod_name, + None => 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, format_description_display) = match tokens.peek() { + // string literal + Some(TokenTree::Literal(_)) => { + let (span, format_string) = helpers::get_string_literal(tokens)?; + let items = format_description::parse_with_version(version, &format_string, span)?; + let items: TokenStream = + items.into_iter().map(|item| quote! { #S(item), }).collect(); + let items = quote! { + const ITEMS: &[::time::format_description::FormatItem<'_>] = &[#S(items)]; + ITEMS + }; + + (items, String::from_utf8_lossy(&format_string).into_owned()) + } + // path + Some(_) => { + let tokens = tokens.collect::<TokenStream>(); + let tokens_string = tokens.to_string(); + ( + 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) as FORMAT; + } + __path_hack::FORMAT + }}, + tokens_string, + ) + } + None => return Err(Error::UnexpectedEndOfInput), + }; + + Ok(serde_format_description::build( + mod_name, + formattable, + format, + format_description_display, + )) + })() + .unwrap_or_else(|err: Error| err.to_compile_error_standalone()) +} |