cargo_wiki/generators/
module_gen.rs

1use crate::gen_path;
2use crate::generators::struct_gen::StructGenerator;
3use crate::generators::{ExternalCrates, Generator, Index, Paths};
4use anyhow::Result;
5use rustdoc_types::{Item, ItemEnum, Module};
6use std::fs;
7
8pub const MODULE_FILE_NAME: &str = "README";
9
10#[derive(Default)]
11pub struct ModuleItems<'a> {
12    pub modules: Vec<ModuleField<'a>>,
13    pub traits: Vec<ModuleField<'a>>,
14    pub functions: Vec<ModuleField<'a>>,
15    pub macros: Vec<ModuleField<'a>>,
16    pub re_exports: Vec<ModuleField<'a>>,
17    pub structs: Vec<ModuleField<'a>>,
18    pub enums: Vec<ModuleField<'a>>,
19    pub consts: Vec<ModuleField<'a>>,
20}
21
22pub struct ModuleField<'a> {
23    pub name: &'a str,
24    pub link: String,
25    pub description: &'a str,
26}
27
28impl<'a> ModuleField<'a> {
29    pub fn to_string(&self) -> String {
30        let mut field_string = String::new();
31
32        field_string.push_str("- [`");
33        field_string.push_str(self.name);
34        field_string.push_str("`](");
35        field_string.push_str(&self.link);
36        field_string.push_str(")\n");
37
38        if !self.description.is_empty() {
39            field_string.push_str("\n\t");
40            field_string.push_str(self.description);
41            field_string.push_str("\n");
42        }
43
44        field_string
45    }
46}
47
48pub struct ModuleGenerator<'a> {
49    pub prefix_path: String,
50    pub module_file_name: &'a str,
51    pub root_item: &'a Item,
52    pub index: &'a Index,
53    pub paths: &'a Paths,
54    pub external_crate: &'a ExternalCrates,
55}
56
57impl<'a> ModuleGenerator<'a> {
58    pub fn new(
59        prefix_path: String,
60        module_file_name: &'a str,
61        root_item: &'a Item,
62        index: &'a Index,
63        paths: &'a Paths,
64        external_crate: &'a ExternalCrates,
65    ) -> Self {
66        Self {
67            prefix_path,
68            module_file_name,
69            root_item,
70            index,
71            paths,
72            external_crate,
73        }
74    }
75
76    pub fn auto(self) -> Result<()> {
77        let Some(module_name) = &self.root_item.name else {
78            return Err(anyhow::Error::msg(format!(
79                "Every module should have a name. Id: {}",
80                self.root_item.id.0
81            )));
82        };
83        let ItemEnum::Module(Module {
84            items, is_stripped, ..
85        }) = &self.root_item.inner
86        else {
87            return Err(anyhow::Error::msg(
88                "The Root module can't have inner item type anything other than Module. If \
89                you think this is an error, please open the issue at \
90                https://github.com/as1100k/cargo-wiki/issues with appropriate logs.",
91            ));
92        };
93        let mut path = format!("{}/{}", self.prefix_path, module_name);
94        let mut module_file_content = format!("# {}\n\n", module_name);
95        let mut module_information = ModuleItems::default();
96
97        gen_path(&path)?;
98
99        for item in items {
100            let Some(item) = self.index.get(item) else {
101                eprintln!("Failed to find item with id: {} in index", item.0);
102                continue;
103            };
104            let Some(item_name) = &item.name else {
105                // Ignoring the items that don't have name as these items are later fetched
106                // by there Id's where they are required
107                continue;
108            };
109
110            let item_description = match &item.docs {
111                Some(doc) => &doc[..doc.len().min(50)],
112                None => ""
113            };
114
115            let mut path = format!("{}", path);
116            let mut file_content = format!("# {}\n\n", item_name);
117
118            match &item.inner {
119                ItemEnum::Module(_) => {
120                    module_information.modules.push(ModuleField {
121                        name: item_name,
122                        link: format!("./{}/{}.md", item_name, self.module_file_name),
123                        description: &item_description,
124                    });
125
126                    let new_module_generator = Self::new(
127                        path,
128                        &self.module_file_name,
129                        item,
130                        self.index,
131                        self.paths,
132                        self.external_crate,
133                    );
134                    new_module_generator.auto()?;
135                    // Move to the next item as module will document itself separately
136                    continue;
137                }
138                ItemEnum::Struct(_) => {
139                    module_information.structs.push(ModuleField {
140                        name: item_name,
141                        link: format!("./struct.{}.md", item_name),
142                        description: &item_description,
143                    });
144
145                    path.push_str("/struct.");
146                    path.push_str(item_name);
147
148                    let syntax = StructGenerator::generate_syntax(
149                        item,
150                        self.index,
151                        self.paths,
152                        self.external_crate,
153                    )?;
154                    file_content.push_str(&syntax);
155                }
156                _ => continue,
157            }
158
159            path.push_str(".md");
160            fs::write(path, file_content)
161                .expect("TODO: panic message at `src/generators/module_gen.rs`");
162        }
163
164        path.push_str("/");
165        path.push_str(self.module_file_name);
166        path.push_str(".md");
167
168        module_file_content.push_str(&Self::generate_module_docs(&module_information));
169        fs::write(path, module_file_content).expect(
170            "TODO: panic message at `src/generators/module_gen.rs` while saving module file",
171        );
172
173        Ok(())
174    }
175
176    pub fn generate_module_docs(module_information: &ModuleItems) -> String {
177        let mut module_information_string = String::new();
178
179        // Modules
180        if module_information.modules.len() > 0 {
181            module_information_string.push_str("\n## Modules\n\n");
182            for field in &module_information.modules {
183                module_information_string.push_str(&field.to_string());
184            }
185        }
186
187        // Constants
188        if module_information.consts.len() > 0 {
189            module_information_string.push_str("## Constants\n\n");
190            for field in &module_information.consts {
191                module_information_string.push_str(&field.to_string());
192            }
193        }
194
195        // Structs
196        if module_information.structs.len() > 0 {
197            module_information_string.push_str("## Structs\n\n");
198            for field in &module_information.structs {
199                module_information_string.push_str(&field.to_string());
200            }
201        }
202
203        // Enums
204        if module_information.enums.len() > 0 {
205            module_information_string.push_str("## Enums\n\n");
206            for field in &module_information.enums {
207                module_information_string.push_str(&field.to_string());
208            }
209        }
210
211        // Traits
212        if module_information.traits.len() > 0 {
213            module_information_string.push_str("## Traits\n\n");
214            for field in &module_information.traits {
215                module_information_string.push_str(&field.to_string());
216            }
217        }
218
219        // Functions
220        if module_information.functions.len() > 0 {
221            module_information_string.push_str("## Functions\n\n");
222            for field in &module_information.functions {
223                module_information_string.push_str(&field.to_string());
224            }
225        }
226
227        // Macros
228        if module_information.macros.len() > 0 {
229            module_information_string.push_str("## Macros\n\n");
230            for field in &module_information.macros {
231                module_information_string.push_str(&field.to_string());
232            }
233        }
234
235        // Re-exports
236        if module_information.re_exports.len() > 0 {
237            module_information_string.push_str("## Re-exports\n\n");
238            for field in &module_information.re_exports {
239                module_information_string.push_str(&field.to_string());
240            }
241        }
242
243        module_information_string
244    }
245}