Skip to content

Commit

Permalink
refactor!: Rename AutodocsError to Error and change the name of i…
Browse files Browse the repository at this point in the history
…t's variants to display better error messages (#17)

* feat: replace generate_for_docusaurus and generate_for_mdbook functions by buider patterns to provide more options

* feat: add slug option to docusaurus generation

* chore: update docusaurus example with slug option

* fix: slug path

* chore: update docusaurus examples with generated slugs

* fix: slug without options appens / to the module name

* docs: update readme to reflect the changes

* chore: fix lint

* docs: update unresolved items

* refact: hide function unused by the user

* refacto: split export and generate code into their own modules

* chore: update examples

* chore: hide index patter constant

* chore!: Rename error struct to Error, and display better error messages
  • Loading branch information
ltabis authored Apr 29, 2024
1 parent d33f3ef commit 1e133d6
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 189 deletions.
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

0 comments on commit 1e133d6

Please sign in to comment.