xsd_parser/models/code/
module.rs

1use 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/// Manages the code that is generated for a specific module.
14///
15/// This type stores the generated code, the related using directives as well as
16/// any sub-module for a specific module generated by the code generator.
17#[derive(Default, Debug)]
18pub struct Module {
19    /// The actual code that is manages by this module.
20    pub code: TokenStream,
21
22    /// A set of using directives this module needs.
23    pub usings: BTreeSet<String>,
24
25    /// A map of sub-modules contained inside this module.
26    pub modules: BTreeMap<String, Module>,
27}
28
29/// Defines how the sub modules of the [`Module`] will be generated when calling
30/// [`Module::to_code()`].
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum SubModules {
33    /// No code is generated for any sub-module.
34    None,
35
36    /// The sub-modules are references as separate files.
37    ///
38    /// This will generate something like this:
39    ///
40    /// ```rust,ignore
41    /// pub mod sub_module;
42    /// pub mod another_module;
43    ///
44    /// // Actual module code
45    /// ```
46    Files,
47
48    /// The sub-modules are generated as inline modules.
49    ///
50    /// This will generate something like this:
51    ///
52    /// ```rust,ignore
53    /// // Actual module code
54    ///
55    /// pub mod sub_module {
56    ///     // Sub-Module Code
57    /// }
58    ///
59    /// pub mod another_module {
60    ///     // Sub-Module Code
61    /// }
62    /// ```
63    Inline,
64}
65
66impl Module {
67    /// Append the passed `code` to this module code.
68    pub fn append(&mut self, code: TokenStream) -> &mut Self {
69        self.code.extend(code);
70
71        self
72    }
73
74    /// Prepend the passed `code` to this module code.
75    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    /// Add using directives to the set of this module.
82    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    /// Get a reference to a sub-module identified by the passed `ident`.
95    ///
96    /// If the module does not exist `None` is returned.
97    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    /// Get a mutable reference to a sub-module identified by the passed `ident`.
105    ///
106    /// If the module does not exist it will be created.
107    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    /// Write the module to the passed `token` stream.
115    ///
116    /// This method writes the current module to the passed `token` stream. The
117    /// `sub_modules` parameter defines how the sub-modules of this module will
118    /// be rendered.
119    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    /// Write the code of the current module to the passed `directory`.
159    ///
160    /// This will split up the current module into it's sub modules and
161    /// writes them to the passed `directory`. Each sub-module is written to
162    /// it's own module file.
163    ///
164    /// # Errors
165    /// Returns an [`IoError`] if writing a module to a file failed.
166    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    /// Write the code of the different sub-modules by using the passed
191    /// function `f`.
192    ///
193    /// This will split up the current module into it's sub modules and
194    /// passes the generated code for each module to the passed function `f`.
195    ///
196    /// # Errors
197    /// Forwards the error raised by `f`.
198    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}