localize_it 2.5.0

Simple and fast library for localization
Documentation
pub mod argument;
mod types;

use crate::utils::input::types::ParseFunction;
use argument::Argument;
use proc_macro2::Ident;
use std::{collections::HashSet, vec::IntoIter};
use syn::{
    parse::{Parse, ParseStream}, spanned::Spanned, Error, Expr, ExprArray, ExprLit, ExprPath, Lit,
    Path,
    Token,
};

#[cfg(feature = "from_files")]
use quote::ToTokens;
#[cfg(feature = "from_files")]
use syn::{parse2, ExprTuple, Type};

pub struct Input {
    arguments: Vec<Argument>,
}

impl Input {
    pub fn parse_bool(name: &str, value: &Expr) -> Result<bool, Error> {
        match value {
            Expr::Lit(ExprLit {
                lit: Lit::Bool(bool),
                ..
            }) => Ok(bool.value),
            _ => Err(Error::new(
                value.span(),
                format!("Expected `{name}` to be a `bool`"),
            )),
        }
    }

    pub fn parse_ident(name: &str, value: &Expr) -> Result<Ident, Error> {
        match value {
            Expr::Path(ExprPath { path, .. }) => {
                if let Some(ident) = path.get_ident() {
                    Ok(ident.clone())
                } else {
                    Err(Error::new(
                        path.span(),
                        format!("Expected `{name}` to be an `Ident`"),
                    ))
                }
            }
            _ => Err(Error::new(
                value.span(),
                format!("Expected `{name}` to be an `Ident`"),
            )),
        }
    }

    #[cfg(feature = "from_files")]
    pub fn parse_type(name: &str, value: &Expr) -> Result<Type, Error> {
        let r#type = parse2(value.to_token_stream());

        r#type.map_err(|_| Error::new(value.span(), format!("Expected `{name}` to be a `Type`")))
    }

    pub fn parse_path(name: &str, value: &Expr) -> Result<Path, Error> {
        match value {
            Expr::Path(ExprPath { path, .. }) => Ok(path.clone()),
            Expr::Group(group) => Self::parse_path(name, &*group.expr),
            _ => Err(Error::new(
                value.span(),
                format!("Expected `{name}` to be a `Path`"),
            )),
        }
    }

    #[cfg(feature = "from_files")]
    pub fn parse_tuple(name: &str, value: &Expr) -> Result<Vec<Expr>, Error> {
        match value {
            Expr::Tuple(ExprTuple {
                elems: elements, ..
            }) => Ok(elements.into_iter().map(|value| value.clone()).collect()),
            _ => Err(Error::new(
                value.span(),
                format!("Expected `{name}` to be an `Tuple`"),
            )),
        }
    }

    pub fn parse_array<T>(
        name: &str,
        value: &Expr,
        parse_function: ParseFunction<T>,
    ) -> Result<Vec<T>, Error> {
        match value {
            Expr::Array(ExprArray {
                elems: elements, ..
            }) => elements
                .into_iter()
                .map(|element| parse_function(&format!("{} item", name), element))
                .collect(),
            _ => Err(Error::new(
                value.span(),
                format!("Expected `{name}` to be an array"),
            )),
        }
    }

    #[cfg(feature = "from_files")]
    pub fn parse_optional<T>(
        name: &str,
        value: Option<&Expr>,
        default: T,
        parse_function: ParseFunction<T>,
    ) -> Result<T, Error> {
        match value {
            Some(value) => parse_function(name, value),
            None => Ok(default),
        }
    }
}

impl Parse for Input {
    fn parse(input: ParseStream) -> Result<Self, Error> {
        let mut arguments = Vec::new();

        let mut exists_names = HashSet::new();
        while !input.is_empty() {
            let argument = input.parse()?;

            match &argument {
                Argument::Mapped { name, .. } => {
                    if exists_names.len() > 0 {
                        return Err(Error::new(
                            name.span(),
                            "Expected variants list to precede optional arguments",
                        ));
                    }
                }
                Argument::Named { name, .. } => {
                    if !exists_names.insert(name.clone()) {
                        return Err(Error::new(
                            name.span(),
                            format!("Duplicate argument `{name}`"),
                        ));
                    }
                }
            }

            arguments.push(argument);

            if input.peek(Token![,]) {
                input.parse::<Token![,]>()?;
            }
        }

        Ok(Self { arguments })
    }
}

impl IntoIterator for Input {
    type Item = Argument;
    type IntoIter = IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.arguments.into_iter()
    }
}