use super::{DocOptions, DocOutput};
use crate::types::{ArtDescriptor, ArtStatus, ArtVariant};
#[inline]
pub fn generate_component_doc(art: &ArtDescriptor<'_>, options: &DocOptions) -> DocOutput {
let mut md = String::with_capacity(4096);
md.push_str("# ");
md.push_str(art.metadata.title);
md.push_str("\n\n");
if art.metadata.status != ArtStatus::Ready {
md.push_str(&format_status_badge(art.metadata.status));
md.push_str("\n\n");
}
if let Some(desc) = art.metadata.description {
md.push_str(desc);
md.push_str("\n\n");
}
if options.include_metadata {
md.push_str(&generate_metadata_section(art));
}
if options.include_toc && art.variants.len() >= options.toc_threshold {
md.push_str(&generate_toc(&art.variants));
}
md.push_str("## Variants\n\n");
for variant in &art.variants {
md.push_str(&generate_variant_doc(variant, options));
}
if let Some(component) = art.metadata.component {
md.push_str("## Source\n\n");
md.push_str("```\n");
md.push_str(component);
md.push_str("\n```\n\n");
}
let filename = format!("{}.md", slugify(art.metadata.title));
DocOutput {
markdown: md,
filename,
title: art.metadata.title.to_string(),
category: art.metadata.category.map(|s| s.to_string()),
variant_count: art.variants.len(),
}
}
#[inline]
pub fn generate_variant_doc(variant: &ArtVariant<'_>, options: &DocOptions) -> String {
let mut md = String::with_capacity(512);
md.push_str("### ");
md.push_str(variant.name);
if variant.is_default {
md.push_str(" `default`");
}
if variant.skip_vrt {
md.push_str(" `skip-vrt`");
}
md.push_str("\n\n");
if let Some(ref viewport) = variant.viewport {
md.push_str(&format!(
"**Viewport:** {}x{}",
viewport.width, viewport.height
));
if let Some(scale) = viewport.device_scale_factor {
md.push_str(&format!(" @{:.1}x", scale));
}
md.push_str("\n\n");
}
if !variant.args.is_empty() {
md.push_str("**Args:**\n\n");
md.push_str("| Prop | Value |\n");
md.push_str("|------|-------|\n");
for (key, value) in &variant.args {
let value_str = match value {
serde_json::Value::String(s) => format!("`\"{}\"`", s),
serde_json::Value::Bool(b) => format!("`{}`", b),
serde_json::Value::Number(n) => format!("`{}`", n),
_ => format!("`{}`", value),
};
md.push_str(&format!("| `{}` | {} |\n", key, value_str));
}
md.push('\n');
}
if options.include_templates && !variant.template.is_empty() {
md.push_str("```vue\n");
md.push_str(variant.template);
md.push_str("\n```\n\n");
}
md.push_str("---\n\n");
md
}
fn generate_metadata_section(art: &ArtDescriptor<'_>) -> String {
let mut md = String::new();
let has_metadata = art.metadata.category.is_some()
|| !art.metadata.tags.is_empty()
|| art.metadata.order.is_some();
if !has_metadata {
return md;
}
md.push_str("| | |\n");
md.push_str("|---|---|\n");
if let Some(category) = art.metadata.category {
md.push_str(&format!("| **Category** | `{}` |\n", category));
}
if !art.metadata.tags.is_empty() {
let tags: Vec<String> = art
.metadata
.tags
.iter()
.map(|t| format!("`{}`", t))
.collect();
md.push_str(&format!("| **Tags** | {} |\n", tags.join(" ")));
}
if let Some(order) = art.metadata.order {
md.push_str(&format!("| **Order** | {} |\n", order));
}
md.push_str(&format!("| **Variants** | {} |\n", art.variants.len()));
md.push('\n');
md
}
fn generate_toc(variants: &[ArtVariant<'_>]) -> String {
let mut md = String::new();
md.push_str("## Table of Contents\n\n");
for variant in variants {
let anchor = slugify(variant.name);
md.push_str(&format!("- [{}](#{})", variant.name, anchor));
if variant.is_default {
md.push_str(" *(default)*");
}
md.push('\n');
}
md.push('\n');
md
}
fn format_status_badge(status: ArtStatus) -> String {
match status {
ArtStatus::Draft => "> **Status:** 🚧 Draft".to_string(),
ArtStatus::Deprecated => "> **Status:** ⚠️ Deprecated".to_string(),
ArtStatus::Ready => String::new(),
}
}
#[inline]
fn slugify(s: &str) -> String {
s.chars()
.map(|c| {
if c.is_alphanumeric() {
c.to_ascii_lowercase()
} else {
'-'
}
})
.collect::<String>()
.split('-')
.filter(|s| !s.is_empty())
.collect::<Vec<_>>()
.join("-")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slugify() {
assert_eq!(slugify("Hello World"), "hello-world");
assert_eq!(slugify("With Icon"), "with-icon");
assert_eq!(slugify("my-button"), "my-button");
assert_eq!(slugify("Button_Primary"), "button-primary");
}
#[test]
fn test_format_status_badge() {
assert!(format_status_badge(ArtStatus::Draft).contains("Draft"));
assert!(format_status_badge(ArtStatus::Deprecated).contains("Deprecated"));
assert!(format_status_badge(ArtStatus::Ready).is_empty());
}
}