Skip to content

Commit cdfded1

Browse files
committed
docs
1 parent 4dc2720 commit cdfded1

File tree

18 files changed

+611
-9
lines changed

18 files changed

+611
-9
lines changed

godot-core/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ homepage = "https://godot-rust.github.io"
1212

1313
[features]
1414
default = []
15+
docs = []
1516
codegen-rustfmt = ["godot-ffi/codegen-rustfmt", "godot-codegen/codegen-rustfmt"]
1617
codegen-full = ["godot-codegen/codegen-full"]
1718
codegen-lazy-fptrs = [

godot-core/src/docs.rs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
use crate::registry::plugin::PluginItem;
8+
use std::collections::HashMap;
9+
10+
/// Created for documentation on
11+
/// ```ignore
12+
/// #[derive(GodotClass)]
13+
/// /// Documented
14+
/// struct Struct {
15+
/// /// documented
16+
/// x: f32,
17+
/// }
18+
/// ```
19+
#[derive(Clone, Copy, Debug, Default)]
20+
pub struct StructDocs {
21+
pub base: &'static str,
22+
pub description: &'static str,
23+
pub members: &'static str,
24+
}
25+
26+
/// Created for documentation on
27+
/// ```ignore
28+
/// #[godot_api]
29+
/// impl Struct {
30+
/// #[func]
31+
/// /// This function panics!
32+
/// fn panic() -> f32 { panic!() }
33+
/// }
34+
/// ```
35+
#[derive(Clone, Copy, Debug, Default)]
36+
pub struct InherentImplDocs {
37+
pub methods: &'static str,
38+
pub signals: &'static str,
39+
pub constants: &'static str,
40+
}
41+
42+
#[derive(Default)]
43+
struct DocPieces {
44+
definition: StructDocs,
45+
inherent: InherentImplDocs,
46+
virtual_methods: &'static str,
47+
}
48+
49+
#[doc(hidden)]
50+
/// This function scours the registered plugins to find their documentation pieces,
51+
/// and strings them together.
52+
///
53+
/// It returns an iterator over XML documents.
54+
pub fn gather_xml_docs() -> impl Iterator<Item = String> {
55+
let mut map = HashMap::<&'static str, DocPieces>::new();
56+
crate::private::iterate_plugins(|x| match x.item {
57+
PluginItem::InherentImpl {
58+
docs: Some(docs), ..
59+
} => map.entry(x.class_name.as_str()).or_default().inherent = docs,
60+
PluginItem::ITraitImpl {
61+
virtual_method_docs,
62+
..
63+
} => {
64+
map.entry(x.class_name.as_str())
65+
.or_default()
66+
.virtual_methods = virtual_method_docs
67+
}
68+
PluginItem::Struct {
69+
docs: Some(docs), ..
70+
} => map.entry(x.class_name.as_str()).or_default().definition = docs,
71+
_ => (),
72+
});
73+
map.into_iter().map(|(class, pieces)| {
74+
let StructDocs {
75+
base,
76+
description,
77+
members,
78+
} = pieces.definition;
79+
80+
let InherentImplDocs {
81+
methods,
82+
signals,
83+
constants,
84+
} = pieces.inherent;
85+
86+
let virtual_methods = pieces.virtual_methods;
87+
let brief = description.lines().next().unwrap_or_default();
88+
format!(r#"
89+
<?xml version="1.0" encoding="UTF-8"?>
90+
<class name="{class}" inherits="{base}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
91+
<brief_description>{brief}</brief_description>
92+
<description>{description}</description>
93+
<methods>{methods}{virtual_methods}</methods>
94+
<constants>{constants}</constants>
95+
<signals>{signals}</signals>
96+
<members>{members}</members>
97+
</class>"#)
98+
},
99+
)
100+
}
101+
102+
/// # Safety
103+
///
104+
/// The Godot binding must have been initialized before calling this function.
105+
///
106+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
107+
pub unsafe fn register() {
108+
for xml in gather_xml_docs() {
109+
crate::sys::interface_fn!(editor_help_load_xml_from_utf8_chars_and_len)(
110+
xml.as_ptr() as *const std::ffi::c_char,
111+
xml.len() as i64,
112+
);
113+
}
114+
}

godot-core/src/init/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ fn gdext_on_level_init(level: InitLevel) {
136136
ensure_godot_features_compatible();
137137
}
138138
InitLevel::Editor => {
139+
#[cfg(all(since_api = "4.3", feature = "docs"))]
140+
crate::docs::register();
139141
sys::load_class_method_table(sys::ClassApiLevel::Editor);
140142
}
141143
}

godot-core/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
pub mod builder;
1616
pub mod builtin;
1717
pub mod classes;
18+
#[cfg(all(since_api = "4.3", feature = "docs"))]
19+
pub mod docs;
20+
#[doc(hidden)]
21+
pub mod possibly_docs {
22+
#[cfg(all(since_api = "4.3", feature = "docs"))]
23+
pub use crate::docs::*;
24+
}
1825
pub mod global;
1926
pub mod init;
2027
pub mod meta;
@@ -70,3 +77,9 @@ pub mod log {
7077
godot_error, godot_print, godot_print_rich, godot_script_error, godot_warn,
7178
};
7279
}
80+
81+
// ----
82+
// Validation
83+
84+
#[cfg(all(feature = "docs", before_api = "4.3"))]
85+
compile_error!("Documentation generation requires 4.3.");

