feat: add markdown renderer for review documents

This commit is contained in:
2026-04-08 22:49:10 +08:00
parent f4b21c182f
commit 75e3239a16
4 changed files with 229 additions and 0 deletions
+1
View File
@@ -2,6 +2,7 @@ pub mod cli;
pub mod error;
pub mod model;
pub mod output;
pub mod render;
pub fn run() -> anyhow::Result<()> {
Ok(())
+109
View File
@@ -0,0 +1,109 @@
use crate::model::{CommentThread, PrReviewDocument};
fn backtick_fence(body: &str) -> String {
let mut max_run = 0usize;
let mut current_run = 0usize;
for ch in body.chars() {
if ch == '`' {
current_run += 1;
max_run = max_run.max(current_run);
} else {
current_run = 0;
}
}
let fence_len = std::cmp::max(3, max_run + 1);
"`".repeat(fence_len)
}
fn render_body(body: &str) -> String {
let fence = backtick_fence(body);
format!("{fence}md\n{body}\n{fence}\n")
}
fn render_thread(thread_index: usize, thread: &CommentThread) -> String {
let mut out = String::new();
out.push_str(&format!("### Comment {}.1\n", thread_index));
if let Some(file_path) = &thread.file_path {
if let Some(line) = thread.line {
out.push_str(&format!("{file_path}:{line}\n"));
} else {
out.push_str(&format!("{file_path}\n"));
}
}
out.push_str(&format!("{}:\n", thread.root_comment.user));
out.push_str(&render_body(&thread.root_comment.body));
for (reply_index, reply) in thread.replies.iter().enumerate() {
let reply_number = format!("{thread_index}.1.{}", reply_index + 1);
out.push_str(&format!("\n### Reply {reply_number}\n"));
out.push_str(&format!("{}:\n", reply.user));
out.push_str(&render_body(&reply.body));
}
out
}
pub fn render_markdown(doc: &PrReviewDocument) -> String {
let mut out = String::new();
out.push_str(&format!(
"# {} `#{}` {}\n\n",
doc.meta.repo, doc.meta.pr_index, doc.meta.title
));
out.push_str("## Metadata\n\n");
out.push_str(&format!("- state: {}\n", doc.meta.state));
out.push_str(&format!("- author: {}\n", doc.meta.author));
out.push_str(&format!("- base branch: {}\n", doc.meta.base_branch));
out.push_str(&format!("- head branch: {}\n", doc.meta.head_branch));
out.push_str(&format!("- created at: {}\n", doc.meta.created_at));
out.push_str(&format!("- updated at: {}\n", doc.meta.updated_at));
out.push_str(&format!(
"- merged at: {}\n",
doc.meta.merged_at.as_deref().unwrap_or("null")
));
out.push_str("\n");
out.push_str("## Commits\n\n");
for commit in &doc.commits {
out.push_str(&format!(
"- {} {} ({}, {})\n",
commit.short_sha, commit.title, commit.author, commit.date
));
}
out.push_str("\n");
out.push_str("## Diff Stat\n\n");
out.push_str(&format!(
"- files changed: {}\n- additions: {}\n- deletions: {}\n",
doc.diff_stat.files_changed, doc.diff_stat.additions, doc.diff_stat.deletions
));
for file in &doc.diff_stat.files {
out.push_str(&format!(
"- {}: +{}, -{}\n",
file.path, file.additions, file.deletions
));
}
out.push_str("\n");
for (review_index, review) in doc.reviews.iter().enumerate() {
out.push_str(&format!(
"## Review {} ({})\n\n",
review_index + 1,
review.state
));
out.push_str(&format!("> {}\n\n", review.reviewer));
if let Some(submitted_at) = &review.submitted_at {
out.push_str(&format!("- submitted at: {}\n\n", submitted_at));
}
}
for (thread_index, thread) in doc.threads.iter().enumerate() {
out.push_str(&render_thread(thread_index + 1, thread));
out.push('\n');
}
out.trim_end().to_string()
}
+1
View File
@@ -0,0 +1 @@
pub mod markdown;