1use std::{env,path::{self, Path}};
2
3use fancy_regex::Regex;
4use proc_macro::TokenStream;
5use serde::Deserialize;
6
7#[derive(Clone, Debug, Deserialize)]
10struct JSONConfig {
11 pub directory: String,
12 pub recursive: Option<bool>,
13
14 pub inter_process: Option<String>,
15 pub post_process: Option<String>,
16
17 pub public_module: Option<bool>,
18 pub module: Option<bool>,
19
20 #[serde(default = "Vec::new")]
21 pub exclude_files: Vec<String>,
22
23 #[serde(default = "Vec::new")]
24 pub exclude_dirs: Vec<String>,
25
26 #[serde(default = "Vec::new")]
27 pub include_files: Vec<String>,
28
29 #[serde(default = "Vec::new")]
30 pub include_dirs: Vec<String>,
31}
32
33struct Config {
36 pub directory: String,
38
39 pub recursive: bool,
41
42 pub inter_process: Option<String>,
45
46 pub post_process: Option<String>,
49
50 pub public_module: Option<bool>,
52
53 pub module: Option<bool>,
58
59 pub exclude_files: Vec<Regex>,
63
64 pub exclude_dirs: Vec<Regex>,
69
70 pub include_files: Vec<Regex>,
72
73 pub include_dirs: Vec<Regex>,
75}
76
77fn files_from(path: &str, config: &Config) -> Vec<String> {
80 let mut modules = Vec::new();
81
82 for path in std::fs::read_dir(path).unwrap() {
83 let path = path.unwrap().path();
84
85 if path.is_dir() {
86 let path = path.to_str().unwrap();
87
88 if config.include_dirs.iter().any(|pattern| {
89 pattern.is_match(&path).expect(&format!(
90 "Failed to match include_dirs pattern: {}.",
91 pattern
92 ))
93 }) {
94 continue;
95 }
96
97 if config.exclude_dirs.iter().any(|pattern| {
98 pattern.is_match(&path).expect(&format!(
99 "Failed to match exclude_dirs pattern: {}.",
100 pattern
101 ))
102 }) {
103 continue;
104 }
105
106 if config.recursive {
107 modules.extend(files_from(path, config));
108 }
109
110 modules.push(path.to_string());
111 } else {
112 let path = path.to_str().unwrap();
113
114 if config.include_files.iter().any(|pattern| {
115 !pattern.is_match(&path).expect(&format!(
116 "Failed to match include_files pattern: {}.",
117 pattern
118 ))
119 }) {
120 continue;
121 }
122
123 if config.exclude_files.iter().any(|pattern| {
124 pattern.is_match(&path).expect(&format!(
125 "Failed to match exclude_files pattern: {}.",
126 pattern
127 ))
128 }) {
129 continue;
130 }
131
132 modules.push(path.to_string());
133 }
134 }
135
136 modules
137}
138
139fn parse_modules(base: &str, config: &Config, data: Vec<String>) -> Vec<String> {
142 let mut modules = Vec::new();
143
144 for module in data {
145 let module = module.replace(base, "");
146
147 let module = module.replace(path::MAIN_SEPARATOR_STR, "::");
149 let module = module.replace(".rs", "");
150
151 if module.len() == 0 {
152 continue;
153 }
154
155 let mut module = module;
156
157 if let Some(inter) = &config.inter_process {
158 if let Some(state) = config.public_module {
159 if state {
160 panic!("Cannot use public_module and process_as at the same time.");
161 }
162
163 }
164
165 if let Some(state) = config.module {
166 if state {
167 panic!("Cannot use module and process_as at the same time.");
168 }
169 }
170
171 module = inter.replace("{}", &module);
172 } else {
173 let pub_module_state = config.public_module.unwrap_or(false);
174 let module_state = config.module.unwrap_or(true);
175
176 if pub_module_state && module_state {
177 module.insert_str(0, "pub mod ");
178 } else if module_state {
179 module.insert_str(0, "mod ");
180 } else {
181 panic!("You must set the module to true to use public_module.");
182 }
183
184 module.push(';');
185 }
186
187 modules.push(module);
188 }
189
190 modules
191}
192
193#[proc_macro]
253pub fn import(input: TokenStream) -> TokenStream {
254 let input = input.to_string();
255
256 let mut config = serde_json::from_str::<JSONConfig>(&input)
257 .expect("Failed to parse config.");
258
259 let exc = match env::consts::OS {
260 "windows" => r"(\\)?$",
261 _ => r"(/)?$",
262 };
263
264 config.exclude_files.push(r"(lib|main|mod).rs$".to_string());
266 config.exclude_dirs
267 .push(("(.git|.github|lib|src|target|tests)".to_string()) + exc);
268
269 config.directory = config.directory.replace("/", path::MAIN_SEPARATOR_STR);
270
271 if !config.directory.ends_with(path::MAIN_SEPARATOR) {
272 config.directory.push(path::MAIN_SEPARATOR);
273 }
274
275 let config = Config {
277 directory: config.directory,
278 recursive: config.recursive.unwrap_or(false),
279
280 inter_process: config.inter_process,
281 post_process: config.post_process,
282
283 public_module: config.public_module,
284 module: config.module,
285
286 exclude_files: config
287 .exclude_files.iter()
288 .map(|pattern| {
289 Regex::new(pattern).expect(&format!(
290 "Failed to parse exclude_files pattern: {}.",
291 pattern
292 ))
293 })
294 .collect(),
295
296 exclude_dirs: config
297 .exclude_dirs.iter()
298 .map(|pattern| {
299 Regex::new(pattern).expect(&format!(
300 "Failed to parse exclude_dirs pattern: {}.",
301 pattern
302 ))
303 })
304 .collect(),
305
306 include_files: config
307 .include_files.iter()
308 .map(|pattern| {
309 Regex::new(pattern).expect(&format!(
310 "Failed to parse include_files pattern: {}.",
311 pattern
312 ))
313 })
314 .collect(),
315
316 include_dirs: config
317 .include_dirs.iter()
318 .map(|pattern| {
319 Regex::new(pattern).expect(&format!(
320 "Failed to parse include_dirs pattern: {}.",
321 pattern
322 ))
323 })
324 .collect(),
325 };
326
327 let manifiest_dir = env::var("CARGO_MANIFEST_DIR")
329 .expect("Failed to get CARGO_MANIFEST_DIR env variable.");
330
331 let path = Path::new(&manifiest_dir)
332 .join(&config.directory);
333
334 let path = path.to_str()
335 .unwrap();
336
337 let output = files_from(&path, &config);
338 let output = parse_modules(&path, &config, output);
339
340 if let Some(post) = &config.post_process {
341 post.replace("{}", &output.join("")).parse().unwrap()
342 } else {
343 output.join("").parse().unwrap()
344 }
345
346}
347