l10n_impl 0.1.3

Proc macros for l10n.
Documentation
use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
use quote::{format_ident, ToTokens};
use std::collections::HashSet;
use syn::parse::{Parse, ParseStream, Peek};
use syn::token::DotDotDot;
use syn::{
    braced, bracketed, parenthesized, token, Error, Ident, Index, LitInt, LitStr, Result, Token,
};

#[derive(Clone, Default)]
pub struct MessageArgs {
    args: Vec<Argument>,
    incomplete: Option<DotDotDot>,
}

#[derive(Clone)]
pub enum Argument {
    Short {
        name: LitStr,
        value: TokenStream,
    },
    Long {
        name: LitStr,
        equal: Token![=],
        value: TokenStream,
    },
}

impl MessageArgs {
    pub fn first_to_token_stream(&self) -> Option<TokenStream> {
        self.args.first().map(|arg| arg.to_token_stream())
    }

    pub fn is_empty(&self) -> bool {
        self.args.is_empty()
    }

    pub fn is_complete(&self) -> bool {
        self.incomplete.is_none()
    }

    pub fn iter(&self) -> impl Iterator<Item = &Argument> {
        self.args.iter()
    }

    pub fn validate(&self) -> Result<()> {
        #[cfg(not(feature = "allow-incomplete"))]
        if let Some(incomplete) = self.incomplete {
            return Err(Error::new_spanned(
                incomplete,
                r#"incomplete arguments syntax is not authorized because the feature flag "allow-incomplete" is not set "#,
            ));
        }

        let mut duplicate_error: Option<Error> = None;
        let mut visited_args: Vec<_> = vec![];
        for arg in &self.args {
            let name = arg.name();
            if !visited_args.contains(name) {
                visited_args.push(name.clone());
            } else {
                let err = Error::new_spanned(
                    arg.to_token_stream(),
                    format!("argument {} already set", name.to_token_stream()),
                );
                match duplicate_error {
                    Some(ref mut duplicate_error) => duplicate_error.combine(err),
                    _ => duplicate_error = Some(err),
                }
            }
        }

        if let Some(err) = duplicate_error {
            return Err(err);
        }

        Ok(())
    }

    pub fn validate_for_enum(&self) -> Result<()> {
        if let Some(incomplete) = self.incomplete {
            return Err(Error::new_spanned(
                incomplete,
                "incomplete arguments syntax is not authorized on enum",
            ));
        }

        self.validate()
    }

    pub fn merge_enum_arguments(&mut self, enum_arguments: &MessageArgs) {
        let current_argument_names = self
            .iter()
            .map(|arg| arg.name().to_owned())
            .collect::<HashSet<_>>();
        for argument in enum_arguments.args.iter() {
            if !current_argument_names.contains(argument.name()) {
                self.args.push(argument.clone());
            }
        }
    }
}

impl Parse for MessageArgs {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut arguments = Self::default();
        while !input.is_empty() {
            arguments.incomplete = input.parse::<Option<DotDotDot>>()?;
            if let Some(incomplete) = arguments.incomplete {
                if !input.is_empty() {
                    return Err(Error::new_spanned(
                        incomplete,
                        "unknown arguments at compile time (i.e. `...`) must be positioned last",
                    ));
                }
            } else {
                arguments.args.push(input.parse()?);
            }

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

        Ok(arguments)
    }
}

impl Argument {
    pub fn name(&self) -> &LitStr {
        match self {
            Self::Short { name, .. } => name,
            Self::Long { name, .. } => name,
        }
    }

    pub fn value(&self) -> &TokenStream {
        match self {
            Self::Short { value, .. } => value,
            Self::Long { value, .. } => value,
        }
    }

    pub fn to_token_stream(&self) -> TokenStream {
        match self {
            Self::Short { value, .. } => value.to_token_stream(),
            Self::Long { name, equal, value } => TokenStream::from_iter([
                name.to_token_stream(),
                equal.to_token_stream(),
                value.to_token_stream(),
            ]),
        }
    }
}

impl Parse for Argument {
    fn parse(input: ParseStream) -> Result<Self> {
        if input.peek(Ident) || (input.peek(Token![*]) && input.peek2(Ident)) {
            let unary: Option<Token![*]> = input.parse()?;
            let ident: Ident = input.parse()?;
            let name = LitStr::new(&ident.to_string(), ident.span());
            let value = TokenStream::from_iter([unary.to_token_stream(), ident.to_token_stream()]);
            return if !input.is_empty() && !input.peek(Token![,]) {
                Err(input.error("expected `,` after a shorthand argument"))
            } else {
                Ok(Argument::Short { name, value })
            };
        }

        let name: LitStr = input.parse().map_err(|err| {
            Error::new(
                err.span(),
                r#"expected an argument, example: `"variable" = field` or `field`"#,
            )
        })?;
        let equal = input.parse().map_err(|err| {
            Error::new(
                err.span(),
                format!(
                    "expected a value assignment for this argument, example: `{} = field`",
                    name.to_token_stream()
                ),
            )
        })?;
        Ok(Argument::Long {
            name,
            equal,
            value: parse_argument_value(input, true, Token![,], false)?,
        })
    }
}

fn parse_argument_value<T: Peek>(
    input: ParseStream,
    mut begin_expr: bool,
    separator: T,
    in_group: bool,
) -> Result<TokenStream> {
    let mut tokens = Vec::new();
    while !input.is_empty() {
        if !in_group && input.peek(separator) {
            break;
        }

        if begin_expr && input.peek(Token![.]) {
            if input.peek2(Ident) {
                input.parse::<Token![.]>()?;
                begin_expr = false;
                continue;
            }
            if input.peek2(LitInt) {
                input.parse::<Token![.]>()?;
                let int: Index = input.parse()?;
                let ident = format_ident!("__self_{}", int.index, span = int.span);
                tokens.push(TokenTree::Ident(ident));
                begin_expr = false;
                continue;
            }
        }

        begin_expr = input.peek(Token![break])
            || input.peek(Token![continue])
            || input.peek(Token![if])
            || input.peek(Token![in])
            || input.peek(Token![match])
            || input.peek(Token![mut])
            || input.peek(Token![return])
            || input.peek(Token![while])
            || input.peek(Token![+])
            || input.peek(Token![&])
            || input.peek(Token![!])
            || input.peek(Token![^])
            || input.peek(Token![,])
            || input.peek(Token![/])
            || input.peek(Token![=])
            || input.peek(Token![>])
            || input.peek(Token![<])
            || input.peek(Token![|])
            || input.peek(Token![%])
            || input.peek(Token![;])
            || input.peek(Token![*])
            || input.peek(Token![-]);

        let token: TokenTree = if input.peek(token::Paren) {
            let content;
            let delimiter = parenthesized!(content in input);
            let nested = parse_argument_value(&content, true, separator, true)?;
            let mut group = Group::new(Delimiter::Parenthesis, nested);
            group.set_span(delimiter.span.join());
            TokenTree::Group(group)
        } else if input.peek(token::Brace) {
            let content;
            let delimiter = braced!(content in input);
            let nested = parse_argument_value(&content, true, separator, true)?;
            let mut group = Group::new(Delimiter::Brace, nested);
            group.set_span(delimiter.span.join());
            TokenTree::Group(group)
        } else if input.peek(token::Bracket) {
            let content;
            let delimiter = bracketed!(content in input);
            let nested = parse_argument_value(&content, true, separator, true)?;
            let mut group = Group::new(Delimiter::Bracket, nested);
            group.set_span(delimiter.span.join());
            TokenTree::Group(group)
        } else {
            input.parse()?
        };
        tokens.push(token);
    }
    Ok(TokenStream::from_iter(tokens))
}