//! `labels [list|add|remove] [labels...]` — manage labels on //! an issue or PR. Default action: list. use anyhow::{Result, bail}; use clap::{Args as ClapArgs, Subcommand}; use forgejo_api::structs::{DeleteLabelsOption, IssueLabelsOption, IssueListLabelsQuery, Label}; use serde_json::json; use crate::client::{Client, index}; use crate::verbs::print_json; #[derive(ClapArgs)] pub struct Args { /// Issue or PR number. pub(crate) number: u64, #[command(subcommand)] action: Option, } #[derive(Subcommand)] enum Action { /// List labels (default when no action is given). List, /// Add labels by name. Add { /// Label names to add. labels: Vec, }, /// Remove labels by name. Remove { /// Label names to remove. labels: Vec, }, } pub fn run(client: &Client, args: Args) -> Result<()> { let (owner, name) = client.owner_repo()?; let idx = index(args.number)?; match args.action.unwrap_or(Action::List) { Action::List => { let labels = client.api().issue_get_labels(owner, name, idx).send()?; print_label_names(&labels); } Action::Add { labels } => { if labels.is_empty() { bail!("hive-forge labels add: pass at least one label name"); } let all = repo_labels(client)?; let ids: Vec = resolve_ids(&all, &labels) .into_iter() .map(|id| json!(id)) .collect(); let resp = client .api() .issue_add_label( owner, name, idx, IssueLabelsOption { labels: Some(ids), updated_at: None, }, ) .send()?; print_label_names(&resp); } Action::Remove { labels } => { if labels.is_empty() { bail!("hive-forge labels remove: pass at least one label name"); } let all = repo_labels(client)?; for label in &labels { if let Some(id) = lookup_id(&all, label) { let _ = client .api() .issue_remove_label( owner, name, idx, &id.to_string(), DeleteLabelsOption { updated_at: None }, ) .send(); } } let labels = client.api().issue_get_labels(owner, name, idx).send()?; print_label_names(&labels); } } Ok(()) } /// First page (100) of the repo's label set, for name → id resolution. fn repo_labels(client: &Client) -> Result> { let (owner, name) = client.owner_repo()?; let (_, labels) = client .api() .issue_list_labels(owner, name, IssueListLabelsQuery::default()) .page_size(100) .send()?; Ok(labels) } fn resolve_ids(all: &[Label], names: &[String]) -> Vec { names.iter().filter_map(|n| lookup_id(all, n)).collect() } fn lookup_id(all: &[Label], name: &str) -> Option { all.iter() .find(|l| l.name.as_deref() == Some(name)) .and_then(|l| l.id) } fn print_label_names(labels: &[Label]) { let names: Vec<&str> = labels.iter().filter_map(|l| l.name.as_deref()).collect(); let _ = print_json(&json!(names)); }