use std::{env,path::{self, Path}};
use fancy_regex::Regex;
use proc_macro::TokenStream;
use serde::Deserialize;
#[derive(Clone, Debug, Deserialize)]
struct JSONConfig {
pub directory: String,
pub recursive: Option<bool>,
pub inter_process: Option<String>,
pub post_process: Option<String>,
pub public_module: Option<bool>,
pub module: Option<bool>,
#[serde(default = "Vec::new")]
pub exclude_files: Vec<String>,
#[serde(default = "Vec::new")]
pub exclude_dirs: Vec<String>,
#[serde(default = "Vec::new")]
pub include_files: Vec<String>,
#[serde(default = "Vec::new")]
pub include_dirs: Vec<String>,
}
struct Config {
pub directory: String,
pub recursive: bool,
pub inter_process: Option<String>,
pub post_process: Option<String>,
pub public_module: Option<bool>,
pub module: Option<bool>,
pub exclude_files: Vec<Regex>,
pub exclude_dirs: Vec<Regex>,
pub include_files: Vec<Regex>,
pub include_dirs: Vec<Regex>,
}
fn files_from(path: &str, config: &Config) -> Vec<String> {
let mut modules = Vec::new();
for path in std::fs::read_dir(path).unwrap() {
let path = path.unwrap().path();
if path.is_dir() {
let path = path.to_str().unwrap();
if config.include_dirs.iter().any(|pattern| {
pattern.is_match(&path).expect(&format!(
"Failed to match include_dirs pattern: {}.",
pattern
))
}) {
continue;
}
if config.exclude_dirs.iter().any(|pattern| {
pattern.is_match(&path).expect(&format!(
"Failed to match exclude_dirs pattern: {}.",
pattern
))
}) {
continue;
}
if config.recursive {
modules.extend(files_from(path, config));
}
modules.push(path.to_string());
} else {
let path = path.to_str().unwrap();
if config.include_files.iter().any(|pattern| {
!pattern.is_match(&path).expect(&format!(
"Failed to match include_files pattern: {}.",
pattern
))
}) {
continue;
}
if config.exclude_files.iter().any(|pattern| {
pattern.is_match(&path).expect(&format!(
"Failed to match exclude_files pattern: {}.",
pattern
))
}) {
continue;
}
modules.push(path.to_string());
}
}
modules
}
fn parse_modules(base: &str, config: &Config, data: Vec<String>) -> Vec<String> {
let mut modules = Vec::new();
for module in data {
let module = module.replace(base, "");
let module = module.replace(path::MAIN_SEPARATOR_STR, "::");
let module = module.replace(".rs", "");
if module.len() == 0 {
continue;
}
let mut module = module;
if let Some(inter) = &config.inter_process {
if let Some(state) = config.public_module {
if state {
panic!("Cannot use public_module and process_as at the same time.");
}
}
if let Some(state) = config.module {
if state {
panic!("Cannot use module and process_as at the same time.");
}
}
module = inter.replace("{}", &module);
} else {
let pub_module_state = config.public_module.unwrap_or(false);
let module_state = config.module.unwrap_or(true);
if pub_module_state && module_state {
module.insert_str(0, "pub mod ");
} else if module_state {
module.insert_str(0, "mod ");
} else {
panic!("You must set the module to true to use public_module.");
}
module.push(';');
}
modules.push(module);
}
modules
}
#[proc_macro]
pub fn import(input: TokenStream) -> TokenStream {
let input = input.to_string();
let mut config = serde_json::from_str::<JSONConfig>(&input)
.expect("Failed to parse config.");
let exc = match env::consts::OS {
"windows" => r"(\\)?$",
_ => r"(/)?$",
};
config.exclude_files.push(r"(lib|main|mod).rs$".to_string());
config.exclude_dirs
.push(("(.git|.github|lib|src|target|tests)".to_string()) + exc);
config.directory = config.directory.replace("/", path::MAIN_SEPARATOR_STR);
if !config.directory.ends_with(path::MAIN_SEPARATOR) {
config.directory.push(path::MAIN_SEPARATOR);
}
let config = Config {
directory: config.directory,
recursive: config.recursive.unwrap_or(false),
inter_process: config.inter_process,
post_process: config.post_process,
public_module: config.public_module,
module: config.module,
exclude_files: config
.exclude_files.iter()
.map(|pattern| {
Regex::new(pattern).expect(&format!(
"Failed to parse exclude_files pattern: {}.",
pattern
))
})
.collect(),
exclude_dirs: config
.exclude_dirs.iter()
.map(|pattern| {
Regex::new(pattern).expect(&format!(
"Failed to parse exclude_dirs pattern: {}.",
pattern
))
})
.collect(),
include_files: config
.include_files.iter()
.map(|pattern| {
Regex::new(pattern).expect(&format!(
"Failed to parse include_files pattern: {}.",
pattern
))
})
.collect(),
include_dirs: config
.include_dirs.iter()
.map(|pattern| {
Regex::new(pattern).expect(&format!(
"Failed to parse include_dirs pattern: {}.",
pattern
))
})
.collect(),
};
let manifiest_dir = env::var("CARGO_MANIFEST_DIR")
.expect("Failed to get CARGO_MANIFEST_DIR env variable.");
let path = Path::new(&manifiest_dir)
.join(&config.directory);
let path = path.to_str()
.unwrap();
let output = files_from(&path, &config);
let output = parse_modules(&path, &config, output);
if let Some(post) = &config.post_process {
post.replace("{}", &output.join("")).parse().unwrap()
} else {
output.join("").parse().unwrap()
}
}