use crate::{
Result,
ast::{Function, Item, Module, Param, TypeDecl, TypeRef, Validator, Variant},
parser::parse_module,
};
pub fn docs_source(file: &str, source: &str) -> Result<String> {
crate::compile_source(file, source)?;
let module = parse_module(file, source).map_err(|error| error.with_source(source))?;
Ok(render_module_docs(&module))
}
pub fn render_module_docs(module: &Module) -> String {
let mut docs = format!("# Module `{}`\n\n", module.name);
let types = module
.items
.iter()
.filter_map(|item| match item {
Item::Type(type_decl) => Some(type_decl),
_ => None,
})
.collect::<Vec<_>>();
if !types.is_empty() {
docs.push_str("## Types\n\n");
for type_decl in types {
docs.push_str(&format_type_decl(type_decl));
}
}
let constants = module
.items
.iter()
.filter_map(|item| match item {
Item::Const(constant) => Some(constant),
_ => None,
})
.collect::<Vec<_>>();
if !constants.is_empty() {
docs.push_str("## Constants\n\n");
for constant in constants {
docs.push_str(&format!(
"- `{}`: `{}`\n",
constant.name,
format_type_ref(&constant.ty)
));
}
docs.push('\n');
}
let functions = module
.items
.iter()
.filter_map(|item| match item {
Item::Function(function) => Some(function),
_ => None,
})
.collect::<Vec<_>>();
if !functions.is_empty() {
docs.push_str("## Functions\n\n");
for function in functions {
docs.push_str(&format_function(function));
}
docs.push('\n');
}
let validators = module
.items
.iter()
.filter_map(|item| match item {
Item::Validator(validator) => Some(validator),
_ => None,
})
.collect::<Vec<_>>();
if !validators.is_empty() {
docs.push_str("## Validators\n\n");
for validator in validators {
docs.push_str(&format_validator(validator));
}
docs.push('\n');
}
let tests = module
.items
.iter()
.filter_map(|item| match item {
Item::Test(test) => Some(test),
_ => None,
})
.collect::<Vec<_>>();
if !tests.is_empty() {
docs.push_str("## Tests\n\n");
for test in tests {
if test.should_fail {
docs.push_str(&format!("- `{}` (expected failure)\n", test.name));
} else {
docs.push_str(&format!("- `{}`\n", test.name));
}
}
docs.push('\n');
}
docs
}
fn format_type_decl(type_decl: &TypeDecl) -> String {
let mut docs = format!("### `{}`\n\n", type_decl.name);
for variant in &type_decl.variants {
docs.push_str(&format_variant(variant));
}
docs.push('\n');
docs
}
fn format_variant(variant: &Variant) -> String {
if variant.fields.is_empty() {
return format!("- `{}`\n", variant.name);
}
let fields = variant
.fields
.iter()
.map(|field| match &field.name {
Some(name) => format!("{name}: {}", format_type_ref(&field.ty)),
None => format_type_ref(&field.ty),
})
.collect::<Vec<_>>()
.join(", ");
format!("- `{}({fields})`\n", variant.name)
}
fn format_function(function: &Function) -> String {
format!(
"- `fn {}({}) -> {}`\n",
function.name,
format_params(&function.params),
format_type_ref(&function.return_type)
)
}
fn format_validator(validator: &Validator) -> String {
format!(
"- `validator {}({})`\n",
validator.name,
format_params(&validator.params)
)
}
fn format_params(params: &[Param]) -> String {
params
.iter()
.map(|param| format!("{}: {}", param.name, format_type_ref(¶m.ty)))
.collect::<Vec<_>>()
.join(", ")
}
fn format_type_ref(ty: &TypeRef) -> String {
if ty.args.is_empty() {
ty.name.clone()
} else {
let args = ty
.args
.iter()
.map(format_type_ref)
.collect::<Vec<_>>()
.join(", ");
format!("{}<{args}>", ty.name)
}
}