1use anyhow::{Context, Result};
2use proc_macro2::TokenTree;
3use std::fs::read_to_string;
4use std::path::Path;
5use syn::{parse_file, File, Item};
6
7pub fn concat_module<P: AsRef<Path> + std::fmt::Debug>(
8 mod_file_path: P,
9 is_mod_file: bool,
10) -> Result<File> {
11 log::info!("Parsing {:?}", mod_file_path);
12 let content = read_to_string(&mod_file_path)?;
13 let mut file = parse_file(&content)?;
14
15 let mut cur_path = mod_file_path.as_ref().to_path_buf();
16 if !is_mod_file {
17 let ident = cur_path
18 .file_stem()
19 .with_context(|| format!("Invalid path operation for {:?}", cur_path))?
20 .to_os_string();
21 assert!(cur_path.pop(), "Invalid path operation for {:?}", cur_path);
22 cur_path.push(ident);
23 cur_path.push("dummy.rs");
24 }
25
26 for item in file.items.iter_mut() {
27 if let Item::Mod(item) = item {
28 if item.semi.is_none() {
29 continue;
30 }
31 if let Some(file) = find_file_from_attr(&item.attrs) {
32 let mut path = mod_file_path.as_ref().to_path_buf();
33 assert!(path.pop(), "Invalid path operation.");
34 path.push(file);
35 log::info!("path={:?}", path);
36 if path.exists() && path.is_file() {
37 item.semi = None;
38 let File { items, .. } = concat_module(path, false)?;
39 item.content = Some((syn::token::Brace::default(), items));
40 continue;
41 }
42 }
43
44 let ident = item.ident.to_string();
45 let mut path1 = cur_path.clone();
46 assert!(path1.pop(), "Invalid path operation.");
47 path1.push(&ident);
48 assert!(path1.set_extension("rs"), "Invalid path operation");
49 if path1.exists() && path1.is_file() {
50 item.semi = None;
51 let File { items, .. } = concat_module(path1, false)?;
52 item.content = Some((syn::token::Brace::default(), items));
53 continue;
54 }
55
56 let mut path2 = cur_path.clone();
57 assert!(path2.pop(), "Invalid path operation.");
58 path2.push(&ident);
59 path2.push("mod.rs");
60 if path2.exists() && path2.is_file() {
61 item.semi = None;
62 let File { items, .. } = concat_module(path2, true)?;
63 item.content = Some((syn::token::Brace::default(), items));
64 continue;
65 }
66
67 let path1 = path1
68 .to_str()
69 .with_context(|| format!("Failed to operate {:?}", path1))?;
70 let path2 = path2
71 .to_str()
72 .with_context(|| format!("Failed to operate {:?}", path2))?;
73 return Err(anyhow::anyhow!("Neither {} nor {} exists.", path1, path2));
74 }
75 }
76
77 Ok(file)
78}
79
80fn find_file_from_attr(attrs: &[syn::Attribute]) -> Option<String> {
81 for attr in attrs {
82 if attr
83 .path
84 .segments
85 .iter()
86 .all(|seg| seg.ident.to_string().as_str() != "path")
87 {
88 continue;
89 }
90
91 let mut token = attr.tokens.clone().into_iter();
92 let first = token.next();
93 let second = token.next();
94 if let (Some(TokenTree::Punct(_)), Some(TokenTree::Literal(lit))) = (first, second) {
95 let lit = lit.to_string();
97 return Some(String::from(&lit[1..lit.len() - 1]));
98 }
99 }
100 None
101}