KeiSeiKit-1.0/_primitives/_rust/kei-changelog/src/render.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

74 lines
2.2 KiB
Rust

//! Render a `Grouped` set of commits as a CHANGELOG.md section.
use crate::group::Grouped;
use chrono::{DateTime, Utc};
/// Options governing the rendered section.
#[derive(Debug, Clone)]
pub struct RenderOpts {
/// Heading for the version block, e.g. "v0.7.0" or "Unreleased".
pub version: String,
/// Optional release date. If `None`, uses today (UTC).
pub date: Option<DateTime<Utc>>,
/// If true, include short (7-char) SHA suffix on each line.
pub include_sha: bool,
}
impl RenderOpts {
#[must_use]
pub fn new(version: impl Into<String>) -> Self {
Self {
version: version.into(),
date: None,
include_sha: true,
}
}
}
fn fmt_line(subj: &str, scope: Option<&str>, sha: Option<&str>) -> String {
let mut s = String::new();
s.push_str("- ");
if let Some(sc) = scope {
s.push_str("**");
s.push_str(sc);
s.push_str(":** ");
}
s.push_str(subj);
if let Some(h) = sha {
s.push_str(&format!(" (`{}`)", &h[..h.len().min(7)]));
}
s
}
/// Render the grouped commits into markdown. Returns an empty string if the
/// grouping has no entries (caller can detect via `Grouped::is_empty`).
#[must_use]
pub fn render_markdown(grouped: &Grouped, opts: &RenderOpts) -> String {
if grouped.is_empty() {
return String::new();
}
let date = opts.date.unwrap_or_else(Utc::now).format("%Y-%m-%d");
let mut out = String::new();
out.push_str(&format!("## {}{date}\n\n", opts.version));
if !grouped.breaking.is_empty() {
out.push_str("### BREAKING CHANGES\n\n");
for c in &grouped.breaking {
let sha = if opts.include_sha { Some(c.sha.as_str()) } else { None };
out.push_str(&fmt_line(&c.subject, c.scope.as_deref(), sha));
out.push('\n');
}
out.push('\n');
}
for (_, (kind, commits)) in &grouped.by_kind {
out.push_str(&format!("### {}\n\n", kind.heading()));
for c in commits {
let sha = if opts.include_sha { Some(c.sha.as_str()) } else { None };
out.push_str(&fmt_line(&c.subject, c.scope.as_deref(), sha));
out.push('\n');
}
out.push('\n');
}
out
}