mod gut;
mod html;
mod markdown;
mod resolve;
use crate::documentation::{Documentation, GdnativeClass, Method, Property};
use pulldown_cmark::{Alignment, CowStr, Event, LinkType, Options as MarkdownOptions, Parser, Tag};
use std::path::PathBuf;
pub(super) use gut::GutCallbacks;
pub(super) use html::HtmlCallbacks;
pub(super) use markdown::MarkdownCallbacks;
pub use resolve::Resolver;
macro_rules! broken_link_callback {
($resolver:expr) => {
move |broken_link: ::pulldown_cmark::BrokenLink| {
use ::pulldown_cmark::CowStr;
let mut link = broken_link.reference;
if link.starts_with('`') && link.ends_with('`') && link.len() > 1 {
link = &link[1..link.len() - 1];
}
$resolver
.resolve(link)
.map(|string| (CowStr::from(string), CowStr::Borrowed("")))
}
};
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Backend {
Markdown {
output_dir: PathBuf,
},
Html {
output_dir: PathBuf,
},
Gut {
output_dir: PathBuf,
},
}
pub trait Callbacks {
fn extension(&self) -> &'static str;
fn start_class(&mut self, _s: &mut String, _resolver: &Resolver, _class: &GdnativeClass) {}
fn start_method(&mut self, _s: &mut String, _resolver: &Resolver, _method: &Method) {}
fn start_property(&mut self, _s: &mut String, _resolver: &Resolver, _property: &Property) {}
fn encode(&mut self, s: &mut String, events: Vec<Event<'_>>);
fn finish_encoding(&mut self, _s: &mut String) {}
}
impl dyn Callbacks {
pub fn start_method_default(&mut self, s: &mut String, property: &Resolver, method: &Method) {
let link = &format!("<a id=\"func-{}\"></a>", method.name);
self.encode(
s,
vec![
Event::Start(Tag::Heading(3)),
Event::Html(CowStr::Borrowed(link)),
],
);
let mut method_header = String::from("func ");
method_header.push_str(&method.name);
method_header.push('(');
for (index, (name, typ, _)) in method.parameters.iter().enumerate() {
method_header.push_str(&name);
method_header.push_str(": ");
self.encode(s, vec![Event::Text(CowStr::Borrowed(&method_header))]);
method_header.clear();
self.encode(s, property.encode_type(typ));
if index + 1 != method.parameters.len() {
method_header.push_str(", ");
}
}
method_header.push_str(") -> ");
let mut last_events = vec![Event::Text(CowStr::Borrowed(&method_header))];
last_events.extend(property.encode_type(&method.return_type));
last_events.push(Event::End(Tag::Heading(3)));
last_events.push(Event::Rule);
self.encode(s, last_events);
}
pub fn start_property_default(
&mut self,
s: &mut String,
resolver: &Resolver,
property: &Property,
) {
let link = &format!(
"<a id=\"property-{}\"></a> {}: ",
property.name, property.name
);
self.encode(
s,
vec![
Event::Start(Tag::Heading(3)),
Event::Html(CowStr::Borrowed(link)),
],
);
let mut last_events = resolver.encode_type(&property.typ);
last_events.push(Event::End(Tag::Heading(3)));
last_events.push(Event::Rule);
self.encode(s, last_events);
}
}
impl std::fmt::Debug for dyn Callbacks {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("Callbacks")
}
}
#[derive(Debug)]
pub(crate) struct Generator<'a> {
resolver: &'a Resolver,
callbacks: Box<dyn Callbacks>,
documentation: &'a Documentation,
markdown_options: MarkdownOptions,
}
impl<'a> Generator<'a> {
pub(crate) fn new(
resolver: &'a Resolver,
documentation: &'a Documentation,
callbacks: Box<dyn Callbacks>,
markdown_options: MarkdownOptions,
) -> Self {
Self {
resolver,
callbacks,
documentation,
markdown_options,
}
}
pub(crate) fn generate_root_file(&mut self, extension: &str) -> String {
let mut root_file = String::new();
let resolver = self.resolver;
let mut broken_link_callback = broken_link_callback!(resolver);
let class_iterator = EventIterator {
context: resolver,
parser: pulldown_cmark::Parser::new_with_broken_link_callback(
&self.documentation.root_documentation,
self.markdown_options,
Some(&mut broken_link_callback),
),
};
let mut events: Vec<_> = class_iterator.into_iter().collect();
events.extend(vec![
Event::Start(Tag::Heading(1)),
Event::Text(CowStr::Borrowed("Classes:")),
Event::End(Tag::Heading(1)),
Event::Start(Tag::List(None)),
]);
for (class_name, _) in &self.documentation.classes {
let link = Tag::Link(
LinkType::Inline,
format!("./{}.{}", class_name, extension).into(),
CowStr::Borrowed(""),
);
events.extend(vec![
Event::Start(Tag::Item),
Event::Start(link.clone()),
Event::Text(CowStr::Borrowed(&class_name)),
Event::End(link.clone()),
Event::End(Tag::Item),
])
}
events.push(Event::End(Tag::List(None)));
self.callbacks.encode(&mut root_file, events);
self.callbacks.finish_encoding(&mut root_file);
root_file
}
pub(crate) fn generate_files(&mut self) -> Vec<(String, String)> {
let mut results = Vec::new();
for (name, class) in &self.documentation.classes {
let mut class_file = String::new();
let callbacks = &mut self.callbacks;
let resolver = &self.resolver;
callbacks.start_class(&mut class_file, resolver, class);
let inherit_link = resolver.resolve(&class.inherit);
let mut events = vec![
Event::Start(Tag::Heading(1)),
Event::Text(CowStr::Borrowed(&name)),
Event::End(Tag::Heading(1)),
Event::Start(Tag::Paragraph),
Event::Start(Tag::Strong),
Event::Text(CowStr::Borrowed("Inherit:")),
Event::End(Tag::Strong),
Event::Text(CowStr::Borrowed(" ")),
];
if let Some(inherit_link) = inherit_link.as_ref() {
events.extend(vec![
Event::Start(Tag::Link(
LinkType::Shortcut,
CowStr::Borrowed(&inherit_link),
CowStr::Borrowed(""),
)),
Event::Text(CowStr::Borrowed(&class.inherit)),
Event::End(Tag::Link(
LinkType::Shortcut,
CowStr::Borrowed(&inherit_link),
CowStr::Borrowed(""),
)),
])
} else {
events.push(Event::Text(CowStr::Borrowed(&class.inherit)))
}
events.extend(vec![
Event::End(Tag::Paragraph),
Event::Start(Tag::Heading(2)),
Event::Text(CowStr::Borrowed("Description")),
Event::End(Tag::Heading(2)),
]);
callbacks.encode(&mut class_file, events);
let mut broken_link_callback = broken_link_callback!(resolver);
let class_documentation = EventIterator {
context: resolver,
parser: pulldown_cmark::Parser::new_with_broken_link_callback(
&class.documentation,
self.markdown_options,
Some(&mut broken_link_callback),
),
}
.into_iter()
.collect();
callbacks.encode(&mut class_file, class_documentation);
if !class.properties.is_empty() {
callbacks.encode(
&mut class_file,
Self::properties_table(&class.properties, resolver),
)
}
callbacks.encode(
&mut class_file,
Self::methods_table(&class.methods, resolver),
);
if !class.properties.is_empty() {
callbacks.encode(
&mut class_file,
vec![
Event::Start(Tag::Heading(2)),
Event::Text(CowStr::Borrowed("Properties Descriptions")),
Event::End(Tag::Heading(2)),
],
);
for property in &class.properties {
callbacks.start_property(&mut class_file, resolver, property);
let mut broken_link_callback = broken_link_callback!(resolver);
let property_documentation = EventIterator {
context: resolver,
parser: pulldown_cmark::Parser::new_with_broken_link_callback(
&property.documentation,
self.markdown_options,
Some(&mut broken_link_callback),
),
}
.into_iter()
.collect();
callbacks.encode(&mut class_file, property_documentation);
}
}
callbacks.encode(
&mut class_file,
vec![
Event::Start(Tag::Heading(2)),
Event::Text(CowStr::Borrowed("Methods Descriptions")),
Event::End(Tag::Heading(2)),
],
);
for method in &class.methods {
callbacks.start_method(&mut class_file, resolver, method);
let mut broken_link_callback = broken_link_callback!(resolver);
let method_documentation = EventIterator {
context: resolver,
parser: pulldown_cmark::Parser::new_with_broken_link_callback(
&method.documentation,
self.markdown_options,
Some(&mut broken_link_callback),
),
}
.into_iter()
.collect();
callbacks.encode(&mut class_file, method_documentation);
}
callbacks.finish_encoding(&mut class_file);
results.push((name.clone(), class_file))
}
results
}
fn properties_table<'ev>(
properties: &'ev [Property],
resolver: &'ev Resolver,
) -> Vec<Event<'ev>> {
let mut events = vec![
Event::Start(Tag::Heading(2)),
Event::Text(CowStr::Borrowed("Properties")),
Event::End(Tag::Heading(2)),
Event::Start(Tag::Table(vec![Alignment::Left, Alignment::Left])),
Event::Start(Tag::TableHead),
Event::Start(Tag::TableCell),
Event::Text(CowStr::Borrowed("type")),
Event::End(Tag::TableCell),
Event::Start(Tag::TableCell),
Event::Text(CowStr::Borrowed("property")),
Event::End(Tag::TableCell),
Event::End(Tag::TableHead),
];
for property in properties {
let link = Tag::Link(
LinkType::Reference,
format!("#property-{}", property.name).into(),
property.name.as_str().into(),
);
events.push(Event::Start(Tag::TableRow));
events.push(Event::Start(Tag::TableCell));
events.extend(resolver.encode_type(&property.typ));
events.extend(vec![
Event::End(Tag::TableCell),
Event::Start(Tag::TableCell),
Event::Start(link.clone()),
Event::Text(CowStr::Borrowed(property.name.as_str())),
Event::End(link),
Event::End(Tag::TableCell),
Event::End(Tag::TableRow),
]);
}
events.push(Event::End(Tag::Table(vec![
Alignment::Left,
Alignment::Left,
])));
events
}
fn methods_table<'ev>(methods: &'ev [Method], resolver: &'ev Resolver) -> Vec<Event<'ev>> {
let mut events = vec![
Event::Start(Tag::Heading(2)),
Event::Text(CowStr::Borrowed("Methods")),
Event::End(Tag::Heading(2)),
Event::Start(Tag::Table(vec![Alignment::Left, Alignment::Left])),
Event::Start(Tag::TableHead),
Event::Start(Tag::TableCell),
Event::Text(CowStr::Borrowed("returns")),
Event::End(Tag::TableCell),
Event::Start(Tag::TableCell),
Event::Text(CowStr::Borrowed("method")),
Event::End(Tag::TableCell),
Event::End(Tag::TableHead),
];
for method in methods {
let link = format!("#func-{}", method.name);
events.push(Event::Start(Tag::TableRow));
events.push(Event::Start(Tag::TableCell));
events.extend(resolver.encode_type(&method.return_type));
events.push(Event::End(Tag::TableCell));
events.push(Event::Start(Tag::TableCell));
let link = Tag::Link(
LinkType::Reference,
link.into(),
method.name.as_str().into(),
);
events.extend(vec![
Event::Start(link.clone()),
Event::Text(CowStr::Borrowed(&method.name)),
Event::End(link),
Event::Text(CowStr::Borrowed("( ")),
]);
for (index, (name, typ, _)) in method.parameters.iter().enumerate() {
events.push(Event::Text(format!("{}: ", name).into()));
events.extend(resolver.encode_type(typ));
if index + 1 != method.parameters.len() {
events.push(Event::Text(CowStr::Borrowed(", ")));
}
}
events.extend(vec![
Event::Text(CowStr::Borrowed(" )")),
Event::End(Tag::TableCell),
Event::End(Tag::TableRow),
]);
}
events.push(Event::End(Tag::Table(vec![
Alignment::Left,
Alignment::Left,
])));
events
}
}
struct EventIterator<'resolver, 'parser> {
context: &'resolver Resolver,
parser: Parser<'parser>,
}
impl<'resolver, 'parser> Iterator for EventIterator<'resolver, 'parser> {
type Item = Event<'parser>;
fn next(&mut self) -> Option<Self::Item> {
let mut next_event = self.parser.next()?;
next_event = match next_event {
Event::Start(Tag::Link(LinkType::ShortcutUnknown, dest, title)) => {
Event::Start(Tag::Link(LinkType::Shortcut, dest, title))
}
Event::End(Tag::Link(LinkType::ShortcutUnknown, dest, title)) => {
Event::End(Tag::Link(LinkType::Shortcut, dest, title))
}
_ => next_event,
};
self.context.resolve_event(&mut next_event);
Some(next_event)
}
}