use colored::*;
use diplomat_core::Env;
use std::fmt::Write;
use std::fs;
use std::path::PathBuf;
use std::{collections::HashMap, fmt};
use diplomat_core::ast;
use indenter::indented;
use crate::cpp::{config::LibraryConfig, structs::gen_method_interface, types::gen_type};
use crate::docs_util::{CppRst, FromMarkdown};
pub fn gen_docs(
env: &Env,
library_config_path: &Option<PathBuf>,
outs: &mut HashMap<String, String>,
docs_url_gen: &ast::DocsUrlGenerator,
) -> fmt::Result {
let mut library_config = LibraryConfig::default();
if let Some(path) = library_config_path {
if let Ok(contents) = fs::read_to_string(path) {
match toml::from_str(&contents) {
Ok(config) => library_config = config,
Err(err) => {
eprintln!(
"{}Unable to parse library configuration file: {:?}\n{}",
"Error: ".red().bold(),
path,
err,
);
std::process::exit(1);
}
}
}
}
let index_out = outs
.entry("index.rst".to_string())
.or_insert_with(String::new);
writeln!(index_out, "Documentation")?;
writeln!(index_out, "=============")?;
writeln!(index_out)?;
writeln!(index_out, ".. toctree::")?;
let mut toctree_indent = indented(index_out).with_str(" ");
writeln!(&mut toctree_indent, ":maxdepth: 3")?;
writeln!(&mut toctree_indent, ":caption: Modules:")?;
writeln!(&mut toctree_indent)?;
for (in_path, module) in env.iter_modules() {
if module
.items()
.any(|k| matches!(k, ast::ModSymbol::CustomType(_)))
{
writeln!(&mut toctree_indent, "{}", in_path.elements.join("_"))?;
}
}
writeln!(index_out)?;
writeln!(index_out, "Indices and tables")?;
writeln!(index_out, "==================")?;
writeln!(index_out)?;
writeln!(index_out, "* :ref:`genindex`")?;
writeln!(index_out, "* :ref:`search`")?;
for (in_path, module) in env.iter_modules() {
if module
.items()
.any(|k| matches!(k, ast::ModSymbol::CustomType(_)))
{
let out = outs
.entry(format!("{}.rst", in_path.elements.join("_")))
.or_insert_with(String::new);
let title = format!("``{}``", in_path.elements.join("::"));
writeln!(out, "{}", title)?;
writeln!(out, "{}", "=".repeat(title.len()))?;
for item in module.items() {
if let ast::ModSymbol::CustomType(ref typ) = item {
writeln!(out)?;
gen_custom_type_docs(out, typ, in_path, env, &library_config, docs_url_gen)?;
}
}
}
}
Ok(())
}
pub fn gen_custom_type_docs<W: fmt::Write>(
out: &mut W,
typ: &ast::CustomType,
in_path: &ast::Path,
env: &Env,
library_config: &LibraryConfig,
docs_url_gen: &ast::DocsUrlGenerator,
) -> fmt::Result {
match typ {
ast::CustomType::Struct(_) => writeln!(out, ".. cpp:struct:: {}", typ.name())?,
ast::CustomType::Enum(_) => writeln!(out, ".. cpp:enum-struct:: {}", typ.name())?,
ast::CustomType::Opaque(_) => writeln!(out, ".. cpp:class:: {}", typ.name())?,
}
let mut class_indented = indented(out).with_str(" ");
if !typ.docs().is_empty() {
CppRst::from_markdown(
&typ.docs()
.to_markdown(docs_url_gen, ast::MarkdownStyle::RstCompat),
in_path,
env,
&mut class_indented,
)?;
writeln!(class_indented)?;
}
if let ast::CustomType::Struct(strct) = typ {
for field in strct.fields.iter() {
writeln!(&mut class_indented)?;
gen_field_docs(
&mut class_indented,
field,
in_path,
env,
library_config,
docs_url_gen,
)?;
}
} else if let ast::CustomType::Enum(enm) = typ {
for variant in &enm.variants {
writeln!(&mut class_indented)?;
gen_enum_variant_docs(&mut class_indented, variant, in_path, env, docs_url_gen)?;
}
}
for method in typ.methods() {
writeln!(&mut class_indented)?;
gen_method_docs(
method,
typ,
in_path,
true,
env,
library_config,
docs_url_gen,
&mut class_indented,
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn gen_method_docs<W: fmt::Write>(
method: &ast::Method,
enclosing_type: &ast::CustomType,
in_path: &ast::Path,
writeable_to_string: bool,
env: &Env,
library_config: &LibraryConfig,
docs_url_gen: &ast::DocsUrlGenerator,
out: &mut W,
) -> fmt::Result {
let rearranged_writeable = method.is_writeable_out() && writeable_to_string;
let has_writeable_param = method.has_writeable_param() && !writeable_to_string;
if rearranged_writeable {
gen_method_docs(
method,
enclosing_type,
in_path,
false,
env,
library_config,
docs_url_gen,
out,
)?;
writeln!(out)?;
}
write!(out, ".. cpp:function:: ")?;
let _ = gen_method_interface(
method,
enclosing_type,
in_path,
true,
has_writeable_param,
rearranged_writeable,
env,
library_config,
out,
writeable_to_string,
)?;
writeln!(out)?;
let docs =
gen_docs_and_lifetime_notes_markdown(method, docs_url_gen, ast::MarkdownStyle::RstCompat);
if !docs.is_empty() {
CppRst::from_markdown(&docs, in_path, env, &mut indented(out).with_str(" "))?;
}
writeln!(out)
}
pub fn gen_docs_and_lifetime_notes_markdown(
method: &ast::Method,
docs_url_gen: &ast::DocsUrlGenerator,
style: ast::MarkdownStyle,
) -> String {
let mut docs = if !method.docs.is_empty() {
method.docs.to_markdown(docs_url_gen, style)
} else {
String::new()
};
let borrowed_params = method.borrowed_params();
if !borrowed_params.is_empty() {
if !docs.is_empty() {
writeln!(docs).unwrap();
writeln!(docs).unwrap();
}
write!(docs, "Lifetimes:").unwrap();
let mut return_names = borrowed_params.return_names(&ast::Ident::THIS);
if let Some(first) = return_names.next() {
write!(docs, " `{}`", first).unwrap();
for param in return_names {
write!(docs, ", `{}`", param).unwrap();
}
writeln!(docs, " must live at least as long as the output.").unwrap();
}
let mut static_names = borrowed_params.static_names();
if let Some(first) = static_names.next() {
write!(docs, " `{}`", first).unwrap();
for param in static_names {
write!(docs, ", `{}`", param).unwrap();
}
writeln!(docs, " must live for the duration of the program.").unwrap();
}
}
docs
}
pub fn gen_field_docs<W: fmt::Write>(
out: &mut W,
field: &(ast::Ident, ast::TypeName, ast::Docs),
in_path: &ast::Path,
env: &Env,
library_config: &LibraryConfig,
docs_url_gen: &ast::DocsUrlGenerator,
) -> fmt::Result {
let ty_name = gen_type(&field.1, in_path, None, env, library_config, true)?;
writeln!(out, ".. cpp:member:: {} {}", ty_name, field.0)?;
if !field.2.is_empty() {
let mut field_indented = indented(out).with_str(" ");
CppRst::from_markdown(
&field
.2
.to_markdown(docs_url_gen, ast::MarkdownStyle::RstCompat),
in_path,
env,
&mut field_indented,
)?;
writeln!(field_indented)?;
}
Ok(())
}
pub fn gen_enum_variant_docs<W: fmt::Write>(
out: &mut W,
variant: &(ast::Ident, isize, ast::Docs),
in_path: &ast::Path,
env: &Env,
docs_url_gen: &ast::DocsUrlGenerator,
) -> fmt::Result {
write!(out, ".. cpp:enumerator:: {}", variant.0)?;
writeln!(out)?;
if !variant.2.is_empty() {
let mut enum_indented = indented(out).with_str(" ");
CppRst::from_markdown(
&variant
.2
.to_markdown(docs_url_gen, ast::MarkdownStyle::RstCompat),
in_path,
env,
&mut enum_indented,
)?;
writeln!(enum_indented)?;
}
Ok(())
}