hypermod 0.2.0

Automatically build the module tree from the src/ dir.
Documentation
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::quote;

use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use std::result::Result;

#[proc_macro]
pub fn hypermod(_args: TokenStream) -> TokenStream {
    let src_dir = match env::var_os("CARGO_MANIFEST_DIR") {
        Some(manifest_dir) => PathBuf::from(manifest_dir).join("src"),
        None => PathBuf::from("src"),
    };
    let quote = match build_directory_mods_recursive(src_dir.as_path()) {
        Ok(expanded) => expanded,
        Err(err) => syn::Error::new(Span::call_site(), err).to_compile_error(),
    };
    quote.into()
}

fn build_directory_mods_recursive(directory: &Path) -> Result<TokenStream2, Error> {
    if !directory.is_dir() {
        return Err(Error::from(ErrorKind::InvalidInput));
    }

    let mut files = Vec::new();
    let mut submodules = HashMap::new();

    for entry in directory.read_dir()? {
        let entry = entry?;
        let file_type = entry.file_type()?;
        if file_type.is_dir() {
            let directory = entry.path();
            let dirname = directory
                .clone()
                .file_name()
                .unwrap()
                .to_os_string()
                .into_string()
                .unwrap();
            let submodule = build_directory_mods_recursive(directory.as_path())?;
            if !submodule.is_empty() {
                submodules.insert(Ident::new(dirname.as_str(), Span::call_site()), submodule);
            }
        } else if file_type.is_file() {
            let file_name = entry.file_name();
            let file_path = entry.path();
            if file_name == "mod.rs" || file_name == "lib.rs" || file_name == "main.rs" {
                continue;
            } else if file_path.extension() != Some(OsStr::new("rs")) {
                continue;
            }
            let file_ident = Ident::new(
                file_path
                    .file_stem()
                    .unwrap()
                    .to_os_string()
                    .into_string()
                    .unwrap()
                    .as_str(),
                Span::call_site(),
            );
            files.push(file_ident);
        }
    }

    let dirnames = submodules.keys();
    let modstreams = submodules.values();
    if submodules.is_empty() && files.is_empty() {
        return Ok(TokenStream2::new());
    }
    Ok(quote! {
        #(
            pub mod #dirnames {
                #modstreams
            }
        )*
        #(
            pub mod #files;
            pub use self::#files::*;
        )*
    })
}