use crate::{
config::ConfigFile,
documentation::{self, Documentation, Type},
};
use pulldown_cmark::{CowStr, Event, Tag};
use std::collections::HashMap;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Resolver {
pub godot_items: HashMap<String, String>,
pub rust_to_godot: HashMap<String, String>,
pub url_overrides: HashMap<String, String>,
pub rename_classes: HashMap<String, String>,
}
const GODOT_DOCUMENTATION_URL: &str = "https://docs.godotengine.org/en/stable/classes";
const GODOT_CLASSES: &[&str] = &include!("../../fetch_godot_classes/godot_classes-3.2.txt");
const GODOT_CONSTANTS: &[(&str, &str, &str)] = &[
("true", "class_bool", ""),
("false", "class_bool", ""),
("PI", "class_@gdscript", "constants"),
("TAU", "class_@gdscript", "constants"),
("INF", "class_@gdscript", "constants"),
("NAN", "class_@gdscript", "constants"),
("FAILED", "class_@globalscope", "enum-globalscope-error"),
("OK", "class_@globalscope", "enum-globalscope-error"),
];
const RUST_TO_GODOT: &[(&str, &str)] = &[
("i32", "int"),
("i64", "int"),
("f32", "float"),
("f64", "float"),
("GodotString", "String"),
("VariantArray", "Array"),
("Int32Array", "PoolIntArray"),
("Float32Array", "PoolRealArray"),
];
impl Default for Resolver {
fn default() -> Self {
Self {
godot_items: Self::godot_items(),
rust_to_godot: Self::rust_to_godot(),
url_overrides: HashMap::new(),
rename_classes: HashMap::new(),
}
}
}
impl Resolver {
fn godot_items() -> HashMap<String, String> {
let mut godot_items = HashMap::new();
for class in GODOT_CLASSES {
godot_items.insert(
class.to_string(),
format!(
"{}/class_{}.html",
GODOT_DOCUMENTATION_URL,
class.to_lowercase()
),
);
}
for (name, links_to, section) in GODOT_CONSTANTS {
let mut link = format!("{}/{}.html", GODOT_DOCUMENTATION_URL, links_to);
if !section.is_empty() {
link.push('#');
link.push_str(section)
}
godot_items.insert(name.to_string(), link);
}
godot_items
}
fn rust_to_godot() -> HashMap<String, String> {
let mut rust_to_godot = HashMap::new();
for (rust, godot) in RUST_TO_GODOT {
rust_to_godot.insert(rust.to_string(), godot.to_string());
}
rust_to_godot
}
pub(crate) fn apply_user_config(&mut self, user_config: ConfigFile) {
self.url_overrides = user_config.url_overrides.unwrap_or_default();
self.rename_classes = user_config.rename_classes.unwrap_or_default();
}
pub(crate) fn rename_classes(&self, documentation: &mut Documentation) {
let replace = |name: &mut String| {
if let Some(rename) = self.rename_classes.get(name) {
*name = rename.clone();
} else if let Some(rename) = self.rust_to_godot.get(name) {
*name = rename.clone();
}
};
let mut renamed_classes = HashMap::new();
let classes = std::mem::take(&mut documentation.classes);
for (mut name, mut class) in classes {
for method in &mut class.methods {
for (_, typ, _) in &mut method.parameters {
match typ {
documentation::Type::Option(name) | documentation::Type::Named(name) => {
replace(name)
}
documentation::Type::Unit => {}
}
}
match &mut method.return_type {
documentation::Type::Option(name) | documentation::Type::Named(name) => {
replace(name)
}
documentation::Type::Unit => {}
}
}
for property in &mut class.properties {
match &mut property.typ {
Type::Option(name) | Type::Named(name) => replace(name),
Type::Unit => {}
}
}
replace(&mut name);
replace(&mut class.inherit);
renamed_classes.insert(name, class);
}
documentation.classes = renamed_classes;
}
pub(super) fn resolve(&self, link: &str) -> Option<&str> {
if let Some(link) = self.url_overrides.get(link) {
return Some(link);
}
let temporary;
let base = if let Ok(link) = syn::parse_str::<syn::Path>(link) {
match link.segments.last() {
None => return None,
Some(base) => {
temporary = base.ident.to_string();
&temporary
}
}
} else {
link
};
if let Some(path) = self.url_overrides.get(base) {
Some(path)
} else {
let base = match self.rust_to_godot.get(base) {
Some(base) => base.as_str(),
None => base,
};
if let Some(path) = self.godot_items.get(base) {
Some(path)
} else {
None
}
}
}
pub(super) fn resolve_event(&self, event: &mut Event) {
match event {
Event::Start(Tag::Link(_, dest, _)) | Event::End(Tag::Link(_, dest, _)) => {
match self.resolve(&dest) {
Some(new_dest) => *dest = new_dest.to_string().into(),
None => {}
}
}
Event::Start(Tag::Heading(n)) | Event::End(Tag::Heading(n)) => *n += 3,
_ => {}
}
}
pub(super) fn encode_type<'b>(&'b self, typ: &'b Type) -> Vec<Event<'b>> {
let (type_name, optional) = match typ {
Type::Option(typ) => (typ.as_str(), true),
Type::Named(typ) => (typ.as_str(), false),
Type::Unit => ("void", false),
};
let mut events = match self.resolve(type_name).map(|return_link| {
Tag::Link(
pulldown_cmark::LinkType::Shortcut,
CowStr::Borrowed(&return_link),
CowStr::Borrowed(""),
)
}) {
Some(link) => {
vec![
Event::Start(link.clone()),
Event::Text(CowStr::Borrowed(type_name)),
Event::End(link),
]
}
None => {
vec![Event::Text(CowStr::Borrowed(type_name))]
}
};
if optional {
events.push(Event::Text(CowStr::Borrowed(" (opt)")))
}
events
}
}