xsd_parser/models/code/
module.rs1use std::collections::{BTreeMap, BTreeSet};
2use std::fs::{create_dir_all, write};
3use std::io::Error as IoError;
4use std::mem::replace;
5use std::path::Path;
6use std::str::FromStr;
7
8use proc_macro2::{Ident as Ident2, TokenStream};
9use quote::{format_ident, quote, ToTokens};
10
11use super::IdentPath;
12
13#[derive(Default, Debug)]
18pub struct Module {
19 pub code: TokenStream,
21
22 pub usings: BTreeSet<String>,
24
25 pub modules: BTreeMap<String, Module>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum SubModules {
33 None,
35
36 Files,
47
48 Inline,
64}
65
66impl Module {
67 pub fn append(&mut self, code: TokenStream) -> &mut Self {
69 self.code.extend(code);
70
71 self
72 }
73
74 pub fn prepend(&mut self, code: TokenStream) -> &mut Self {
76 let code = replace(&mut self.code, code);
77
78 self.append(code)
79 }
80
81 pub fn usings<I>(&mut self, usings: I) -> &mut Self
83 where
84 I: IntoIterator,
85 I::Item: ToString,
86 {
87 for using in usings {
88 self.usings.insert(using.to_string());
89 }
90
91 self
92 }
93
94 pub fn module<T>(&self, ident: T) -> Option<&Module>
98 where
99 T: AsRef<str>,
100 {
101 self.modules.get(ident.as_ref())
102 }
103
104 pub fn module_mut<T>(&mut self, ident: T) -> &mut Module
108 where
109 T: Into<String>,
110 {
111 self.modules.entry(ident.into()).or_default()
112 }
113
114 pub fn to_code(&self, tokens: &mut TokenStream, sub_modules: SubModules) {
120 let Self {
121 code,
122 usings,
123 modules,
124 } = self;
125
126 if sub_modules == SubModules::Files {
127 let modules = modules.iter().map(|(ident, _module)| {
128 let name = format_ident!("{ident}");
129
130 quote!(pub mod #name;)
131 });
132
133 tokens.extend(quote! {
134 #( #modules )*
135 });
136 }
137
138 let usings = render_usings(usings.iter());
139
140 tokens.extend(quote! {
141 #usings
142 #code
143 });
144
145 if sub_modules == SubModules::Inline {
146 for (ident, module) in modules {
147 let name = format_ident!("{ident}");
148
149 tokens.extend(quote! {
150 pub mod #name {
151 #module
152 }
153 });
154 }
155 }
156 }
157
158 pub fn write_to_files<P>(&self, directory: P) -> Result<(), IoError>
167 where
168 P: AsRef<Path>,
169 {
170 let directory = directory.as_ref();
171
172 self.write_to_files_with(|module: &Module, path: &Path| {
173 let filename = if module.modules.is_empty() {
174 directory.join(path).with_extension("rs")
175 } else {
176 directory.join(path).join("mod.rs")
177 };
178 create_dir_all(filename.parent().unwrap())?;
179
180 let mut code = TokenStream::new();
181 module.to_code(&mut code, SubModules::Files);
182
183 let code = code.to_string();
184 write(filename, code)?;
185
186 Ok(())
187 })
188 }
189
190 pub fn write_to_files_with<F, E>(&self, mut f: F) -> Result<(), E>
199 where
200 F: FnMut(&Module, &Path) -> Result<(), E>,
201 {
202 self.write_to_files_with_impl("", &mut f)
203 }
204
205 fn write_to_files_with_impl<P, F, E>(&self, path: P, f: &mut F) -> Result<(), E>
206 where
207 P: AsRef<Path>,
208 F: FnMut(&Module, &Path) -> Result<(), E>,
209 {
210 let path = path.as_ref();
211
212 f(self, path)?;
213
214 for (ident, module) in &self.modules {
215 let path = path.join(ident);
216
217 module.write_to_files_with_impl(path, f)?;
218 }
219
220 Ok(())
221 }
222}
223
224impl ToTokens for Module {
225 fn to_tokens(&self, tokens: &mut TokenStream) {
226 self.to_code(tokens, SubModules::Inline);
227 }
228}
229
230fn render_usings<I>(usings: I) -> TokenStream
231where
232 I: IntoIterator,
233 I::Item: AsRef<str>,
234{
235 #[derive(Default)]
236 struct Module {
237 usings: BTreeSet<Ident2>,
238 sub_modules: BTreeMap<Ident2, Module>,
239 }
240
241 impl Module {
242 fn render(&self) -> TokenStream {
243 let count = self.usings.len() + self.sub_modules.len();
244
245 let usings = self.usings.iter().map(|ident| quote!(#ident));
246 let sub_modules = self.sub_modules.iter().map(|(ident, module)| {
247 let using = module.render();
248
249 quote!(#ident::#using)
250 });
251
252 let items = usings.chain(sub_modules);
253
254 if count > 1 {
255 quote!({ #( #items ),* })
256 } else {
257 quote!(#( #items )*)
258 }
259 }
260 }
261
262 let mut root = Module::default();
263
264 for using in usings {
265 let using = using.as_ref();
266 let Ok(ident) = IdentPath::from_str(using) else {
267 continue;
268 };
269
270 let (ident, path) = ident.into_parts();
271
272 let mut module = &mut root;
273 for part in path.into_iter().flat_map(|x| x.0) {
274 module = module.sub_modules.entry(part).or_default();
275 }
276
277 module.usings.insert(ident);
278 }
279
280 let mut ret = TokenStream::new();
281 for (ident, module) in &root.sub_modules {
282 let using = module.render();
283 ret.extend(quote!(use #ident::#using;));
284 }
285
286 ret
287}