use crate::library::Library;
use daipendency_extractor::Namespace;
pub fn generate_markdown_documentation(library: &Library) -> String {
let api_content = format_namespaces_content(
&library.namespaces,
&format!("{:?}", library.language).to_lowercase(),
);
format!(
r#"---
library_name: {name}
library_version: {version}
---
{documentation}
# API
{api_content}"#,
name = library.name,
version = library.version.as_deref().unwrap_or("null"),
documentation = library.documentation.trim(),
api_content = api_content
)
}
fn format_namespaces_content(namespaces: &[Namespace], language: &str) -> String {
namespaces
.iter()
.filter(|n| !n.symbols.is_empty())
.map(|n| format_namespace_content(n, language))
.collect::<Vec<_>>()
.join("\n")
}
fn format_namespace_content(namespace: &Namespace, language: &str) -> String {
let mut content = format!("## {}\n\n", namespace.name);
if !namespace.symbols.is_empty() {
let mut code_block = String::new();
if let Some(doc) = &namespace.doc_comment {
code_block.push_str(doc);
code_block.push('\n');
}
code_block.push_str(
&namespace
.symbols
.iter()
.map(|s| s.source_code.as_str())
.collect::<Vec<_>>()
.join("\n\n"),
);
content.push_str(&format!("```{}\n{}\n```\n", language, code_block));
}
content
}
#[cfg(test)]
mod tests {
use super::*;
use crate::languages::Language;
use assertables::assert_contains;
use daipendency_extractor::Symbol;
const STUB_LIBRARY_NAME: &str = "test-lib";
const STUB_LIBRARY_VERSION: &str = "1.0.0";
const STUB_DOCUMENTATION: &str = "Test documentation";
const STUB_LANGUAGE: Language = Language::Rust;
const STUB_LANGUAGE_STR: &str = "rust";
const STUB_SOURCE_CODE: &str = "SOURCE_CODE";
const STUB_MULTI_LINE_SOURCE_CODE: &str = "MULTI_LINE\nSOURCE_CODE";
const STUB_DOC_COMMENT: &str = "This is a doc comment";
fn create_library(namespaces: Vec<Namespace>) -> Library {
Library {
name: STUB_LIBRARY_NAME.to_string(),
version: Some(STUB_LIBRARY_VERSION.to_string()),
documentation: STUB_DOCUMENTATION.to_string(),
namespaces,
language: STUB_LANGUAGE,
}
}
mod metadata {
use super::*;
fn get_frontmatter_lines(documentation: String) -> Option<Vec<String>> {
let mut lines = documentation.lines();
(lines.next() == Some("---")).then(|| {
lines
.take_while(|&line| line != "---")
.map(String::from)
.collect()
})
}
#[test]
fn library_name() {
let library = create_library(vec![]);
let documentation = generate_markdown_documentation(&library);
let frontmatter_lines = get_frontmatter_lines(documentation).unwrap();
assert_contains!(
frontmatter_lines,
&format!("library_name: {STUB_LIBRARY_NAME}")
);
}
#[test]
fn with_library_version() {
let library = create_library(vec![]);
let documentation = generate_markdown_documentation(&library);
let frontmatter_lines = get_frontmatter_lines(documentation).unwrap();
assert_contains!(
frontmatter_lines,
&format!("library_version: {STUB_LIBRARY_VERSION}")
);
}
#[test]
fn without_library_version() {
let mut library = create_library(vec![]);
library.version = None;
let documentation = generate_markdown_documentation(&library);
let frontmatter_lines = get_frontmatter_lines(documentation).unwrap();
assert_contains!(frontmatter_lines, &"library_version: null".to_string());
}
#[test]
fn library_documentation() {
let library = create_library(vec![]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(
documentation,
&format!("\n---\n\n{STUB_DOCUMENTATION}\n\n# API")
);
}
}
mod api {
use super::*;
fn create_namespace(
name: &str,
symbols: Vec<Symbol>,
doc_comment: Option<&str>,
) -> Namespace {
Namespace {
name: name.to_string(),
symbols,
doc_comment: doc_comment.map(String::from),
}
}
fn create_symbol(name: &str, source_code: &str) -> Symbol {
Symbol {
name: name.to_string(),
source_code: source_code.to_string(),
}
}
fn assert_api_is_empty(documentation: &str) {
let api_content = documentation.split("\n# API\n").nth(1).unwrap_or("").trim();
assert_eq!(api_content, "");
}
#[test]
fn no_namespaces() {
let library = create_library(vec![]);
let documentation = generate_markdown_documentation(&library);
assert_api_is_empty(&documentation);
}
#[test]
fn single_namespace() {
let namespace = create_namespace(
"test",
vec![create_symbol("symbol", STUB_SOURCE_CODE)],
None,
);
let namespace_name = namespace.name.clone();
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(
documentation,
&format!(
"## {}\n\n```{}\n{}\n```",
namespace_name, STUB_LANGUAGE_STR, STUB_SOURCE_CODE
)
);
}
#[test]
fn multiple_namespaces() {
let namespace1 = create_namespace(
"test1",
vec![create_symbol("symbol1", STUB_SOURCE_CODE)],
None,
);
let namespace2 = create_namespace(
"test2",
vec![create_symbol("symbol2", STUB_SOURCE_CODE)],
None,
);
let namespace1_name = namespace1.name.clone();
let namespace2_name = namespace2.name.clone();
let library = create_library(vec![namespace1, namespace2]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(
documentation,
&format!("## {}\n\n```{}\n", namespace1_name, STUB_LANGUAGE_STR)
);
assert_contains!(
documentation,
&format!("## {}\n\n```{}\n", namespace2_name, STUB_LANGUAGE_STR)
);
}
mod symbols {
use super::*;
#[test]
fn namespace_without_symbols() {
let namespace = create_namespace("test", vec![], None);
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_api_is_empty(&documentation);
}
#[test]
fn single_symbol() {
let namespace = create_namespace(
"test",
vec![create_symbol("symbol", STUB_SOURCE_CODE)],
None,
);
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(documentation, &format!("```{}\n", STUB_LANGUAGE_STR));
assert_contains!(documentation, STUB_SOURCE_CODE);
assert_contains!(documentation, "\n```");
}
#[test]
fn multiple_symbols() {
let namespace = create_namespace(
"test",
vec![
create_symbol("symbol1", "FIRST"),
create_symbol("symbol2", "SECOND"),
],
None,
);
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(documentation, &format!("```{}\n", STUB_LANGUAGE_STR));
assert_contains!(documentation, "FIRST\n\nSECOND\n");
assert_contains!(documentation, "```\n");
}
#[test]
fn single_line_symbol() {
let namespace = create_namespace(
"test",
vec![create_symbol("symbol", STUB_SOURCE_CODE)],
None,
);
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(documentation, &format!("```{}\n", STUB_LANGUAGE_STR));
assert_contains!(documentation, STUB_SOURCE_CODE);
assert_contains!(documentation, "\n```");
}
#[test]
fn multi_line_symbol() {
let namespace = create_namespace(
"test",
vec![create_symbol("symbol", STUB_MULTI_LINE_SOURCE_CODE)],
None,
);
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(documentation, &format!("```{}\n", STUB_LANGUAGE_STR));
assert_contains!(documentation, STUB_MULTI_LINE_SOURCE_CODE);
assert_contains!(documentation, "\n```");
}
#[test]
fn namespace_without_doc_comment() {
let namespace = create_namespace(
"test",
vec![create_symbol("symbol", STUB_SOURCE_CODE)],
None,
);
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(
documentation,
&format!("```{}\n{}\n```", STUB_LANGUAGE_STR, STUB_SOURCE_CODE)
);
}
#[test]
fn namespace_with_doc_comment() {
let namespace = create_namespace(
"test",
vec![create_symbol("symbol", STUB_SOURCE_CODE)],
Some(STUB_DOC_COMMENT),
);
let library = create_library(vec![namespace]);
let documentation = generate_markdown_documentation(&library);
assert_contains!(
documentation,
&format!(
"```{}\n{}\n{}\n```",
STUB_LANGUAGE_STR, STUB_DOC_COMMENT, STUB_SOURCE_CODE
)
);
}
}
}
}