syn-file-expand 0.1.0

Library and a CLI tool to load full source code of multi-file crates
Documentation
use proc_macro2;
use proc_macro2::TokenStream;

use crate::ErrorCase;

use super::AttrParseError;

use super::Error;

use std::path::PathBuf;

use proc_macro2::TokenTree;

pub(crate) fn extract_path_from_attr(
    tt: Vec<TokenTree>,
    mod_syn_path: &syn::Path,
) -> Result<PathBuf, Error> {
    let ape = |x|Err(Error {
        module: mod_syn_path.clone(),
        inner: ErrorCase::AttrParseError(x),
    }); 
    if tt.len() != 2 {
        return Err(Error {
            module: mod_syn_path.clone(),
            inner: ErrorCase::AttrParseError(AttrParseError::NotExactlyTwoTokens),
        });
    }
    match &tt[0] {
        TokenTree::Punct(x) if x.as_char() == '=' => (),
        _ => {
            return ape(AttrParseError::FirstTokenIsNotEqualSign);
        }
    }
    match &tt[1] {
        TokenTree::Literal(_) => (),
        _ => {
            return ape(AttrParseError::SecondTokenIsNotStringLiteral)
        }
    }
    let ts = TokenStream::from(tt[1].clone());
    let tslit: syn::Lit = syn::parse2(ts).map_err(|e| Error {
        module: mod_syn_path.clone(),
        inner: ErrorCase::SynParseError(e),
    })?;
    let explicit_path = match tslit {
        syn::Lit::Str(x) => PathBuf::from(x.value()),
        _ => {
            return ape(AttrParseError::SecondTokenIsNotStringLiteral)
        }
    };
    Ok(explicit_path)
}

pub(crate) fn read_and_process_attributes(
    input_attrs: &[syn::Attribute],
    path_attrs: &mut Vec<(Vec<TokenTree>, Option<TokenStream>)>,
    attrs: &mut Vec<syn::Attribute>,
    cfg_attrs: &mut Vec<TokenStream>,
) -> Result<(), AttrParseError> {
    for attr in input_attrs {
        match &attr.path {
            x if x.get_ident().map(|x| x.to_string()) == Some("cfg".to_owned()) => {
                let tt = Vec::<TokenTree>::from_iter(attr.tokens.clone());
                if tt.len() != 1 {
                    return Err(AttrParseError::MalformedCfg);
                }
                let g = match tt.into_iter().next().unwrap() {
                    TokenTree::Group(g) if g.delimiter() == proc_macro2::Delimiter::Parenthesis => g,
                    _ => return Err(AttrParseError::MalformedCfg),
                };
                cfg_attrs.push(g.stream());
            }
            x if x.get_ident().map(|x| x.to_string()) == Some("path".to_owned()) => {
                let tt = Vec::<TokenTree>::from_iter(attr.tokens.clone());
                path_attrs.push((tt, None));
            }
            x if x.get_ident().map(|x| x.to_string()) == Some("cfg_attr".to_owned()) => {
                let tt = Vec::<TokenTree>::from_iter(attr.tokens.clone());
                if tt.len() != 1 {
                    return Err(AttrParseError::CfgAttrNotRoundGroup);
                }
                let t = tt.into_iter().next().unwrap();
                let g = match t {
                    TokenTree::Group(g)
                        if g.delimiter() == proc_macro2::Delimiter::Parenthesis =>
                    {
                        g
                    }
                    _ => {
                        return Err(AttrParseError::CfgAttrNotRoundGroup);
                    }
                };
                let inner = Vec::<TokenTree>::from_iter(g.stream());
                if inner.len() < 3 {
                    return Err(AttrParseError::CfgAttrNotTwoParams);
                }
                let mut ts_before_comma = TokenStream::new();
                let mut ts_after_comma = Vec::<TokenTree>::new();
                let mut comma_encountered = false;
                for t in inner.into_iter() {
                    match t {
                        TokenTree::Punct(p) if p.as_char() == ',' => {
                            if comma_encountered {
                                return Err(AttrParseError::CfgAttrNotTwoParams);
                            }
                            comma_encountered = true;
                        }
                        x => {
                            if comma_encountered {
                                ts_after_comma.push(x)
                            } else {
                                ts_before_comma.extend(std::iter::once(x))
                            }
                        }
                    }
                }
                if !comma_encountered {
                    return Err(AttrParseError::CfgAttrNotTwoParams);
                }

                let mut pathy = false;
                if !ts_after_comma.is_empty() {
                    match &ts_after_comma[0] {
                        TokenTree::Ident(i) if i.to_string() == "path" => pathy = true,
                        _ => (),
                    }
                }

                if pathy {
                    path_attrs.push((ts_after_comma[1..].to_vec(), Some(ts_before_comma)));
                } else {
                    attrs.push(attr.clone());
                }
            }
            _ => attrs.push(attr.clone()),
        }
    }
    Ok(())
}