use std::fmt::Write;
use tatara_lisp::domain::{
lookup_compliance, lookup_deps, lookup_doc, lookup_help, lookup_lifecycle,
lookup_observability, lookup_render, lookup_stability, registered_doc_keywords,
registered_keywords,
};
#[must_use]
pub fn render_catalog() -> String {
let mut keywords = registered_keywords();
keywords.sort_unstable();
let mut out = String::new();
let _ = writeln!(out, "# tatara catalog");
let _ = writeln!(out);
let _ = writeln!(
out,
"Auto-generated from the live tatara domain registries — \
compile / render / doc capabilities. {} domain(s) registered.",
keywords.len()
);
let _ = writeln!(out);
if keywords.is_empty() {
let _ = writeln!(
out,
"_No domains registered yet. Call your domain crates' \
`register()` functions before generating the catalog._"
);
return out;
}
let _ = writeln!(out, "## Index");
let _ = writeln!(out);
for kw in &keywords {
let _ = writeln!(out, "- [`{kw}`](#{anchor})", anchor = anchor(kw));
}
let _ = writeln!(out);
for kw in &keywords {
render_section(&mut out, kw);
}
out
}
#[must_use]
pub fn render_one(keyword: &str) -> String {
let mut out = String::new();
render_section(&mut out, keyword);
out
}
#[must_use]
pub fn list_keywords() -> Vec<&'static str> {
let mut v = registered_keywords();
v.sort_unstable();
v
}
fn render_section(out: &mut String, kw: &str) {
let _ = writeln!(out, "## `{kw}` <a id=\"{anchor}\"></a>", anchor = anchor(kw));
let _ = writeln!(out);
if let Some(s) = lookup_stability(kw) {
let _ = writeln!(
out,
"_{} since {}_",
s.stability, s.since_version
);
let _ = writeln!(out);
}
if let Some(r) = lookup_render(kw) {
let _ = writeln!(
out,
"**Renders to**: `{}` / `{}` (Kubernetes CR)",
r.api_version, r.kind
);
let _ = writeln!(out);
}
if let Some(l) = lookup_lifecycle(kw) {
let _ = writeln!(
out,
"**Rollout**: `{:?}`, drain {}s",
l.strategy, l.drain_seconds
);
let _ = writeln!(out);
}
if let Some(d) = lookup_doc(kw) {
if !d.docstring.is_empty() {
let _ = writeln!(out, "{}", d.docstring);
let _ = writeln!(out);
}
if !d.field_docs.is_empty() {
let _ = writeln!(out, "### Fields");
let _ = writeln!(out);
let _ = writeln!(out, "| Field | Description |");
let _ = writeln!(out, "|---|---|");
for (name, doc) in d.field_docs {
let kebab = snake_to_kebab(name);
let _ = writeln!(out, "| `:{kebab}` | {} |", doc.replace('|', "\\|"));
}
let _ = writeln!(out);
}
}
if let Some(dp) = lookup_deps(kw) {
if !dp.depends_on.is_empty() {
let deps: Vec<String> = dp.depends_on.iter().map(|k| format!("`{k}`")).collect();
let _ = writeln!(out, "**Depends on**: {}", deps.join(", "));
let _ = writeln!(out);
}
}
if let Some(c) = lookup_compliance(kw) {
if !c.frameworks.is_empty() || !c.controls.is_empty() {
let _ = writeln!(out, "### Compliance");
let _ = writeln!(out);
if !c.frameworks.is_empty() {
let fws: Vec<String> = c.frameworks.iter().map(|f| format!("`{f}`")).collect();
let _ = writeln!(out, "- Frameworks: {}", fws.join(", "));
}
if !c.controls.is_empty() {
let cs: Vec<String> = c.controls.iter().map(|c| format!("`{c}`")).collect();
let _ = writeln!(out, "- Controls: {}", cs.join(", "));
}
let _ = writeln!(out);
}
}
if let Some(o) = lookup_observability(kw) {
if !o.metric_prefix.is_empty() || !o.log_labels.is_empty() {
let _ = writeln!(out, "### Observability");
let _ = writeln!(out);
if !o.metric_prefix.is_empty() {
let _ = writeln!(out, "- Metric prefix: `{}`", o.metric_prefix);
}
if !o.log_labels.is_empty() {
let labels: Vec<String> =
o.log_labels.iter().map(|l| format!("`{l}`")).collect();
let _ = writeln!(out, "- Log labels: {}", labels.join(", "));
}
let _ = writeln!(out);
}
}
if let Some(h) = lookup_help(kw) {
if !h.mnemonic.is_empty() {
let _ = writeln!(out, "_{}_", h.mnemonic);
let _ = writeln!(out);
}
if !h.examples.is_empty() {
let _ = writeln!(out, "### Examples");
let _ = writeln!(out);
for ex in h.examples {
let _ = writeln!(out, "```lisp");
let _ = writeln!(out, "{ex}");
let _ = writeln!(out, "```");
let _ = writeln!(out);
}
}
}
}
fn anchor(s: &str) -> String {
s.to_lowercase()
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '-' })
.collect()
}
fn snake_to_kebab(s: &str) -> String {
s.replace('_', "-")
}
#[must_use]
pub fn fully_registered_keywords() -> Vec<&'static str> {
let mut v: Vec<&'static str> = registered_doc_keywords()
.into_iter()
.filter(|kw| lookup_render(kw).is_some())
.collect();
v.sort_unstable();
v
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_registry_renders_a_helpful_page() {
let s = render_catalog();
assert!(s.contains("# tatara catalog"));
}
#[test]
fn sorts_keywords_deterministically() {
let mut v: Vec<&'static str> = vec!["zebra", "alpha", "monitor"];
v.sort_unstable();
assert_eq!(v[0], "alpha");
}
}