Skip to content

Commit

Permalink
Docs comments do not show up if only some of the class members/method…
Browse files Browse the repository at this point in the history
…s are documented

- Extend docs test case to feature some undocumented members of godot instances
- Change map&unwrap in docs.rs to filter_map (allow to document only subset of all the properties of a given class)
- Generate documentation blocks for signals & constants on compile-time
  • Loading branch information
Yarwin committed Jul 29, 2024
1 parent bf67856 commit 28c4513
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 63 deletions.
61 changes: 45 additions & 16 deletions godot-core/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,40 +23,56 @@ pub struct StructDocs {
pub members: &'static str,
}

/// Created for documentation on
/// Keeps documentation for Inherent Implementation such as:
/// ```ignore
/// #[godot_api]
/// impl Struct {
/// #[func]
/// /// This function panics!
/// fn panic() -> f32 { panic!() }
/// #[signal]
/// /// this signal signals
/// fn documented_signal(p: Vector3, w: f64);
/// #[constant]
/// /// this constant consts
/// const CON: i64 = 42;
///
/// }
/// ```
#[derive(Clone, Copy, Debug, Default)]
pub struct InherentImplDocs {
pub methods: &'static str,
pub signals: &'static str,
pub constants: &'static str,
pub methods: Option<&'static str>,
pub signals_block: &'static str,
pub constants_block: &'static str,
}

#[derive(Default)]
struct DocPieces {
definition: StructDocs,
inherent: InherentImplDocs,
virtual_methods: &'static str,
virtual_methods: Option<&'static str>,
}