godot-core/src/registry/class.rs

+6
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
232232
is_editor_plugin,
233233
is_hidden,
234234
is_instantiable,
235+
#[cfg(all(since_api = "4.3", feature = "docs"))]
236+
docs: _,
235237
} => {
236238
c.parent_class_name = Some(base_class_name);
237239
c.default_virtual_fn = default_get_virtual_fn;
@@ -282,6 +284,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
282284

283285
PluginItem::InherentImpl {
284286
register_methods_constants_fn,
287+
#[cfg(all(since_api = "4.3", feature = "docs"))]
288+
docs: _,
285289
} => {
286290
c.register_methods_constants_fn = Some(register_methods_constants_fn);
287291
}
@@ -299,6 +303,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
299303
user_free_property_list_fn,
300304
user_property_can_revert_fn,
301305
user_property_get_revert_fn,
306+
#[cfg(all(since_api = "4.3", feature = "docs"))]
307+
virtual_method_docs: _,
302308
} => {
303309
c.user_register_fn = user_register_fn;
304310

godot-core/src/registry/plugin.rs

+11-3
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use std::any::Any;
9-
use std::fmt;
10-
8+
#[cfg(all(since_api = "4.3", feature = "docs"))]
9+
use crate::docs::*;
1110
use crate::init::InitLevel;
1211
use crate::meta::ClassName;
1312
use crate::sys;
13+
use std::any::Any;
14+
use std::fmt;
1415

1516
// TODO(bromeon): some information coming from the proc-macro API is deferred through PluginItem, while others is directly
1617
// translated to code. Consider moving more code to the PluginItem, which allows for more dynamic registration and will
@@ -96,6 +97,8 @@ pub enum PluginItem {
9697

9798
/// Whether the class has a default constructor.
9899
is_instantiable: bool,
100+
#[cfg(all(since_api = "4.3", feature = "docs"))]
101+
docs: Option<StructDocs>,
99102
},
100103

101104
/// Collected from `#[godot_api] impl MyClass`.
@@ -104,10 +107,15 @@ pub enum PluginItem {
104107
///
105108
/// Always present since that's the entire point of this `impl` block.
106109
register_methods_constants_fn: ErasedRegisterFn,
110+
#[cfg(all(since_api = "4.3", feature = "docs"))]
111+
docs: Option<InherentImplDocs>,
107112
},
108113

109114
/// Collected from `#[godot_api] impl I... for MyClass`.
110115
ITraitImpl {
116+
#[cfg(all(since_api = "4.3", feature = "docs"))]
117+
/// Virtual method documentation.
118+
virtual_method_docs: &'static str,
111119
/// Callback to user-defined `register_class` function.
112120
user_register_fn: Option<ErasedRegisterFn>,
113121

godot-macros/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ homepage = "https://godot-rust.github.io"
1212

1313
[features]
1414
api-custom = ["godot-bindings/api-custom"]
15+
docs = ["dep:markdown"]
1516

1617
[lib]
1718
proc-macro = true
@@ -21,6 +22,8 @@ proc-macro = true
2122
proc-macro2 = "1.0.63"
2223
quote = "1.0.29"
2324

25+
# Enabled by `docs`
26+
markdown = { version = "1.0.0-alpha.17", optional = true }
2427
venial = "0.6"
2528

2629
[build-dependencies]

godot-macros/src/class/data_models/field.rs

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub struct Field {
1515
pub var: Option<FieldVar>,
1616
pub export: Option<FieldExport>,
1717
pub is_onready: bool,
18+
#[cfg(feature = "docs")]
19+
pub attributes: Vec<venial::Attribute>,
1820
}
1921

2022
impl Field {
@@ -26,6 +28,8 @@ impl Field {
2628
var: None,
2729
export: None,
2830
is_onready: false,
31+
#[cfg(feature = "docs")]
32+
attributes: field.attributes.clone(),
2933
}
3034
}
3135
}

godot-macros/src/class/data_models/inherent_impl.rs

+6
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<Toke
5151
let (funcs, signals) = process_godot_fns(&class_name, &mut impl_block)?;
5252
let consts = process_godot_constants(&mut impl_block)?;
5353

54+
#[cfg(all(feature = "docs", since_api = "4.3"))]
55+
let docs = crate::docs::make_inherent_impl_docs(&funcs, &consts, &signals);
56+
#[cfg(not(all(feature = "docs", since_api = "4.3")))]
57+
let docs = quote! {};
58+
5459
let signal_registrations = make_signal_registrations(signals, &class_name_obj);
5560

5661
let method_registrations: Vec<TokenStream> = funcs
@@ -80,6 +85,7 @@ pub fn transform_inherent_impl(mut impl_block: venial::Impl) -> ParseResult<Toke
8085
register_methods_constants_fn: #prv::ErasedRegisterFn {
8186
raw: #prv::callbacks::register_user_methods_constants::<#class_name>,
8287
},
88+
#docs
8389
},
8490
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
8591
});

