use crate::fluent;
use std::collections::HashSet;
use heck::ToKebabCase;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::punctuated::Punctuated;
use super::{MacroError, UnsupportedError, UnsupportedReason};
#[derive(Clone, Copy, Debug)]
pub enum ReferenceKind {
EnumField,
StructField,
}
#[derive(Clone, Debug)]
pub struct Context {
pub reference_kind: ReferenceKind,
pub valid_references: HashSet<String>,
}
fn expr_for_message(
group: &mut fluent::Group,
ident: &Ident,
reference_kind: &Context,
) -> Result<TokenStream, fluent::GroupError> {
let canonical_message = group.remove_canonical_message(ident, reference_kind)?;
let (additional_locales, additional_messages): (Vec<_>, Vec<_>) = group
.remove_additional_messages(ident, reference_kind)?
.into_iter()
.unzip();
let additional_locales = additional_locales
.iter()
.map(|locale| locale.id.to_string())
.collect::<Vec<_>>();
Ok(quote! {
const PLURAL_RULE_TYPE: ::fluent_embed::PluralRuleType =
::fluent_embed::PluralRuleType::Cardinal;
let plural_options = ::fluent_embed::PluralRulesOptions::default().with_type(PLURAL_RULE_TYPE);
let plural_rules = ::fluent_embed::PluralRules::try_new(locale.into(), plural_options).unwrap();
#(if locale.normalizing_eq(#additional_locales) {
#additional_messages
return Ok(());
}) else*
#canonical_message
Ok(())
})
}
fn unique_named_fields(named_fields: &syn::FieldsNamed) -> HashSet<String> {
named_fields
.named
.iter()
.map(|field| {
field
.ident
.as_ref()
.expect("Named fields should have an associated ident")
})
.map(|ident| ident.to_string())
.collect::<HashSet<String>>()
}
pub fn locales_for_ident(group: &fluent::Group, ident: &syn::Ident) -> TokenStream {
let id = ident.to_string().to_kebab_case();
let locale_literals = group
.locales_for_message(&id)
.map(|locale| locale.id.to_string())
.map(|locale_string| syn::LitStr::new(&locale_string, proc_macro2::Span::call_site()));
quote!(vec![#(::fluent_embed::langid!(#locale_literals)),*])
}
pub fn message_for_struct(
mut group: fluent::Group,
ident: &syn::Ident,
fields: &syn::Fields,
) -> Result<TokenStream, MacroError> {
let references = match fields {
syn::Fields::Named(named_fields) => Context {
reference_kind: ReferenceKind::StructField,
valid_references: unique_named_fields(named_fields),
},
syn::Fields::Unit => Context {
reference_kind: ReferenceKind::StructField,
valid_references: HashSet::new(),
},
syn::Fields::Unnamed(_unnamed_fields) => {
return Err(MacroError::Unsupported(UnsupportedError {
span: ident.clone(),
reason: UnsupportedReason::UnnamedFields,
}))
}
};
Ok(expr_for_message(&mut group, ident, &references)?)
}
pub fn locales_for_enum(
group: &fluent::Group,
enum_variants: &Punctuated<syn::Variant, syn::token::Comma>,
) -> TokenStream {
let mut match_arms: Vec<TokenStream> = Vec::with_capacity(enum_variants.len());
for enum_variant in enum_variants {
let variant_ident = &enum_variant.ident;
let locales_for_variant = locales_for_ident(group, variant_ident);
match_arms.push(quote!(Self::#variant_ident { .. } => #locales_for_variant));
}
quote! {
match self {
#(#match_arms),*
}
}
}
pub fn messages_for_enum(
mut group: fluent::Group,
enum_variants: &Punctuated<syn::Variant, syn::token::Comma>,
) -> Result<TokenStream, MacroError> {
let mut match_arms: Vec<TokenStream> = Vec::with_capacity(enum_variants.len());
for enum_variant in enum_variants {
let variant_pascal_case = &enum_variant.ident;
let destructuring_pattern = match &enum_variant.fields {
syn::Fields::Named(named_fields) => {
let named_field_idents = named_fields.named.iter().map(|field| &field.ident);
quote!(#variant_pascal_case { #(#named_field_idents),* })
}
syn::Fields::Unit => quote!(#variant_pascal_case),
syn::Fields::Unnamed(_unnamed_fields) => {
return Err(MacroError::Unsupported(UnsupportedError {
span: enum_variant.ident.clone(),
reason: UnsupportedReason::UnnamedFields,
}))
}
};
let references = match &enum_variant.fields {
syn::Fields::Named(named_fields) => Context {
reference_kind: ReferenceKind::EnumField,
valid_references: unique_named_fields(named_fields),
},
syn::Fields::Unit => Context {
reference_kind: ReferenceKind::EnumField,
valid_references: HashSet::new(),
},
syn::Fields::Unnamed(_unnamed_fields) => {
return Err(MacroError::Unsupported(UnsupportedError {
span: enum_variant.ident.clone(),
reason: UnsupportedReason::UnnamedFields,
}))
}
};
let arm_body = expr_for_message(&mut group, &enum_variant.ident, &references)?;
match_arms.push(quote!(Self::#destructuring_pattern => { #arm_body }));
}
Ok(quote! {
match self {
#(#match_arms),*
}
})
}