#[doc(hidden)]
/// This function scours the registered plugins to find their documentation pieces,
/// and strings them together.
///
/// It returns an iterator over XML documents.
/// Returns an iterator over XML documents.
///
/// Documentation for signals and constants is being processed at compile time
/// and can take the form of an already formatted XML `<block><doc></doc>…</block>`, or an
/// empty string if no such attribute has been documented.
///
/// Since documentation for methods comes from two different sources
/// -– inherent Implementation (`methods`) and I* trait implementation (`virtual_method_docs`) –-
/// it is undesirable to merge them at compile time. Instead, they are being kept as an optional
/// strings of not-yet-parented XML tags
/// (or nothing if no method has been documented).
pub fn gather_xml_docs() -> impl Iterator<Item = String> {
let mut map = HashMap::<&'static str, DocPieces>::new();
crate::private::iterate_plugins(|x| match x.item {
PluginItem::InherentImpl {
docs: Some(docs), ..
} => map.entry(x.class_name.as_str()).or_default().inherent = docs,
PluginItem::InherentImpl { docs, .. } => {
map.entry(x.class_name.as_str()).or_default().inherent = docs
}
PluginItem::ITraitImpl {
virtual_method_docs,
..
Expand All @@ -79,20 +95,33 @@ pub fn gather_xml_docs() -> impl Iterator<Item = String> {

let InherentImplDocs {
methods,
signals,
constants,
signals_block,
constants_block,
} = pieces.inherent;

let virtual_methods = pieces.virtual_methods;
let brief = description.split_once("[br]").map(|(x, _)| x).unwrap_or_default();
let methods_block = if methods.is_some() || virtual_methods.is_some() {
format!(
"<methods>{m}{vm}</methods>",
m=methods.unwrap_or_default(),
vm=virtual_methods.unwrap_or_default())
} else {
String::new()
};

let brief = description
.split_once("[br]")
.map(|(x, _)| x)
.unwrap_or_default();

format!(r#"
<?xml version="1.0" encoding="UTF-8"?>
<class name="{class}" inherits="{base}" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>{brief}</brief_description>
<description>{description}</description>
<methods>{methods}{virtual_methods}</methods>
<constants>{constants}</constants>
<signals>{signals}</signals>
{methods_block}
{constants_block}
{signals_block}
<members>{members}</members>
</class>"#)
},
Expand Down
4 changes: 2 additions & 2 deletions godot-core/src/registry/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,14 @@ pub enum PluginItem {
/// Always present since that's the entire point of this `impl` block.
register_methods_constants_fn: ErasedRegisterFn,
#[cfg(all(since_api = "4.3", feature = "docs"))]
docs: Option<InherentImplDocs>,
docs: InherentImplDocs,
},

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

Expand Down
82 changes: 51 additions & 31 deletions godot-macros/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub fn make_definition_docs(
let members = members
.into_iter()
.filter(|x| x.var.is_some() | x.export.is_some())
.map(member)
.collect::<Option<String>>()?;
.filter_map(member)
.collect::<String>();
Some(quote! {
docs: ::godot::docs::StructDocs {
base: #base,
Expand All @@ -39,52 +39,72 @@ pub fn make_inherent_impl_docs(
constants: &[ConstDefinition],
signals: &[SignalDefinition],
) -> TokenStream {
/// Generates TokenStream containing field definitions for documented methods and documentation blocks for constants and signals.
fn pieces(
functions: &[FuncDefinition],
signals: &[SignalDefinition],
constants: &[ConstDefinition],
) -> Option<TokenStream> {
) -> TokenStream {
let to_tagged = |s: String, tag: &str| -> String {
if s.is_empty() {
s
} else {
format!("<{tag}>{s}</{tag}>")
}
};

let signals_block = to_tagged(
signals
.iter()
.filter_map(make_signal_docs)
.collect::<String>(),
"signals",
);
let constants_block = to_tagged(
constants
.iter()
.map(|ConstDefinition { raw_constant }| raw_constant)
.filter_map(make_constant_docs)
.collect::<String>(),
"constants",
);

let methods = functions
.iter()
.map(make_method_docs)
.collect::<Option<String>>()?;
let signals = signals
.iter()
.map(make_signal_docs)
.collect::<Option<String>>()?;
let constants = constants
.iter()
.map(|ConstDefinition { raw_constant: x }| x)
.map(make_constant_docs)
.collect::<Option<String>>()?;
let field_definition = quote! {
.filter_map(make_method_docs)
.collect::<String>();

let methods = if methods.is_empty() {
quote! { None }
} else {
quote! { Some(#methods) }
};

quote! {
docs: ::godot::docs::InherentImplDocs {
methods: #methods,
signals: #signals,
constants: #constants,
}.into()
};
Some(field_definition)
signals_block: #signals_block,
constants_block: #constants_block,
}
}
}
pieces(functions, signals, constants).unwrap_or_else(|| quote! { docs: None })
pieces(functions, signals, constants)
}

pub fn make_virtual_impl_docs(vmethods: &[ImplMember]) -> TokenStream {
match vmethods
let virtual_methods = vmethods
.iter()
.filter_map(|x| match x {
venial::ImplMember::AssocFunction(f) => Some(f.clone()),
_ => None,
})
.map(make_virtual_method_docs)
.collect::<Option<String>>()
{
Some(vmethods) => quote! {
virtual_method_docs: #vmethods,
},
None => quote! {
virtual_method_docs: ""
},
.filter_map(make_virtual_method_docs)
.collect::<String>();

if virtual_methods.is_empty() {
quote! { virtual_method_docs: None, }
} else {
quote! { virtual_method_docs: #virtual_methods.into(), }
}
}

Expand Down
52 changes: 44 additions & 8 deletions godot/tests/docs.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![cfg(feature = "register-docs")]
/*
* Copyright (c) godot-rust; Bromeon and contributors.
* This Source Code Form is subject to the terms of the Mozilla Public
Expand Down Expand Up @@ -75,57 +76,92 @@ use godot::prelude::*;
/// these
#[derive(GodotClass)]
#[class(base=Node)]
pub struct ExtremelyDocumented {
pub struct FairlyDocumented {
#[doc = r#"this is very documented"#]
#[var]
item: f32,
/// is it documented?
#[var]
item_2: i64,
/// this isnt documented
_other_item: (),
/// nor this
base: Base<Node>,
}

#[godot_api]
impl INode for ExtremelyDocumented {
impl INode for FairlyDocumented {
/// initialize this
fn init(base: Base<Node>) -> Self {
Self {
base,
item: 883.0,
item_2: 25,
_other_item: {},
}
}
}

#[godot_api]
impl ExtremelyDocumented {
#[constant]
impl FairlyDocumented {
/// Documentation.
#[constant]
const RANDOM: i64 = 4;

#[constant]
const PURPOSE: i64 = 42;

#[func]
fn totally_undocumented_function(&self) -> i64 {
5
}

/// huh
#[func]
fn ye(&self) -> f32 {
self.item
}

#[func]
#[func(gd_self, virtual)]
fn virtual_undocumented(_s: Gd<Self>) {
panic!("no implementation")
}

/// some virtual function that should be overridden by a user
///
/// some multiline doc
#[func(gd_self, virtual)]
fn virtual_documented(_s: Gd<Self>) {
panic!("please provide user implementation")
}

/// wow
///
/// some multiline doc
#[func]
fn ne(_x: f32) -> Gd<Self> {
panic!()
}

#[signal]
fn undocumented_signal(p: Vector3, w: f64);

/// some user signal
///
/// some multiline doc
#[signal]
fn documented_signal(p: Vector3, w: f64);
}

#[test]
#[cfg(feature = "register-docs")]
fn correct() {
// Uncomment if implementation changes and expected output file should be rewritten.
// std::fs::write(
// "tests/docs.xml",
// "tests/test_data/docs.xml",
// godot_core::docs::gather_xml_docs().next().unwrap(),
// );
assert_eq!(
include_str!("docs.xml"),
include_str!("test_data/docs.xml"),
godot_core::docs::gather_xml_docs().next().unwrap()
);
}
25 changes: 20 additions & 5 deletions godot/tests/docs.xml → godot/tests/test_data/docs.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

<?xml version="1.0" encoding="UTF-8"?>
<class name="ExtremelyDocumented" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<class name="FairlyDocumented" inherits="Node" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>[i]documented[/i] ~ [b]documented[/b] ~ [AABB] [url=https://github.com/godot-rust/gdext/pull/748]pr[/url]</brief_description>
<description>[i]documented[/i] ~ [b]documented[/b] ~ [AABB] [url=https://github.com/godot-rust/gdext/pull/748]pr[/url][br][br]a few tests:[br][br]headings:[br][br]Some heading[br][br]lists:[br][br][br][br][br][br]links with back-references:[br][br]Blah blah [br][br][br][br]footnotes:[br][br]We cannot florbinate the glorb[br][br][br][br]task lists:[br][br]We must ensure that we've completed[br][br][br][br]tables:[br][br][br][br]images:[br][br][img]http://url/a.png[/img][br][br]blockquotes:[br][br][br][br]ordered list:[br][br][br][br]Something here < this is technically header syntax[br][br]And here[br][br]smart punctuation[br][br]codeblocks:[br][br][codeblock]#![no_main]
#[link_section=\".text\"]
Expand All @@ -16,11 +16,19 @@ these</description>
</description>
</method>

<method name="virtual_documented">
<return type="()" />

<description>
some virtual function that should be overridden by a user[br][br]some multiline doc
</description>
</method>

<method name="ne">
<return type="Gd < ExtremelyDocumented >" />
<return type="Gd < FairlyDocumented >" />
<param index="0" name="x" type="f32" />
<description>
wow
wow[br][br]some multiline doc
</description>
</method>

Expand All @@ -33,6 +41,13 @@ these</description>
</method>
</methods>
<constants><constant name="RANDOM" value="4">Documentation.</constant></constants>
<signals></signals>
<members><member name="item" type="f32" default="">this is very documented</member></members>
<signals>
<signal name="documented_signal">
<param index="0" name="p" type="Vector3" /><param index="1" name="w" type="f64" />
<description>
some user signal[br][br]some multiline doc
</description>
</signal>
</signals>
<members><member name="item" type="f32" default="">this is very documented</member><member name="item_2" type="i64" default="">is it documented?</member></members>
</class>
2 changes: 1 addition & 1 deletion itest/rust/src/register_tests/constant_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ godot::sys::plugin_add!(
raw: ::godot::private::callbacks::register_user_methods_constants::<HasOtherConstants>,
},
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
docs: None,
docs: ::godot::docs::InherentImplDocs::default(),
},
init_level: HasOtherConstants::INIT_LEVEL,
}
Expand Down

0 comments on commit 28c4513

Please sign in to comment.