gdnative_doc/backend/
mod.rs

1//! Module for implementing your own backend.
2//!
3//! To implement your own backend:
4//! 1. Create a structure that represent your backend, and implement [`Callbacks`] on
5//! it.
6//!
7//!     You can look in the source code of this crate to get examples of what that
8//!     would look like.
9//! 2. Add your backend to the `Builder` via the [`add_backend_with_callbacks`]
10//! method.
11//!
12//! [`add_backend_with_callbacks`]: crate::Builder::add_backend_with_callbacks
13
14mod callbacks;
15mod gut;
16mod html;
17mod markdown;
18mod resolve;
19
20use crate::documentation::{Documentation, GdnativeClass, Method, Property};
21use pulldown_cmark::{
22    Alignment, CowStr, Event, HeadingLevel, LinkType, Options as MarkdownOptions, Parser, Tag,
23};
24
25pub(super) use gut::GutCallbacks;
26pub(super) use html::HtmlCallbacks;
27pub(super) use markdown::MarkdownCallbacks;
28
29pub use callbacks::Callbacks;
30pub use resolve::Resolver;
31
32/// Generate a callback to resolve broken links.
33///
34/// We have to generate a new one for each use because the lifetimes on
35/// `pulldown_cmark::Parser::new_with_broken_link_callback` are not yet
36/// refined enough.
37macro_rules! broken_link_callback {
38    ($resolver:expr) => {
39        move |broken_link: ::pulldown_cmark::BrokenLink| {
40            use ::pulldown_cmark::CowStr;
41
42            let mut link: &str = &broken_link.reference;
43            if link.starts_with('`') && link.ends_with('`') && link.len() > 1 {
44                link = &link[1..link.len() - 1];
45            }
46            $resolver
47                .resolve(link)
48                .map(|string| (CowStr::from(string), CowStr::Borrowed("")))
49        }
50    };
51}
52
53/// Backend already implemented by this library.
54///
55/// This must be used in the [`Builder::add_backend`] method.
56///
57/// [`Builder::add_backend`]: crate::Builder::add_backend
58#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub enum BuiltinBackend {
60    /// Markdown backend
61    ///
62    /// This generates a file for every structure that implements `NativeClass` + an
63    /// `index.md` file that contains the crate's documentation.
64    Markdown,
65    /// Html backend
66    ///
67    /// This generates a file for every structure that implements `NativeClass` + an
68    /// `index.html` file that contains the crate's documentation.
69    ///
70    /// Also generates css and javascript files (for styling and code highlighting).
71    Html,
72    /// Gut backend
73    ///
74    /// This generates a file for every structure that implements `NativeClass`,
75    /// generating tests from `gdscript` code blocks:
76    /// ```
77    /// # use gdnative::prelude::*;
78    /// #[derive(NativeClass)]
79    /// #[inherit(Node)]
80    /// pub struct MyClass {}
81    ///
82    /// #[methods]
83    /// impl MyClass {
84    ///     /// ```gdscript
85    ///     /// var x = 0
86    ///     /// assert_eq(x, 0)
87    ///     /// ```
88    ///     pub fn new(_: &Node) -> Self {
89    ///         // ...
90    /// # unimplemented!()
91    ///     }
92    /// }
93    /// ```
94    /// Will generates the following in `MyClass.gd`:
95    /// ```gdscript
96    /// extends "res://addons/gut/test.gd"
97    ///
98    /// func test_new():
99    ///     var x = 0
100    ///     assert_eq(x, 0)
101    /// ```
102    Gut,
103}
104
105/// Holds the information necessary to generate the output files.
106///
107/// This is used by structures implementing [`Callbacks`].
108#[derive(Debug)]
109pub struct Generator<'a> {
110    /// Used to resolve links.
111    pub resolver: &'a Resolver,
112    /// Holds the crate's documentation.
113    pub documentation: &'a Documentation,
114    /// Enabled markdown options
115    pub markdown_options: MarkdownOptions,
116    /// Control if an opening comment with meta-information should ba added to
117    /// generated files.
118    ///
119    /// See [`ConfigFile::opening_comment`](crate::ConfigFile::opening_comment)
120    pub opening_comment: bool,
121}
122
123impl<'a> Generator<'a> {
124    pub(crate) fn new(
125        resolver: &'a Resolver,
126        documentation: &'a Documentation,
127        markdown_options: MarkdownOptions,
128        opening_comment: bool,
129    ) -> Self {
130        Self {
131            resolver,
132            documentation,
133            markdown_options,
134            opening_comment,
135        }
136    }
137
138    /// Generate the root documentation file of the crate.
139    ///
140    /// The following will be generated (in markdown style):
141    /// ```text
142    /// <crate documentation>
143    ///
144    /// # Classes:
145    ///
146    /// <list of GDNative classes>
147    /// ```
148    ///
149    /// This then uses [`Callbacks::encode`] to encode this in the target format.
150    pub fn generate_root_file(&self, extension: &str, callbacks: &mut dyn Callbacks) -> String {
151        let resolver = self.resolver;
152        let mut broken_link_callback = broken_link_callback!(resolver);
153        let class_iterator = EventIterator {
154            context: resolver,
155            parser: pulldown_cmark::Parser::new_with_broken_link_callback(
156                &self.documentation.root_documentation,
157                self.markdown_options,
158                Some(&mut broken_link_callback),
159            ),
160        };
161        let mut events: Vec<_> = class_iterator.into_iter().collect();
162        events.extend(vec![
163            Event::Start(Tag::Heading(HeadingLevel::H1, None, Vec::new())),
164            Event::Text(CowStr::Borrowed("Classes:")),
165            Event::End(Tag::Heading(HeadingLevel::H1, None, Vec::new())),
166            Event::Start(Tag::List(None)),
167        ]);
168        for class_name in self.documentation.classes.keys() {
169            let link = Tag::Link(
170                LinkType::Inline,
171                format!("./{}.{}", class_name, extension).into(),
172                CowStr::Borrowed(""),
173            );
174            events.extend(vec![
175                Event::Start(Tag::Item),
176                Event::Start(link.clone()),
177                Event::Text(CowStr::Borrowed(class_name)),
178                Event::End(link.clone()),
179                Event::End(Tag::Item),
180            ])
181        }
182        events.push(Event::End(Tag::List(None)));
183        let mut root_file = String::new();
184        callbacks.encode(&mut root_file, events);
185        root_file
186    }
187
188    /// Generate the documentation for a class.
189    ///
190    /// The following will be generated (in markdown style):
191    /// ```text
192    /// # <class name>
193    ///
194    /// **Inherit:** <inherited class>
195    ///
196    /// ## Description
197    ///
198    /// <class documentation>
199    ///
200    /// ## Properties
201    ///
202    /// <table of class properties>
203    ///
204    /// ## Methods
205    ///
206    /// <table of class methods>
207    ///
208    /// ## Properties Descriptions
209    ///
210    /// <list of the class properties with their documentation>
211    ///
212    /// ## Methods Descriptions
213    ///
214    /// <list of the class methods with their documentation>
215    /// ```
216    ///
217    /// This then uses [`Callbacks::encode`] to encode this in the target format.
218    pub fn generate_file(
219        &self,
220        name: &str,
221        class: &GdnativeClass,
222        callbacks: &mut dyn Callbacks,
223    ) -> String {
224        let mut class_file = String::new();
225        let resolver = &self.resolver;
226
227        let inherit_link = resolver.resolve(&class.inherit);
228
229        // Name of the class + inherit
230        let mut events = vec![
231            Event::Start(Tag::Heading(HeadingLevel::H1, None, Vec::new())),
232            Event::Text(CowStr::Borrowed(name)),
233            Event::End(Tag::Heading(HeadingLevel::H1, None, Vec::new())),
234            Event::Start(Tag::Paragraph),
235            Event::Start(Tag::Strong),
236            Event::Text(CowStr::Borrowed("Inherit:")),
237            Event::End(Tag::Strong),
238            Event::Text(CowStr::Borrowed(" ")),
239        ];
240        if let Some(inherit_link) = inherit_link.as_ref() {
241            let link = Tag::Link(
242                LinkType::Shortcut,
243                CowStr::Borrowed(inherit_link),
244                CowStr::Borrowed(""),
245            );
246            events.extend(vec![
247                Event::Start(link.clone()),
248                Event::Text(CowStr::Borrowed(&class.inherit)),
249                Event::End(link),
250            ])
251        } else {
252            events.push(Event::Text(CowStr::Borrowed(&class.inherit)))
253        }
254        events.extend(vec![
255            Event::End(Tag::Paragraph),
256            Event::Start(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
257            Event::Text(CowStr::Borrowed("Description")),
258            Event::End(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
259        ]);
260        callbacks.encode(&mut class_file, events);
261
262        // Class description
263        let mut broken_link_callback = broken_link_callback!(resolver);
264        let class_documentation = EventIterator {
265            context: resolver,
266            parser: pulldown_cmark::Parser::new_with_broken_link_callback(
267                &class.documentation,
268                self.markdown_options,
269                Some(&mut broken_link_callback),
270            ),
271        }
272        .into_iter()
273        .collect();
274        callbacks.encode(&mut class_file, class_documentation);
275
276        // Properties table
277        if !class.properties.is_empty() {
278            callbacks.encode(
279                &mut class_file,
280                Self::properties_table(&class.properties, resolver),
281            )
282        }
283
284        // Methods table
285        callbacks.encode(
286            &mut class_file,
287            Self::methods_table(&class.methods, resolver),
288        );
289
290        // Properties descriptions
291        if !class.properties.is_empty() {
292            callbacks.encode(
293                &mut class_file,
294                vec![
295                    Event::Start(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
296                    Event::Text(CowStr::Borrowed("Properties Descriptions")),
297                    Event::End(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
298                ],
299            );
300            for property in &class.properties {
301                callbacks.start_property(&mut class_file, resolver, property);
302                let mut broken_link_callback = broken_link_callback!(resolver);
303                let property_documentation = EventIterator {
304                    context: resolver,
305                    parser: pulldown_cmark::Parser::new_with_broken_link_callback(
306                        &property.documentation,
307                        self.markdown_options,
308                        Some(&mut broken_link_callback),
309                    ),
310                }
311                .into_iter()
312                .collect();
313                callbacks.encode(&mut class_file, property_documentation);
314            }
315        }
316
317        // Methods descriptions
318        callbacks.encode(
319            &mut class_file,
320            vec![
321                Event::Start(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
322                Event::Text(CowStr::Borrowed("Methods Descriptions")),
323                Event::End(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
324            ],
325        );
326        for method in &class.methods {
327            callbacks.start_method(&mut class_file, resolver, method);
328            let mut broken_link_callback = broken_link_callback!(resolver);
329            let method_documentation = EventIterator {
330                context: resolver,
331                parser: pulldown_cmark::Parser::new_with_broken_link_callback(
332                    &method.documentation,
333                    self.markdown_options,
334                    Some(&mut broken_link_callback),
335                ),
336            }
337            .into_iter()
338            .collect();
339            callbacks.encode(&mut class_file, method_documentation);
340        }
341        class_file
342    }
343
344    /// Create a table summarizing the properties.
345    fn properties_table<'ev>(
346        properties: &'ev [Property],
347        resolver: &'ev Resolver,
348    ) -> Vec<Event<'ev>> {
349        let mut events = vec![
350            Event::Start(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
351            Event::Text(CowStr::Borrowed("Properties")),
352            Event::End(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
353            Event::Start(Tag::Table(vec![Alignment::Left, Alignment::Left])),
354            Event::Start(Tag::TableHead),
355            Event::Start(Tag::TableCell),
356            Event::Text(CowStr::Borrowed("type")),
357            Event::End(Tag::TableCell),
358            Event::Start(Tag::TableCell),
359            Event::Text(CowStr::Borrowed("property")),
360            Event::End(Tag::TableCell),
361            Event::End(Tag::TableHead),
362        ];
363
364        for property in properties {
365            let link = Tag::Link(
366                LinkType::Reference,
367                format!("#property-{}", property.name).into(),
368                property.name.as_str().into(),
369            );
370            events.push(Event::Start(Tag::TableRow));
371            events.push(Event::Start(Tag::TableCell));
372            events.extend(resolver.encode_type(&property.typ));
373            events.extend(vec![
374                Event::End(Tag::TableCell),
375                Event::Start(Tag::TableCell),
376                Event::Start(link.clone()),
377                Event::Text(CowStr::Borrowed(property.name.as_str())),
378                Event::End(link),
379                Event::End(Tag::TableCell),
380                Event::End(Tag::TableRow),
381            ]);
382        }
383
384        events.push(Event::End(Tag::Table(vec![
385            Alignment::Left,
386            Alignment::Left,
387        ])));
388
389        events
390    }
391
392    /// Create a table summarizing the methods.
393    fn methods_table<'ev>(methods: &'ev [Method], resolver: &'ev Resolver) -> Vec<Event<'ev>> {
394        let mut events = vec![
395            Event::Start(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
396            Event::Text(CowStr::Borrowed("Methods")),
397            Event::End(Tag::Heading(HeadingLevel::H2, None, Vec::new())),
398            Event::Start(Tag::Table(vec![Alignment::Left, Alignment::Left])),
399            Event::Start(Tag::TableHead),
400            Event::Start(Tag::TableCell),
401            Event::Text(CowStr::Borrowed("returns")),
402            Event::End(Tag::TableCell),
403            Event::Start(Tag::TableCell),
404            Event::Text(CowStr::Borrowed("method")),
405            Event::End(Tag::TableCell),
406            Event::End(Tag::TableHead),
407        ];
408
409        for method in methods {
410            let link = format!("#func-{}", method.name);
411            events.push(Event::Start(Tag::TableRow));
412            events.push(Event::Start(Tag::TableCell));
413            events.extend(resolver.encode_type(&method.return_type));
414            events.push(Event::End(Tag::TableCell));
415            events.push(Event::Start(Tag::TableCell));
416
417            let link = Tag::Link(
418                LinkType::Reference,
419                link.into(),
420                method.name.as_str().into(),
421            );
422            events.extend(vec![
423                Event::Start(link.clone()),
424                Event::Text(CowStr::Borrowed(&method.name)),
425                Event::End(link),
426                Event::Text(CowStr::Borrowed("( ")),
427            ]);
428            for (index, (name, typ, _)) in method.parameters.iter().enumerate() {
429                events.push(Event::Text(format!("{}: ", name).into()));
430                events.extend(resolver.encode_type(typ));
431                if index + 1 != method.parameters.len() {
432                    events.push(Event::Text(CowStr::Borrowed(", ")));
433                }
434            }
435
436            events.extend(vec![
437                Event::Text(CowStr::Borrowed(" )")),
438                Event::End(Tag::TableCell),
439                Event::End(Tag::TableRow),
440            ]);
441        }
442
443        events.push(Event::End(Tag::Table(vec![
444            Alignment::Left,
445            Alignment::Left,
446        ])));
447
448        events
449    }
450}
451
452/// Iterate over [events](Event), resolving links and changing the resolved
453/// broken links types.
454struct EventIterator<'resolver, 'input, 'cb> {
455    context: &'resolver Resolver,
456    parser: Parser<'input, 'cb>,
457}
458
459impl<'resolver, 'input, 'cb> Iterator for EventIterator<'resolver, 'input, 'cb> {
460    type Item = Event<'input>;
461
462    fn next(&mut self) -> Option<Self::Item> {
463        let mut next_event = self.parser.next()?;
464        next_event = match next_event {
465            // matches broken reference links that have been restored by the callback
466            // and replaces them by shortcut variants
467            Event::Start(Tag::Link(LinkType::ShortcutUnknown, dest, title)) => {
468                Event::Start(Tag::Link(LinkType::Shortcut, dest, title))
469            }
470            Event::End(Tag::Link(LinkType::ShortcutUnknown, dest, title)) => {
471                Event::End(Tag::Link(LinkType::Shortcut, dest, title))
472            }
473            _ => next_event,
474        };
475        self.context.resolve_event(&mut next_event);
476        Some(next_event)
477    }
478}