pub mod error;
pub mod options;
pub use self::options::options;
use self::{error::AutodocsError, options::Options};
use crate::custom_types::CustomTypesMetadata;
use crate::doc_item::DocItem;
use crate::function::FunctionMetadata;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct ModuleDocumentation {
pub namespace: String,
pub name: String,
pub sub_modules: Vec<ModuleDocumentation>,
pub documentation: String,
pub items: Vec<DocItem>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ModuleMetadata {
pub doc: Option<String>,
pub functions: Option<Vec<FunctionMetadata>>,
pub custom_types: Option<Vec<CustomTypesMetadata>>,
pub modules: Option<serde_json::Map<String, serde_json::Value>>,
}
pub fn generate_module_documentation(
engine: &rhai::Engine,
options: &Options,
) -> Result<ModuleDocumentation, AutodocsError> {
let json_fns = engine
.gen_fn_metadata_to_json(options.include_standard_packages)
.map_err(|error| AutodocsError::Metadata(error.to_string()))?;
let metadata = serde_json::from_str::<ModuleMetadata>(&json_fns)
.map_err(|error| AutodocsError::Metadata(error.to_string()))?;
generate_module_documentation_inner(options, None, "global", &metadata)
}
fn generate_module_documentation_inner(
options: &Options,
namespace: Option<String>,
name: impl Into<String>,
metadata: &ModuleMetadata,
) -> Result<ModuleDocumentation, AutodocsError> {
let name = name.into();
let namespace = namespace.map_or(name.clone(), |namespace| namespace);
let documentation = metadata
.doc
.clone()
.map(|dc| DocItem::remove_test_code(&DocItem::fmt_doc_comments(dc)))
.unwrap_or_default();
let mut md = ModuleDocumentation {
namespace: namespace.clone(),
name,
documentation,
sub_modules: vec![],
items: vec![],
};
let mut items = vec![];
if let Some(types) = &metadata.custom_types {
for ty in types {
items.push(DocItem::new_custom_type(ty.clone(), &namespace, options)?);
}
}
if let Some(functions) = &metadata.functions {
for (name, polymorphisms) in group_functions(functions) {
if let Ok(doc_item) =
DocItem::new_function(&polymorphisms[..], &name, &namespace, options)
{
items.push(doc_item);
}
}
}
let items = items.into_iter().flatten().collect::<Vec<DocItem>>();
md.items = options.items_order.order_items(items);
if let Some(sub_modules) = &metadata.modules {
for (sub_module, value) in sub_modules {
md.sub_modules.push(generate_module_documentation_inner(
options,
Some(format!("{}/{}", namespace, sub_module)),
sub_module,
&serde_json::from_value::<ModuleMetadata>(value.clone())
.map_err(|error| AutodocsError::Metadata(error.to_string()))?,
)?);
}
}
Ok(md)
}
pub(crate) fn group_functions(
functions: &[FunctionMetadata],
) -> std::collections::HashMap<String, Vec<FunctionMetadata>> {
let mut function_groups = std::collections::HashMap::<String, Vec<FunctionMetadata>>::default();
functions.iter().for_each(|metadata| {
let name = metadata.generate_function_definition().name();
match function_groups.get_mut(&name) {
Some(polymorphisms) => polymorphisms.push(metadata.clone()),
None => {
function_groups.insert(name.to_string(), vec![metadata.clone()]);
}
};
});
function_groups
}
#[cfg(test)]
mod test {
use crate::{generate_for_docusaurus, module::options::ItemsOrder};
use super::*;
use rhai::plugin::*;
#[export_module]
mod my_module {
#[rhai_fn(global)]
pub fn hello_world() {
println!("Hello, World!");
}
#[rhai_fn(global)]
pub fn add(a: rhai::INT, b: rhai::INT) -> rhai::INT {
a + b
}
}
#[test]
fn test_order_by_index() {
let mut engine = rhai::Engine::new();
engine.register_static_module("my_module", rhai::exported_module!(my_module).into());
let docs = options::options()
.include_standard_packages(false)
.order_items_with(ItemsOrder::ByIndex)
.generate(&engine)
.expect("failed to generate documentation");
let docs = generate_for_docusaurus(&docs).unwrap();
pretty_assertions::assert_eq!(
docs.get("global")
.unwrap(),
"---\ntitle: global\nslug: /global\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n```Namespace: global```\n\n\n\n"
);
pretty_assertions::assert_eq!(
docs.get("my_module").unwrap(),
r#"---
title: my_module
slug: /my_module
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
```Namespace: global/my_module```
My own module.
## <code>fn</code> hello_world
```js
fn hello_world()
```
<Tabs>
<TabItem value="Description" default>
A function that prints to stdout.
</TabItem>
</Tabs>
## <code>fn</code> add
```js
fn add(a: int, b: int) -> int
```
<Tabs>
<TabItem value="Description" default>
A function that adds two integers together.
</TabItem>
</Tabs>
"#
);
}
}