gdext_gen/gdext/
icons.rs

1//! Module for the generation of the icons section of the `.gdextension` file.
2
3use std::{
4    fs::File,
5    io::{Result, Write},
6};
7
8use toml::Table;
9
10use super::GDExtension;
11use crate::{args::icons::IconsConfig, NODES_RUST, NODES_RUST_FILENAMES};
12
13#[cfg(feature = "find_icons")]
14use crate::args::icons::DefaultNodeIcon;
15#[cfg(feature = "find_icons")]
16use glob::glob;
17#[cfg(feature = "find_icons")]
18use regex::{Match, Regex};
19#[cfg(feature = "find_icons")]
20use std::{
21    collections::HashMap,
22    io::{BufRead, BufReader},
23};
24
25/*
26const base_checkers: [&str; 2] = ["base", "="];
27const struct_checker: &str = "struct";
28
29enum FirstCheck {
30    Base,
31    Equal,
32    Struct,
33    None,
34}
35
36enum CurrentTraversal {
37    InComment,
38    FindBase,
39    FindEqual,
40    FindIcon,
41    FindStruct,
42    FindName,
43}
44*/
45
46impl GDExtension {
47    /// Generates the icons section of the [`GDExtension`].
48    ///
49    /// # Parameters
50    ///
51    /// * `icon_config` - Configuration struct for the generation of icons. If `relative_directory` of the [`IconsDirectories`](crate::args::IconsDirectories) is [`None`] it will use the default value.
52    ///
53    /// # Returns
54    ///
55    ///
56    /// * [`Ok`] (&mut [`GDExtension`]) - If there has been no problem infering the nodes and their corresponding icons nor copying them, the same [`GDExtension`] mutable reference it was passed to it.
57    /// * [`Err`] ([`Error`](std::io::Error)) - If there was a problem reading the `src` files, or copying the icons to their corresponding folder.
58    pub fn generate_icons(&mut self, icons_config: IconsConfig) -> Result<&mut Self> {
59        let mut icons = Table::new();
60
61        #[cfg(feature = "find_icons")]
62        if icons_config.default != DefaultNodeIcon::Node {
63            let mut base_class_to_nodes = HashMap::<String, Vec<String>>::new();
64
65            find_children(&mut base_class_to_nodes)?;
66
67            for (icon, nodes) in base_class_to_nodes {
68                for node in nodes {
69                    icons.insert(
70                        node,
71                        match icons_config.default {
72                            DefaultNodeIcon::BaseClass => format!(
73                                "{}{}.svg",
74                                &icons_config
75                                    .directories
76                                    .relative_directory
77                                    .unwrap_or_default()
78                                    .as_str(),
79                                (&icons_config.directories.base_directory)
80                                    .join(&icons_config.directories.editor_directory)
81                                    .join(&icon)
82                                    .to_string_lossy()
83                                    .replace('\\', "/")
84                            )
85                            .into(),
86                            DefaultNodeIcon::Custom(ref custom_path) => format!(
87                                "{}{}",
88                                &icons_config
89                                    .directories
90                                    .relative_directory
91                                    .unwrap_or_default()
92                                    .as_str(),
93                                (&icons_config.directories.base_directory)
94                                    .join(&custom_path)
95                                    .to_string_lossy()
96                                    .replace('\\', "/")
97                            )
98                            .into(),
99                            DefaultNodeIcon::NodeRust(node_rust, ref rust_path) => format!(
100                                "{}{}/{}",
101                                &icons_config
102                                    .directories
103                                    .relative_directory
104                                    .unwrap_or_default()
105                                    .as_str(),
106                                (&icons_config.directories.base_directory)
107                                    .join(&rust_path)
108                                    .to_string_lossy()
109                                    .replace('\\', "/"),
110                                NODES_RUST_FILENAMES[node_rust as usize],
111                            )
112                            .into(),
113                            DefaultNodeIcon::Node => "ERROR".into(),
114                        },
115                    );
116                }
117            }
118        }
119
120        if let Some(custom_icons) = &icons_config.custom_icons {
121            for (node, icon) in custom_icons {
122                icons.insert(
123                    node.clone(),
124                    format!(
125                        "{}{}",
126                        &icons_config
127                            .directories
128                            .relative_directory
129                            .unwrap_or_default()
130                            .as_str(),
131                        (&icons_config.directories.base_directory)
132                            .join(&icons_config.directories.custom_directory)
133                            .join(icon)
134                            .to_string_lossy()
135                            .replace('\\', "/")
136                    )
137                    .into(),
138                );
139            }
140        }
141
142        #[allow(unused_mut)]
143        let mut copy_files = icons_config.copy_strategy.copy_all;
144        #[cfg(feature = "find_icons")]
145        {
146            copy_files |= icons_config.copy_strategy.copy_node_rust;
147        }
148
149        if copy_files {
150            let base_directory_path = icons_config.copy_strategy.path_node_rust;
151            let mut nodes_rust = Vec::new();
152
153            if icons_config.copy_strategy.copy_all {
154                nodes_rust.extend(NODES_RUST_FILENAMES.into_iter().zip(NODES_RUST));
155            } else {
156                #[cfg(feature = "find_icons")]
157                if icons_config.copy_strategy.copy_node_rust {
158                    if let DefaultNodeIcon::NodeRust(node_rust, _) = icons_config.default {
159                        nodes_rust.push((
160                            NODES_RUST_FILENAMES[node_rust as usize],
161                            NODES_RUST[node_rust as usize],
162                        ));
163                    }
164                }
165            }
166
167            for (file_name, node_rust) in nodes_rust {
168                let path_node_rust = (&base_directory_path).join(file_name);
169                if icons_config.copy_strategy.force_copy | !path_node_rust.exists() {
170                    File::create(path_node_rust)?.write_all(node_rust.as_bytes())?;
171                }
172            }
173        }
174
175        self.icons = Some(icons);
176
177        Ok(self)
178    }
179}
180
181/// Finds the structs that have inherited each base class, updating the base_class_to_nodes HashMap.
182///
183/// # Parameters
184///
185/// `base_class_to_nodes` - [`HashMap`] to fill with relationships `base_class: [struct1, ..., structn]`, of the structs that have inherited the base_class.
186///
187/// # Returns
188///
189/// * [`Ok`] - If the `base_class_to_nodes` [`HashMap`] could be filled.
190/// * [`Err`] - Otherwise.
191#[cfg(feature = "find_icons")]
192fn find_children(base_class_to_nodes: &mut HashMap<String, Vec<String>>) -> Result<()> {
193    // Only works if base = BaseClass contains no comments in between.
194    let base_class_regex = if cfg!(feature = "complex_find") {
195        Regex::new(r"base(\s|(\/\/.*?[\r\n])|(\/\*.*?\*\/))*\=(\s|(\/\/.*?[\r\n])|(\/\*.*?\*\/))*[\w_\d]+(\s|(\/\/.*?[\r\n])|(\/\*.*?\*\/))*[),]")
196    } else {
197        Regex::new(r"base\s*\=\s*[\w_\d]+\s*[),]")
198    }.expect("Invalid regex pattern.");
199    // Only works if struct StructName contains no comments in between.
200    let struct_regex = if cfg!(feature = "complex_find") {
201        Regex::new(r"struct(\s|(\/\/.*?[\r\n])|(\/\*.*?\*\/))*[\w_\d]+(\s|(\/\/.*?[\r\n])|(\/\*.*?\*\/))*[{;<]")
202    } else {
203        Regex::new(r"struct\s*[\w_\d]+\s*[{;<]")
204    }.expect("Invalid regex pattern.");
205
206    let mut base_class = String::new();
207    let mut struct_class;
208    let mut found_base;
209
210    for path_glob in glob("./src/**/*.rs").unwrap() {
211        let path;
212        match path_glob {
213            Ok(pathbuf) => path = pathbuf,
214            Err(_) => continue,
215        }
216        found_base = false;
217        for line in BufReader::new(File::open(path)?).lines() {
218            let line: String = line?;
219            if !line.starts_with("///") & line.contains("base") & line.contains("=") {
220                base_class = if let Some(base_class_match) = base_class_regex.find(&line) {
221                    Match::as_str(&base_class_match)
222                        .replace("base", "")
223                        .replace('=', "")
224                } else {
225                    continue;
226                };
227                // Eliminate the , or ).
228                base_class.pop();
229                base_class = base_class.trim().to_owned();
230                if !base_class_to_nodes.contains_key(&base_class) {
231                    base_class_to_nodes.insert(base_class.clone(), Vec::new());
232                }
233                found_base = true;
234            } else if found_base & !line.starts_with("///") & line.contains("struct") {
235                struct_class = if let Some(struct_class_match) = struct_regex.find(&line) {
236                    Match::as_str(&struct_class_match).replace("struct", "")
237                } else {
238                    continue;
239                };
240                // Eliminate the ;, { or <.
241                struct_class.pop();
242                struct_class = struct_class.trim().to_owned();
243                base_class_to_nodes
244                    .get_mut(&base_class)
245                    .expect("The map doesn't contain the key that was just pushed to it.")
246                    .push(struct_class);
247                found_base = false;
248            }
249        }
250    }
251
252    Ok(())
253}