1use 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)]
12pub struct Resolver {
14 pub godot_items: HashMap<String, String>,
18 pub rust_to_godot: HashMap<String, String>,
20 pub url_overrides: HashMap<String, String>,
24 pub rename_classes: HashMap<String, String>,
28}
29
30const 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
36const GODOT_CLASSES_3_2: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.2.txt");
38const GODOT_CLASSES_3_3: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.3.txt");
40const GODOT_CLASSES_3_4: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.4.txt");
42const GODOT_CLASSES_3_5: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.5.txt");
44
45const 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
59const 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 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 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 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}