diff --git a/hive-forge/src/verbs/pr_reviews.rs b/hive-forge/src/verbs/pr_reviews.rs index 91b9d2f8..4533f468 100644 --- a/hive-forge/src/verbs/pr_reviews.rs +++ b/hive-forge/src/verbs/pr_reviews.rs @@ -87,74 +87,94 @@ fn fetch_inline_comments(client: &Client, repo: &str, pr: u64, review_id: u64) - .unwrap_or_default() } -/// List all reviews for a PR, including inline diff comments per review. +/// List all reviews for a PR, dispatching to the appropriate output mode. fn list_reviews(client: &Client, number: u64) -> Result<()> { let repo = client.repo(); let v = client.get_json(&format!("/repos/{repo}/pulls/{number}/reviews"))?; let reviews = v.as_array().cloned().unwrap_or_default(); - if client.json_mode() { - let trimmed: Vec = reviews - .iter() - .map(|r| { - let id = r.get("id").and_then(Value::as_u64).unwrap_or(0); - let inline: Vec = if id > 0 { - fetch_inline_comments(client, repo, number, id) - .iter() - .map(|c| { - json!({ - "id": c.get("id"), - "path": c.get("path"), - "line": c.get("line"), - "body": c.get("body"), - }) - }) - .collect() - } else { - vec![] - }; - json!({ - "id": r.get("id"), - "state": r.get("state"), - "user": r.get("user").and_then(|u| u.get("login")), - "body": r.get("body"), - "comments_count": r.get("comments_count"), - "comments": inline, - }) - }) - .collect(); - print_json(&Value::Array(trimmed)) + list_reviews_json(client, repo, number, &reviews) } else { - if reviews.is_empty() { - println!("(no reviews)"); - return Ok(()); - } - for r in &reviews { - let id = r.get("id").and_then(Value::as_u64).unwrap_or(0); - let user = r - .get("user") - .and_then(|u| u.get("login")) - .and_then(Value::as_str) - .unwrap_or("?"); - let state = r.get("state").and_then(Value::as_str).unwrap_or("?"); - let body = r.get("body").and_then(Value::as_str).unwrap_or("").trim(); - println!("### review by {user} ({state})"); - if !body.is_empty() { - println!("{body}"); - } - if id > 0 { - for c in &fetch_inline_comments(client, repo, number, id) { - let path = c.get("path").and_then(Value::as_str).unwrap_or("?"); - let cbody = c.get("body").and_then(Value::as_str).unwrap_or("").trim(); - // PR-level comments have no line; omit `:line` when absent. - match c.get("line").and_then(Value::as_u64) { - Some(line) => println!(" [{path}:{line}] {cbody}"), - None => println!(" [{path}] {cbody}"), - } - } - } - println!(); - } - Ok(()) + list_reviews_text(client, repo, number, &reviews) } } + +/// JSON output: one object per review, with an inline `comments` array. +fn list_reviews_json( + client: &Client, + repo: &str, + number: u64, + reviews: &[Value], +) -> Result<()> { + let trimmed: Vec = reviews + .iter() + .map(|r| { + let id = r.get("id").and_then(Value::as_u64).unwrap_or(0); + let inline: Vec = if id > 0 { + fetch_inline_comments(client, repo, number, id) + .iter() + .map(|c| { + json!({ + "id": c.get("id"), + "path": c.get("path"), + "line": c.get("line"), + "body": c.get("body"), + }) + }) + .collect() + } else { + vec![] + }; + json!({ + "id": r.get("id"), + "state": r.get("state"), + "user": r.get("user").and_then(|u| u.get("login")), + "body": r.get("body"), + "comments_count": r.get("comments_count"), + "comments": inline, + }) + }) + .collect(); + print_json(&Value::Array(trimmed)) +} + +/// Human-readable output: Markdown-style heading per review, inline +/// comments as `[path:line] body` (line omitted for PR-level comments). +fn list_reviews_text( + client: &Client, + repo: &str, + number: u64, + reviews: &[Value], +) -> Result<()> { + if reviews.is_empty() { + println!("(no reviews)"); + return Ok(()); + } + for r in reviews { + let id = r.get("id").and_then(Value::as_u64).unwrap_or(0); + let user = r + .get("user") + .and_then(|u| u.get("login")) + .and_then(Value::as_str) + .unwrap_or("?"); + let state = r.get("state").and_then(Value::as_str).unwrap_or("?"); + let body = r.get("body").and_then(Value::as_str).unwrap_or("").trim(); + println!("### review by {user} ({state})"); + if !body.is_empty() { + println!("{body}"); + } + if id > 0 { + for c in &fetch_inline_comments(client, repo, number, id) { + let path = c.get("path").and_then(Value::as_str).unwrap_or("?"); + let cbody = c.get("body").and_then(Value::as_str).unwrap_or("").trim(); + // PR-level comments have no line; omit `:line` when absent. + match c.get("line").and_then(Value::as_u64) { + Some(line) => println!(" [{path}:{line}] {cbody}"), + None => println!(" [{path}] {cbody}"), + } + } + } + println!(); + } + Ok(()) +}