leptos_i18n_codegen 0.6.2

codegen crate for the leptos_i18n crate
Documentation
use std::ops::Bound;

use leptos_i18n_parser::{
    parse_locales::{
        locale::InterpolOrLit,
        parsed_value::ParsedValue,
        ranges::{Range, RangeNumber, Ranges, UntypedRangesInner},
    },
    utils::{Key, KeyPath, UnwrapAt},
};
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};

use crate::{load_locales::parsed_value, utils::EitherOfWrapper};

#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Default)]
pub enum RangeType {
    I8,
    I16,
    #[default]
    I32,
    I64,
    U8,
    U16,
    U32,
    U64,
    F32,
    F64,
}

impl From<leptos_i18n_parser::parse_locales::ranges::RangeType> for RangeType {
    fn from(value: leptos_i18n_parser::parse_locales::ranges::RangeType) -> Self {
        match value {
            leptos_i18n_parser::parse_locales::ranges::RangeType::I8 => RangeType::I8,
            leptos_i18n_parser::parse_locales::ranges::RangeType::I16 => RangeType::I16,
            leptos_i18n_parser::parse_locales::ranges::RangeType::I32 => RangeType::I32,
            leptos_i18n_parser::parse_locales::ranges::RangeType::I64 => RangeType::I64,
            leptos_i18n_parser::parse_locales::ranges::RangeType::U8 => RangeType::U8,
            leptos_i18n_parser::parse_locales::ranges::RangeType::U16 => RangeType::U16,
            leptos_i18n_parser::parse_locales::ranges::RangeType::U32 => RangeType::U32,
            leptos_i18n_parser::parse_locales::ranges::RangeType::U64 => RangeType::U64,
            leptos_i18n_parser::parse_locales::ranges::RangeType::F32 => RangeType::F32,
            leptos_i18n_parser::parse_locales::ranges::RangeType::F64 => RangeType::F64,
        }
    }
}

impl ToTokens for RangeType {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let range_type = match self {
            RangeType::I8 => quote!(i8),
            RangeType::I16 => quote!(i16),
            RangeType::I32 => quote!(i32),
            RangeType::I64 => quote!(i64),
            RangeType::U8 => quote!(u8),
            RangeType::U16 => quote!(u16),
            RangeType::U32 => quote!(u32),
            RangeType::U64 => quote!(u64),
            RangeType::F32 => quote!(f32),
            RangeType::F64 => quote!(f64),
        };
        tokens.extend(range_type)
    }
}

fn to_tokens_integers_string<T: RangeNumber>(
    ranges: &[(Range<T>, ParsedValue)],
    count_key: &Key,
    strings_count: usize,
) -> TokenStream {
    let match_arms = ranges.iter().map(|(range, value)| {
        let value = parsed_value::as_string_impl(value, strings_count);
        let range = range_to_token_stream(range);
        quote!(#range => #value)
    });

    quote! {
        {
            match *#count_key {
                #(
                    #match_arms,
                )*
            }
        }
    }
}

