//! `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 serde_json::{Value, json}; use crate::client::Client; use crate::verbs::print_json; #[derive(ClapArgs)] pub struct Args { /// Issue or PR number. 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 repo = client.repo(); match args.action.unwrap_or(Action::List) { Action::List => { let labels = client.get_json(&format!("/repos/{repo}/issues/{}/labels", args.number))?; print_label_names(&labels); } Action::Add { labels } => { if labels.is_empty() { bail!("hive-forge labels add: pass at least one label name"); } let all = client.get_json(&format!("/repos/{repo}/labels?limit=100"))?; let ids = resolve_ids(&all, &labels); let resp = client.post_json( &format!("/repos/{repo}/issues/{}/labels", args.number), &json!({ "labels": ids }), )?; print_label_names(&resp); } Action::Remove { labels } => { if labels.is_empty() { bail!("hive-forge labels remove: pass at least one label name"); } let all = client.get_json(&format!("/repos/{repo}/labels?limit=100"))?; for name in &labels { if let Some(id) = lookup_id(&all, name) { let _ = client.delete( &format!("/repos/{repo}/issues/{}/labels/{id}", args.number), None, ); } } let labels = client.get_json(&format!("/repos/{repo}/issues/{}/labels", args.number))?; print_label_names(&labels); } } Ok(()) } fn resolve_ids(all: &Value, names: &[String]) -> Vec { let Some(arr) = all.as_array() else { return Vec::new(); }; names .iter() .filter_map(|n| { arr.iter().find_map(|l| { let lname = l.get("name").and_then(Value::as_str)?; if lname == n { l.get("id").and_then(Value::as_u64) } else { None } }) }) .collect() } fn lookup_id(all: &Value, name: &str) -> Option { let arr = all.as_array()?; arr.iter().find_map(|l| { let lname = l.get("name").and_then(Value::as_str)?; if lname == name { l.get("id").and_then(Value::as_u64) } else { None } }) } fn print_label_names(v: &Value) { let names: Vec<&str> = v .as_array() .map(|a| { a.iter() .filter_map(|l| l.get("name").and_then(Value::as_str)) .collect() }) .unwrap_or_default(); let _ = print_json(&json!(names)); }