/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use cg; use quote::Tokens; use syn::{DeriveInput, Path}; use synstructure; use to_css::CssVariantAttrs; #[darling(attributes(parse), default)] #[derive(Default, FromVariant)] pub struct ParseVariantAttrs { pub aliases: Option, pub condition: Option, } pub fn derive(input: DeriveInput) -> Tokens { let name = &input.ident; let s = synstructure::Structure::new(&input); let mut saw_condition = false; let match_body = s.variants().iter().fold(quote!(), |match_body, variant| { let bindings = variant.bindings(); assert!( bindings.is_empty(), "Parse is only supported for single-variant enums for now" ); let css_variant_attrs = cg::parse_variant_attrs_from_ast::(&variant.ast()); let parse_attrs = cg::parse_variant_attrs_from_ast::(&variant.ast()); if css_variant_attrs.skip { return match_body; } let identifier = cg::to_css_identifier( &css_variant_attrs.keyword.unwrap_or(variant.ast().ident.as_ref().into()), ); let ident = &variant.ast().ident; saw_condition |= parse_attrs.condition.is_some(); let condition = match parse_attrs.condition { Some(ref p) => quote! { if #p(context) }, None => quote! { }, }; let mut body = quote! { #match_body #identifier #condition => Ok(#name::#ident), }; let aliases = match parse_attrs.aliases { Some(aliases) => aliases, None => return body, }; for alias in aliases.split(",") { body = quote! { #body #alias #condition => Ok(#name::#ident), }; } body }); let context_ident = if saw_condition { quote! { context } } else { quote! { _ } }; let parse_body = if saw_condition { quote! { let location = input.current_source_location(); let ident = input.expect_ident()?; match_ignore_ascii_case! { &ident, #match_body _ => Err(location.new_unexpected_token_error( ::cssparser::Token::Ident(ident.clone()) )) } } } else { quote! { Self::parse(input) } }; let parse_trait_impl = quote! { impl ::parser::Parse for #name { #[inline] fn parse<'i, 't>( #context_ident: &::parser::ParserContext, input: &mut ::cssparser::Parser<'i, 't>, ) -> Result> { #parse_body } } }; if saw_condition { return parse_trait_impl; } // TODO(emilio): It'd be nice to get rid of these, but that makes the // conversion harder... let methods_impl = quote! { impl #name { /// Parse this keyword. #[inline] pub fn parse<'i, 't>( input: &mut ::cssparser::Parser<'i, 't>, ) -> Result> { let location = input.current_source_location(); let ident = input.expect_ident()?; Self::from_ident(ident.as_ref()).map_err(|()| { location.new_unexpected_token_error( ::cssparser::Token::Ident(ident.clone()) ) }) } /// Parse this keyword from a string slice. #[inline] pub fn from_ident(ident: &str) -> Result { match_ignore_ascii_case! { ident, #match_body _ => Err(()), } } } }; quote! { #parse_trait_impl #methods_impl } }