fn to_tokens_floats_string<T: RangeNumber>(
    ranges: &[(Range<T>, ParsedValue)],
    count_key: &Key,
    strings_count: usize,
) -> TokenStream {
    let mut ifs = ranges.iter().map(|(range, value)| {
        let value = parsed_value::as_string_impl(value, strings_count);
        match range_to_condition(range) {
            None => quote!({ #value }),
            Some(condition) => quote!(if #condition { #value }),
        }
    });
    let first = ifs.next();
    let ifs = quote! {
        #first
        #(else #ifs)*
    };

    quote! {
        {
            let plural_count = *#count_key;
            #ifs
        }
    }
}

fn to_tokens_integers<T: RangeNumber>(
    ranges: &[(Range<T>, ParsedValue)],
    count_key: &Key,
    strings_count: usize,
) -> TokenStream {
    let either_of = EitherOfWrapper::new(ranges.len());
    let match_arms = ranges.iter().enumerate().map(|(i, (range, value))| {
        let ts = parsed_value::to_token_stream(value, strings_count);
        let ts = either_of.wrap(i, ts);
        let range = range_to_token_stream(range);
        quote!(#range => { #ts })
    });

    let mut captured_values =
        InterpolOrLit::Lit(leptos_i18n_parser::parse_locales::locale::LiteralType::String);
    let mut key_path = KeyPath::new(None);

    for (_, value) in ranges {
        value
            .get_keys_inner(&mut key_path, &mut captured_values, false)
            .unwrap_at("ranges::to_tokens_integers_1");
    }

    let captured_values = captured_values.is_interpol().map(|keys| {
        let keys = keys
            .iter_keys()
            .map(|key| quote!(let #key = core::clone::Clone::clone(&#key);));
        quote!(#(#keys)*)
    });
    let match_statement = quote! {
        {
            match #count_key() {
                #(
                    #match_arms,
                )*
            }
        }
    };

    quote! {
        {
            #captured_values
            move || #match_statement
        }
    }
}

fn to_tokens_floats<T: RangeNumber>(
    ranges: &[(Range<T>, ParsedValue)],
    count_key: &Key,
    strings_count: usize,
) -> TokenStream {
    let either_of = EitherOfWrapper::new(ranges.len());
    let mut ifs = ranges.iter().enumerate().map(|(i, (range, value))| {
        let ts = parsed_value::to_token_stream(value, strings_count);
        let ts = either_of.wrap(i, ts);
        match range_to_condition(range) {
            None => quote!({ #ts }),
            Some(condition) => quote!(if #condition { #ts }),
        }
    });
    let first = ifs.next();
    let ifs = quote! {
        #first
        #(else #ifs)*
    };

    let mut captured_values =
        InterpolOrLit::Lit(leptos_i18n_parser::parse_locales::locale::LiteralType::String);
    let mut key_path = KeyPath::new(None);

    for (_, value) in ranges {
        value
            .get_keys_inner(&mut key_path, &mut captured_values, false)
            .unwrap_at("ranges::to_tokens_floats_1");
    }

    let captured_values = captured_values.is_interpol().map(|keys| {
        let keys = keys
            .iter_keys()
            .map(|key| quote!(let #key = core::clone::Clone::clone(&#key);));
        quote!(#(#keys)*)
    });

    quote! {
        {
            #captured_values
            move || {
                let plural_count = #count_key();
                #ifs
            }
        }
    }
}

pub fn to_token_stream(this: &Ranges, strings_count: usize) -> TokenStream {
    match &this.inner {
        UntypedRangesInner::I8(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::I16(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::I32(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::I64(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U8(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U16(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U32(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U64(ranges) => {
            to_tokens_integers(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::F32(ranges) => to_tokens_floats(ranges, &this.count_key, strings_count),
        UntypedRangesInner::F64(ranges) => to_tokens_floats(ranges, &this.count_key, strings_count),
    }
}

pub fn as_string_impl(this: &Ranges, strings_count: usize) -> TokenStream {
    match &this.inner {
        UntypedRangesInner::I8(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::I16(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::I32(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::I64(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U8(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U16(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U32(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::U64(ranges) => {
            to_tokens_integers_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::F32(ranges) => {
            to_tokens_floats_string(ranges, &this.count_key, strings_count)
        }
        UntypedRangesInner::F64(ranges) => {
            to_tokens_floats_string(ranges, &this.count_key, strings_count)
        }
    }
}

fn range_to_condition<T: RangeNumber>(range: &Range<T>) -> Option<TokenStream> {
    match range {
        Range::Exact(exact) => Some(quote!(plural_count == #exact)),
        Range::Bounds { .. } => {
            let ts = range_to_token_stream(range);
            Some(quote!(core::ops::RangeBounds::contains(&(#ts), &plural_count)))
        }
        Range::Multiple(conditions) => {
            let mut conditions = conditions.iter().filter_map(range_to_condition);
            let first = conditions.next();
            Some(quote!(#first #(|| #conditions)*))
        }
        Range::Fallback => None,
    }
}

fn range_to_token_stream<T: RangeNumber>(range: &Range<T>) -> proc_macro2::TokenStream {
    match range {
        Range::Exact(num) => quote!(#num),
        Range::Bounds {
            start,
            end: Bound::Included(end),
        } => {
            quote!(#start..=#end)
        }
        Range::Bounds {
            start,
            end: Bound::Unbounded,
        } => {
            quote!(#start..)
        }
        Range::Bounds {
            start,
            end: Bound::Excluded(end),
        } => {
            quote!(#start..#end)
        }
        Range::Fallback => quote!(_),
        Range::Multiple(matchs) => {
            let mut matchs = matchs.iter().map(range_to_token_stream);
            if let Some(first) = matchs.next() {
                quote!(#first #(| #matchs)*)
            } else {
                quote!()
            }
        }
    }
}