ref:main
use crate::client::Client;
use crate::config;
use crate::output;
use clap::{Args, Subcommand};
use serde::Deserialize;
#[derive(Args)]
pub struct LabelArgs {
#[command(subcommand)]
pub command: LabelCommand,
}
#[derive(Subcommand)]
pub enum LabelCommand {
/// List labels for a repository
List {
/// Repository (org/repo)
repo: Option<String>,
},
/// Create a new label
Create {
/// Label name
#[arg(long)]
name: String,
/// Label color (hex, e.g. "#ff0000")
#[arg(long, default_value = "#6b7280")]
color: String,
/// Label description
#[arg(long)]
description: Option<String>,
/// Repository (org/repo)
#[arg(long)]
repo: Option<String>,
},
/// Add a label to an issue
Add {
/// Issue number
#[arg(long)]
issue: u32,
/// Label name
#[arg(long)]
name: String,
/// Repository (org/repo)
#[arg(long)]
repo: Option<String>,
},
/// Remove a label from an issue
Remove {
/// Issue number
#[arg(long)]
issue: u32,
/// Label name
#[arg(long)]
name: String,
/// Repository (org/repo)
#[arg(long)]
repo: Option<String>,
},
}
#[derive(Debug, Deserialize)]
struct Label {
name: Option<String>,
color: Option<String>,
description: Option<String>,
}
pub async fn run(args: LabelArgs) -> Result<(), Box<dyn std::error::Error>> {
match args.command {
LabelCommand::List { repo } => list(repo.as_deref()).await,
LabelCommand::Create {
name,
color,
description,
repo,
} => create(repo.as_deref(), &name, &color, description.as_deref()).await,
LabelCommand::Add { issue, name, repo } => add(repo.as_deref(), issue, &name).await,
LabelCommand::Remove { issue, name, repo } => remove(repo.as_deref(), issue, &name).await,
}
}
async fn list(repo: Option<&str>) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let resp: serde_json::Value = client.get(&format!("/{org}/{name}/labels")).await?;
let labels: Vec<Label> = resp
.get("labels")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
output::header(&format!("Labels ({org}/{name})"));
let rows: Vec<Vec<String>> = labels
.iter()
.map(|l| {
vec![
l.name.clone().unwrap_or_default(),
l.color.clone().unwrap_or_default(),
l.description.clone().unwrap_or_default(),
]
})
.collect();
output::print_table(&["NAME", "COLOR", "DESCRIPTION"], &rows);
Ok(())
}
async fn create(
repo: Option<&str>,
label_name: &str,
color: &str,
description: Option<&str>,
) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let mut payload = serde_json::json!({"name": label_name, "color": color});
if let Some(desc) = description {
payload["description"] = serde_json::json!(desc);
}
let _resp: serde_json::Value = client
.post(&format!("/{org}/{name}/labels"), &payload)
.await?;
output::success(&format!("Created label '{label_name}'"));
Ok(())
}
async fn add(
repo: Option<&str>,
issue_number: u32,
label_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
let _resp: serde_json::Value = client
.post(
&format!("/{org}/{name}/issues/{issue_number}/labels"),
&serde_json::json!({"name": label_name}),
)
.await?;
output::success(&format!(
"Added label '{label_name}' to issue #{issue_number}"
));
Ok(())
}
async fn remove(
repo: Option<&str>,
issue_number: u32,
label_name: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::from_config()?;
let (org, name) = config::resolve_repo(repo)?;
client
.delete_empty(&format!(
"/{org}/{name}/issues/{issue_number}/labels/{label_name}"
))
.await?;
output::success(&format!(
"Removed label '{label_name}' from issue #{issue_number}"
));
Ok(())
}