Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: Rename AutodocsError to Error and change the name of it's variants to display better error messages #17

Merged
merged 16 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/basic/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ fn main() {
// register custom functions and types ...
let docs = rhai_autodocs::export::options()
.include_standard_packages(false)
.order_items_with(rhai_autodocs::module::options::ItemsOrder::ByIndex)
.order_items_with(rhai_autodocs::export::ItemsOrder::ByIndex)
.export(&engine)
.expect("failed to generate documentation");

Expand Down
4 changes: 2 additions & 2 deletions examples/docusaurus/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ fn main() {
// Generate documentation structure.
let docs = rhai_autodocs::export::options()
.include_standard_packages(false)
.order_items_with(rhai_autodocs::module::options::ItemsOrder::ByIndex)
.format_sections_with(rhai_autodocs::module::options::SectionFormat::Tabs)
.order_items_with(rhai_autodocs::export::ItemsOrder::ByIndex)
.format_sections_with(rhai_autodocs::export::SectionFormat::Tabs)
.export(&engine)
.expect("failed to generate documentation");

Expand Down
4 changes: 2 additions & 2 deletions examples/mdbook/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ fn main() {
// register custom functions and types ...
let docs = rhai_autodocs::export::options()
.include_standard_packages(false)
.order_items_with(rhai_autodocs::module::options::ItemsOrder::ByIndex)
.format_sections_with(rhai_autodocs::module::options::SectionFormat::Tabs)
.order_items_with(rhai_autodocs::export::ItemsOrder::ByIndex)
.format_sections_with(rhai_autodocs::export::SectionFormat::Tabs)
.export(&engine)
.expect("failed to generate documentation");

Expand Down
19 changes: 6 additions & 13 deletions src/doc_item.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
use crate::{
custom_types::CustomTypesMetadata,
export::{ItemsOrder, Options, RHAI_ITEM_INDEX_PATTERN},
function::FunctionMetadata,
module::{
error::AutodocsError,
options::{Options, RHAI_ITEM_INDEX_PATTERN},
},
ItemsOrder,
module::Error,
};
use serde::ser::SerializeStruct;

Expand Down Expand Up @@ -127,7 +124,7 @@ impl DocItem {
metadata: &[FunctionMetadata],
name: &str,
options: &Options,
) -> Result<Option<Self>, AutodocsError> {
) -> Result<Option<Self>, Error> {
// Takes the first valid comments found for a function group.
let root = metadata
.iter()
Expand Down Expand Up @@ -160,7 +157,7 @@ impl DocItem {
pub(crate) fn new_custom_type(
metadata: CustomTypesMetadata,
options: &Options,
) -> Result<Option<Self>, AutodocsError> {
) -> Result<Option<Self>, Error> {
if matches!(options.items_order, ItemsOrder::ByIndex) {
Self::find_index(metadata.doc_comments.as_ref().unwrap_or(&vec![]))?
} else {
Expand Down Expand Up @@ -188,16 +185,12 @@ impl DocItem {
}

/// Find the order index of the item by searching for the index pattern.
pub(crate) fn find_index(doc_comments: &[String]) -> Result<Option<usize>, AutodocsError> {
pub(crate) fn find_index(doc_comments: &[String]) -> Result<Option<usize>, Error> {
for line in doc_comments {
if let Some((_, index)) = line.rsplit_once(RHAI_ITEM_INDEX_PATTERN) {
return index
.parse::<usize>()
.map_err(|err| {
AutodocsError::PreProcessing(format!(
"failed to parsed order metadata: {err}"
))
})
.map_err(Error::ParseOrderMetadata)
.map(Some);
}
}
Expand Down
160 changes: 160 additions & 0 deletions src/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use crate::{
doc_item::DocItem,
glossary::{generate_module_glossary, ModuleGlossary},
module::{generate_module_documentation, Error, ModuleDocumentation},
};

pub(crate) const RHAI_ITEM_INDEX_PATTERN: &str = "# rhai-autodocs:index:";

/// Types of markdown processor where the documentation generated will be hosted.
#[derive(Default)]
pub enum MarkdownProcessor {
/// Generate documentation for mdbook: <https://rust-lang.github.io/mdBook/>
MdBook,
/// Generate documentation for docusaurus. <https://docusaurus.io/>
#[default]
Docusaurus,
}

#[derive(Default)]
/// Options to configure documentation generation.
pub struct Options {
pub(crate) items_order: ItemsOrder,
pub(crate) sections_format: SectionFormat,
pub(crate) include_standard_packages: bool,
}

impl Options {
/// Include the standard package functions and modules documentation
/// in the generated documentation markdown.
pub fn include_standard_packages(mut self, include_standard_packages: bool) -> Self {
self.include_standard_packages = include_standard_packages;

self
}

/// Order documentation items in a specific way.
/// See [`ItemsOrder`] for more details.
pub fn order_items_with(mut self, items_order: ItemsOrder) -> Self {
self.items_order = items_order;

self
}

/// Format doc comments 'sections', markdown that starts with the `#` character,
/// with special formats.
/// See [`SectionFormat`] for more details.
pub fn format_sections_with(mut self, sections_format: SectionFormat) -> Self {
self.sections_format = sections_format;

self
}

/// Generate documentation based on an engine instance.
/// Make sure all the functions, operators, plugins, etc. are registered inside this instance.
///
/// # Result
/// * A vector of documented modules.
///
/// # Errors
/// * Failed to generate function metadata as json.
/// * Failed to parse module metadata.
pub fn export(self, engine: &rhai::Engine) -> Result<ModuleDocumentation, Error> {
generate_module_documentation(engine, &self)
}

/// Generate documentation based on an engine instance and a list of all functions signature.
/// Make sure all the functions, operators, plugins, etc. are registered inside this instance.
///
/// # Result
/// * A vector of documented modules and the glossary.
///
/// # Errors
/// * Failed to generate function metadata as json.
/// * Failed to parse module metadata.
pub fn export_with_glossary(
&self,
engine: &rhai::Engine,
) -> Result<(ModuleDocumentation, ModuleGlossary), Error> {
Ok((
generate_module_documentation(engine, self)?,
generate_module_glossary(engine, self)?,
))
}
}

/// Select in which order each doc item will be displayed.
#[derive(Default)]
pub enum ItemsOrder {
/// Display functions by alphabetical order.
#[default]
Alphabetical,
/// Display functions by index using a pre-processing comment with the `# rhai-autodocs:index:<number>` syntax.
/// The `# rhai-autodocs:index:<number>` line will be removed in the final generated markdown.
///
/// # Example
///
/// ```ignore
/// /// Function that will appear first in docs.
/// ///
/// /// # rhai-autodocs:index:1
/// #[rhai_fn(global)]
/// pub fn my_function1() {}
///
/// /// Function that will appear second in docs.
/// ///
/// /// # rhai-autodocs:index:2
/// #[rhai_fn(global)]
/// pub fn my_function2() {}
/// ```
///
/// Adding, removing or re-ordering your functions from your api can be a chore
/// because you have to update all indexes by hand. Thankfully, you will found
/// a python script in the `scripts` folder of the `rhai-autodocs` repository
/// that will update the indexes by hand just for you.
///
/// The script generates a .autodocs file from your original source file,
/// make sure to check that it did not mess with your source code using
/// a diff tool.
ByIndex,
}

impl ItemsOrder {
/// Order [`DocItem`]s following the given option.
pub(crate) fn order_items(&'_ self, mut items: Vec<DocItem>) -> Vec<DocItem> {
match self {
Self::Alphabetical => {
items.sort_by(|i1, i2| i1.name().cmp(i2.name()));
items
}
Self::ByIndex => {
items.sort_by_key(DocItem::index);
items
}
}
}
}

/// Options to format the display of sections marked with the `#`
/// tag in markdown.
#[derive(Default)]
pub enum SectionFormat {
/// Display sections the same as Rust doc comments, using the
/// default markdown titles.
#[default]
Rust,
/// Display sections using tabs that wraps all underlying
/// documentation in them.
Tabs,
}

#[derive(Default, Clone, serde::Serialize)]
struct Section {
pub name: String,
pub body: String,
}

/// Create new options used to configure docs generation.
pub fn options() -> Options {
Options::default()
}
117 changes: 117 additions & 0 deletions src/generate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use serde_json::json;

use crate::ModuleDocumentation;

#[derive(Default)]
pub struct DocusaurusOptions {
slug: Option<String>,
}

impl DocusaurusOptions {
/// Format the slug in the metadata section of the generated MDX document by concatenating the `slug` parameter with the module name.
///
/// For example, if the documentation for a module called `my_module` is generated with
/// the slug `/docs/api/`, the slug set in the document will be `/docs/api/my_module`.
///
/// By default the root `/` path is used.
pub fn with_slug(mut self, slug: &str) -> Self {
self.slug = Some(slug.to_string());

self
}

/// Build MDX documentation for docusaurus from the given module documentation struct.
///
/// Returns a hashmap with the name of the module as the key and its raw documentation as the value.
pub fn build(
self,
module: &ModuleDocumentation,
) -> Result<std::collections::HashMap<String, String>, handlebars::RenderError> {
let mut hbs_registry = handlebars::Handlebars::new();

hbs_registry
.register_template_string(
"docusaurus-module",
include_str!("handlebars/docusaurus/header.hbs"),
)
.expect("template is valid");

// A partial used to keep indentation for mdx to render correctly.
hbs_registry
.register_partial("ContentPartial", "{{{content}}}")
.expect("partial is valid");

generate(
module,
"docusaurus-module",
self.slug.as_deref(),
&hbs_registry,
)
}
}

/// Create a new builder to generate documentation for docusaurus from a [`super::module::ModuleDocumentation`] object.
pub fn docusaurus() -> DocusaurusOptions {
DocusaurusOptions::default()
}

#[derive(Default)]
pub struct MDBookOptions;

impl MDBookOptions {
/// Build html documentation for mdbook from the given module documentation struct.
///
/// Returns a hashmap with the name of the module as the key and its raw documentation as the value.
pub fn build(
self,
module: &ModuleDocumentation,
) -> Result<std::collections::HashMap<String, String>, handlebars::RenderError> {
let mut hbs_registry = handlebars::Handlebars::new();

hbs_registry
.register_template_string(
"mdbook-module",
include_str!("handlebars/mdbook/header.hbs"),
)
.expect("template is valid");

// A partial used to keep indentation for md to render correctly.
hbs_registry
.register_partial("ContentPartial", "{{{content}}}")
.expect("partial is valid");

generate(module, "mdbook-module", None, &hbs_registry)
}
}

/// Create a new builder to generate documentation for mdbook from a [`super::module::ModuleDocumentation`] object.
pub fn mdbook() -> MDBookOptions {
MDBookOptions
}

fn generate(
module: &ModuleDocumentation,
template: &str,
slug: Option<&str>,
hbs_registry: &handlebars::Handlebars,
) -> Result<std::collections::HashMap<String, String>, handlebars::RenderError> {
let mut documentation = std::collections::HashMap::default();
let data = json!({
"title": module.name,
"slug": slug.map_or(format!("/{}", module.name), |slug| format!("{}/{}", slug, module.name)),
"description": module.documentation,
"namespace": module.namespace,
"items": module.items,
});

documentation.insert(
module.name.to_string(),
hbs_registry.render(template, &data)?,
);

for sub in &module.sub_modules {
documentation.extend(generate(sub, template, slug, hbs_registry)?);
}

Ok(documentation)
}
Loading
Loading