hive-forge: comments --tail N flag (#694 part 3) — last N comments in chronological order
This commit is contained in:
parent
f7e38c0b42
commit
5f4494c239
1 changed files with 71 additions and 26 deletions
|
|
@ -1,6 +1,17 @@
|
||||||
//! `comments <number> [--limit N]` — list all comments on an issue
|
//! `comments <number> [--limit N | --tail N]` — list comments on an
|
||||||
//! or PR. Closes the curl-fallback gap (#418). Use the global
|
//! issue or PR. Closes the curl-fallback gap (#418); `--tail`
|
||||||
//! `--json` flag for JSON output (#421).
|
//! closes the third of the four #694 gaps (paging-for-long-threads
|
||||||
|
//! awkwardness).
|
||||||
|
//!
|
||||||
|
//! - `--limit N` (default 50, Forgejo's cap) returns the first N
|
||||||
|
//! comments — same shape this verb has always had.
|
||||||
|
//! - `--tail N` returns the *last* N comments in chronological
|
||||||
|
//! order. Internally pages through every comment on the issue,
|
||||||
|
//! then slices the tail. Use this for "what was the conclusion
|
||||||
|
//! on this long thread?" without scrolling through the whole
|
||||||
|
//! history.
|
||||||
|
//!
|
||||||
|
//! Use the global `--json` flag for JSON output (#421).
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Args as ClapArgs;
|
use clap::Args as ClapArgs;
|
||||||
|
|
@ -9,42 +20,52 @@ use serde_json::{Value, json};
|
||||||
use crate::client::Client;
|
use crate::client::Client;
|
||||||
use crate::verbs::print_json;
|
use crate::verbs::print_json;
|
||||||
|
|
||||||
|
/// Hard cap on `--tail` pagination so a confused caller can't
|
||||||
|
/// accidentally paginate forever on a runaway-comment thread.
|
||||||
|
/// 20 pages × Forgejo's 50-per-page cap = 1000 comments which
|
||||||
|
/// covers every issue in the hyperhive repo today with headroom.
|
||||||
|
/// Tune up if a future thread breaches it (the truncated tail
|
||||||
|
/// will surface as a smaller-than-expected slice).
|
||||||
|
const TAIL_MAX_PAGES: u32 = 20;
|
||||||
|
|
||||||
#[derive(ClapArgs)]
|
#[derive(ClapArgs)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
/// Issue or PR number.
|
/// Issue or PR number.
|
||||||
number: u64,
|
number: u64,
|
||||||
/// Page size (Forgejo caps at 50 by default).
|
/// Page size for the head-of-thread shape (Forgejo caps at 50).
|
||||||
#[arg(long, default_value_t = 50)]
|
/// Mutually exclusive with `--tail`.
|
||||||
|
#[arg(long, default_value_t = 50, conflicts_with = "tail")]
|
||||||
limit: u64,
|
limit: u64,
|
||||||
|
/// Return the last `N` comments in chronological order. Pages
|
||||||
|
/// through every comment on the issue under the hood, then
|
||||||
|
/// slices the tail. Mutually exclusive with `--limit`.
|
||||||
|
#[arg(long)]
|
||||||
|
tail: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(client: &Client, args: Args) -> Result<()> {
|
pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
let repo = client.repo();
|
let repo = client.repo();
|
||||||
let v = client.get_json(&format!(
|
let comments = match args.tail {
|
||||||
"/repos/{repo}/issues/{}/comments?limit={}",
|
Some(n) => fetch_tail(client, &repo, args.number, n)?,
|
||||||
args.number, args.limit
|
None => fetch_head(client, &repo, args.number, args.limit)?,
|
||||||
))?;
|
};
|
||||||
if client.json_mode() {
|
if client.json_mode() {
|
||||||
let trimmed: Vec<Value> = v
|
let trimmed: Vec<Value> = comments
|
||||||
.as_array()
|
.iter()
|
||||||
.map(|a| {
|
.map(|c| {
|
||||||
a.iter()
|
json!({
|
||||||
.map(|c| {
|
"id": c.get("id"),
|
||||||
json!({
|
"user": c.get("user").and_then(|u| u.get("login")),
|
||||||
"id": c.get("id"),
|
"created_at": c.get("created_at"),
|
||||||
"user": c.get("user").and_then(|u| u.get("login")),
|
"updated_at": c.get("updated_at"),
|
||||||
"created_at": c.get("created_at"),
|
"body": c.get("body"),
|
||||||
"updated_at": c.get("updated_at"),
|
"url": c.get("html_url"),
|
||||||
"body": c.get("body"),
|
})
|
||||||
"url": c.get("html_url"),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.collect();
|
||||||
print_json(&Value::Array(trimmed))
|
print_json(&Value::Array(trimmed))
|
||||||
} else {
|
} else {
|
||||||
for c in v.as_array().cloned().unwrap_or_default() {
|
for c in &comments {
|
||||||
let user = c
|
let user = c
|
||||||
.get("user")
|
.get("user")
|
||||||
.and_then(|u| u.get("login"))
|
.and_then(|u| u.get("login"))
|
||||||
|
|
@ -58,3 +79,27 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch the first page's worth of comments (existing behaviour).
|
||||||
|
fn fetch_head(client: &Client, repo: &str, number: u64, limit: u64) -> Result<Vec<Value>> {
|
||||||
|
let v = client.get_json(&format!(
|
||||||
|
"/repos/{repo}/issues/{number}/comments?limit={limit}"
|
||||||
|
))?;
|
||||||
|
Ok(v.as_array().cloned().unwrap_or_default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetch every comment via paginated GET, then return the last `n`
|
||||||
|
/// items in chronological order. Forgejo orders
|
||||||
|
/// `/issues/<n>/comments` oldest-first so the trailing slice maps
|
||||||
|
/// directly to "most recent N".
|
||||||
|
fn fetch_tail(client: &Client, repo: &str, number: u64, n: usize) -> Result<Vec<Value>> {
|
||||||
|
let all = client.get_json_all(
|
||||||
|
&format!("/repos/{repo}/issues/{number}/comments?limit=50"),
|
||||||
|
TAIL_MAX_PAGES,
|
||||||
|
)?;
|
||||||
|
let total = all.len();
|
||||||
|
if total <= n {
|
||||||
|
return Ok(all);
|
||||||
|
}
|
||||||
|
Ok(all.into_iter().skip(total - n).collect())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue