intercom-common 0.4.0

See 'intercom'
Documentation
use syn::parse::{Parse, ParseStream, Result};

/// An empty type that cannot be parsed.
///
/// Used especially with attributes that don't take positional arguments.
#[derive(Debug)]
pub enum NoParams {}
impl Parse for NoParams
{
    fn parse(input: ParseStream) -> Result<Self>
    {
        Err(input.error("Attribute does not accept positional parameters"))
    }
}

/// Literal string or 'None' if there should be no value.
#[derive(Debug)]
pub enum StrOption
{
    Str(::syn::LitStr),
    None,
}

impl Parse for StrOption
{
    fn parse(input: ParseStream) -> Result<Self>
    {
        if input.peek(::syn::LitStr) {
            return Ok(StrOption::Str(input.parse()?));
        }

        let ident: ::syn::Ident = input.parse()?;
        if ident == "None" {
            return Ok(StrOption::None);
        }

        Err(input.error("Expected string or `None`"))
    }
}

/// Defines intercom attribute parameter parsing.
///
/// ```ignore
/// intercom_attribute!(
///     SomeAttr< SomeAttrParam, Ident > {
///         param1: Ident,
///         param2: LitStr,
///         param3: Expr,
///     }
/// );
/// ```
///
/// Will define structures to parse attribute params such as:
///
/// ```ignore
/// #[some_attr( Ident1, Ident2, Ident3 )]
/// #[some_attr( param1 = SomeIdent, List, Idents )]
/// #[some_attr( param2 = "literal", param3 = expression() + 1 )]
/// ```
///
/// If the attribute should accept no positional arguments, use the `NoParams` type as the
/// positional parameter type.
macro_rules! intercom_attribute {

    // Match the top level invocation
    //
    // # Params
    //
    // * `$attr` - Attribute name.
    // * `$attr_param` - Name for the attribute parameter enum generated by the macro.
    // * `$params` - Type for the positional arguments
    // * `( $name, $type )*` - Name/type pairs for the optional named arguments.
    ( $attr:ident < $attr_param:ident, $params:ident > { $( $name:ident : $type:ident, )* } ) => {

        // Recurse into the attribute with stringified name parameters.
        //
        // These are needed for the #[doc..] attributes.
        intercom_attribute!( INTERNAL
                $attr,
                stringify!( $attr ),
                $attr_param,
                $params,
                $( $name : $type : stringify!( $name), )* );
    };

    // Attribute invocation with the stringified attributes.
    ( INTERNAL
            $attr:ident,
            $sattr:expr,
            $attr_param:ident,
            $params:ident,
            $( $name:ident : $type:ident : $sname:expr, )* ) => {

        // Define the enum that can represent the parameters.
        #[doc = "`" ]
        #[doc = $sattr ]
        #[doc = "` attribute parameter type." ]
        #[allow(non_camel_case_types)]
        #[derive(Debug)]
        enum $attr_param {
            $( $name ( $type ), )*
            args( $params ),
        }

        // Define the implementation for the syn parse function for the parameters.
        impl ::syn::parse::Parse for $attr_param {
            fn parse( input : ::syn::parse::ParseStream ) -> ::syn::parse::Result<Self> {

                // Check for the presence of equals sign (`=`) to figure out whether this is an
                // optional named parameter or a positional parameter.
                if ! input.peek2( Token![=] ) {

                    // Variable parameter. Emit it as such from the parse result.
                    return Ok( $attr_param::args( input.parse()? ) );
                }

                // Fetch the parameter ident and skip the punctuation (`=` as required above).
                let ident : ::syn::Ident = input.parse()?;
                let _punct : Token![=] = input.parse()?;

                // Match the ident to the name and parse the correct enum alternative from the
                // value.
                Ok( match ident.to_string().as_ref() {
                    $(
                        stringify!( $name ) => $attr_param::$name( input.parse()? ),
                    )*

                    // Ident wasn't a known string.
                    other => return Err( input.error( format!( "Unexpected parameter: `{}`", other ) ) )
                } )
            }
        }

        // Define the attribute data mdoel.
        //
        // The attribute is represented by a vector of arguments.
        #[doc = "`"]
        #[doc = $sattr ]
        #[doc = "` attribute data model"]
        struct $attr( Vec<$attr_param> );

        // Define the parse implementation for the full attribute.
        impl ::syn::parse::Parse for $attr {
            fn parse( input : ::syn::parse::ParseStream ) -> ::syn::parse::Result<Self> {

                fn parse_parens(input: ::syn::parse::ParseStream) -> ::syn::Result<::syn::parse::ParseBuffer> {
                    let content;
                    ::syn::parenthesized!(content in input);
                    Ok(content)
                }

                // When parsing #[foo(bar)] attributes with syn, syn will include
                // the parenthess in the tts:
                // > ( bar )
                //
                // However these parentheses are not present when the parameters
                // are presented to us by rustc proc macros.
                //
                // As a result we'll need to support both.
                let params = match parse_parens( &input ) {

                    // If the parens existed, call the parse for the paren content.
                    Ok( content ) => content.call(
                        ::syn::punctuated::Punctuated::<$attr_param, ::syn::token::Comma>::parse_terminated )?,

                    // If the parens were not present, call the parse for the whole input.
                    Err( _ ) => input.call(
                        ::syn::punctuated::Punctuated::<$attr_param, ::syn::token::Comma>::parse_terminated )?,
                };

                // Return the attribute.
                Ok( $attr( params.into_iter().collect() ) )
            }
        }

        #[allow(dead_code)]
        impl $attr {

            // Define getters for the various named arguments.
            $(
                #[doc = "Getter for the `"]
                #[doc = $sname ]
                #[doc = "` optional argument.\n\n"]
                #[doc = "Returns a failure if the parameter was defined multiple times. "]
                #[doc = "Otherwise results in an `Option` depending on whether the parameter"]
                #[doc = "was defined or not."]
                pub fn $name( &self ) -> Result< Option< &$type >, String >
                {
                    // Find the required parameters from the vector.
                    //
                    // We'll limit the filter into two items at max because there's no need to find
                    // more. Once we find two we'll know there are more than one and we'll result
                    // in an error.
                    let v = self.0.iter().filter_map( |p| match p {
                        $attr_param::$name( v ) => Some( v ),
                        _ => None
                    } ).take( 2 ).collect::<Vec<_>>();

                    // Decide on the return value based on how many times the parameter was
                    // defined.
                    match v.len() {
                        0 => Ok( None ),
                        1 => Ok( Some( &v[0] ) ),
                        _ => Err( format!(
                                "Multiple {} arguments", stringify!( $name ) ) )
                    }
                }
            )*

            #[doc = "Gets the positional arguments"]
            pub fn args<'a>( &'a self ) -> Vec<&'a $params>
            {
                // Return the positional arguments.
                self.0.iter().filter_map( |p| match p {
                    $attr_param::args( v ) => Some( v ),
                    _ => None
                } ).collect()
            }
        }
    }
}