//! `milestone list|create|close` — manage milestones. Default action: //! list. use anyhow::Result; 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 { #[command(subcommand)] action: Option, } #[derive(Subcommand)] enum Action { /// List open milestones as JSON. List, /// Create a milestone, print {id,title}. Create { /// Milestone title. #[arg(long)] title: String, /// Description. #[arg(long)] desc: Option, /// Due date YYYY-MM-DD. #[arg(long)] due: Option, }, /// Close a milestone by id. Close { /// Milestone id. id: u64, }, } pub fn run(client: &Client, args: Args) -> Result<()> { let repo = client.repo(); match args.action.unwrap_or(Action::List) { Action::List => { let v = client.get_json(&format!("/repos/{repo}/milestones?state=open&limit=50"))?; let trimmed: Vec = v .as_array() .map(|a| { a.iter() .map(|m| { json!({ "id": m.get("id"), "title": m.get("title"), "open_issues": m.get("open_issues"), "closed_issues": m.get("closed_issues"), "due_on": m.get("due_on"), "description": m.get("description"), }) }) .collect() }) .unwrap_or_default(); print_json(&Value::Array(trimmed)) } Action::Create { title, desc, due } => { let mut payload = json!({ "title": title }); if let Some(d) = desc.filter(|s| !s.is_empty()) { payload["description"] = Value::String(d); } if let Some(d) = due.filter(|s| !s.is_empty()) { payload["due_on"] = Value::String(format!("{d}T00:00:00Z")); } let resp = client.post_json(&format!("/repos/{repo}/milestones"), &payload)?; print_json(&json!({ "id": resp.get("id"), "title": resp.get("title"), })) } Action::Close { id } => { let resp = client.patch_json( &format!("/repos/{repo}/milestones/{id}"), &json!({ "state": "closed" }), )?; print_json(&json!({ "id": resp.get("id"), "title": resp.get("title"), "state": resp.get("state"), })) } } }