1use super::classes::generate_enum_name;
2use miniserde::Deserialize;
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5use std::collections::{HashMap, HashSet};
6
7miniserde::make_place!(Place);
8
9pub struct Api {
10 pub classes: Vec<GodotClass>,
11 pub api_underscore: HashSet<String>,
12}
13
14impl Api {
15 pub fn new(data: &str) -> Self {
25 let mut api = Self {
26 classes: miniserde::json::from_str(data).expect("Invalid JSON data"),
27 api_underscore: Default::default(),
28 };
29
30 api.strip_leading_underscores();
31 api.generate_module_names();
32
33 api.classes
34 .iter_mut()
35 .flat_map(|class| class.enums.iter_mut())
36 .for_each(|e| e.strip_common_prefix());
37
38 api
39 }
40
41 pub fn find_class(&self, name: &str) -> Option<&GodotClass> {
42 self.classes.iter().find(|&class| class.name == name)
43 }
44
45 pub fn class_inherits(&self, class: &GodotClass, base_class_name: &str) -> bool {
46 if class.base_class == base_class_name {
47 return true;
48 }
49
50 if let Some(parent) = self.find_class(&class.base_class) {
51 return self.class_inherits(parent, base_class_name);
52 }
53
54 false
55 }
56
57 fn strip_leading_underscores(&mut self) {
58 for class in &mut self.classes {
59 if class.name.starts_with('_') {
60 class.name = class.name[1..].to_string();
61 self.api_underscore.insert(class.name.clone());
62 }
63 for method in &mut class.methods {
64 if method.return_type.starts_with('_') {
65 method.return_type = method.return_type[1..].to_string();
66 }
67 for arg in &mut method.arguments {
68 if arg.ty.starts_with('_') {
69 arg.ty = arg.ty[1..].to_string();
70 }
71 }
72 }
73 }
74 }
75
76 fn generate_module_names(&mut self) {
77 self.classes
78 .iter_mut()
79 .for_each(|class| class.generate_module_name());
80 }
81}
82
83#[derive(Deserialize, Debug)]
84pub struct GodotClass {
85 pub name: String,
86 pub base_class: String,
87 pub api_type: String,
88 pub singleton: bool,
89 pub is_reference: bool,
90 #[serde(rename = "instanciable")]
91 pub instantiable: bool,
92
93 pub properties: Vec<Property>,
94 pub methods: Vec<GodotMethod>,
95 pub enums: Vec<Enum>,
96 pub constants: HashMap<ConstantName, ConstantValue>,
97
98 module_name: Option<String>,
99 base_class_module_name: Option<String>,
100}
101
102impl GodotClass {
103 pub fn generate_module_name(&mut self) {
104 let module_name = module_name_from_class_name(&self.name);
105 self.module_name.replace(module_name);
106
107 let base_class_module_name = module_name_from_class_name(&self.base_class);
108 self.base_class_module_name.replace(base_class_module_name);
109 }
110
111 pub fn module(&self) -> &str {
112 self.module_name
113 .as_ref()
114 .expect("Module Names should have been generated.")
115 }
116
117 pub fn base_class_module(&self) -> &str {
118 self.base_class_module_name
119 .as_ref()
120 .expect("Module Names should have been generated.")
121 }
122
123 pub fn base_class_name(&self) -> Option<&str> {
125 if self.base_class.is_empty() {
126 None
127 } else {
128 Some(&self.base_class)
129 }
130 }
131
132 pub fn is_singleton_thread_safe(&self) -> bool {
133 assert!(self.singleton, "class is not a singleton");
134 !matches!(
135 self.name.as_str(),
136 "VisualServer" | "PhysicsServer" | "Physics3DServer" | "Physics2DServer"
137 )
138 }
139
140 pub fn base_class<'a>(&self, api: &'a Api) -> Option<&'a Self> {
142 self.base_class_name()
143 .map(|name| api.find_class(name).expect("base class should exist"))
144 }
145
146 pub fn is_refcounted(&self) -> bool {
147 self.is_reference || &self.name == "Reference"
148 }
149
150 pub fn is_pointer_safe(&self) -> bool {
151 self.is_refcounted() || self.singleton
152 }
153
154 pub fn is_getter(&self, name: &str) -> bool {
155 self.properties.iter().any(|p| p.getter == name)
156 }
157
158 pub fn has_related_module(&self) -> bool {
160 !self.enums.is_empty()
161 }
162}
163
164pub type ConstantName = String;
165pub type ConstantValue = i64;
166
167#[derive(PartialEq, Eq, Deserialize, Debug)]
168pub struct Enum {
169 pub name: String,
170 pub values: HashMap<String, i64>,
171}
172
173impl core::cmp::Ord for Enum {
174 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
175 core::cmp::Ord::cmp(&self.name, &other.name)
176 }
177}
178
179impl core::cmp::PartialOrd for Enum {
180 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
181 core::cmp::PartialOrd::partial_cmp(&self.name, &other.name)
182 }
183}
184
185impl Enum {
186 pub fn strip_common_prefix(&mut self) {
187 if self.values.len() <= 1 {
189 return;
190 }
191
192 let mut variants: Vec<_> = self.values.iter().map(|v| (&v.0[..], *v.1)).collect();
193
194 loop {
196 let underscore_index = variants[0].0.chars().enumerate().find_map(|(index, ch)| {
197 if ch == '_' {
198 Some(index)
199 } else {
200 None
201 }
202 });
203
204 let underscore_index = match underscore_index {
205 Some(index) if index > 0 => index,
206 Some(_) | None => break,
207 };
208
209 let prefix = &variants[0].0[..=underscore_index];
211
212 if !variants.iter().all(|v| v.0.starts_with(prefix)) {
213 break;
214 }
215
216 variants.iter_mut().for_each(|(ref mut name, _)| {
218 *name = &name[prefix.len()..];
219 });
220 }
221
222 let new_variants: HashMap<_, _> = variants
223 .into_iter()
224 .map(|(name, value)| {
225 let starts_with_number = name
226 .chars()
227 .next()
228 .expect("name should not be empty")
229 .is_numeric();
230
231 let capacity = if starts_with_number {
232 name.len() + 1
233 } else {
234 name.len()
235 };
236
237 let mut n = String::with_capacity(capacity);
238
239 if starts_with_number {
241 n.push('_');
242 }
243
244 n.push_str(name);
245 (n, value)
246 })
247 .collect();
248
249 self.values = new_variants;
250 }
251}
252
253#[derive(Deserialize, Debug)]
254pub struct Property {
255 pub name: String,
256 #[serde(rename = "type")]
257 pub type_: String,
258 pub getter: String,
259 pub setter: String,
260 pub index: i64,
261}
262
263#[derive(Deserialize, Debug)]
264pub struct GodotMethod {
265 pub name: String,
266 pub return_type: String,
267
268 pub is_editor: bool,
269 pub is_noscript: bool,
270 pub is_const: bool,
271 pub is_reverse: bool,
272 pub is_virtual: bool,
273 pub has_varargs: bool,
274
275 pub arguments: Vec<GodotArgument>,
276}
277
278#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
279pub struct MethodName<'a> {
280 pub rust_name: &'a str,
281 pub original_name: &'a str,
282}
283
284impl GodotMethod {
285 pub fn get_name(&self) -> MethodName {
286 if &self.name == "new" {
289 return MethodName {
290 rust_name: "_new",
291 original_name: "new",
292 };
293 }
294
295 MethodName {
296 rust_name: &self.name,
297 original_name: &self.name,
298 }
299 }
300
301 pub fn get_return_type(&self) -> Ty {
302 Ty::from_src(&self.return_type)
303 }
304}
305
306#[derive(Deserialize, Debug)]
307pub struct GodotArgument {
308 pub name: String,
309 #[serde(rename = "type")]
310 pub ty: String,
311 pub has_default_value: bool,
312 pub default_value: String,
313}
314
315impl GodotArgument {
316 pub fn get_type(&self) -> Ty {
317 Ty::from_src(&self.ty)
318 }
319}
320
321#[derive(Clone, Eq, PartialEq)]
322pub enum Ty {
323 Void,
324 String,
325 F64,
326 I64,
327 Bool,
328 Vector2,
329 Vector3,
330 Vector3Axis,
331 Quat,
332 Transform,
333 Transform2D,
334 Rect2,
335 Plane,
336 Basis,
337 Color,
338 NodePath,
339 Variant,
340 Aabb,
341 Rid,
342 VariantArray,
343 Dictionary,
344 ByteArray,
345 StringArray,
346 Vector2Array,
347 Vector3Array,
348 ColorArray,
349 Int32Array,
350 Float32Array,
351 Result,
352 VariantType,
353 VariantOperator,
354 Enum(syn::TypePath),
355 Object(syn::TypePath),
356}
357
358impl Ty {
359 pub fn from_src(src: &str) -> Self {
361 match src {
362 "void" => Ty::Void,
363 "String" => Ty::String,
364 "float" => Ty::F64,
365 "int" => Ty::I64,
366 "bool" => Ty::Bool,
367 "Vector2" => Ty::Vector2,
368 "Vector3" => Ty::Vector3,
369 "Quat" => Ty::Quat,
370 "Transform" => Ty::Transform,
371 "Transform2D" => Ty::Transform2D,
372 "Rect2" => Ty::Rect2,
373 "Plane" => Ty::Plane,
374 "Basis" => Ty::Basis,
375 "Color" => Ty::Color,
376 "NodePath" => Ty::NodePath,
377 "Variant" => Ty::Variant,
378 "AABB" => Ty::Aabb,
379 "RID" => Ty::Rid,
380 "Array" => Ty::VariantArray,
381 "Dictionary" => Ty::Dictionary,
382 "PoolByteArray" => Ty::ByteArray,
383 "PoolStringArray" => Ty::StringArray,
384 "PoolVector2Array" => Ty::Vector2Array,
385 "PoolVector3Array" => Ty::Vector3Array,
386 "PoolColorArray" => Ty::ColorArray,
387 "PoolIntArray" => Ty::Int32Array,
388 "PoolRealArray" => Ty::Float32Array,
389 "enum.Error" => Ty::Result,
390 "enum.Variant::Type" => Ty::VariantType,
391 "enum.Variant::Operator" => Ty::VariantOperator,
392 "enum.Vector3::Axis" => Ty::Vector3Axis,
393 ty if ty.starts_with("enum.") => {
394 let mut split = ty[5..].split("::");
396 let class_name = split.next().unwrap();
397 let enum_raw_name = split.next().unwrap();
398 let name = format_ident!("{}", generate_enum_name(class_name, enum_raw_name));
399 let module = format_ident!("{}", module_name_from_class_name(class_name));
400 match Ty::from_src(class_name) {
402 Ty::Enum(_) | Ty::Object(_) => {
403 Ty::Enum(syn::parse_quote! { crate::generated::#module::#name })
404 }
405 _ => Ty::Enum(syn::parse_quote! { #module::#name }),
406 }
407 }
408 ty => {
409 let ty = format_ident!("{}", ty);
410 Ty::Object(syn::parse_quote! { crate::generated::#ty })
411 }
412 }
413 }
414
415 pub fn to_rust(&self) -> syn::Type {
416 match self {
417 Ty::Void => syn::parse_quote! {()},
418 Ty::String => syn::parse_quote! { GodotString },
419 Ty::F64 => syn::parse_quote! { f64 },
420 Ty::I64 => syn::parse_quote! { i64 },
421 Ty::Bool => syn::parse_quote! { bool },
422 Ty::Vector2 => syn::parse_quote! { Vector2 },
423 Ty::Vector3 => syn::parse_quote! { Vector3 },
424 Ty::Vector3Axis => syn::parse_quote! { Axis },
425 Ty::Quat => syn::parse_quote! { Quat },
426 Ty::Transform => syn::parse_quote! { Transform },
427 Ty::Transform2D => syn::parse_quote! { Transform2D },
428 Ty::Rect2 => syn::parse_quote! { Rect2 },
429 Ty::Plane => syn::parse_quote! { Plane },
430 Ty::Basis => syn::parse_quote! { Basis },
431 Ty::Color => syn::parse_quote! { Color },
432 Ty::NodePath => syn::parse_quote! { NodePath },
433 Ty::Variant => syn::parse_quote! { Variant },
434 Ty::Aabb => syn::parse_quote! { Aabb },
435 Ty::Rid => syn::parse_quote! { Rid },
436 Ty::VariantArray => syn::parse_quote! { VariantArray },
437 Ty::Dictionary => syn::parse_quote! { Dictionary },
438 Ty::ByteArray => syn::parse_quote! { PoolArray<u8> },
439 Ty::StringArray => syn::parse_quote! { PoolArray<GodotString> },
440 Ty::Vector2Array => syn::parse_quote! { PoolArray<Vector2> },
441 Ty::Vector3Array => syn::parse_quote! { PoolArray<Vector3> },
442 Ty::ColorArray => syn::parse_quote! { PoolArray<Color> },
443 Ty::Int32Array => syn::parse_quote! { PoolArray<i32> },
444 Ty::Float32Array => syn::parse_quote! { PoolArray<f32> },
445 Ty::Result => syn::parse_quote! { GodotResult },
446 Ty::VariantType => syn::parse_quote! { VariantType },
447 Ty::VariantOperator => syn::parse_quote! { VariantOperator },
448 Ty::Enum(path) => syn::parse_quote! { #path },
449 Ty::Object(path) => {
450 syn::parse_quote! { Option<Ref<#path, ownership::Shared>> }
451 }
452 }
453 }
454
455 pub fn to_rust_arg(&self) -> syn::Type {
456 match self {
457 Ty::Variant => syn::parse_quote! { impl OwnedToVariant },
458 Ty::NodePath => syn::parse_quote! { impl Into<NodePath> },
459 Ty::String => syn::parse_quote! { impl Into<GodotString> },
460 Ty::Object(ref name) => {
461 syn::parse_quote! { impl AsArg<#name> }
462 }
463 _ => self.to_rust(),
464 }
465 }
466
467 pub fn to_icall_arg(&self) -> syn::Type {
468 match self {
469 Ty::Object(_) => syn::parse_quote! { *mut sys::godot_object },
470 _ => self.to_rust(),
471 }
472 }
473
474 pub fn to_icall_return(&self) -> syn::Type {
475 match self {
476 Ty::Void => syn::parse_quote! { () },
477 Ty::String => syn::parse_quote! { sys::godot_string },
478 Ty::F64 => syn::parse_quote! { f64 },
479 Ty::I64 => syn::parse_quote! { i64 },
480 Ty::Bool => syn::parse_quote! { sys::godot_bool },
481 Ty::Vector2 => syn::parse_quote! { sys::godot_vector2 },
482 Ty::Vector3 => syn::parse_quote! { sys::godot_vector3 },
483
484 Ty::Quat => syn::parse_quote! { sys::godot_quat },
485 Ty::Transform => syn::parse_quote! { sys::godot_transform },
486 Ty::Transform2D => syn::parse_quote! { sys::godot_transform2d },
487 Ty::Rect2 => syn::parse_quote! { sys::godot_rect2 },
488 Ty::Plane => syn::parse_quote! { sys::godot_plane },
489 Ty::Basis => syn::parse_quote! { sys::godot_basis },
490 Ty::Color => syn::parse_quote! { sys::godot_color },
491 Ty::NodePath => syn::parse_quote! { sys::godot_node_path },
492 Ty::Variant => syn::parse_quote! { sys::godot_variant },
493 Ty::Aabb => syn::parse_quote! { sys::godot_aabb },
494 Ty::Rid => syn::parse_quote! { sys::godot_rid },
495 Ty::VariantArray => syn::parse_quote! { sys::godot_array },
496 Ty::Dictionary => syn::parse_quote! { sys::godot_dictionary },
497 Ty::ByteArray => syn::parse_quote! { sys::godot_pool_byte_array },
498 Ty::StringArray => syn::parse_quote! { sys::godot_pool_string_array },
499 Ty::Vector2Array => syn::parse_quote! { sys::godot_pool_vector2_array },
500 Ty::Vector3Array => syn::parse_quote! { sys::godot_pool_vector3_array },
501 Ty::ColorArray => syn::parse_quote! { sys::godot_pool_color_array },
502 Ty::Int32Array => syn::parse_quote! { sys::godot_pool_int_array },
503 Ty::Float32Array => syn::parse_quote! { sys::godot_pool_real_array },
504
505 Ty::Vector3Axis | Ty::Result | Ty::VariantType | Ty::VariantOperator | Ty::Enum(_) => {
506 syn::parse_quote! { i64 }
507 }
508
509 Ty::Object(_) => syn::parse_quote! { *mut sys::godot_object },
510 }
511 }
512
513 pub fn to_sys(&self) -> Option<syn::Type> {
514 match self {
515 Ty::Void => None,
516 Ty::String => Some(syn::parse_quote! { sys::godot_string }),
517 Ty::F64 => Some(syn::parse_quote! { sys::godot_real }),
518 Ty::I64 => Some(syn::parse_quote! { sys::godot_int }),
519 Ty::Bool => Some(syn::parse_quote! { sys::godot_bool }),
520 Ty::Vector2 => Some(syn::parse_quote! { sys::godot_vector2 }),
521 Ty::Vector3 => Some(syn::parse_quote! { sys::godot_vector3 }),
522 Ty::Vector3Axis => None,
523 Ty::Quat => Some(syn::parse_quote! { sys::godot_quat }),
524 Ty::Transform => Some(syn::parse_quote! { sys::godot_transform }),
525 Ty::Transform2D => Some(syn::parse_quote! { sys::godot_transform2d }),
526 Ty::Rect2 => Some(syn::parse_quote! { sys::godot_rect2 }),
527 Ty::Plane => Some(syn::parse_quote! { sys::godot_plane }),
528 Ty::Basis => Some(syn::parse_quote! { sys::godot_basis }),
529 Ty::Color => Some(syn::parse_quote! { sys::godot_color }),
530 Ty::NodePath => Some(syn::parse_quote! { sys::godot_node_path }),
531 Ty::Variant => Some(syn::parse_quote! { sys::godot_variant }),
532 Ty::Aabb => Some(syn::parse_quote! { sys::godot_aabb }),
533 Ty::Rid => Some(syn::parse_quote! { sys::godot_rid }),
534 Ty::VariantArray => Some(syn::parse_quote! { sys::godot_array }),
535 Ty::Dictionary => Some(syn::parse_quote! { sys::godot_dictionary }),
536 Ty::ByteArray => Some(syn::parse_quote! { sys::godot_pool_byte_array }),
537 Ty::StringArray => Some(syn::parse_quote! { sys::godot_pool_string_array }),
538 Ty::Vector2Array => Some(syn::parse_quote! { sys::godot_pool_vector2_array }),
539 Ty::Vector3Array => Some(syn::parse_quote! { sys::godot_pool_vector3_array }),
540 Ty::ColorArray => Some(syn::parse_quote! { sys::godot_pool_color_array }),
541 Ty::Int32Array => Some(syn::parse_quote! { sys::godot_pool_int_array }),
542 Ty::Float32Array => Some(syn::parse_quote! { sys::godot_pool_real_array }),
543 Ty::Result => Some(syn::parse_quote! { sys::godot_error }),
544 Ty::VariantType => Some(syn::parse_quote! { sys::variant_type }),
545 Ty::VariantOperator => Some(syn::parse_quote! { sys::godot_variant_operator }),
546 Ty::Enum(_) => None,
547 Ty::Object(_) => Some(syn::parse_quote! { sys::godot_object }),
548 }
549 }
550
551 pub fn to_return_post(&self) -> TokenStream {
552 match self {
553 Ty::Void => Default::default(),
554 Ty::F64 | Ty::I64 | Ty::Bool => {
555 quote! { ret as _ }
556 }
557 Ty::Enum(path) => {
558 quote! { #path(ret) }
559 }
560
561 Ty::Vector2
562 | Ty::Vector3
563 | Ty::Transform
564 | Ty::Transform2D
565 | Ty::Quat
566 | Ty::Aabb
567 | Ty::Rect2
568 | Ty::Basis
569 | Ty::Plane
570 | Ty::Color => {
571 quote! { mem::transmute(ret) }
572 }
573 Ty::Vector3Axis => {
574 quote! { mem::transmute(ret as u32) }
575 }
576 Ty::Rid => {
577 quote! { Rid::from_sys(ret) }
578 }
579 Ty::String
580 | Ty::NodePath
581 | Ty::VariantArray
582 | Ty::Dictionary
583 | Ty::ByteArray
584 | Ty::StringArray
585 | Ty::Vector2Array
586 | Ty::Vector3Array
587 | Ty::ColorArray
588 | Ty::Int32Array
589 | Ty::Float32Array
590 | Ty::Variant => {
591 let rust_ty = self.to_rust();
592 quote! {
593 <#rust_ty>::from_sys(ret)
594 }
595 }
596 Ty::Object(ref path) => {
597 quote! {
598 ptr::NonNull::new(ret)
599 .map(|sys| <Ref<#path, ownership::Shared>>::move_from_sys(sys))
600 }
601 }
602 Ty::Result => {
603 quote! { GodotError::result_from_sys(ret as _) }
604 }
605 Ty::VariantType => {
606 quote! { VariantType::from_sys(ret as _) }
607 }
608 Ty::VariantOperator => {
609 quote! {
610 VariantOperator::try_from_sys(ret as _).expect("enum variant should be valid")
611 }
612 }
613 }
614 }
615
616 pub fn to_return_post_variant(&self) -> TokenStream {
617 match self {
618 Ty::Void => Default::default(),
619 Ty::F64 | Ty::I64 | Ty::Bool => {
620 let rust_type = self.to_rust();
621 quote! {
622 <#rust_type>::coerce_from_variant(&ret)
623 }
624 }
625
626 Ty::Result => {
629 quote! { GodotError::result_from_sys(sys::godot_error::from_variant(&ret).expect("Unexpected variant type")) }
630 }
631 Ty::VariantType => {
632 quote! { VariantType::from_sys(sys::godot_variant_type::from_variant(&ret).expect("Unexpected variant type")) }
633 }
634 Ty::VariantOperator => {
635 quote! {
636 VariantOperator::try_from_sys(sys::godot_variant_operator::from_variant(&ret).expect("Unexpected variant type"))
637 .expect("enum variant should be valid")
638 }
639 }
640
641 Ty::Vector3Axis => {
642 quote! {
643 unsafe {
644 mem::transmute::<u32, Axis>(u32::from_variant(&ret).expect("Unexpected variant type") as _)
645 }
646 }
647 }
648 _ => {
649 let rust_type = self.to_rust();
650 quote! {
652 <#rust_type>::from_variant(&ret).expect("Unexpected variant type")
653 }
654 }
655 }
656 }
657}
658
659pub fn module_name_from_class_name(class_name: &str) -> String {
660 let mut class_chars = class_name.bytes().filter(|&ch| ch != b'_').peekable();
662
663 let mut previous: [Option<u8>; 2] = [None, None]; #[inline(always)]
668 fn up_or_num<T>(ch: T) -> bool
669 where
670 T: Into<Option<u8>>,
671 {
672 let ch = ch.into();
673 match ch {
674 Some(ch) => ch.is_ascii_digit() || ch.is_ascii_uppercase(),
675 None => false,
676 }
677 }
678
679 #[inline(always)]
681 fn is_lowercase_or<'a, T>(ch: T, default: bool) -> bool
682 where
683 T: Into<Option<&'a u8>>,
684 {
685 let ch = ch.into();
686 match ch {
687 Some(ch) => ch.is_ascii_lowercase(),
688 None => default,
689 }
690 }
691
692 let mut result = Vec::with_capacity(class_name.len());
693 while let Some(current) = class_chars.next() {
694 let next = class_chars.peek();
695
696 let [two_prev, one_prev] = previous;
697
698 let caps_to_lowercase = up_or_num(one_prev)
700 && up_or_num(current)
701 && is_lowercase_or(next, false)
702 && !is_lowercase_or(&two_prev, true);
703
704 let lower_to_uppercase = is_lowercase_or(&one_prev, false) && up_or_num(current);
707
708 if caps_to_lowercase || lower_to_uppercase {
709 result.push(b'_');
710 }
711 result.push(current.to_ascii_lowercase());
712
713 previous = [previous[1], Some(current)];
715 }
716
717 let mut result = String::from_utf8(result).unwrap();
718
719 if let Some(range) = result.find("_vec_3").map(|i| i..i + 6) {
723 result.replace_range(range, "_vec3_")
724 }
725 if let Some(range) = result.find("gd_native").map(|i| i..i + 9) {
726 result.replace_range(range, "gdnative")
727 }
728 if let Some(range) = result.find("gd_script").map(|i| i..i + 9) {
729 result.replace_range(range, "gdscript")
730 }
731
732 if result == "gdnative" {
734 return "gdnative_".into();
735 }
736
737 result
738}
739
740#[cfg(test)]
741mod tests {
742 use super::*;
743
744 #[test]
745 fn module_name_generator() {
746 let tests = vec![
747 ("Ab_Cdefg", "ab_cdefg"),
758 ("_Abcd", "abcd"),
759 ("Abcd_", "abcd"),
760 ("Abcdefg", "abcdefg"),
762 ("abcdefG", "abcdef_g"),
763 ("ABCDefg", "abc_defg"),
765 ("AbcDEFg", "abc_de_fg"),
766 ("AbcdEF10", "abcd_ef10"),
767 ("AbcDEFG", "abc_defg"),
768 ("ABCDEFG", "abcdefg"),
769 ("ABC", "abc"),
770 ("AbcDefg", "abc_defg"),
772 ("ABcdefg", "abcdefg"),
774 ("ABcde2G", "abcde_2g"),
775 ("AbcDEfg", "abc_defg"),
776 ("ABcDe2G", "abc_de_2g"),
777 ("abcdeFG", "abcde_fg"),
778 ("AB", "ab"),
779 ("AbcdefG", "abcdef_g"), ("FooVec3Uni", "foo_vec3_uni"),
783 ("GDNative", "gdnative_"),
784 ("GDScript", "gdscript"),
785 ];
786 tests.iter().for_each(|(class_name, expected)| {
787 let actual = module_name_from_class_name(class_name);
788 assert_eq!(*expected, actual, "Input: {class_name}");
789 });
790 }
791}