Add documentation to Module.

This commit is contained in:
Stephen Chung 2022-07-25 17:42:15 +08:00
parent 276d4457c3
commit 492d79d8a2
4 changed files with 127 additions and 14 deletions

View File

@ -17,6 +17,7 @@ New features
* A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most). * A new feature, `no_custom_syntax`, is added to remove custom syntax support from Rhai for applications that do not require it (which should be most).
* Comment lines beginning with `//!` (requires the `metadata` feature) are now collected as the script file's _module documentation_. * Comment lines beginning with `//!` (requires the `metadata` feature) are now collected as the script file's _module documentation_.
* `AST` and `Module` have methods to access and manipulate documentation.
Enhancements Enhancements
------------ ------------

View File

@ -195,6 +195,7 @@ impl AST {
/// Leading white-spaces are stripped, and each line always starts with `//!`. /// Leading white-spaces are stripped, and each line always starts with `//!`.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline(always)] #[inline(always)]
#[must_use]
pub fn doc(&self) -> &str { pub fn doc(&self) -> &str {
&self.doc &self.doc
} }
@ -211,6 +212,7 @@ impl AST {
/// Only available under `metadata`. /// Only available under `metadata`.
#[cfg(feature = "metadata")] #[cfg(feature = "metadata")]
#[inline(always)] #[inline(always)]
#[must_use]
pub(crate) fn doc_mut(&mut self) -> &mut SmartString { pub(crate) fn doc_mut(&mut self) -> &mut SmartString {
&mut self.doc &mut self.doc
} }

View File

@ -250,6 +250,9 @@ pub struct Module {
/// ID identifying the module. /// ID identifying the module.
/// No ID if string is empty. /// No ID if string is empty.
id: Identifier, id: Identifier,
/// Module documentation.
#[cfg(feature = "metadata")]
doc: crate::SmartString,
/// Is this module internal? /// Is this module internal?
pub(crate) internal: bool, pub(crate) internal: bool,
/// Is this module part of a standard library? /// Is this module part of a standard library?
@ -290,31 +293,27 @@ impl fmt::Debug for Module {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Module"); let mut d = f.debug_struct("Module");
if !self.id.is_empty() { d.field("id", &self.id)
d.field("id", &self.id); .field(
}
if !self.modules.is_empty() {
d.field(
"modules", "modules",
&self &self
.modules .modules
.keys() .keys()
.map(|m| m.as_str()) .map(|m| m.as_str())
.collect::<BTreeSet<_>>(), .collect::<BTreeSet<_>>(),
); )
} .field("vars", &self.variables)
if !self.variables.is_empty() { .field(
d.field("vars", &self.variables);
}
if !self.functions.is_empty() {
d.field(
"functions", "functions",
&self &self
.iter_fn() .iter_fn()
.map(|f| f.func.to_string()) .map(|f| f.func.to_string())
.collect::<BTreeSet<_>>(), .collect::<BTreeSet<_>>(),
); );
}
#[cfg(feature = "metadata")]
d.field("doc", &self.doc);
d.finish() d.finish()
} }
} }
@ -363,6 +362,8 @@ impl Module {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
id: Identifier::new_const(), id: Identifier::new_const(),
#[cfg(feature = "metadata")]
doc: crate::SmartString::new_const(),
internal: false, internal: false,
standard: false, standard: false,
custom_types: CustomTypesCollection::new(), custom_types: CustomTypesCollection::new(),
@ -423,6 +424,7 @@ impl Module {
self.id = id.into(); self.id = id.into();
self self
} }
/// Clear the ID of the [`Module`]. /// Clear the ID of the [`Module`].
/// ///
/// # Example /// # Example
@ -441,10 +443,68 @@ impl Module {
self self
} }
/// Get the documentation of the [`Module`], if any.
/// Exported under the `metadata` feature only.
///
/// # Example
///
/// ```
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_doc("//! This is my special module.");
/// assert_eq!(module.doc(), "//! This is my special module.");
/// ```
#[cfg(feature = "metadata")]
#[inline]
#[must_use]
pub fn doc(&self) -> &str {
&self.doc
}
/// Set the documentation of the [`Module`].
/// Exported under the `metadata` feature only.
///
/// If the string is empty, it is equivalent to clearing the documentation.
///
/// # Example
///
/// ```
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_doc("//! This is my special module.");
/// assert_eq!(module.doc(), "//! This is my special module.");
/// ```
#[cfg(feature = "metadata")]
#[inline(always)]
pub fn set_doc(&mut self, doc: impl Into<crate::SmartString>) -> &mut Self {
self.doc = doc.into();
self
}
/// Clear the documentation of the [`Module`].
///
/// # Example
///
/// ```
/// # use rhai::Module;
/// let mut module = Module::new();
/// module.set_doc("//! This is my special module.");
/// assert_eq!(module.doc(), "//! This is my special module.");
/// module.clear_id();
/// assert_eq!(module.doc(), "");
/// ```
#[cfg(feature = "metadata")]
#[inline(always)]
pub fn clear_doc(&mut self) -> &mut Self {
self.doc.clear();
self
}
/// Clear the [`Module`]. /// Clear the [`Module`].
#[inline(always)] #[inline(always)]
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.id.clear(); self.id.clear();
self.doc.clear();
self.internal = false; self.internal = false;
self.standard = false; self.standard = false;
self.custom_types.clear(); self.custom_types.clear();
@ -1563,6 +1623,15 @@ impl Module {
self.all_type_iterators.clear(); self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self.contains_indexed_global_functions = false; self.contains_indexed_global_functions = false;
#[cfg(feature = "metadata")]
if !other.doc.is_empty() {
if !self.doc.is_empty() {
self.doc.push('\n');
}
self.doc.push_str(&other.doc);
}
self self
} }
@ -1584,6 +1653,15 @@ impl Module {
self.all_type_iterators.clear(); self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self.contains_indexed_global_functions = false; self.contains_indexed_global_functions = false;
#[cfg(feature = "metadata")]
if !other.doc.is_empty() {
if !self.doc.is_empty() {
self.doc.push('\n');
}
self.doc.push_str(&other.doc);
}
self self
} }
@ -1614,6 +1692,15 @@ impl Module {
self.all_type_iterators.clear(); self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self.contains_indexed_global_functions = false; self.contains_indexed_global_functions = false;
#[cfg(feature = "metadata")]
if !other.doc.is_empty() {
if !self.doc.is_empty() {
self.doc.push('\n');
}
self.doc.push_str(&other.doc);
}
self self
} }
@ -1667,6 +1754,15 @@ impl Module {
self.all_type_iterators.clear(); self.all_type_iterators.clear();
self.indexed = false; self.indexed = false;
self.contains_indexed_global_functions = false; self.contains_indexed_global_functions = false;
#[cfg(feature = "metadata")]
if !other.doc.is_empty() {
if !self.doc.is_empty() {
self.doc.push('\n');
}
self.doc.push_str(&other.doc);
}
self self
} }
@ -1978,6 +2074,9 @@ impl Module {
module.set_id(ast.source_raw().clone()); module.set_id(ast.source_raw().clone());
#[cfg(feature = "metadata")]
module.set_doc(ast.doc());
module.build_index(); module.build_index();
Ok(module) Ok(module)

View File

@ -3,7 +3,7 @@
#![cfg(feature = "metadata")] #![cfg(feature = "metadata")]
use crate::module::{calc_native_fn_hash, FuncInfo}; use crate::module::{calc_native_fn_hash, FuncInfo};
use crate::{calc_fn_hash, Engine, AST}; use crate::{calc_fn_hash, Engine, SmartString, AST};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -158,6 +158,9 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct ModuleMetadata<'a> { struct ModuleMetadata<'a> {
#[cfg(feature = "metadata")]
#[serde(skip_serializing_if = "SmartString::is_empty")]
pub doc: SmartString,
#[serde(skip_serializing_if = "BTreeMap::is_empty")] #[serde(skip_serializing_if = "BTreeMap::is_empty")]
pub modules: BTreeMap<&'a str, Self>, pub modules: BTreeMap<&'a str, Self>,
#[serde(skip_serializing_if = "Vec::is_empty")] #[serde(skip_serializing_if = "Vec::is_empty")]
@ -168,6 +171,8 @@ impl ModuleMetadata<'_> {
#[inline(always)] #[inline(always)]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
#[cfg(feature = "metadata")]
doc: SmartString::new_const(),
modules: BTreeMap::new(), modules: BTreeMap::new(),
functions: Vec::new(), functions: Vec::new(),
} }
@ -180,6 +185,7 @@ impl<'a> From<&'a crate::Module> for ModuleMetadata<'a> {
functions.sort(); functions.sort();
Self { Self {
doc: module.doc().into(),
modules: module modules: module
.iter_sub_modules() .iter_sub_modules()
.map(|(name, m)| (name, m.as_ref().into())) .map(|(name, m)| (name, m.as_ref().into()))
@ -240,6 +246,11 @@ impl Engine {
global.functions.sort(); global.functions.sort();
#[cfg(feature = "metadata")]
{
global.doc = ast.doc().into();
}
serde_json::to_string_pretty(&global) serde_json::to_string_pretty(&global)
} }