gdnative_doc/backend/
resolve.rs

1//! Facilities related to link resolution.
2
3use crate::{
4    config::ConfigFile,
5    documentation::{self, Documentation, Type},
6    GodotVersion,
7};
8use pulldown_cmark::{CowStr, Event, Tag};
9use std::collections::HashMap;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12/// Information to resolve links.
13pub struct Resolver {
14    /// Link to godot items' documentation.
15    ///
16    /// Contains the link to godot classes, but also `true`, `INF`, `Err`...
17    pub godot_items: HashMap<String, String>,
18    /// Mapping from Rust to Godot types.
19    pub rust_to_godot: HashMap<String, String>,
20    /// User-defined overrides.
21    ///
22    /// These are defined in the [toml configuration file](crate::ConfigFile).
23    pub url_overrides: HashMap<String, String>,
24    /// User-defined Rust to Godot mapping.
25    ///
26    /// These are defined in the [toml configuration file](crate::ConfigFile).
27    pub rename_classes: HashMap<String, String>,
28}
29
30/// Url for the (stable) godot documentation
31const GODOT_DOCUMENTATION_URL_3_2: &str = "https://docs.godotengine.org/en/3.2/classes";
32const GODOT_DOCUMENTATION_URL_3_3: &str = "https://docs.godotengine.org/en/3.3/classes";
33const GODOT_DOCUMENTATION_URL_3_4: &str = "https://docs.godotengine.org/en/3.4/classes";
34const GODOT_DOCUMENTATION_URL_3_5: &str = "https://docs.godotengine.org/en/3.5/classes";
35
36/// List of godot 3.2 classes, like `Array`, `int`, `Transform2D`...
37const GODOT_CLASSES_3_2: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.2.txt");
38/// List of godot 3.3 classes, like `Array`, `int`, `Transform2D`...
39const GODOT_CLASSES_3_3: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.3.txt");
40/// List of godot 3.4 classes, like `Array`, `int`, `Transform2D`...
41const GODOT_CLASSES_3_4: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.4.txt");
42/// List of godot 3.5 classes, like `Array`, `int`, `Transform2D`...
43const GODOT_CLASSES_3_5: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.5.txt");
44
45/// List of some godot constants and information about where they sould link to.
46///
47/// link for `<constant.0>`: `<godot_doc_url>/<constant.1>.html#<constant.2>`
48const GODOT_CONSTANTS: &[(&str, &str, &str)] = &[
49    ("true", "class_bool", ""),
50    ("false", "class_bool", ""),
51    ("PI", "class_@gdscript", "constants"),
52    ("TAU", "class_@gdscript", "constants"),
53    ("INF", "class_@gdscript", "constants"),
54    ("NAN", "class_@gdscript", "constants"),
55    ("FAILED", "class_@globalscope", "enum-globalscope-error"),
56    ("OK", "class_@globalscope", "enum-globalscope-error"),
57];
58
59/// Mapping from Rust to Godot types.
60const RUST_TO_GODOT: &[(&str, &str)] = &[
61    ("i32", "int"),
62    ("i64", "int"),
63    ("f32", "float"),
64    ("f64", "float"),
65    ("GodotString", "String"),
66    ("VariantArray", "Array"),
67    ("Int32Array", "PoolIntArray"),
68    ("Float32Array", "PoolRealArray"),
69];
70
71impl Resolver {
72    pub(crate) fn new(godot_version: GodotVersion) -> Self {
73        Self {
74            godot_items: Self::godot_items(godot_version),
75            rust_to_godot: Self::rust_to_godot(),
76            url_overrides: HashMap::new(),
77            rename_classes: HashMap::new(),
78        }
79    }
80
81    fn godot_items(godot_version: GodotVersion) -> HashMap<String, String> {
82        let mut godot_items = HashMap::new();
83        let classes = match godot_version {
84            GodotVersion::Version32 => GODOT_CLASSES_3_2,
85            GodotVersion::Version33 => GODOT_CLASSES_3_3,
86            GodotVersion::Version34 => GODOT_CLASSES_3_4,
87            GodotVersion::Version35 => GODOT_CLASSES_3_5,
88        };
89        let documentation_url = match godot_version {
90            GodotVersion::Version32 => GODOT_DOCUMENTATION_URL_3_2,
91            GodotVersion::Version33 => GODOT_DOCUMENTATION_URL_3_3,
92            GodotVersion::Version34 => GODOT_DOCUMENTATION_URL_3_4,
93            GodotVersion::Version35 => GODOT_DOCUMENTATION_URL_3_5,
94        };
95        for class in classes {
96            godot_items.insert(
97                class.to_string(),
98                format!("{}/class_{}.html", documentation_url, class.to_lowercase()),
99            );
100        }
101
102        for (name, links_to, section) in GODOT_CONSTANTS {
103            let mut link = format!("{}/{}.html", documentation_url, links_to);
104            if !section.is_empty() {
105                link.push('#');
106                link.push_str(section)
107            }
108            godot_items.insert(name.to_string(), link);
109        }
110        godot_items
111    }
112
113    fn rust_to_godot() -> HashMap<String, String> {
114        let mut rust_to_godot = HashMap::new();
115        for (rust, godot) in RUST_TO_GODOT {
116            rust_to_godot.insert(rust.to_string(), godot.to_string());
117        }
118        rust_to_godot
119    }
120
121    pub(crate) fn apply_user_config(&mut self, user_config: &ConfigFile) {
122        self.url_overrides = user_config.url_overrides.clone().unwrap_or_default();
123        self.rename_classes = user_config.rename_classes.clone().unwrap_or_default();
124    }
125
126    /// Convert all type names from Rust to Godot.
127    ///
128    /// This will convert `i32` to `int`, `Int32Array` to `PoolIntArray`...
129    ///
130    /// See [`ConfigFile::rename_classes`] for user-defined renaming.
131    pub(crate) fn rename_classes(&self, documentation: &mut Documentation) {
132        let replace = |name: &mut String| {
133            if let Some(rename) = self.rename_classes.get(name) {
134                *name = rename.clone();
135            } else if let Some(rename) = self.rust_to_godot.get(name) {
136                *name = rename.clone();
137            }
138        };
139
140        let mut renamed_classes = HashMap::new();
141        let classes = std::mem::take(&mut documentation.classes);
142        for (mut name, mut class) in classes {
143            for method in &mut class.methods {
144                for (_, typ, _) in &mut method.parameters {
145                    match typ {
146                        documentation::Type::Option(name) | documentation::Type::Named(name) => {
147                            replace(name)
148                        }
149                        documentation::Type::Unit => {}
150                    }
151                }
152                match &mut method.return_type {
153                    documentation::Type::Option(name) | documentation::Type::Named(name) => {
154                        replace(name)
155                    }
156                    documentation::Type::Unit => {}
157                }
158            }
159            for property in &mut class.properties {
160                match &mut property.typ {
161                    Type::Option(name) | Type::Named(name) => replace(name),
162                    Type::Unit => {}
163                }
164            }
165            replace(&mut name);
166            replace(&mut class.inherit);
167            renamed_classes.insert(name, class);
168        }
169        documentation.classes = renamed_classes;
170    }
171
172    /// Resolve a name to the location it must link to.
173    ///
174    /// `link` must already have been stripped off the enclosing \`.
175    pub fn resolve(&self, link: &str) -> Option<&str> {
176        if let Some(link) = self.url_overrides.get(link) {
177            return Some(link);
178        }
179        let temporary;
180        let base = if let Ok(link) = syn::parse_str::<syn::Path>(link) {
181            match link.segments.last() {
182                None => return None,
183                Some(base) => {
184                    temporary = base.ident.to_string();
185                    &temporary
186                }
187            }
188        } else {
189            link
190        };
191
192        if let Some(path) = self.url_overrides.get(base) {
193            Some(path)
194        } else {
195            let base = match self.rust_to_godot.get(base) {
196                Some(base) => base.as_str(),
197                None => base,
198            };
199            if let Some(path) = self.godot_items.get(base) {
200                Some(path)
201            } else {
202                None
203            }
204        }
205    }
206
207    /// Increase the header count, and resolve link destinations
208    pub(super) fn resolve_event(&self, event: &mut Event) {
209        use pulldown_cmark::HeadingLevel;
210        fn increase_heading_level(level: HeadingLevel) -> HeadingLevel {
211            match level {
212                HeadingLevel::H1 => HeadingLevel::H4,
213                HeadingLevel::H2 => HeadingLevel::H5,
214                HeadingLevel::H3 | HeadingLevel::H4 | HeadingLevel::H5 | HeadingLevel::H6 => {
215                    HeadingLevel::H6
216                }
217            }
218        }
219        match event {
220            Event::Start(Tag::Link(_, dest, _)) | Event::End(Tag::Link(_, dest, _)) => {
221                if let Some(new_dest) = self.resolve(dest) {
222                    *dest = new_dest.to_string().into()
223                }
224            }
225            Event::Start(Tag::Heading(n, _, _)) | Event::End(Tag::Heading(n, _, _)) => {
226                *n = increase_heading_level(*n);
227            }
228            _ => {}
229        }
230    }
231
232    pub(super) fn encode_type<'b>(&'b self, typ: &'b Type) -> Vec<Event<'b>> {
233        let (type_name, optional) = match typ {
234            Type::Option(typ) => (typ.as_str(), true),
235            Type::Named(typ) => (typ.as_str(), false),
236            Type::Unit => ("void", false),
237        };
238        let mut events = match self.resolve(type_name).map(|return_link| {
239            Tag::Link(
240                pulldown_cmark::LinkType::Shortcut,
241                CowStr::Borrowed(return_link),
242                CowStr::Borrowed(""),
243            )
244        }) {
245            Some(link) => {
246                vec![
247                    Event::Start(link.clone()),
248                    Event::Text(CowStr::Borrowed(type_name)),
249                    Event::End(link),
250                ]
251            }
252            None => {
253                vec![Event::Text(CowStr::Borrowed(type_name))]
254            }
255        };
256        if optional {
257            events.push(Event::Text(CowStr::Borrowed(" (opt)")))
258        }
259        events
260    }
261}