cargo_concat/
module.rs

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            //FIXME
96            let lit = lit.to_string();
97            return Some(String::from(&lit[1..lit.len() - 1]));
98        }
99    }
100    None
101}