#![forbid(unsafe_code)]
#![deny(missing_docs)]
extern crate proc_macro2;
use std::collections::HashMap;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
#[derive(Default, Debug)]
struct Namespace {
children: HashMap<String, Namespace>,
generate: bool,
}
impl Namespace {
fn new(generate: bool) -> Self {
Self {
children: HashMap::new(),
generate,
}
}
}
#[proc_macro]
pub fn namespaced(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = input.into_iter().collect::<Vec<_>>();
let packages: Vec<_> = input
.iter()
.filter_map(|item| match item {
proc_macro::TokenTree::Literal(lit) => {
let mut s = lit.to_string();
s.retain(|c| c != '"');
Some(s)
}
_ => None,
})
.collect();
let packages: Vec<_> = packages
.iter()
.map(|i| i.split('.').collect::<Vec<_>>())
.collect();
let mut namespaces_map: HashMap<String, Namespace> = HashMap::new();
for package in packages {
let mut map = &mut namespaces_map;
for (i, import) in package.iter().enumerate() {
let entry = map
.entry(import.to_string())
.or_insert(Namespace::new(i == package.len() - 1));
map = &mut entry.children;
}
}
let tokens: Vec<_> = namespaces_map
.iter()
.map(|(name, namespace)| build_namespace_tokens(name.to_string(), name, namespace))
.collect();
let tokens = quote! {
#(#tokens)*
};
tokens.into()
}
fn build_namespace_tokens(path: String, name: &str, namespace: &Namespace) -> TokenStream {
let inner: Vec<_> = namespace
.children
.iter()
.map(|(name, namespace)| {
build_namespace_tokens(format!("{}.{}", path, name), name, namespace)
})
.collect();
let formatted_name = format_ident!("{}", name);
let include_token = if namespace.generate {
quote! {
tonic::include_proto!(#path);
}
} else {
quote! {}
};
quote! {
pub mod #formatted_name {
#include_token
#(#inner)*
}
}
}