lru-cache-macros 0.3.1

A procedural macro for automatically caching the output of functions.
Documentation
use std::default::Default;
use std::collections::HashSet;

use proc_macro2;
use syn::{self, Token, parenthesized};
use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;

use crate::error::{DiagnosticError, Result};

pub struct Config {
    pub cache_type: syn::Path,
    pub ignore_args: HashSet<syn::Ident>,
    pub use_tls: bool,
}

struct IgnoreArgsAttrib {
    ignore_args: HashSet<syn::Ident>,
}

struct CacheTypeAttrib {
    type_path: syn::Path,
}

enum ConfigAttrib {
    CacheType(CacheTypeAttrib),
    IgnoreArgs(IgnoreArgsAttrib),
    UseTls,
}

impl Config {
    // Parse any additional attributes present after `lru_cache` and return a configuration object
    // created from their contents. Additionally, return any attributes that were not handled here.
    pub fn parse_from_attributes(attribs: &[syn::Attribute]) -> Result<(Config, Vec<syn::Attribute>)> {
        let mut parsed_attributes = Vec::new();
        let mut remaining_attributes = Vec::new();

        for attrib in attribs {
            let segs = &attrib.path.segments;
            if segs.len() > 0 {
                if segs[0].ident.to_string() == "lru_config" {
                    let tokens = attrib.tts.clone();
                    let parsed = syn::parse2::<ConfigAttrib>(tokens);
                    match parsed {
                        Ok(parsed_attrib) => parsed_attributes.push(parsed_attrib),
                        Err(e) => {
                            let diag = e.span().unstable()
                                .error(format!("{}", e));
                            return Err(DiagnosticError::new_with_syn_error(diag, e));
                        }
                    }
                }
                else {
                    remaining_attributes.push(attrib.clone());
                }
            }
        }

        let mut config: Config = Default::default();

        for parsed_attrib in parsed_attributes {
            match parsed_attrib {
                ConfigAttrib::CacheType(val) => config.cache_type = val.type_path,
                ConfigAttrib::IgnoreArgs(val) => config.ignore_args = val.ignore_args,
                ConfigAttrib::UseTls => config.use_tls = true,
            }
        }

        Ok((config, remaining_attributes))
    }
}

impl Default for Config {
    fn default() -> Config {
        Config {
            cache_type: make_path_from_segments(&["lru_cache", "LruCache"], true, proc_macro2::Span::call_site()),
            ignore_args: HashSet::new(),
            use_tls: false,
        }
    }
}

fn make_path_from_segments(segments: &[&str], has_leading_colon: bool, span: proc_macro2::Span) -> syn::Path {
    let leading_colon = if has_leading_colon {
        Some(syn::token::Colon2 { spans: [span; 2] })
    } else {
        None
    };
    let mut segments_ast = Punctuated::<syn::PathSegment, Token![::]>::new();
    for name in segments {
        let ident = syn::Ident::new(name, span);
        let seg = syn::PathSegment {
            ident,
            arguments: syn::PathArguments::None,
        };
        segments_ast.push(seg);
    }

    syn::Path {
        leading_colon,
        segments: segments_ast,
    }
}

impl Parse for ConfigAttrib {
    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
        let content;
        let _paren = parenthesized!(content in input);
        let name = content.parse::<syn::Ident>()?;

        match &name.to_string()[..] {
            "cache_type" => Ok(ConfigAttrib::CacheType(content.parse::<CacheTypeAttrib>()?)),
            "ignore_args" => Ok(ConfigAttrib::IgnoreArgs(content.parse::<IgnoreArgsAttrib>()?)),
            "thread_local" => Ok(ConfigAttrib::UseTls),
            _ => Err(syn::parse::Error::new(
                name.span(), format!("unrecognized config option '{}'", name.to_string())
            ))
        }
    }
}

impl Parse for CacheTypeAttrib {
    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
        input.parse::<Token![=]>()?;
        Ok(CacheTypeAttrib {
            type_path: input.parse::<syn::Path>()?
        })
    }
}

impl Parse for IgnoreArgsAttrib {
    fn parse(input: ParseStream) -> syn::parse::Result<Self> {
        input.parse::<Token![=]>()?;
        let elems = syn::punctuated::Punctuated::<syn::Ident, Token![,]>::parse_terminated(input)?;
        Ok(IgnoreArgsAttrib {
            ignore_args: elems.into_iter().collect(),
        })
    }
}