godot-macros/src/class/data_models/interface_trait_impl.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
4242
let mut virtual_method_names = vec![];
4343

4444
let prv = quote! { ::godot::private };
45-
45+
#[cfg(all(feature = "docs", since_api = "4.3"))]
46+
let docs = crate::docs::make_virtual_impl_docs(&original_impl.body_items);
47+
#[cfg(not(all(feature = "docs", since_api = "4.3")))]
48+
let docs = quote! {};
4649
for item in original_impl.body_items.iter() {
4750
let method = if let venial::ImplMember::AssocFunction(f) = item {
4851
f
@@ -400,6 +403,7 @@ pub fn transform_trait_impl(original_impl: venial::Impl) -> ParseResult<TokenStr
400403
user_property_get_revert_fn: #property_get_revert_fn,
401404
user_property_can_revert_fn: #property_can_revert_fn,
402405
get_virtual_fn: #prv::callbacks::get_virtual::<#class_name>,
406+
#docs
403407
},
404408
init_level: <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL,
405409
});

godot-macros/src/class/derive_godot_class.rs

+15-5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
3535
let is_editor_plugin = struct_cfg.is_editor_plugin;
3636
let is_hidden = struct_cfg.is_hidden;
3737
let base_ty = &struct_cfg.base_ty;
38+
#[cfg(all(feature = "docs", since_api = "4.3"))]
39+
let docs = crate::docs::make_definition_docs(
40+
base_ty.to_string(),
41+
&class.attributes,
42+
&fields.all_fields,
43+
);
44+
#[cfg(not(all(feature = "docs", since_api = "4.3")))]
45+
let docs = quote! {};
3846
let base_class = quote! { ::godot::classes::#base_ty };
3947
let base_class_name_obj = util::class_name_obj(&base_class);
4048
let inherits_macro = format_ident!("unsafe_inherits_transitive_{}", base_ty);
@@ -75,7 +83,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
7583

7684
match struct_cfg.init_strategy {
7785
InitStrategy::Generated => {
78-
godot_init_impl = make_godot_init_impl(class_name, fields);
86+
godot_init_impl = make_godot_init_impl(class_name, &fields);
7987
create_fn = quote! { Some(#prv::callbacks::create::<#class_name>) };
8088

8189
if cfg!(since_api = "4.2") {
@@ -142,6 +150,7 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
142150
is_editor_plugin: #is_editor_plugin,
143151
is_hidden: #is_hidden,
144152
is_instantiable: #is_instantiable,
153+
#docs
145154
},
146155
init_level: {
147156
let level = <#class_name as ::godot::obj::GodotClass>::INIT_LEVEL;
@@ -193,17 +202,18 @@ struct ClassAttributes {
193202
rename: Option<Ident>,
194203
}
195204

196-
fn make_godot_init_impl(class_name: &Ident, fields: Fields) -> TokenStream {
197-
let base_init = if let Some(Field { name, .. }) = fields.base_field {
205+
fn make_godot_init_impl(class_name: &Ident, fields: &Fields) -> TokenStream {
206+
let base_init = if let Some(Field { name, .. }) = &fields.base_field {
198207
quote! { #name: base, }
199208
} else {
200209
TokenStream::new()
201210
};
202211

203-
let rest_init = fields.all_fields.into_iter().map(|field| {
204-
let field_name = field.name;
212+
let rest_init = fields.all_fields.iter().map(|field| {
213+
let field_name = field.name.clone();
205214
let value_expr = field
206215
.default
216+
.clone()
207217
.unwrap_or_else(|| quote! { ::std::default::Default::default() });
208218

209219
quote! { #field_name: #value_expr, }

0 commit comments

Comments
 (0)